/*
* Copyright 2015-2017 Amazon.com, Inc. or its affiliates. All Rights
* Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is
* distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either
* express or implied. See the License for the specific language
* governing
* permissions and limitations under the License.
*/
package com.amazonaws.http.server;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine;
import com.amazonaws.util.IOUtils;
import com.amazonaws.util.StringInputStream;
/**
* MockServer implementation with several different configurable behaviors
*/
public class MockServer {
public enum ServerBehavior {
UNRESPONSIVE,
OVERLOADED,
DUMMY_RESPONSE;
}
public static MockServer createMockServer(ServerBehavior serverBehavior) {
switch (serverBehavior) {
case UNRESPONSIVE:
return new MockServer(new UnresponsiveServerBehavior());
case OVERLOADED:
return new MockServer(new OverloadedServerBehavior());
default:
throw new IllegalArgumentException("Unsupported implementation for server issue: " + serverBehavior);
}
}
private final ServerBehaviorStrategy serverBehaviorStrategy;
/**
* The server socket which the test service will listen to.
*/
private ServerSocket serverSocket;
private Thread listenerThread;
public MockServer(final ServerBehaviorStrategy serverBehaviorStrategy) {
this.serverBehaviorStrategy = serverBehaviorStrategy;
}
public void startServer() {
try {
serverSocket = new ServerSocket(0); // auto-assign a port at localhost
System.out.println("Listening on port " + serverSocket.getLocalPort());
} catch (IOException e) {
throw new RuntimeException("Unable to start the server socker.", e);
}
listenerThread = new MockServerListenerThread(serverSocket, serverBehaviorStrategy);
listenerThread.setDaemon(true);
listenerThread.start();
}
public void stopServer() {
listenerThread.interrupt();
try {
listenerThread.join(10 * 1000);
} catch (InterruptedException e1) {
System.err.println("The listener thread didn't terminate " + "after waiting for 10 seconds.");
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
throw new RuntimeException("Unable to stop the server socket.", e);
}
}
}
public int getPort() {
return serverSocket.getLocalPort();
}
public String getEndpoint() {
return "http://localhost:" + getPort();
}
public String getHttpsEndpoint() {
return "https://localhost:" + getPort();
}
private static class MockServerListenerThread extends Thread {
/** The server socket which this thread listens and responds to */
private final ServerSocket serverSocket;
private final ServerBehaviorStrategy behaviorStrategy;
public MockServerListenerThread(ServerSocket serverSocket, ServerBehaviorStrategy behaviorStrategy) {
super(behaviorStrategy.getClass().getName());
this.serverSocket = serverSocket;
this.behaviorStrategy = behaviorStrategy;
setDaemon(true);
}
@Override
public void run() {
this.behaviorStrategy.runServer(serverSocket);
}
}
public interface ServerBehaviorStrategy {
public void runServer(ServerSocket serverSocket);
}
/**
* A daemon thread which runs a simple server that listens to a specific server socket. Whenever
* a connection is created, the server simply keeps holding the connection open while
* periodically writing data. The test client talking to this server is expected to timeout
* appropriately, instead of hanging and waiting for the response forever.
*/
public static class OverloadedServerBehavior implements ServerBehaviorStrategy {
@Override
public void runServer(ServerSocket serverSocket) {
try {
while (true) {
try {
Socket socket = serverSocket.accept();
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeBytes("HTTP/1.1 200 OK\r\n");
out.writeBytes("Content-Type: text/html\r\n\r\n");
out.writeBytes("<html><head></head><body><h1>Hello.");
while (true) {
Thread.sleep(1 * 1000);
out.writeBytes("Hi.");
}
} catch (SocketException se) {
}
}
} catch (IOException e) {
throw new RuntimeException("Error when waiting for new socket connection.", e);
} catch (InterruptedException e) {
System.err.println("Socket listener thread interrupted. Terminating the thread...");
return;
}
}
}
/**
* A daemon thread which runs a simple server that listens to a specific server socket. Whenever
* a connection is created, the server simply keeps holding the connection open and no byte will
* be written to the socket. The test client talking to this server is expected to timeout
* appropriately, instead of hanging and waiting for the response forever.
*/
public static class UnresponsiveServerBehavior implements ServerBehaviorStrategy {
@Override
public void runServer(ServerSocket serverSocket) {
try {
Socket socket = serverSocket.accept();
System.out.println("Socket created on port " + socket.getLocalPort());
while (true) {
System.out.println("I don't want to talk.");
Thread.sleep(10 * 1000);
}
} catch (IOException e) {
throw new RuntimeException("Error when waiting for new socket connection.", e);
} catch (InterruptedException e) {
System.err.println("Socket listener thread interrupted. Terminating the thread...");
return;
}
}
}
public static class DummyResponseServerBehavior implements ServerBehaviorStrategy {
private final HttpResponse response;
private String content;
public static DummyResponseServerBehavior build(int statusCode, String statusMessage, String content) {
HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), statusCode, statusMessage));
setEntity(response, content);
response.addHeader("Content-Length", String.valueOf(content.getBytes().length));
response.addHeader("Connection", "close");
return new DummyResponseServerBehavior(response);
}
private static void setEntity(HttpResponse response, String content) {
try {
BasicHttpEntity entity = new BasicHttpEntity();
entity.setContent(new StringInputStream(content));
response.setEntity(entity);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public DummyResponseServerBehavior(HttpResponse response) {
this.response = response;
try {
this.content = IOUtils.toString(response.getEntity().getContent());
} catch (Exception e) {
}
}
@Override
public void runServer(ServerSocket serverSocket) {
try {
while (true) {
try {
Socket socket = serverSocket.accept();
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
StringBuilder builder = new StringBuilder();
builder.append(response.getStatusLine().toString() + "\r\n");
for (Header header : response.getAllHeaders()) {
builder.append(header.getName() + ":" + header.getValue() + "\r\n");
}
builder.append("\r\n");
builder.append(content);
System.out.println(builder.toString());
out.writeBytes(builder.toString());
} catch (SocketException se) {
}
}
} catch (IOException e) {
throw new RuntimeException("Error when waiting for new socket connection.", e);
}
}
}
}