All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.kristofa.test.http.LoggingHttpProxy Maven / Gradle / Ivy

Go to download

Mock and Proxy HTTP Server for testing purposes. Forked from https://github.com/jharlap/mock-http-server

There is a newer version: 4.1
Show newest version
package com.github.kristofa.test.http;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map.Entry;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.core.Container;
import org.simpleframework.transport.connect.Connection;
import org.simpleframework.transport.connect.SocketConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.kristofa.test.http.client.ApacheHttpClientImpl;
import com.github.kristofa.test.http.client.HttpClient;
import com.github.kristofa.test.http.client.HttpClientResponse;
import com.github.kristofa.test.http.client.HttpRequestException;

/**
 * Http proxy that supports logging requests/reponses. Its purpose is to be a 'man in the middle' which can be used to
 * capture request/responses that can be mocked later on for testing purposes. It forwards requests it gets to another
 * service and returns the result of that service unmodified to the requester. While doing that it also allows logging
 * request/response pairs.
 * 

* Those persisted request/response pairs can be mocked by {@link MockHttpServer}. *

* Using the {@link LoggingHttpProxy} to persist request/responses and using them with the {@link MockHttpServer} is * especially useful for complex responses that are not that easy to mock by hand. It allows building a hermetic server. *

* The {@link LoggingHttpProxy} will return following HTTP return codes when things go wrong: *

    *
  • 570: We could not build a forward request for input request. Missing of faulty {@link ForwardHttpRequestBuilder}. *
  • 571: Forward request failed. Forward URL invalid? *
  • 572: Copying response of forwarding request failed. *
  • 573: Unknown exception. *
* The body of the response will contain the error message. * * @author kristof */ public class LoggingHttpProxy { private final static Logger LOGGER = LoggerFactory.getLogger(LoggingHttpProxy.class); private final int port; private final Collection requestBuilders = new HashSet(); private final HttpRequestResponseLoggerFactory loggerFactory; private Connection connection; private ProxyImplementation proxy; private class ProxyImplementation implements Container { private static final int UNKNOWN_EXCEPTION_HTTP_CODE = 573; private static final int FORWARD_REQUEST_FAILED_HTTP_CODE = 571; private static final int COPY_RESPONSE_FAILED_ERROR_HTTP_CODE = 572; private static final int NO_FORWARD_REQUEST_ERROR_HTTP_CODE = 570; private static final String CONTENT_TYPE = "Content-Type"; public ProxyImplementation() { super(); } /** * {@inheritDoc} */ @Override public void handle(final Request request, final Response response) { try { final FullHttpRequestImpl httpRequest = buildHttpRequest(request); FullHttpRequest forwardHttpRequest = null; for (final ForwardHttpRequestBuilder forwardRequestBuilder : requestBuilders) { forwardHttpRequest = forwardRequestBuilder.getForwardRequest(httpRequest); if (forwardHttpRequest != null) { break; } } if (forwardHttpRequest == null) { errorResponse(response, NO_FORWARD_REQUEST_ERROR_HTTP_CODE, "Received unexpected request:\n" + httpRequest.toString()); } else { final HttpRequestResponseLogger logger = loggerFactory.getHttpRequestResponseLogger(); logger.log(httpRequest); try { final HttpClientResponse forwardResponse = forward(forwardHttpRequest); try { final InputStream inputStream = forwardResponse.getResponseEntity(); // This is tricky as we keep the full response in memory... reason is that we need to copy it // twice. // Once to return to response, another time to log. final byte[] responseEntity = IOUtils.toByteArray(inputStream); inputStream.close(); final HttpResponse httpResponse = new HttpResponseImpl(forwardResponse.getHttpCode(), forwardResponse.getContentType(), responseEntity); logger.log(httpResponse); response.setCode(forwardResponse.getHttpCode()); response.set(CONTENT_TYPE, forwardResponse.getContentType()); final OutputStream outputStream = response.getOutputStream(); final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(responseEntity); IOUtils.copy(byteArrayInputStream, outputStream); byteArrayInputStream.close(); outputStream.close(); } catch (final IOException e) { LOGGER.error("IOException when trying to copy response of forward request.", e); errorResponse(response, COPY_RESPONSE_FAILED_ERROR_HTTP_CODE, "Exception when copying streams." + e.getMessage()); } finally { forwardResponse.close(); } } catch (final HttpRequestException e) { LOGGER.error("HttpRequestException when forwarding request.", e); errorResponse(response, FORWARD_REQUEST_FAILED_HTTP_CODE, "Exception when forwarding request." + e.getMessage()); } } } catch (final Exception e) { LOGGER.error("Exception.", e); errorResponse(response, UNKNOWN_EXCEPTION_HTTP_CODE, "Exception: " + e.getMessage()); } } private FullHttpRequestImpl buildHttpRequest(final Request request) { byte[] data = null; try { if (request.getContentLength() > 0) { final InputStream inputStream = request.getInputStream(); data = IOUtils.toByteArray(inputStream); inputStream.close(); } } catch (final IOException e) { throw new IllegalStateException("Exception when getting request content.", e); } final FullHttpRequestImpl httpRequestImpl = new FullHttpRequestImpl(); httpRequestImpl.method(Method.valueOf(request.getMethod())); httpRequestImpl.path(request.getPath().getPath()); httpRequestImpl.content(data); for (final String headerField : request.getNames()) { if (HttpMessageHeaderField.CONTENTTYPE.getValue().equals(headerField)) { for (final String headerFieldValue : request.getValues(headerField)) { httpRequestImpl.httpMessageHeader(headerField, headerFieldValue); } } } // domain (host) and port are not important. httpRequestImpl.domain(null); httpRequestImpl.port(null); for (final Entry entry : request.getQuery().entrySet()) { httpRequestImpl.queryParameter(entry.getKey(), entry.getValue()); } return httpRequestImpl; } private HttpClientResponse forward(final FullHttpRequest request) throws HttpRequestException { final HttpClient client = new ApacheHttpClientImpl(); return client.execute(request); } private void errorResponse(final Response response, final int httpCode, final String message) { response.setCode(httpCode); response.set(CONTENT_TYPE, "text/plain;charset=utf-8"); PrintStream body; try { body = response.getPrintStream(); body.print(message); body.close(); } catch (final IOException e) { throw new IllegalStateException("Exception when building response.", e); } } } /** * Create a new instance. * * @param port Port at which proxy will be running. * @param requestBuilders Forward request builders. Should not be null and at least 1 should be specified. * @param loggerFactory Request/Response logger factory.. Should not be null. */ public LoggingHttpProxy(final int port, final Collection requestBuilders, final HttpRequestResponseLoggerFactory loggerFactory) { Validate.isTrue(requestBuilders != null && !requestBuilders.isEmpty(), "At least 1 ForwardHttpRequestBuilder should be provided."); Validate.notNull(loggerFactory, "HttpRequestResponseLoggerFactory should not be null."); this.port = port; this.requestBuilders.addAll(requestBuilders); this.loggerFactory = loggerFactory; } /** * Starts proxy. * * @throws Exception In case starting fails. */ public void start() throws IOException { // Close existing connection if it exists. if (connection != null) { connection.close(); } proxy = new ProxyImplementation(); connection = new SocketConnection(proxy); final SocketAddress address = new InetSocketAddress(port); connection.connect(address); LOGGER.debug("Started on port: " + port); } /** * Stops proxy. * * @throws IOException In case closing connection fails. */ public void stop() throws IOException { LOGGER.debug("Stopping and closing connection."); connection.close(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy