package com.github.kristofa.test.http;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import org.apache.commons.lang3.StringUtils;
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;
/**
* Mock Http Server which can be used to return upfront defined responses for a set of http requests.
* <p/>
* Mock http server will in its default configuration return code 598 in case it receives a request which was not foreseen.
* In case of an internal exception it will return http returncode 599.
*
* @author kristof
*/
public class MockHttpServer {
private final static Logger LOGGER = LoggerFactory.getLogger(MockHttpServer.class);
public class ExpectationHandler implements Container {
public ExpectationHandler() {
}
@Override
public void handle(final Request req, final Response response) {
try {
final FullHttpRequest receivedFullRequest = RequestConvertor.convert(req);
// We need to copy it because HttpResponseProvider works with HttpRequest, not with FullHttpRequest.
// If we did not copy matching would fail.
final HttpRequest receivedRequest = new HttpRequestImpl(receivedFullRequest);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Got request: " + receivedRequest);
}
final HttpResponse expectedResponse = responseProvider.getResponse(receivedRequest);
if (expectedResponse != null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Got response for request: " + expectedResponse);
}
response.setCode(expectedResponse.getHttpCode());
if (!StringUtils.isEmpty(expectedResponse.getContentType())) {
response.set("Content-Type", expectedResponse.getContentType());
}
OutputStream body = null;
try {
body = response.getOutputStream();
if (expectedResponse.getContent() != null) {
body.write(expectedResponse.getContent());
}
body.close();
} catch (final IOException e) {
LOGGER.error("IOException when getting response content.", e);
}
} else {
LOGGER.error("Did receive an unexpected request:" + receivedRequest);
response.setCode(noMatchFoundResponseCode);
response.set("Content-Type", "text/plain;charset=utf-8");
PrintStream body;
try {
body = response.getPrintStream();
body.print("Received unexpected request " + receivedRequest);
body.close();
} catch (final IOException e) {
LOGGER.error("IOException when writing response content.", e);
}
}
} catch (final Exception e) {
LOGGER.error("Unexpected exception.", e);
response.setCode(exceptionResponseCode);
try {
response.getPrintStream().close();
} catch (final IOException e2) {
LOGGER.error("IOException when writing response content.", e2);
}
}
}
public void verify() throws UnsatisfiedExpectationException {
responseProvider.verify();
}
}
private ExpectationHandler handler;
private final HttpResponseProvider responseProvider;
private final int port;
public static final String GET = "GET";
public static final String POST = "POST";
public static final String PUT = "PUT";
public static final String DELETE = "DELETE";
private Connection connection;
private int connectedPort = -1;
private int noMatchFoundResponseCode = 598;
private int exceptionResponseCode = 599;
/**
* Creates a new instance.
*
* @param port Port on which mock server should operate. If you provide 0 as port number a free port will be choosen for you. You can get the port through {@link MockHttpServer#getPort()}
* @param responseProvider {@link HttpResponseProvider}. Should not be <code>null</code>.
*/
public MockHttpServer(final int port, final HttpResponseProvider responseProvider) {
Validate.notNull(responseProvider);
this.port = port;
this.responseProvider = responseProvider;
}
/**
* Starts the server.
*
* @return Port used by server.
* @throws IOException In case starting fails.
*/
public int start() throws IOException {
handler = new ExpectationHandler();
connection = new SocketConnection(handler);
final SocketAddress address = new InetSocketAddress(port);
final InetSocketAddress connectedAddress = (InetSocketAddress) connection.connect(address);
connectedPort = connectedAddress.getPort();
return connectedPort;
}
/**
* Return the port used by the server.
*
* @return The port in case the server is successfully started or -1 in case the server has not been started yet.
*/
public int getPort() {
return connectedPort;
}
/**
* Closes the server.
*
* @throws IOException In case closing fails.
*/
public void stop() throws IOException {
connection.close();
}
/**
* Verify if we got all requests as expected.
*
* @throws UnsatisfiedExpectationException In case we got unexpected requests or we did not get all requests we expected.
*/
public void verify() throws UnsatisfiedExpectationException {
handler.verify();
}
/**
* Allows you to set a custom response code to be returned when no matching response is found.
* <p/>
* If not set the default code is 598.
*
* @param code HTTP response code to return when no matching response is found.
*/
public void setNoMatchFoundResponseCode(final int code) {
noMatchFoundResponseCode = code;
}
/**
* Allows to set a custom response code to be returned when an unexpected exception happens.
* <p/>
* If not set the default code is 599.
*
* @param code HTTP response code to return when an unexpected exception happens.
*/
public void setExceptionResponseCode(final int code) {
exceptionResponseCode = code;
}
}