package org.mockserver.server; import com.google.common.base.Strings; import com.google.common.net.MediaType; import org.mockserver.client.serialization.*; import org.mockserver.filters.RequestLogFilter; import org.mockserver.mappers.HttpServletRequestToMockServerRequestDecoder; import org.mockserver.mappers.MockServerResponseToHttpServletResponseEncoder; import org.mockserver.mock.Expectation; import org.mockserver.mock.MockServerMatcher; import org.mockserver.mock.action.ActionHandler; import org.mockserver.mock.action.ExpectationCallback; import org.mockserver.model.*; import org.mockserver.streams.IOStreamUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.nio.charset.Charset; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static org.mockserver.configuration.ConfigurationProperties.enableCORSForAPI; import static org.mockserver.configuration.ConfigurationProperties.enableCORSForAllResponses; import static org.mockserver.model.Header.header; import static org.mockserver.model.HttpResponse.notFoundResponse; import static org.mockserver.model.PortBinding.portBinding; /** * @author jamesdbloom */ public class MockServerServlet extends HttpServlet { private static final String NOT_SUPPORTED_MESSAGE = " is not supported by MockServer deployable WAR due to limitations in the JEE specification; use mockserver-netty to enable these features"; private final Logger logger = LoggerFactory.getLogger(this.getClass()); // mockserver private MockServerMatcher mockServerMatcher = new MockServerMatcher(); private RequestLogFilter requestLogFilter = new RequestLogFilter(); private ActionHandler actionHandler = new ActionHandler(requestLogFilter); // mappers private HttpServletRequestToMockServerRequestDecoder httpServletRequestToMockServerRequestDecoder = new HttpServletRequestToMockServerRequestDecoder(); private MockServerResponseToHttpServletResponseEncoder mockServerResponseToHttpServletResponseEncoder = new MockServerResponseToHttpServletResponseEncoder(); // serializers private ExpectationSerializer expectationSerializer = new ExpectationSerializer(); private HttpRequestSerializer httpRequestSerializer = new HttpRequestSerializer(); private PortBindingSerializer portBindingSerializer = new PortBindingSerializer(); private VerificationSerializer verificationSerializer = new VerificationSerializer(); private VerificationSequenceSerializer verificationSequenceSerializer = new VerificationSequenceSerializer(); @Override public void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { HttpRequest request = null; try { request = httpServletRequestToMockServerRequestDecoder.mapHttpServletRequestToMockServerRequest(httpServletRequest); if ((enableCORSForAPI() || enableCORSForAllResponses()) && request.getMethod().getValue().equals("OPTIONS") && !request.getFirstHeader("Origin").isEmpty()) { httpServletResponse.setStatus(HttpStatusCode.OK_200.code()); addCORSHeadersForAPI(httpServletResponse); } else if (request.getPath().getValue().equals("/_mockserver_callback_websocket")) { writeNotSupportedResponse(ExpectationCallback.class, httpServletResponse); } else if (request.matches("PUT", "/status")) { httpServletResponse.setStatus(HttpStatusCode.OK_200.code()); httpServletResponse.setHeader(CONTENT_TYPE.toString(), MediaType.JSON_UTF_8.toString()); IOStreamUtils.writeToOutputStream(portBindingSerializer.serialize(portBinding(httpServletRequest.getLocalPort())).getBytes(), httpServletResponse); addCORSHeadersForAPI(httpServletResponse); } else if (request.matches("PUT", "/bind")) { httpServletResponse.setStatus(HttpStatusCode.NOT_IMPLEMENTED_501.code()); addCORSHeadersForAPI(httpServletResponse); } else if (request.matches("PUT", "/expectation")) { Expectation expectation = expectationSerializer.deserialize(request.getBodyAsString()); addCORSHeadersForAPI(httpServletResponse); Action action = expectation.getAction(); if (validateSupportedFeatures(action, httpServletResponse)) { mockServerMatcher.when(expectation.getHttpRequest(), expectation.getTimes(), expectation.getTimeToLive()).thenRespond(expectation.getHttpResponse()).thenForward(expectation.getHttpForward()).thenCallback(expectation.getHttpClassCallback()); httpServletResponse.setStatus(HttpStatusCode.CREATED_201.code()); } } else if (request.matches("PUT", "/clear")) { HttpRequest httpRequest = httpRequestSerializer.deserialize(request.getBodyAsString()); if (request.hasQueryStringParameter("type", "expectation")) { mockServerMatcher.clear(httpRequest); } else if (request.hasQueryStringParameter("type", "log")) { requestLogFilter.clear(httpRequest); } else { requestLogFilter.clear(httpRequest); mockServerMatcher.clear(httpRequest); } httpServletResponse.setStatus(HttpStatusCode.ACCEPTED_202.code()); addCORSHeadersForAPI(httpServletResponse); } else if (request.matches("PUT", "/reset")) { requestLogFilter.reset(); mockServerMatcher.reset(); httpServletResponse.setStatus(HttpStatusCode.ACCEPTED_202.code()); addCORSHeadersForAPI(httpServletResponse); } else if (request.matches("PUT", "/dumpToLog")) { mockServerMatcher.dumpToLog(httpRequestSerializer.deserialize(request.getBodyAsString())); httpServletResponse.setStatus(HttpStatusCode.ACCEPTED_202.code()); addCORSHeadersForAPI(httpServletResponse); } else if (request.matches("PUT", "/retrieve")) { addCORSHeadersForAPI(httpServletResponse); if (request.hasQueryStringParameter("type", "expectation")) { Expectation[] expectations = mockServerMatcher.retrieveExpectations(httpRequestSerializer.deserialize(request.getBodyAsString())); httpServletResponse.setStatus(HttpStatusCode.OK_200.code()); httpServletResponse.setHeader(CONTENT_TYPE.toString(), MediaType.JSON_UTF_8.toString()); IOStreamUtils.writeToOutputStream(expectationSerializer.serialize(expectations).getBytes(), httpServletResponse); } else { HttpRequest[] requests = requestLogFilter.retrieve(httpRequestSerializer.deserialize(request.getBodyAsString())); httpServletResponse.setStatus(HttpStatusCode.OK_200.code()); httpServletResponse.setHeader(CONTENT_TYPE.toString(), MediaType.JSON_UTF_8.toString()); IOStreamUtils.writeToOutputStream(httpRequestSerializer.serialize(requests).getBytes(), httpServletResponse); } } else if (request.matches("PUT", "/verify")) { String result = requestLogFilter.verify(verificationSerializer.deserialize(request.getBodyAsString())); addCORSHeadersForAPI(httpServletResponse); verifyResponse(httpServletResponse, result); } else if (request.matches("PUT", "/verifySequence")) { String result = requestLogFilter.verify(verificationSequenceSerializer.deserialize(request.getBodyAsString())); addCORSHeadersForAPI(httpServletResponse); verifyResponse(httpServletResponse, result); } else if (request.matches("PUT", "/stop")) { httpServletResponse.setStatus(HttpStatusCode.NOT_IMPLEMENTED_501.code()); addCORSHeadersForAPI(httpServletResponse); } else { Action action = mockServerMatcher.retrieveAction(request); if (validateSupportedFeatures(action, httpServletResponse)) { mapResponse(actionHandler.processAction(action, request), httpServletResponse); addCORSHeadersForAllResponses(httpServletResponse); } } } catch (Exception e) { logger.error("Exception processing " + (request != null ? request : httpServletRequest), e); httpServletResponse.setStatus(HttpStatusCode.BAD_REQUEST_400.code()); } } private void verifyResponse(HttpServletResponse httpServletResponse, String result) { if (result.isEmpty()) { httpServletResponse.setStatus(HttpStatusCode.ACCEPTED_202.code()); } else { httpServletResponse.setStatus(HttpStatusCode.NOT_ACCEPTABLE_406.code()); httpServletResponse.setHeader(CONTENT_TYPE.toString(), MediaType.PLAIN_TEXT_UTF_8.toString()); IOStreamUtils.writeToOutputStream(result.getBytes(), httpServletResponse); } } private void addCORSHeadersForAPI(HttpServletResponse httpServletResponse) { if (enableCORSForAPI()) { addCORSHeaders(httpServletResponse); } else { addCORSHeadersForAllResponses(httpServletResponse); } } private void addCORSHeadersForAllResponses(HttpServletResponse httpServletResponse) { if (enableCORSForAllResponses()) { addCORSHeaders(httpServletResponse); } } private void addCORSHeaders(HttpServletResponse httpServletResponse) { String methods = "CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, TRACE"; String headers = "Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary"; if (httpServletResponse.getHeaders("Access-Control-Allow-Origin").isEmpty()) { httpServletResponse.setHeader("Access-Control-Allow-Origin", "*"); } if (httpServletResponse.getHeaders("Access-Control-Allow-Methods").isEmpty()) { httpServletResponse.setHeader("Access-Control-Allow-Methods", methods); } if (httpServletResponse.getHeaders("Access-Control-Allow-Headers").isEmpty()) { httpServletResponse.setHeader("Access-Control-Allow-Headers", headers); } if (httpServletResponse.getHeaders("Access-Control-Expose-Headers").isEmpty()) { httpServletResponse.setHeader("Access-Control-Expose-Headers", headers); } if (httpServletResponse.getHeaders("Access-Control-Max-Age").isEmpty()) { httpServletResponse.setHeader("Access-Control-Max-Age", "1"); } if (httpServletResponse.getHeaders("X-CORS").isEmpty()) { httpServletResponse.setHeader("X-CORS", "MockServer CORS support enabled by default, to disable ConfigurationProperties.enableCORSForAPI(false) or -Dmockserver.disableCORS=false"); } } private boolean validateSupportedFeatures(Action action, HttpServletResponse httpServletResponse) { boolean valid = true; if (action instanceof HttpResponse && ((HttpResponse) action).getConnectionOptions() != null) { writeNotSupportedResponse(ConnectionOptions.class, httpServletResponse); valid = false; } else if (action instanceof HttpObjectCallback) { writeNotSupportedResponse(HttpObjectCallback.class, httpServletResponse); valid = false; } else if (action instanceof HttpError) { writeNotSupportedResponse(HttpError.class, httpServletResponse); valid = false; } return valid; } private void writeNotSupportedResponse(Class<?> notSupportedFeature, HttpServletResponse httpServletResponse) { httpServletResponse.setStatus(HttpStatusCode.NOT_ACCEPTABLE_406.code()); IOStreamUtils.writeToOutputStream((notSupportedFeature.getSimpleName() + NOT_SUPPORTED_MESSAGE).getBytes(), httpServletResponse); } private void mapResponse(HttpResponse httpResponse, HttpServletResponse httpServletResponse) { if (httpResponse == null) { httpResponse = notFoundResponse(); } addContentTypeHeader(httpResponse); mockServerResponseToHttpServletResponseEncoder.mapMockServerResponseToHttpServletResponse(httpResponse, httpServletResponse); } private void addContentTypeHeader(HttpResponse response) { if (response.getBody() != null && Strings.isNullOrEmpty(response.getFirstHeader(CONTENT_TYPE.toString()))) { Charset bodyCharset = response.getBody().getCharset(null); String bodyContentType = response.getBody().getContentType(); if (bodyCharset != null) { response.updateHeader(header(CONTENT_TYPE.toString(), bodyContentType + "; charset=" + bodyCharset.name().toLowerCase())); } else if (bodyContentType != null) { response.updateHeader(header(CONTENT_TYPE.toString(), bodyContentType)); } } } }