/******************************************************************************* * 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; } }