/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.monitor; import com.eviware.soapui.SoapUI; import com.eviware.soapui.impl.wsdl.mock.DispatchException; import com.eviware.soapui.impl.wsdl.support.soap.SoapMessageBuilder; import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion; import com.eviware.soapui.model.mock.MockResult; import com.eviware.soapui.model.mock.MockRunner; import com.eviware.soapui.model.mock.MockService; import com.eviware.soapui.model.propertyexpansion.PropertyExpander; import com.eviware.soapui.settings.HttpSettings; import com.eviware.soapui.settings.SSLSettings; import com.eviware.soapui.support.StringUtils; import com.eviware.soapui.support.Tools; import com.eviware.soapui.support.UISupport; import com.eviware.soapui.support.log.JettyLogger; import org.apache.log4j.Logger; import org.mortbay.component.AbstractLifeCycle; import org.mortbay.io.Connection; import org.mortbay.io.EndPoint; import org.mortbay.io.nio.SelectChannelEndPoint; import org.mortbay.jetty.Connector; import org.mortbay.jetty.HttpConnection; import org.mortbay.jetty.Request; import org.mortbay.jetty.RequestLog; import org.mortbay.jetty.Response; import org.mortbay.jetty.Server; import org.mortbay.jetty.handler.AbstractHandler; import org.mortbay.jetty.handler.RequestLogHandler; import org.mortbay.jetty.nio.SelectChannelConnector; import org.mortbay.jetty.security.SslSocketConnector; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringReader; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; /** * Core Mock-Engine hosting a Jetty web server * * @author ole.matzura */ public class JettyMockEngine implements MockEngine { public final static Logger log = Logger.getLogger(JettyMockEngine.class); private Server server; private Map<Integer, Map<String, List<MockRunner>>> runners = new HashMap<Integer, Map<String, List<MockRunner>>>(); private Map<Integer, SoapUIConnector> connectors = new HashMap<Integer, SoapUIConnector>(); private List<MockRunner> mockRunners = new CopyOnWriteArrayList<MockRunner>(); private SslSocketConnector sslConnector; private boolean addedSslConnector; public JettyMockEngine() { System.setProperty("org.mortbay.log.class", JettyLogger.class.getName()); } public boolean hasRunningMock(MockService mockService) { for (MockRunner runner : mockRunners) { if (runner.getMockContext().getMockService() == mockService) { return true; } } return false; } public synchronized void startMockService(MockRunner runner) throws Exception { if (server == null) { initServer(); } synchronized (server) { MockService mockService = runner.getMockContext().getMockService(); int port = mockService.getPort(); if (SoapUI.getSettings().getBoolean(SSLSettings.ENABLE_MOCK_SSL) && !addedSslConnector) { updateSslConnectorSettings(); server.addConnector(sslConnector); addedSslConnector = true; } else { if (addedSslConnector) { server.removeConnector(sslConnector); } addedSslConnector = false; } if (!runners.containsKey(port)) { SoapUIConnector connector = new SoapUIConnector(); PropertySupport.applySystemProperties(connector, "soapui.mock.connector", runner.getMockContext().getMockService()); connector.setPort(port); if (sslConnector != null) { connector.setConfidentialPort(sslConnector.getPort()); } if (mockService.getBindToHostOnly()) { String host = PropertyExpander.expandProperties(mockService, mockService.getHost()); if (StringUtils.hasContent(host)) { connector.setHost(host); } } boolean wasRunning = server.isRunning(); if (wasRunning) { server.stop(); } server.addConnector(connector); try { server.start(); } catch (RuntimeException e) { UISupport.showErrorMessage(e); server.removeConnector(connector); if (wasRunning) { server.start(); return; } } connectors.put(port, connector); runners.put(port, new HashMap<String, List<MockRunner>>()); } Map<String, List<MockRunner>> map = runners.get(port); String path = mockService.getPath(); if (!map.containsKey(path)) { map.put(path, new ArrayList<MockRunner>()); } map.get(path).add(runner); mockRunners.add(runner); log.info("Started mockService [" + mockService.getName() + "] on port [" + port + "] at path [" + path + "]"); } } private void initServer() throws Exception { server = new Server(); server.setThreadPool(new SoapUIJettyThreadPool()); server.setHandler(new ServerHandler()); RequestLogHandler logHandler = new RequestLogHandler(); logHandler.setRequestLog(new MockRequestLog()); server.addHandler(logHandler); sslConnector = new SslSocketConnector(); sslConnector.setMaxIdleTime(30000); } private void updateSslConnectorSettings() { sslConnector.setKeystore(SoapUI.getSettings().getString(SSLSettings.MOCK_KEYSTORE, null)); sslConnector.setPassword(SoapUI.getSettings().getString(SSLSettings.MOCK_PASSWORD, null)); sslConnector.setKeyPassword(SoapUI.getSettings().getString(SSLSettings.MOCK_KEYSTORE_PASSWORD, null)); String trustStore = SoapUI.getSettings().getString(SSLSettings.MOCK_TRUSTSTORE, null); if (StringUtils.hasContent(trustStore)) { sslConnector.setTruststore(trustStore); sslConnector.setTrustPassword(SoapUI.getSettings().getString(SSLSettings.MOCK_TRUSTSTORE_PASSWORD, null)); } sslConnector.setPort((int) SoapUI.getSettings().getLong(SSLSettings.MOCK_PORT, 443)); sslConnector.setNeedClientAuth(SoapUI.getSettings().getBoolean(SSLSettings.CLIENT_AUTHENTICATION)); } public void stopMockService(MockRunner runner) { synchronized (server) { MockService mockService = runner.getMockContext().getMockService(); final Integer port = mockService.getPort(); Map<String, List<MockRunner>> map = runners.get(port); if (map == null || !map.containsKey(mockService.getPath())) { return; } map.get(mockService.getPath()).remove(runner); if (map.get(mockService.getPath()).isEmpty()) { map.remove(mockService.getPath()); } mockRunners.remove(runner); log.info("Stopped MockService [" + mockService.getName() + "] on port [" + port + "]"); if (map.isEmpty() && !SoapUI.getSettings().getBoolean(HttpSettings.LEAVE_MOCKENGINE)) { SoapUIConnector connector = connectors.get(port); if (connector == null) { log.warn("Missing connectors on port [" + port + "]"); return; } try { log.info("Stopping connector on port " + port); if (!connector.waitUntilIdle(5000)) { log.warn("Failed to wait for idle.. stopping connector anyway.."); } connector.stop(); } catch (Exception e) { SoapUI.logError(e); } server.removeConnector(connector); runners.remove(port); if (runners.isEmpty()) { try { log.info("No more connectors.. stopping server"); server.stop(); if (sslConnector != null) { // server.removeConnector( sslConnector ); // sslConnector.stop(); // sslConnector = null; } } catch (Exception e) { SoapUI.logError(e); } } } } } private class SoapUIConnector extends SelectChannelConnector { private Set<HttpConnection> connections = new HashSet<HttpConnection>(); @Override protected void connectionClosed(HttpConnection arg0) { super.connectionClosed(arg0); connections.remove(arg0); } @Override protected void connectionOpened(HttpConnection arg0) { super.connectionOpened(arg0); connections.add(arg0); } @Override protected Connection newConnection(SocketChannel socketChannel, SelectChannelEndPoint selectChannelEndPoint) { return new SoapUIHttpConnection(SoapUIConnector.this, selectChannelEndPoint, getServer()); } public boolean waitUntilIdle(long maxWait) throws Exception { while (maxWait > 0 && hasActiveConnections()) { System.out.println("Waiting for active connections to finish.."); Thread.sleep(500); maxWait -= 500; } return !hasActiveConnections(); } private boolean hasActiveConnections() { for (HttpConnection connection : connections) { if (!connection.isIdle()) { return true; } } return false; } } private class SoapUIHttpConnection extends HttpConnection { private CapturingServletInputStream capturingServletInputStream; private BufferedServletInputStream bufferedServletInputStream; private CapturingServletOutputStream capturingServletOutputStream; public SoapUIHttpConnection(Connector connector, EndPoint endPoint, Server server) { super(connector, endPoint, server); } @Override public ServletInputStream getInputStream() { if (SoapUI.getSettings().getBoolean(HttpSettings.ENABLE_MOCK_WIRE_LOG)) { if (capturingServletInputStream == null) { capturingServletInputStream = new CapturingServletInputStream(super.getInputStream()); bufferedServletInputStream = new BufferedServletInputStream(capturingServletInputStream); } } else { bufferedServletInputStream = new BufferedServletInputStream(super.getInputStream()); } return bufferedServletInputStream; } @Override public ServletOutputStream getOutputStream() { if (SoapUI.getSettings().getBoolean(HttpSettings.ENABLE_MOCK_WIRE_LOG)) { if (capturingServletOutputStream == null) { capturingServletOutputStream = new CapturingServletOutputStream(super.getOutputStream()); } return capturingServletOutputStream; } else { return super.getOutputStream(); } } } private class BufferedServletInputStream extends ServletInputStream { private InputStream source = null; private byte[] data = null; private InputStream buffer1 = null; public BufferedServletInputStream(InputStream is) { super(); source = is; } public InputStream getBuffer() throws IOException { if (source.available() > 0) { // New request content available data = null; } if (data == null) { ByteArrayOutputStream out = Tools.readAll(source, Tools.READ_ALL); data = out.toByteArray(); } if (buffer1 == null) { buffer1 = new ByteArrayInputStream(data); } return buffer1; } public int read() throws IOException { return getBuffer().read(); } public int readLine(byte[] b, int off, int len) throws IOException { if (len <= 0) { return 0; } int count = 0, c; while ((c = read()) != -1) { b[off++] = (byte) c; count++; if (c == '\n' || count == len) { break; } } return count > 0 ? count : -1; } public int read(byte[] b) throws IOException { return getBuffer().read(b); } public int read(byte[] b, int off, int len) throws IOException { return getBuffer().read(b, off, len); } public long skip(long n) throws IOException { return getBuffer().skip(n); } public int available() throws IOException { return getBuffer().available(); } public void close() throws IOException { getBuffer().close(); } public void mark(int readlimit) { // buffer.mark( readlimit ); } public boolean markSupported() { return false; } public void reset() throws IOException { buffer1 = null; } } private class CapturingServletOutputStream extends ServletOutputStream { private ServletOutputStream outputStream; private ByteArrayOutputStream captureOutputStream = new ByteArrayOutputStream(); public CapturingServletOutputStream(ServletOutputStream outputStream) { this.outputStream = outputStream; } public void print(String s) throws IOException { outputStream.print(s); } public void print(boolean b) throws IOException { outputStream.print(b); } public void print(char c) throws IOException { outputStream.print(c); } public void print(int i) throws IOException { outputStream.print(i); } public void print(long l) throws IOException { outputStream.print(l); } public void print(float v) throws IOException { outputStream.print(v); } public void print(double v) throws IOException { outputStream.print(v); } public void println() throws IOException { outputStream.println(); } public void println(String s) throws IOException { outputStream.println(s); } public void println(boolean b) throws IOException { outputStream.println(b); } public void println(char c) throws IOException { outputStream.println(c); } public void println(int i) throws IOException { outputStream.println(i); } public void println(long l) throws IOException { outputStream.println(l); } public void println(float v) throws IOException { outputStream.println(v); } public void println(double v) throws IOException { outputStream.println(v); } public void write(int b) throws IOException { captureOutputStream.write(b); outputStream.write(b); } public void write(byte[] b) throws IOException { captureOutputStream.write(b); outputStream.write(b); } public void write(byte[] b, int off, int len) throws IOException { captureOutputStream.write(b, off, len); outputStream.write(b, off, len); } public void flush() throws IOException { outputStream.flush(); } public void close() throws IOException { outputStream.close(); // log.info( "Closing output stream, captured: " + // captureOutputStream.toString() ); } } private class CapturingServletInputStream extends ServletInputStream { private ServletInputStream inputStream; private ByteArrayOutputStream captureOutputStream = new ByteArrayOutputStream(); public CapturingServletInputStream(ServletInputStream inputStream) { this.inputStream = inputStream; } public int read() throws IOException { int i = inputStream.read(); captureOutputStream.write(i); return i; } public int readLine(byte[] bytes, int i, int i1) throws IOException { int result = inputStream.readLine(bytes, i, i1); captureOutputStream.write(bytes, i, i1); return result; } public int read(byte[] b) throws IOException { int i = inputStream.read(b); captureOutputStream.write(b); return i; } public int read(byte[] b, int off, int len) throws IOException { int result = inputStream.read(b, off, len); if (result != -1) { captureOutputStream.write(b, off, result); } return result; } public long skip(long n) throws IOException { return inputStream.skip(n); } public int available() throws IOException { return inputStream.available(); } public void close() throws IOException { inputStream.close(); } public void mark(int readLimit) { inputStream.mark(readLimit); } public boolean markSupported() { return inputStream.markSupported(); } public void reset() throws IOException { inputStream.reset(); } } private class ServerHandler extends AbstractHandler { public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) throws IOException, ServletException { // find mockService Map<String, List<MockRunner>> map = runners.get(request.getLocalPort()); // ssl? if (map == null && sslConnector != null && request.getLocalPort() == sslConnector.getPort()) { for (Map<String, List<MockRunner>> runnerMap : runners.values()) { if (runnerMap.containsKey(request.getPathInfo())) { map = runnerMap; break; } } } if (map != null) { List<MockRunner> wsdlMockRunners = map.get(request.getPathInfo()); if (wsdlMockRunners == null) { String bestMatchedRootPath = ""; for (String root : map.keySet()) { if (request.getPathInfo().startsWith(root) && root.length() > bestMatchedRootPath.length()) { bestMatchedRootPath = root; wsdlMockRunners = map.get(root); } } } if (wsdlMockRunners != null) { try { DispatchException ex = null; MockResult result = null; for (MockRunner wsdlMockRunner : wsdlMockRunners) { if (!wsdlMockRunner.isRunning()) { continue; } try { result = wsdlMockRunner.dispatchRequest(request, response); if (result != null) { result.finish(); break; } } catch (DispatchException e) { ex = e; } } if (ex != null && result == null) { throw ex; } } catch (Exception e) { SoapUI.logError(e); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setContentType("text/html"); response.getWriter().print( SoapMessageBuilder.buildFault("Server", e.getMessage(), SoapVersion.Utils .getSoapVersionForContentType(request.getContentType(), SoapVersion.Soap11))); // throw new ServletException( e ); } } else { printMockServiceList(response); } } else { printMockServiceList(response); } response.flushBuffer(); } private void printMockServiceList(HttpServletResponse response) throws IOException { response.setStatus(HttpServletResponse.SC_OK); response.setContentType("text/html"); MockRunner[] mockRunners = getMockRunners(); PrintWriter out = response.getWriter(); out.print("<html><body><p>There are currently " + mockRunners.length + " running SoapUI MockServices</p><ul>"); for (MockRunner mockRunner : mockRunners) { out.print("<li><a href=\""); out.print(mockRunner.getMockContext().getMockService().getPath() + "?WSDL"); out.print("\">" + mockRunner.getMockContext().getMockService().getName() + "</a></li>"); } out.print("</ul></p></body></html>"); } } public MockRunner[] getMockRunners() { return mockRunners.toArray(new MockRunner[mockRunners.size()]); } private class MockRequestLog extends AbstractLifeCycle implements RequestLog { public void log(Request request, Response response) { if (!SoapUI.getSettings().getBoolean(HttpSettings.ENABLE_MOCK_WIRE_LOG)) { return; } if (SoapUI.getLogMonitor() == null || SoapUI.getLogMonitor().getLogArea("jetty log") == null || SoapUI.getLogMonitor().getLogArea("jetty log").getLoggers() == null) { return; } Logger logger = SoapUI.getLogMonitor().getLogArea("jetty log").getLoggers()[0]; try { ServletInputStream inputStream = request.getInputStream(); if (inputStream instanceof CapturingServletInputStream) { ByteArrayOutputStream byteArrayOutputStream = ((CapturingServletInputStream) inputStream).captureOutputStream; String str = request.toString() + byteArrayOutputStream.toString(); BufferedReader reader = new BufferedReader(new StringReader(str)); ((CapturingServletInputStream) inputStream).captureOutputStream = new ByteArrayOutputStream(); String line = reader.readLine(); while (line != null) { logger.info(">> \"" + line + "\""); line = reader.readLine(); } } } catch (Exception e) { SoapUI.logError(e); } try { ServletOutputStream outputStream = response.getOutputStream(); if (outputStream instanceof CapturingServletOutputStream) { ByteArrayOutputStream byteArrayOutputStream = ((CapturingServletOutputStream) outputStream).captureOutputStream; String str = request.toString() + byteArrayOutputStream.toString(); BufferedReader reader = new BufferedReader(new StringReader(str)); ((CapturingServletOutputStream) outputStream).captureOutputStream = new ByteArrayOutputStream(); String line = reader.readLine(); while (line != null) { logger.info("<< \"" + line + "\""); line = reader.readLine(); } } } catch (Exception e) { SoapUI.logError(e); } } } }