summaryrefslogtreecommitdiffstats
path: root/support/src
diff options
context:
space:
mode:
Diffstat (limited to 'support/src')
-rw-r--r--support/src/test/java/tests/support/Support_HttpConstants.java157
-rw-r--r--support/src/test/java/tests/support/Support_TestWebData.java197
-rw-r--r--support/src/test/java/tests/support/Support_TestWebServer.java942
3 files changed, 1296 insertions, 0 deletions
diff --git a/support/src/test/java/tests/support/Support_HttpConstants.java b/support/src/test/java/tests/support/Support_HttpConstants.java
new file mode 100644
index 0000000..e99bdc1
--- /dev/null
+++ b/support/src/test/java/tests/support/Support_HttpConstants.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 tests.support;
+
+interface Support_HttpConstants {
+ /** 2XX: generally "OK" */
+ public static final int HTTP_OK = 200;
+ public static final int HTTP_CREATED = 201;
+ public static final int HTTP_ACCEPTED = 202;
+ public static final int HTTP_NOT_AUTHORITATIVE = 203;
+ public static final int HTTP_NO_CONTENT = 204;
+ public static final int HTTP_RESET = 205;
+ public static final int HTTP_PARTIAL = 206;
+
+ /** 3XX: relocation/redirect */
+ public static final int HTTP_MULT_CHOICE = 300;
+ public static final int HTTP_MOVED_PERM = 301;
+ public static final int HTTP_MOVED_TEMP = 302;
+ public static final int HTTP_SEE_OTHER = 303;
+ public static final int HTTP_NOT_MODIFIED = 304;
+ public static final int HTTP_USE_PROXY = 305;
+
+ /** 4XX: client error */
+ public static final int HTTP_BAD_REQUEST = 400;
+ public static final int HTTP_UNAUTHORIZED = 401;
+ public static final int HTTP_PAYMENT_REQUIRED = 402;
+ public static final int HTTP_FORBIDDEN = 403;
+ public static final int HTTP_NOT_FOUND = 404;
+ public static final int HTTP_BAD_METHOD = 405;
+ public static final int HTTP_NOT_ACCEPTABLE = 406;
+ public static final int HTTP_PROXY_AUTH = 407;
+ public static final int HTTP_CLIENT_TIMEOUT = 408;
+ public static final int HTTP_CONFLICT = 409;
+ public static final int HTTP_GONE = 410;
+ public static final int HTTP_LENGTH_REQUIRED = 411;
+ public static final int HTTP_PRECON_FAILED = 412;
+ public static final int HTTP_ENTITY_TOO_LARGE = 413;
+ public static final int HTTP_REQ_TOO_LONG = 414;
+ public static final int HTTP_UNSUPPORTED_TYPE = 415;
+
+ /** 5XX: server error */
+ public static final int HTTP_SERVER_ERROR = 500;
+ public static final int HTTP_INTERNAL_ERROR = 501;
+ public static final int HTTP_BAD_GATEWAY = 502;
+ public static final int HTTP_UNAVAILABLE = 503;
+ public static final int HTTP_GATEWAY_TIMEOUT = 504;
+ public static final int HTTP_VERSION = 505;
+
+ /** Method IDs */
+ public static final int UNKNOWN_METHOD = 0;
+ public static final int GET_METHOD = 1;
+ public static final int HEAD_METHOD = 2;
+ public static final int POST_METHOD = 3;
+
+ public static final String[] requestHeaders = {
+ "cache-control",
+ "connection",
+ "date",
+ "pragma",
+ "trailer",
+ "transfer-encoding",
+ "upgrade",
+ "via",
+ "warning",
+ "accept",
+ "accept-charset",
+ "accept-encoding",
+ "accept-language",
+ "authorization",
+ "expect",
+ "from",
+ "host",
+ "if-match",
+ "if-modified-since",
+ "if-none-match",
+ "if-range",
+ "if-unmodified-since",
+ "max-forwards",
+ "proxy-authentication",
+ "range",
+ "referer",
+ "te",
+ "user-agent",
+ "keep-alive",
+ "allow",
+ "content-encoding",
+ "content-language",
+ "content-length",
+ "content-location",
+ "content-md5",
+ "content-range",
+ "content-type",
+ "expires",
+ "last-modified",
+ "location",
+ "server"
+
+ };
+
+ public static final int REQ_UNKNOWN = -1;
+ public static final int REQ_CACHE_CONTROL = 0;
+ public static final int REQ_CONNECTION = 1;
+ public static final int REQ_DATE = 2;
+ public static final int REQ_PRAGMA = 3;
+ public static final int REQ_TRAILER = 4;
+ public static final int REQ_TRANSFER_ENCODING = 5;
+ public static final int REQ_UPGRADE = 6;
+ public static final int REQ_VIA = 7;
+ public static final int REQ_WARNING = 8;
+ public static final int REQ_ACCEPT = 9;
+ public static final int REQ_ACCEPT_CHARSET = 10;
+ public static final int REQ_ACCEPT_ENCODING = 11;
+ public static final int REQ_ACCEPT_LANGUAGE = 12;
+ public static final int REQ_AUTHORIZATION = 13;
+ public static final int REQ_EXPECT = 14;
+ public static final int REQ_FROM = 15;
+ public static final int REQ_HOST = 16;
+ public static final int REQ_IF_MATCH = 17;
+ public static final int REQ_IF_MODIFIED_SINCE = 18;
+ public static final int REQ_IF_NONE_MATCH = 19;
+ public static final int REQ_IF_RANGE = 20;
+ public static final int REQ_IF_UNMODIFIED_SINCE = 21;
+ public static final int REQ_MAX_FORWARDS = 22;
+ public static final int REQ_PROXY_AUTHENTICATION = 23;
+ public static final int REQ_RANGE = 24;
+ public static final int REQ_REFERER = 25;
+ public static final int REQ_TE = 26;
+ public static final int REQ_USER_AGENT = 27;
+ public static final int REQ_KEEP_ALIVE = 28;
+ public static final int REQ_ALLOW = 29;
+ public static final int REQ_CONTENT_ENCODING = 30;
+ public static final int REQ_CONTENT_LANGUAGE = 31;
+ public static final int REQ_CONTENT_LENGTH = 32;
+ public static final int REQ_CONTENT_LOCATION = 33;
+ public static final int REQ_CONTENT_MD5 = 34;
+ public static final int REQ_CONTENT_RANGE = 35;
+ public static final int REQ_CONTENT_TYPE = 36;
+ public static final int REQ_EXPIRES = 37;
+ public static final int REQ_LAST_MODIFIED = 38;
+ public static final int REQ_LOCATION = 39;
+ public static final int REQ_SERVER = 40;
+
+}
diff --git a/support/src/test/java/tests/support/Support_TestWebData.java b/support/src/test/java/tests/support/Support_TestWebData.java
new file mode 100644
index 0000000..6f414ea
--- /dev/null
+++ b/support/src/test/java/tests/support/Support_TestWebData.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 tests.support;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * Represents test data used by the Request API tests
+ */
+public class Support_TestWebData {
+
+ /*
+ * Simple Html body
+ * <html>
+ * <body>
+ * <h1>Hello World!</h1>
+ * </body>
+ * </html>
+ */
+ public final static byte[] test1 = {
+ (byte)0x3c, (byte)0x68, (byte)0x74, (byte)0x6d,
+ (byte)0x6c, (byte)0x3e, (byte)0x0a, (byte)0x3c,
+ (byte)0x62, (byte)0x6f, (byte)0x64, (byte)0x79,
+ (byte)0x3e, (byte)0x0a, (byte)0x3c, (byte)0x68,
+ (byte)0x31, (byte)0x3e, (byte)0x48, (byte)0x65,
+ (byte)0x6c, (byte)0x6c, (byte)0x6f, (byte)0x20,
+ (byte)0x57, (byte)0x6f, (byte)0x72, (byte)0x6c,
+ (byte)0x64, (byte)0x21, (byte)0x3c, (byte)0x2f,
+ (byte)0x68, (byte)0x31, (byte)0x3e, (byte)0x0a,
+ (byte)0x3c, (byte)0x2f, (byte)0x62, (byte)0x6f,
+ (byte)0x64, (byte)0x79, (byte)0x3e, (byte)0x0a,
+ (byte)0x3c, (byte)0x2f, (byte)0x68, (byte)0x74,
+ (byte)0x6d, (byte)0x6c, (byte)0x3e, (byte)0x0a
+ };
+
+ /*
+ * Simple Html body
+ * <html>
+ * <body>
+ * <h1>Hello World!</h1>
+ * </body>
+ * </html>
+ */
+ public final static byte[] test2 = {
+ (byte)0x3c, (byte)0x68, (byte)0x74, (byte)0x6d,
+ (byte)0x6c, (byte)0x3e, (byte)0x0a, (byte)0x3c,
+ (byte)0x62, (byte)0x6f, (byte)0x64, (byte)0x79,
+ (byte)0x3e, (byte)0x0a, (byte)0x3c, (byte)0x68,
+ (byte)0x31, (byte)0x3e, (byte)0x48, (byte)0x65,
+ (byte)0x6c, (byte)0x6c, (byte)0x6f, (byte)0x20,
+ (byte)0x57, (byte)0x6f, (byte)0x72, (byte)0x6c,
+ (byte)0x64, (byte)0x21, (byte)0x3c, (byte)0x2f,
+ (byte)0x68, (byte)0x31, (byte)0x3e, (byte)0x0a,
+ (byte)0x3c, (byte)0x2f, (byte)0x62, (byte)0x6f,
+ (byte)0x64, (byte)0x79, (byte)0x3e, (byte)0x0a,
+ (byte)0x3c, (byte)0x2f, (byte)0x68, (byte)0x74,
+ (byte)0x6d, (byte)0x6c, (byte)0x3e, (byte)0x0a
+ };
+
+ // string for test request post body
+ public final static String postContent = "user=111";
+
+ // Array of all test data
+ public final static byte[][] tests = {
+ test1,
+ test2
+ };
+
+ /**
+ * List of static test cases for use with test server
+ */
+ public static Support_TestWebData[] testParams = {
+ new Support_TestWebData(52, 14000000, "test1", "text/html", false, 0),
+ new Support_TestWebData(52, 14000002, "test2", "unknown/unknown", false,
+ new Date().getTime() + 100000)
+ };
+
+ /**
+ * List of response strings for use by the test server
+ */
+ public static String[] testServerResponse = {
+ "Redirecting 301",
+ "Redirecting 302",
+ "Redirecting 303",
+ "Redirecting 307"
+ };
+
+ // Redirection indices into testServerResponse
+ public final static int REDIRECT_301 = 0;
+ public final static int REDIRECT_302 = 1;
+ public final static int REDIRECT_303 = 2;
+ public final static int REDIRECT_307 = 3;
+
+ /**
+ * Creates a data package with information used by the server when responding
+ * to requests
+ */
+ Support_TestWebData(int length, int lastModified, String name, String type, boolean isDir, long expDate) {
+ testLength = length;
+ testLastModified = lastModified;
+ testName = name;
+ testType = type;
+ testDir = isDir;
+ testExp = expDate;
+ }
+
+ /**
+ * Creates a data package with information used by the server when responding
+ * to requests
+ */
+ private Support_TestWebData(String path, String type) {
+ File file = new File(path);
+ testLength = file.length();
+ testLastModified = file.lastModified();
+ testName = file.getName();
+ testType = type;
+ testDir = file.isDirectory();
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ FileInputStream in = null;
+ try {
+ in = new FileInputStream(file);
+ while (in.available() > 0) {
+ out.write(in.read());
+ }
+ in.close();
+ out.flush();
+ test0Data = out.toByteArray();
+ out.close();
+ test0DataAvailable = true;
+ return;
+ } catch (Exception e) {
+ // ignore
+ e.printStackTrace();
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ public static void initDynamicTestWebData(String path, String type) {
+ test0Params = new Support_TestWebData(path, type);
+ }
+
+ // Length of test entity body
+ public long testLength;
+
+ // Last modified date value (milliseconds)
+ public long testLastModified;
+
+ // Test identification name
+ public String testName;
+
+ // The MIME type to assume for this test
+ public String testType;
+
+ // The expiration date
+ public long testExp;
+
+ // Indicates if this is a directory or not
+ public boolean testDir;
+
+ // Indicate if test0 data has bin initialized
+ public static boolean test0DataAvailable = false;
+
+ // test0 data
+ public static byte[] test0Data;
+
+ // test0 parameters
+ public static Support_TestWebData test0Params;
+}
diff --git a/support/src/test/java/tests/support/Support_TestWebServer.java b/support/src/test/java/tests/support/Support_TestWebServer.java
new file mode 100644
index 0000000..8ee7248
--- /dev/null
+++ b/support/src/test/java/tests/support/Support_TestWebServer.java
@@ -0,0 +1,942 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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 tests.support;
+
+import java.io.*;
+import java.lang.Thread;
+import java.net.*;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * TestWebServer is a simulated controllable test server that
+ * can respond to requests from HTTP clients.
+ *
+ * The server can be controlled to change how it reacts to any
+ * requests, and can be told to simulate various events (such as
+ * network failure) that would happen in a real environment.
+ */
+public class Support_TestWebServer implements Support_HttpConstants {
+
+ /* static class data/methods */
+
+ /* The ANDROID_LOG_TAG */
+ private final static String LOGTAG = "httpsv";
+
+ /* Where worker threads stand idle */
+ Vector threads = new Vector();
+
+ /* List of all active worker threads */
+ Vector activeThreads = new Vector();
+
+ /* timeout on client connections */
+ int timeout = 0;
+
+ /* max # worker threads */
+ int workers = 5;
+
+ /* Default port for this server to listen on */
+ final static int DEFAULT_PORT = 8080;
+
+ /* Default socket timeout value */
+ final static int DEFAULT_TIMEOUT = 5000;
+
+ /* Version string (configurable) */
+ protected String HTTP_VERSION_STRING = "HTTP/1.1";
+
+ /* Indicator for whether this server is configured as a HTTP/1.1
+ * or HTTP/1.0 server
+ */
+ private boolean http11 = true;
+
+ /* The thread handling new requests from clients */
+ private AcceptThread acceptT;
+
+ /* timeout on client connections */
+ int mTimeout;
+
+ /* Server port */
+ int mPort;
+
+ /* Switch on/off logging */
+ boolean mLog = false;
+
+ /* If set, this will keep connections alive after a request has been
+ * processed.
+ */
+ boolean keepAlive = true;
+
+ /* If set, this will cause response data to be sent in 'chunked' format */
+ boolean chunked = false;
+
+ /* If set, this will indicate a new redirection host */
+ String redirectHost = null;
+
+ /* If set, this indicates the reason for redirection */
+ int redirectCode = -1;
+
+ /* Set the number of connections the server will accept before shutdown */
+ int acceptLimit = 100;
+
+ /* Count of number of accepted connections */
+ int acceptedConnections = 0;
+
+ public Support_TestWebServer() {
+ }
+
+ /**
+ * Initialize a new server with default port and timeout.
+ * @param log Set true if you want trace output
+ */
+ public void initServer(boolean log) throws Exception {
+ initServer(DEFAULT_PORT, DEFAULT_TIMEOUT, log);
+ }
+
+ /**
+ * Initialize a new server with default timeout.
+ * @param port Sets the server to listen on this port
+ * @param log Set true if you want trace output
+ */
+ public void initServer(int port, boolean log) throws Exception {
+ initServer(port, DEFAULT_TIMEOUT, log);
+ }
+
+ /**
+ * Initialize a new server with default timeout and disabled log.
+ * @param port Sets the server to listen on this port
+ * @param servePath the path to the dynamic web test data
+ * @param contentType the type of the dynamic web test data
+ */
+ public void initServer(int port, String servePath, String contentType)
+ throws Exception {
+ Support_TestWebData.initDynamicTestWebData(servePath, contentType);
+ initServer(port, DEFAULT_TIMEOUT, false);
+ }
+
+ /**
+ * Initialize a new server with default port and timeout.
+ * @param port Sets the server to listen on this port
+ * @param timeout Indicates the period of time to wait until a socket is
+ * closed
+ * @param log Set true if you want trace output
+ */
+ public void initServer(int port, int timeout, boolean log) throws Exception {
+ mPort = port;
+ mTimeout = timeout;
+ mLog = log;
+ keepAlive = true;
+
+ if (acceptT == null) {
+ acceptT = new AcceptThread();
+ acceptT.init();
+ acceptT.start();
+ }
+ }
+
+ /**
+ * Print to the log file (if logging enabled)
+ * @param s String to send to the log
+ */
+ protected void log(String s) {
+ if (mLog) {
+ Logger.global.fine(s);
+ }
+ }
+
+ /**
+ * Set the server to be an HTTP/1.0 or HTTP/1.1 server.
+ * This should be called prior to any requests being sent
+ * to the server.
+ * @param set True for the server to be HTTP/1.1, false for HTTP/1.0
+ */
+ public void setHttpVersion11(boolean set) {
+ http11 = set;
+ if (set) {
+ HTTP_VERSION_STRING = "HTTP/1.1";
+ } else {
+ HTTP_VERSION_STRING = "HTTP/1.0";
+ }
+ }
+
+ /**
+ * Call this to determine whether server connection should remain open
+ * @param value Set true to keep connections open after a request
+ * completes
+ */
+ public void setKeepAlive(boolean value) {
+ keepAlive = value;
+ }
+
+ /**
+ * Call this to indicate whether chunked data should be used
+ * @param value Set true to make server respond with chunk encoded
+ * content data.
+ */
+ public void setChunked(boolean value) {
+ chunked = value;
+ }
+
+ /**
+ * Call this to specify the maximum number of sockets to accept
+ * @param limit The number of sockets to accept
+ */
+ public void setAcceptLimit(int limit) {
+ acceptLimit = limit;
+ }
+
+ /**
+ * Call this to indicate redirection port requirement.
+ * When this value is set, the server will respond to a request with
+ * a redirect code with the Location response header set to the value
+ * specified.
+ * @param redirect The location to be redirected to
+ * @param redirectCode The code to send when redirecting
+ */
+ public void setRedirect(String redirect, int code) {
+ redirectHost = redirect;
+ redirectCode = code;
+ log("Server will redirect output to "+redirect+" code "+code);
+ }
+
+ /**
+ * Cause the thread accepting connections on the server socket to close
+ */
+ public void close() {
+ /* Stop the Accept thread */
+ if (acceptT != null) {
+ log("Closing AcceptThread"+acceptT);
+ acceptT.close();
+ acceptT = null;
+ }
+ }
+ /**
+ * The AcceptThread is responsible for initiating worker threads
+ * to handle incoming requests from clients.
+ */
+ class AcceptThread extends Thread {
+
+ ServerSocket ss = null;
+ boolean running = false;
+
+ public void init() {
+ // Networking code doesn't support ServerSocket(port) yet
+ InetSocketAddress ia = new InetSocketAddress(mPort);
+ while (true) {
+ try {
+ ss = new ServerSocket();
+ // Socket timeout functionality is not available yet
+ //ss.setSoTimeout(5000);
+ ss.setReuseAddress(true);
+ ss.bind(ia);
+ break;
+ } catch (IOException e) {
+ log("IOException in AcceptThread.init()");
+ // wait and retry
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+ }
+
+ /**
+ * Main thread responding to new connections
+ */
+ public synchronized void run() {
+ running = true;
+ try {
+ while (running) {
+ // Log.d(LOGTAG, "TestWebServer run() calling accept()");
+ Socket s = ss.accept();
+ acceptedConnections++;
+ if (acceptedConnections >= acceptLimit) {
+ running = false;
+ }
+
+ Worker w = null;
+ synchronized (threads) {
+ if (threads.isEmpty()) {
+ Worker ws = new Worker();
+ ws.setSocket(s);
+ activeThreads.addElement(ws);
+ (new Thread(ws, "additional worker")).start();
+ } else {
+ w = (Worker) threads.elementAt(0);
+ threads.removeElementAt(0);
+ w.setSocket(s);
+ }
+ }
+ }
+ } catch (SocketException e) {
+ log("SocketException in AcceptThread: probably closed during accept");
+ running = false;
+ } catch (IOException e) {
+ log("IOException in AcceptThread");
+ running = false;
+ }
+ log("AcceptThread terminated" + this);
+ }
+
+ // Close this socket
+ public void close() {
+ try {
+ running = false;
+ /* Stop server socket from processing further. Currently
+ this does not cause the SocketException from ss.accept
+ therefore the acceptLimit functionality has been added
+ to circumvent this limitation */
+ ss.close();
+
+ // Stop worker threads from continuing
+ for (Enumeration e = activeThreads.elements(); e.hasMoreElements();) {
+ Worker w = (Worker)e.nextElement();
+ w.close();
+ }
+ activeThreads.clear();
+
+ } catch (IOException e) {
+ /* We are shutting down the server, so we expect
+ * things to die. Don't propagate.
+ */
+ log("IOException caught by server socket close");
+ }
+ }
+ }
+
+ // Size of buffer for reading from the connection
+ final static int BUF_SIZE = 2048;
+
+ /* End of line byte sequence */
+ static final byte[] EOL = {(byte)'\r', (byte)'\n' };
+
+ /**
+ * The worker thread handles all interactions with a current open
+ * connection. If pipelining is turned on, this will allow this
+ * thread to continuously operate on numerous requests before the
+ * connection is closed.
+ */
+ class Worker implements Support_HttpConstants, Runnable {
+
+ /* buffer to use to hold request data */
+ byte[] buf;
+
+ /* Socket to client we're handling */
+ private Socket s;
+
+ /* Reference to current request method ID */
+ private int requestMethod;
+
+ /* Reference to current requests test file/data */
+ private String testID;
+
+ /* Reference to test number from testID */
+ private int testNum;
+
+ /* Reference to whether new request has been initiated yet */
+ private boolean readStarted;
+
+ /* Indicates whether current request has any data content */
+ private boolean hasContent = false;
+
+ boolean running = false;
+
+ /* Request headers are stored here */
+ private Hashtable<String, String> headers = new Hashtable<String, String>();
+
+ /* Create a new worker thread */
+ Worker() {
+ buf = new byte[BUF_SIZE];
+ s = null;
+ }
+
+ /**
+ * Called by the AcceptThread to unblock this Worker to process
+ * a request.
+ * @param s The socket on which the connection has been made
+ */
+ synchronized void setSocket(Socket s) {
+ this.s = s;
+ notify();
+ }
+
+ /**
+ * Called by the accept thread when it's closing. Potentially unblocks
+ * the worker thread to terminate properly
+ */
+ synchronized void close() {
+ running = false;
+ notify();
+ }
+
+ /**
+ * Main worker thread. This will wait until a request has
+ * been identified by the accept thread upon which it will
+ * service the thread.
+ */
+ public synchronized void run() {
+ running = true;
+ while(running) {
+ if (s == null) {
+ /* nothing to do */
+ try {
+ log(this+" Moving to wait state");
+ wait();
+ } catch (InterruptedException e) {
+ /* should not happen */
+ continue;
+ }
+ if (!running) break;
+ }
+ try {
+ handleClient();
+ } catch (Exception e) {
+ log("Exception during handleClient in the TestWebServer: "
+ + e.getMessage());
+ }
+ /* go back in wait queue if there's fewer
+ * than numHandler connections.
+ */
+ s = null;
+ Vector pool = threads;
+ synchronized (pool) {
+ if (pool.size() >= workers) {
+ /* too many threads, exit this one */
+ activeThreads.remove(this);
+ return;
+ } else {
+ pool.addElement(this);
+ }
+ }
+ }
+ log(this+" terminated");
+ }
+
+ /**
+ * Zero out the buffer from last time
+ */
+ private void clearBuffer() {
+ for (int i = 0; i < BUF_SIZE; i++) {
+ buf[i] = 0;
+ }
+ }
+
+ /**
+ * Utility method to read a line of data from the input stream
+ * @param is Inputstream to read
+ * @return number of bytes read
+ */
+ private int readOneLine(InputStream is) {
+
+ int read = 0;
+
+ clearBuffer();
+ try {
+ log("Reading one line: started ="+readStarted+" avail="+is.available());
+ StringBuilder log = new StringBuilder();
+ while ((!readStarted) || (is.available() > 0)) {
+ int data = is.read();
+ // We shouldn't get EOF but we need tdo check
+ if (data == -1) {
+ log("EOF returned");
+ return -1;
+ }
+
+ buf[read] = (byte)data;
+
+ log.append((char)data);
+
+ readStarted = true;
+ if (buf[read++]==(byte)'\n') {
+ log(log.toString());
+ return read;
+ }
+ }
+ } catch (IOException e) {
+ log("IOException from readOneLine");
+ }
+ return read;
+ }
+
+ /**
+ * Read a chunk of data
+ * @param is Stream from which to read data
+ * @param length Amount of data to read
+ * @return number of bytes read
+ */
+ private int readData(InputStream is, int length) {
+ int read = 0;
+ int count;
+ // At the moment we're only expecting small data amounts
+ byte[] buf = new byte[length];
+
+ try {
+ while (is.available() > 0) {
+ count = is.read(buf, read, length-read);
+ read += count;
+ }
+ } catch (IOException e) {
+ log("IOException from readData");
+ }
+ return read;
+ }
+
+ /**
+ * Read the status line from the input stream extracting method
+ * information.
+ * @param is Inputstream to read
+ * @return number of bytes read
+ */
+ private int parseStatusLine(InputStream is) {
+ int index;
+ int nread = 0;
+
+ log("Parse status line");
+ // Check for status line first
+ nread = readOneLine(is);
+ // Bomb out if stream closes prematurely
+ if (nread == -1) {
+ requestMethod = UNKNOWN_METHOD;
+ return -1;
+ }
+
+ if (buf[0] == (byte)'G' &&
+ buf[1] == (byte)'E' &&
+ buf[2] == (byte)'T' &&
+ buf[3] == (byte)' ') {
+ requestMethod = GET_METHOD;
+ log("GET request");
+ index = 4;
+ } else if (buf[0] == (byte)'H' &&
+ buf[1] == (byte)'E' &&
+ buf[2] == (byte)'A' &&
+ buf[3] == (byte)'D' &&
+ buf[4] == (byte)' ') {
+ requestMethod = HEAD_METHOD;
+ log("HEAD request");
+ index = 5;
+ } else if (buf[0] == (byte)'P' &&
+ buf[1] == (byte)'O' &&
+ buf[2] == (byte)'S' &&
+ buf[3] == (byte)'T' &&
+ buf[4] == (byte)' ') {
+ requestMethod = POST_METHOD;
+ log("POST request");
+ index = 5;
+ } else {
+ // Unhandled request
+ requestMethod = UNKNOWN_METHOD;
+ return -1;
+ }
+
+ // A valid method we understand
+ if (requestMethod > UNKNOWN_METHOD) {
+ // Read file name
+ int i = index;
+ while (buf[i] != (byte)' ') {
+ // There should be HTTP/1.x at the end
+ if ((buf[i] == (byte)'\n') || (buf[i] == (byte)'\r')) {
+ requestMethod = UNKNOWN_METHOD;
+ return -1;
+ }
+ i++;
+ }
+
+ testID = new String(buf, 0, index, i-index);
+ if (testID.startsWith("/")) {
+ testID = testID.substring(1);
+ }
+
+ return nread;
+ }
+ return -1;
+ }
+
+ /**
+ * Read a header from the input stream
+ * @param is Inputstream to read
+ * @return number of bytes read
+ */
+ private int parseHeader(InputStream is) {
+ int index = 0;
+ int nread = 0;
+ log("Parse a header");
+ // Check for status line first
+ nread = readOneLine(is);
+ // Bomb out if stream closes prematurely
+ if (nread == -1) {
+ requestMethod = UNKNOWN_METHOD;
+ return -1;
+ }
+ // Read header entry 'Header: data'
+ int i = index;
+ while (buf[i] != (byte)':') {
+ // There should be an entry after the header
+
+ if ((buf[i] == (byte)'\n') || (buf[i] == (byte)'\r')) {
+ return UNKNOWN_METHOD;
+ }
+ i++;
+ }
+
+ String headerName = new String(buf, 0, i);
+ i++; // Over ':'
+ while (buf[i] == ' ') {
+ i++;
+ }
+ String headerValue = new String(buf, i, nread-1);
+
+ headers.put(headerName, headerValue);
+ return nread;
+ }
+
+ /**
+ * Read all headers from the input stream
+ * @param is Inputstream to read
+ * @return number of bytes read
+ */
+ private int readHeaders(InputStream is) {
+ int nread = 0;
+ log("Read headers");
+ // Headers should be terminated by empty CRLF line
+ while (true) {
+ int headerLen = 0;
+ headerLen = parseHeader(is);
+ if (headerLen == -1)
+ return -1;
+ nread += headerLen;
+ if (headerLen <= 2) {
+ return nread;
+ }
+ }
+ }
+
+ /**
+ * Read content data from the input stream
+ * @param is Inputstream to read
+ * @return number of bytes read
+ */
+ private int readContent(InputStream is) {
+ int nread = 0;
+ log("Read content");
+ String lengthString = headers.get(requestHeaders[REQ_CONTENT_LENGTH]);
+ int length = new Integer(lengthString).intValue();
+
+ // Read content
+ length = readData(is, length);
+ return length;
+ }
+
+ /**
+ * The main loop, reading requests.
+ */
+ void handleClient() throws IOException {
+ InputStream is = new BufferedInputStream(s.getInputStream());
+ PrintStream ps = new PrintStream(s.getOutputStream());
+ int nread = 0;
+
+ /* we will only block in read for this many milliseconds
+ * before we fail with java.io.InterruptedIOException,
+ * at which point we will abandon the connection.
+ */
+ s.setSoTimeout(mTimeout);
+ s.setTcpNoDelay(true);
+
+ do {
+ nread = parseStatusLine(is);
+ if (requestMethod != UNKNOWN_METHOD) {
+
+ // If status line found, read any headers
+ nread = readHeaders(is);
+
+ // Then read content (if any)
+ // TODO handle chunked encoding from the client
+ if (headers.get(requestHeaders[REQ_CONTENT_LENGTH]) != null) {
+ nread = readContent(is);
+ }
+ } else {
+ if (nread > 0) {
+ /* we don't support this method */
+ ps.print(HTTP_VERSION_STRING + " " + HTTP_BAD_METHOD +
+ " unsupported method type: ");
+ ps.write(buf, 0, 5);
+ ps.write(EOL);
+ ps.flush();
+ } else {
+ }
+ if (!keepAlive || nread <= 0) {
+ headers.clear();
+ readStarted = false;
+
+ log("SOCKET CLOSED");
+ s.close();
+ return;
+ }
+ }
+
+ // Reset test number prior to outputing data
+ testNum = -1;
+
+ // Write out the data
+ printStatus(ps);
+ printHeaders(ps);
+
+ // Write line between headers and body
+ psWriteEOL(ps);
+
+ // Write the body
+ if (redirectCode == -1) {
+ switch (requestMethod) {
+ case GET_METHOD:
+ if ((testNum < -1) || (testNum > Support_TestWebData.tests.length - 1)) {
+ send404(ps);
+ } else {
+ sendFile(ps);
+ }
+ break;
+ case HEAD_METHOD:
+ // Nothing to do
+ break;
+ case POST_METHOD:
+ // Post method write body data
+ if ((testNum > 0) || (testNum < Support_TestWebData.tests.length - 1)) {
+ sendFile(ps);
+ }
+
+ break;
+ default:
+ break;
+ }
+ } else { // Redirecting
+ switch (redirectCode) {
+ case 301:
+ // Seems 301 needs a body by neon (although spec
+ // says SHOULD).
+ psPrint(ps, Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_301]);
+ break;
+ case 302:
+ //
+ psPrint(ps, Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_302]);
+ break;
+ case 303:
+ psPrint(ps, Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_303]);
+ break;
+ case 307:
+ psPrint(ps, Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_307]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ ps.flush();
+
+ // Reset for next request
+ readStarted = false;
+ headers.clear();
+
+ } while (keepAlive);
+
+ log("SOCKET CLOSED");
+ s.close();
+ }
+
+ // Print string to log and output stream
+ void psPrint(PrintStream ps, String s) throws IOException {
+ log(s);
+ ps.print(s);
+ }
+
+ // Print bytes to log and output stream
+ void psWrite(PrintStream ps, byte[] bytes, int len) throws IOException {
+ log(new String(bytes));
+ ps.write(bytes, 0, len);
+ }
+
+ // Print CRLF to log and output stream
+ void psWriteEOL(PrintStream ps) throws IOException {
+ log("CRLF");
+ ps.write(EOL);
+ }
+
+
+ // Print status to log and output stream
+ void printStatus(PrintStream ps) throws IOException {
+ // Handle redirects first.
+ if (redirectCode != -1) {
+ log("REDIRECTING TO "+redirectHost+" status "+redirectCode);
+ psPrint(ps, HTTP_VERSION_STRING + " " + redirectCode +" Moved permanently");
+ psWriteEOL(ps);
+ psPrint(ps, "Location: " + redirectHost);
+ psWriteEOL(ps);
+ return;
+ }
+
+
+ if (testID.startsWith("test")) {
+ testNum = Integer.valueOf(testID.substring(4))-1;
+ }
+
+ if ((testNum < -1) || (testNum > Support_TestWebData.tests.length - 1)) {
+ psPrint(ps, HTTP_VERSION_STRING + " " + HTTP_NOT_FOUND + " not found");
+ psWriteEOL(ps);
+ } else {
+ psPrint(ps, HTTP_VERSION_STRING + " " + HTTP_OK+" OK");
+ psWriteEOL(ps);
+ }
+
+ log("Status sent");
+ }
+ /**
+ * Create the server response and output to the stream
+ * @param ps The PrintStream to output response headers and data to
+ */
+ void printHeaders(PrintStream ps) throws IOException {
+ if ((testNum < -1) || (testNum > Support_TestWebData.tests.length - 1)) {
+ // 404 status already sent
+ return;
+ }
+ SimpleDateFormat df = new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss");
+
+ psPrint(ps,"Server: TestWebServer"+mPort);
+ psWriteEOL(ps);
+ psPrint(ps, "Date: " + df.format(new Date()));
+ psWriteEOL(ps);
+ psPrint(ps, "Connection: " + ((keepAlive) ? "Keep-Alive" : "Close"));
+ psWriteEOL(ps);
+
+ // Yuk, if we're not redirecting, we add the file details
+ if (redirectCode == -1) {
+
+ if (testNum == -1) {
+ if (!Support_TestWebData.test0DataAvailable) {
+ log("testdata was not initilaized");
+ return;
+ }
+ if (chunked) {
+ psPrint(ps, "Transfer-Encoding: chunked");
+ } else {
+ psPrint(ps, "Content-length: "
+ + Support_TestWebData.test0Data.length);
+ }
+ psWriteEOL(ps);
+
+ psPrint(ps, "Last Modified: " + (new Date(
+ Support_TestWebData.test0Params.testLastModified)));
+ psWriteEOL(ps);
+
+ psPrint(ps, "Content-type: "
+ + Support_TestWebData.test0Params.testType);
+ psWriteEOL(ps);
+
+ if (Support_TestWebData.testParams[testNum].testExp > 0) {
+ long exp;
+ exp = Support_TestWebData.testParams[testNum].testExp;
+ psPrint(ps, "expires: "
+ + df.format(exp) + " GMT");
+ psWriteEOL(ps);
+ }
+ } else if (!Support_TestWebData.testParams[testNum].testDir) {
+ if (chunked) {
+ psPrint(ps, "Transfer-Encoding: chunked");
+ } else {
+ psPrint(ps, "Content-length: "+Support_TestWebData.testParams[testNum].testLength);
+ }
+ psWriteEOL(ps);
+
+ psPrint(ps,"Last Modified: " + (new
+ Date(Support_TestWebData.testParams[testNum].testLastModified)));
+ psWriteEOL(ps);
+
+ psPrint(ps, "Content-type: " + Support_TestWebData.testParams[testNum].testType);
+ psWriteEOL(ps);
+
+ if (Support_TestWebData.testParams[testNum].testExp > 0) {
+ long exp;
+ exp = Support_TestWebData.testParams[testNum].testExp;
+ psPrint(ps, "expires: "
+ + df.format(exp) + " GMT");
+ psWriteEOL(ps);
+ }
+ } else {
+ psPrint(ps, "Content-type: text/html");
+ psWriteEOL(ps);
+ }
+ } else {
+ // Content-length of 301, 302, 303, 307 are the same.
+ psPrint(ps, "Content-length: "+(Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_301]).length());
+ psWriteEOL(ps);
+ psWriteEOL(ps);
+ }
+ log("Headers sent");
+
+ }
+
+ /**
+ * Sends the 404 not found message
+ * @param ps The PrintStream to write to
+ */
+ void send404(PrintStream ps) throws IOException {
+ ps.println("Not Found\n\n"+
+ "The requested resource was not found.\n");
+ }
+
+ /**
+ * Sends the data associated with the headers
+ * @param ps The PrintStream to write to
+ */
+ void sendFile(PrintStream ps) throws IOException {
+ // For now just make a chunk with the whole of the test data
+ // It might be worth making this multiple chunks for large
+ // test data to test multiple chunks.
+ if (testNum == -1) {
+ if (!Support_TestWebData.test0DataAvailable) {
+ log("testdata was not initilaized");
+ return;
+ }
+ int dataSize = Support_TestWebData.test0Data.length;
+ if (chunked) {
+ psPrint(ps, Integer.toHexString(dataSize));
+ psWriteEOL(ps);
+ psWrite(ps, Support_TestWebData.test0Data, dataSize);
+ psWriteEOL(ps);
+ psPrint(ps, "0");
+ psWriteEOL(ps);
+ psWriteEOL(ps);
+ } else {
+ psWrite(ps, Support_TestWebData.test0Data, dataSize);
+ }
+ } else {
+ int dataSize = Support_TestWebData.tests[testNum].length;
+ if (chunked) {
+ psPrint(ps, Integer.toHexString(dataSize));
+ psWriteEOL(ps);
+ psWrite(ps, Support_TestWebData.tests[testNum], dataSize);
+ psWriteEOL(ps);
+ psPrint(ps, "0");
+ psWriteEOL(ps);
+ psWriteEOL(ps);
+ } else {
+ psWrite(ps, Support_TestWebData.tests[testNum], dataSize);
+ }
+ }
+ }
+ }
+}