blob: 7a4421eed98ed1fba5ff250df5da19623a866d42 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2012 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.commons.sdk.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import junit.framework.AssertionFailedError;
/**
* A server that listens for requests and returns preconfigured responses, used for testing HTTP clients.
*
* @author Steffen Pingel
*/
public class MockServer implements Runnable {
public static String CRLF = "\r\n";
public static class Message {
public List<String> headers = new ArrayList<String>();
public String request;
private String charset;
public Message(String request) {
this.request = request;
this.charset = "ISO-8859-1";
}
public String getCharset() {
return charset;
}
public String getHeader(String prefix) {
if (headers != null) {
for (String header : headers) {
if (header.startsWith(prefix)) {
return header;
}
}
}
return null;
}
public String getHeaderValue(String prefix) {
String header = getHeader(prefix);
if (header != null) {
int i = header.indexOf(": ");
return (i != -1) ? header.substring(i + 2) : "";
}
return null;
}
public String getMethod() {
int i = request.indexOf(" ");
return (i != -1) ? request.substring(0, i) : request;
}
public void setCharset(String charset) {
this.charset = charset;
}
public String getStatusLine() {
int i = request.indexOf("\n");
return (i != -1) ? request.substring(0, i) : request;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(request);
sb.append("\n");
if (headers != null) {
for (String header : headers) {
sb.append(header);
sb.append("\n");
}
}
sb.append("\n");
return sb.toString().replaceAll("\n", CRLF);
}
}
public static final String HEADER_CONNECTION_CLOSE = "Connection: Close";
public static final String HEADER_NO_CONTENT = "Content-Length: 0";
public static final Message NOT_FOUND = new Message("HTTP/1.1 404 Not Found");
public static final Message OK = new Message("HTTP/1.1 200 OK");
public static final Message TIMEOUT = new Message("HTTP/1.1 200 OK");
public static final Message UNAUTHORIZED = new Message("HTTP/1.1 401 Unauthorized");
public static final Message SERVICE_UNVAILABLE = createEmptyMessage("HTTP/1.1 503 Service Unavailable");
static {
NOT_FOUND.headers.add(HEADER_CONNECTION_CLOSE);
OK.headers.add(HEADER_CONNECTION_CLOSE);
SERVICE_UNVAILABLE.headers.add(HEADER_CONNECTION_CLOSE);
TIMEOUT.headers.add("Content-Length: 500");
UNAUTHORIZED.headers.add("WWW-Authenticate: Basic realm=\"Test\"");
}
private static Message createEmptyMessage(String status) {
return new Message(status + "\n" + HEADER_NO_CONTENT + "\n\n");
}
private boolean autoClose;
private IOException exception;
private final int listenPort;
private final List<Message> requests = new ArrayList<Message>();
private final List<Message> responses = new ArrayList<Message>();
private Thread runner;
private volatile ServerSocket serverSocket;
private volatile boolean stopped;
private boolean waitForResponse;
private boolean closeOnConnect;
private boolean debugEnabled;
public MockServer() {
this(0);
}
public MockServer(int listenPort) {
this.listenPort = listenPort;
this.autoClose = true;
}
public synchronized void addRequest(Message request) {
this.requests.add(request);
notifyAll();
}
public synchronized void addResponse(Message response) {
this.responses.add(response);
notifyAll();
}
public synchronized void addResponse(String response) {
this.responses.add(new Message(response));
notifyAll();
}
public synchronized void checkForException() throws IOException {
if (exception != null) {
throw exception;
}
}
public int getPort() {
return serverSocket.getLocalPort();
}
public synchronized Message getRequest() throws InterruptedException {
if (requests.isEmpty()) {
throw new AssertionFailedError("Request list is empty");
}
return requests.remove(0);
}
private void handleConnection(Socket socket) {
try {
while (!closeOnConnect && !stopped) {
Message request = readMessage(socket.getInputStream());
if (stopped || request == null) {
break;
}
addRequest(request);
if (hasMoreResponses() || waitForResponse) {
Message response = waitForResponse();
if (stopped || response == null) {
break;
}
writeMessage(response, socket.getOutputStream());
if (autoClose && response.toString().contains(HEADER_CONNECTION_CLOSE)) {
break;
}
} else {
writeMessage(SERVICE_UNVAILABLE, socket.getOutputStream());
System.err.println("Unexpected request: ");
System.err.println(request.toString());
break;
}
}
} catch (IOException e) {
setException(e);
} catch (InterruptedException e) {
} finally {
try {
socket.close();
} catch (IOException e1) {
}
}
}
private synchronized boolean hasMoreResponses() {
return !responses.isEmpty();
}
public synchronized boolean hasRequest() {
return !requests.isEmpty();
}
public boolean isAutoClose() {
return autoClose;
}
public boolean isCloseOnConnect() {
return closeOnConnect;
}
private Message readMessage(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Message message = null;
String line;
while ((line = reader.readLine()) != null) {
if (line.length() == 0) {
if (message == null) {
throw new IOException("Incomplete message");
}
return message;
}
if (message == null) {
if (isDebugEnabled()) {
System.err.println("< " + line);
}
message = new Message(line);
} else {
message.headers.add(line);
}
}
throw new EOFException();
}
public void run() {
try {
serverSocket = new ServerSocket(listenPort);
while (!stopped) {
Socket socket = serverSocket.accept();
handleConnection(socket);
}
} catch (InterruptedIOException e) {
// ignore
} catch (IOException e) {
setException(e);
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
}
}
}
}
public void setCloseOnConnect(boolean closeOnConnect) {
this.closeOnConnect = closeOnConnect;
}
public void setAutoClose(boolean autoClose) {
this.autoClose = autoClose;
}
private synchronized void setException(IOException exception) {
this.exception = exception;
notifyAll();
}
public void setWaitForResponse(boolean waitForResponse) {
this.waitForResponse = waitForResponse;
}
public void start() {
runner = new Thread(this, "MockServer :" + listenPort);
runner.start();
}
public int startAndWait() throws InterruptedException {
start();
while (serverSocket == null || serverSocket.getLocalPort() == -1) {
Thread.sleep(100);
}
return serverSocket.getLocalPort();
}
public String getUrl() {
InetSocketAddress address = new InetSocketAddress("localhost", serverSocket.getLocalPort());
return "http://" + address.getHostName() + ":" + address.getPort() + "/";
// try {
// return "http://" + InetAddress.getLocalHost().getHostAddress() + ":" + serverSocket.getLocalPort();
// } catch (UnknownHostException e) {
// return "http://localhost:" + serverSocket.getLocalPort();
// }
}
public void stop() {
stopped = true;
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e1) {
// ignore
}
runner.interrupt();
try {
runner.join(500);
} catch (InterruptedException e) {
}
}
public synchronized Message waitForRequest() throws InterruptedException {
while (requests.isEmpty()) {
if (stopped) {
return null;
}
wait();
}
return requests.remove(0);
}
public synchronized Message waitForResponse() throws InterruptedException {
while (!stopped && responses.isEmpty()) {
if (stopped || autoClose) {
return null;
}
wait();
}
return responses.remove(0);
}
private void writeMessage(Message message, OutputStream out) throws IOException {
if (isDebugEnabled()) {
System.err.println("> " + message.getStatusLine());
}
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, message.getCharset()));
writer.write(message.toString());
writer.flush();
}
public boolean isDebugEnabled() {
return debugEnabled;
}
public void setDebugEnabled(boolean debugEnabled) {
this.debugEnabled = debugEnabled;
}
}