Package com.github.kristofa.test.http

Source Code of com.github.kristofa.test.http.LoggingHttpProxy$ProxyImplementation

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 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.
* <p>
* Those persisted request/response pairs can be mocked by {@link MockHttpServer}.
* <p>
* 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 <a
* href="http://googletesting.blogspot.be/2012/10/hermetic-servers.html">hermetic server</a>.
* <p>
* The {@link LoggingHttpProxy} will return following HTTP return codes when things go wrong:
* <ul>
* <li>570: We could not build a forward request for input request. Missing of faulty {@link ForwardHttpRequestBuilder}.
* <li>571: Forward request failed. Forward URL invalid?
* <li>572: Copying response of forwarding request failed.
* <li>573: Unknown exception.
* </ul>
* 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<ForwardHttpRequestBuilder> requestBuilders = new HashSet<ForwardHttpRequestBuilder>();
    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 FullHttpRequest httpRequest = RequestConvertor.convert(request);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Received request: " + httpRequest);
                }

                FullHttpRequest forwardHttpRequest = null;
                for (final ForwardHttpRequestBuilder forwardRequestBuilder : requestBuilders) {
                    forwardHttpRequest = forwardRequestBuilder.getForwardRequest(httpRequest);
                    if (forwardHttpRequest != null) {
                        break;
                    }
                }

                if (forwardHttpRequest == null) {
                    LOGGER.error("Got unexpected request: " + httpRequest);
                    errorResponse(response, NO_FORWARD_REQUEST_ERROR_HTTP_CODE, "Received unexpected request:\n"
                        + httpRequest.toString());
                } else {

                    LOGGER.debug("Logging request.");

                    final HttpRequestResponseLogger logger = loggerFactory.getHttpRequestResponseLogger();
                    logger.log(httpRequest);
                    try {
                        LOGGER.debug("Forward request.");
                        final HttpClientResponse<InputStream> forwardResponse = forward(forwardHttpRequest);
                        LOGGER.debug("Got response for forward request.");
                        try {
                            final InputStream inputStream = forwardResponse.getResponseEntity();
                            byte[] responseEntity;
                            try {
                                // 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.
                                responseEntity = IOUtils.toByteArray(inputStream);
                            } finally {
                                inputStream.close();
                            }
                            final HttpResponse httpResponse =
                                new HttpResponseImpl(forwardResponse.getHttpCode(), forwardResponse.getContentType(),
                                    responseEntity);
                            LOGGER.debug("Logging response");
                            logger.log(httpResponse);

                            response.setCode(forwardResponse.getHttpCode());
                            response.set(CONTENT_TYPE, forwardResponse.getContentType());
                            final OutputStream outputStream = response.getOutputStream();
                            try {
                                final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(responseEntity);
                                IOUtils.copy(byteArrayInputStream, outputStream);
                                byteArrayInputStream.close();
                            } finally {
                                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 HttpClientResponse<InputStream> 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 <code>null</code> and at least 1 should be specified.
     * @param loggerFactory Request/Response logger factory.. Should not be <code>null</code>.
     */
    public LoggingHttpProxy(final int port, final Collection<ForwardHttpRequestBuilder> 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();
    }
}
TOP

Related Classes of com.github.kristofa.test.http.LoggingHttpProxy$ProxyImplementation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.