1b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob/*
2b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * Copyright (C) 2007 The Android Open Source Project
3b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob *
4b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * Licensed under the Apache License, Version 2.0 (the "License");
5b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * you may not use this file except in compliance with the License.
6b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * You may obtain a copy of the License at
7b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob *
8b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob *      http://www.apache.org/licenses/LICENSE-2.0
9b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob *
10b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * Unless required by applicable law or agreed to in writing, software
11b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * distributed under the License is distributed on an "AS IS" BASIS,
12b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * See the License for the specific language governing permissions and
14b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * limitations under the License.
15b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob */
16b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
17b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grobpackage tests.support;
18b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
19b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grobimport java.io.*;
20b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grobimport java.lang.Thread;
21b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grobimport java.net.*;
22b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grobimport java.text.SimpleDateFormat;
23b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grobimport java.util.*;
2428f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilsonimport java.util.concurrent.ConcurrentHashMap;
25b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grobimport java.util.logging.Logger;
26b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
27b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob/**
28b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * TestWebServer is a simulated controllable test server that
29b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * can respond to requests from HTTP clients.
30b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob *
31b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * The server can be controlled to change how it reacts to any
32b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * requests, and can be told to simulate various events (such as
33b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob * network failure) that would happen in a real environment.
34b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob */
35b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grobpublic class Support_TestWebServer implements Support_HttpConstants {
36b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
37b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* static class data/methods */
38b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
39b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* The ANDROID_LOG_TAG */
40b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    private final static String LOGTAG = "httpsv";
41b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
4228f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson    /** maps the recently requested URLs to the full request snapshot */
4328f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson    private final Map<String, Request> pathToRequest
4428f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson            = new ConcurrentHashMap<String, Request>();
4528f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson
46b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* timeout on client connections */
47b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    int timeout = 0;
48b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
49b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* Default port for this server to listen on */
50b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    final static int DEFAULT_PORT = 8080;
51b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
52b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* Default socket timeout value */
53b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    final static int DEFAULT_TIMEOUT = 5000;
54b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
55b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* Version string (configurable) */
56b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    protected String HTTP_VERSION_STRING = "HTTP/1.1";
57b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
58b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* Indicator for whether this server is configured as a HTTP/1.1
59b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * or HTTP/1.0 server
60b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
61b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    private boolean http11 = true;
62b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
63b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* The thread handling new requests from clients */
64b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    private AcceptThread acceptT;
65b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
66b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* timeout on client connections */
67b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    int mTimeout;
68b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
69b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* Server port */
70b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    int mPort;
71b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
72b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* Switch on/off logging */
73b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    boolean mLog = false;
74b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
75b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* If set, this will keep connections alive after a request has been
76b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * processed.
77b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
78b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    boolean keepAlive = true;
79b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
80b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* If set, this will cause response data to be sent in 'chunked' format */
81b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    boolean chunked = false;
8268d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes    int maxChunkSize = 1024;
83b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
84b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* If set, this will indicate a new redirection host */
85b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    String redirectHost = null;
86b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
87b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* If set, this indicates the reason for redirection */
88b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    int redirectCode = -1;
89b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
90b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* Set the number of connections the server will accept before shutdown */
91b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    int acceptLimit = 100;
92b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
93b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* Count of number of accepted connections */
94b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    int acceptedConnections = 0;
95b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
96b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public Support_TestWebServer() {
97b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
98b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
99b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
100b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Initialize a new server with default port and timeout.
101b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param log Set true if you want trace output
102b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
103b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void initServer(boolean log) throws Exception {
104b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        initServer(DEFAULT_PORT, DEFAULT_TIMEOUT, log);
105b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
106b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
107b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
108b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Initialize a new server with default timeout.
109b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param port Sets the server to listen on this port
110b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param log Set true if you want trace output
111b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
112b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void initServer(int port, boolean log) throws Exception {
113b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        initServer(port, DEFAULT_TIMEOUT, log);
114b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
115b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
116b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
117b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Initialize a new server with default timeout and disabled log.
118b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param port Sets the server to listen on this port
119b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param servePath the path to the dynamic web test data
120b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param contentType the type of the dynamic web test data
121b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
122b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void initServer(int port, String servePath, String contentType)
123b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            throws Exception {
124b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        Support_TestWebData.initDynamicTestWebData(servePath, contentType);
125b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        initServer(port, DEFAULT_TIMEOUT, false);
126b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
127b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
128b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
129b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Initialize a new server with default port and timeout.
130b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param port Sets the server to listen on this port
131b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param timeout Indicates the period of time to wait until a socket is
132b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     *                closed
133b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param log Set true if you want trace output
134b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
135b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void initServer(int port, int timeout, boolean log) throws Exception {
136b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        mPort = port;
137b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        mTimeout = timeout;
138b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        mLog = log;
139b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        keepAlive = true;
140b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
141b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        if (acceptT == null) {
142b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            acceptT = new AcceptThread();
143b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            acceptT.init();
144b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            acceptT.start();
145b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
146b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
147b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
148b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
149b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Print to the log file (if logging enabled)
150b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param s String to send to the log
151b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
152b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    protected void log(String s) {
153b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        if (mLog) {
154b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            Logger.global.fine(s);
155b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
156b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
157b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
158b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
159b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Set the server to be an HTTP/1.0 or HTTP/1.1 server.
160b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * This should be called prior to any requests being sent
161b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * to the server.
162b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param set True for the server to be HTTP/1.1, false for HTTP/1.0
163b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
164b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void setHttpVersion11(boolean set) {
165b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        http11 = set;
166b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        if (set) {
167b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            HTTP_VERSION_STRING = "HTTP/1.1";
168b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        } else {
169b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            HTTP_VERSION_STRING = "HTTP/1.0";
170b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
171b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
172b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
173b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
174b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Call this to determine whether server connection should remain open
175b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param value Set true to keep connections open after a request
176b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     *              completes
177b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
178b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void setKeepAlive(boolean value) {
179b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        keepAlive = value;
180b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
181b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
182b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
183b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Call this to indicate whether chunked data should be used
184b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param value Set true to make server respond with chunk encoded
185b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     *              content data.
186b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
187b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void setChunked(boolean value) {
188b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        chunked = value;
189b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
190b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
191b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
19268d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes     * Sets the maximum byte count of any chunk if the server is using
19368d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes     * the "chunked" transfer encoding.
19468d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes     */
19568d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes    public void setMaxChunkSize(int maxChunkSize) {
19668d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes        this.maxChunkSize = maxChunkSize;
19768d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes    }
19868d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes
19968d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes    /**
200b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Call this to specify the maximum number of sockets to accept
201b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param limit The number of sockets to accept
202b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
203b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void setAcceptLimit(int limit) {
204b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        acceptLimit = limit;
205b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
206b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
207b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
208b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Call this to indicate redirection port requirement.
209b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * When this value is set, the server will respond to a request with
210b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * a redirect code with the Location response header set to the value
211b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * specified.
212b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * @param redirect The location to be redirected to
21328f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson     * @param code The code to send when redirecting
214b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
215b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void setRedirect(String redirect, int code) {
216b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        redirectHost = redirect;
217b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        redirectCode = code;
218b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        log("Server will redirect output to "+redirect+" code "+code);
219b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
220b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
221b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
22228f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson     * Returns a map from recently-requested paths (like "/index.html") to a
22328f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson     * snapshot of the request data.
22428f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson     */
22528f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson    public Map<String, Request> pathToRequest() {
22628f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        return pathToRequest;
22728f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson    }
22828f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson
229fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson    public int getNumAcceptedConnections() {
230fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson        return acceptedConnections;
231fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson    }
232fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson
23328f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson    /**
234b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * Cause the thread accepting connections on the server socket to close
235b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
236b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    public void close() {
237b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Stop the Accept thread */
238b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        if (acceptT != null) {
239b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("Closing AcceptThread"+acceptT);
240b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            acceptT.close();
241b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            acceptT = null;
242b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
243b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
244b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
245b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * The AcceptThread is responsible for initiating worker threads
246b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * to handle incoming requests from clients.
247b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
248b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    class AcceptThread extends Thread {
249b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
250b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        ServerSocket ss = null;
251b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        boolean running = false;
252b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
253b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        public void init() {
254b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Networking code doesn't support ServerSocket(port) yet
255b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            InetSocketAddress ia = new InetSocketAddress(mPort);
256b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            while (true) {
257b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                try {
258b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    ss = new ServerSocket();
259b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    // Socket timeout functionality is not available yet
260b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    //ss.setSoTimeout(5000);
261b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    ss.setReuseAddress(true);
262b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    ss.bind(ia);
263b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    break;
264b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                } catch (IOException e) {
265b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    log("IOException in AcceptThread.init()");
266b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    // wait and retry
267b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    try {
268b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        Thread.sleep(1000);
269b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    } catch (InterruptedException e1) {
270b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        e1.printStackTrace();
271b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
272b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
273b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
274b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
275b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
276b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
277b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Main thread responding to new connections
278b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
279b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        public synchronized void run() {
280b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            running = true;
281fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson            while (running) {
282fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson                try {
283b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    Socket s = ss.accept();
284b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    acceptedConnections++;
285b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (acceptedConnections >= acceptLimit) {
286b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        running = false;
287b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
288c5176ecbb029aeef6e45a78fbf2695971c877865Elliott Hughes                    new Thread(new Worker(s), "additional worker").start();
289fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson                } catch (SocketException e) {
290fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson                    log(e.getMessage());
291fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson                } catch (IOException e) {
292fd1674c75d7b6500168137a2213877e45e49bbd2Jesse Wilson                    log(e.getMessage());
293b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
294b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
295b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("AcceptThread terminated" + this);
296b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
297b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
298b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        // Close this socket
299b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        public void close() {
300b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            try {
301b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                running = false;
302b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                /* Stop server socket from processing further. Currently
303b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                   this does not cause the SocketException from ss.accept
304b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                   therefore the acceptLimit functionality has been added
305b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                   to circumvent this limitation */
306b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                ss.close();
307b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } catch (IOException e) {
308b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                /* We are shutting down the server, so we expect
309b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                 * things to die. Don't propagate.
310b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                 */
311b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                log("IOException caught by server socket close");
312b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
313b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
314b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
315b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
316b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    // Size of buffer for reading from the connection
317b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    final static int BUF_SIZE = 2048;
318b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
319b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /* End of line byte sequence */
320b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    static final byte[] EOL = {(byte)'\r', (byte)'\n' };
321b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
322b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    /**
32328f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson     * An immutable snapshot of an HTTP request.
32428f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson     */
32528f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson    public static class Request {
32628f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        private final String path;
32728f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        private final Map<String, String> headers;
32828f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        // TODO: include posted content?
32928f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson
33028f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        public Request(String path, Map<String, String> headers) {
33128f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson            this.path = path;
33228f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson            this.headers = new LinkedHashMap<String, String>(headers);
33328f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        }
33428f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson
33528f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        public String getPath() {
33628f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson            return path;
33728f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        }
33828f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson
33928f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        public Map<String, String> getHeaders() {
34028f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson            return headers;
34128f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        }
34228f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson    }
34328f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson
34428f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson    /**
345b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * The worker thread handles all interactions with a current open
346b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * connection. If pipelining is turned on, this will allow this
347b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * thread to continuously operate on numerous requests before the
348b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     * connection is closed.
349b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob     */
350b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    class Worker implements Support_HttpConstants, Runnable {
351b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
352b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* buffer to use to hold request data */
353b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        byte[] buf;
354b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
355b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Socket to client we're handling */
356b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private Socket s;
357b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
358b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Reference to current request method ID */
359b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private int requestMethod;
360b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
361b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Reference to current requests test file/data */
362b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private String testID;
363b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
36428f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        /* The requested path, such as "/test1" */
36528f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        private String path;
36628f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson
367b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Reference to test number from testID */
368b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private int testNum;
369b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
370b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Reference to whether new request has been initiated yet */
371b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private boolean readStarted;
372b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
373b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Indicates whether current request has any data content */
374b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private boolean hasContent = false;
375b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
376b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Request headers are stored here */
37728f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson        private Map<String, String> headers = new LinkedHashMap<String, String>();
378b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
379b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /* Create a new worker thread */
380c5176ecbb029aeef6e45a78fbf2695971c877865Elliott Hughes        Worker(Socket s) {
381c5176ecbb029aeef6e45a78fbf2695971c877865Elliott Hughes            this.buf = new byte[BUF_SIZE];
382b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            this.s = s;
383b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
384b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
385b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        public synchronized void run() {
386c5176ecbb029aeef6e45a78fbf2695971c877865Elliott Hughes            try {
387c5176ecbb029aeef6e45a78fbf2695971c877865Elliott Hughes                handleClient();
388c5176ecbb029aeef6e45a78fbf2695971c877865Elliott Hughes            } catch (Exception e) {
389c5176ecbb029aeef6e45a78fbf2695971c877865Elliott Hughes                log("Exception during handleClient in the TestWebServer: " + e.getMessage());
390b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
391b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log(this+" terminated");
392b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
393b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
394b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
395b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Zero out the buffer from last time
396b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
397b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private void clearBuffer() {
398b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            for (int i = 0; i < BUF_SIZE; i++) {
399b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                buf[i] = 0;
400b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
401b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
402b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
403b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
404b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Utility method to read a line of data from the input stream
405b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param is Inputstream to read
406b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @return number of bytes read
407b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
408b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private int readOneLine(InputStream is) {
409b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
410b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int read = 0;
411b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
412b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            clearBuffer();
413b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            try {
414b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                log("Reading one line: started ="+readStarted+" avail="+is.available());
415b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                StringBuilder log = new StringBuilder();
416b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                while ((!readStarted) || (is.available() > 0)) {
417b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    int data = is.read();
418b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    // We shouldn't get EOF but we need tdo check
419b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (data == -1) {
420b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        log("EOF returned");
421b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        return -1;
422b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
423b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
424b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    buf[read] = (byte)data;
425b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
426b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    log.append((char)data);
427b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
428b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    readStarted = true;
429b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (buf[read++]==(byte)'\n') {
430b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        log(log.toString());
431b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        return read;
432b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
433b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
434b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } catch (IOException e) {
435b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                log("IOException from readOneLine");
436b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
437b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            return read;
438b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
439b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
440b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
441b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Read a chunk of data
442b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param is Stream from which to read data
443b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param length Amount of data to read
444b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @return number of bytes read
445b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
446b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private int readData(InputStream is, int length) {
447b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int read = 0;
448b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int count;
449b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // At the moment we're only expecting small data amounts
450b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            byte[] buf = new byte[length];
451b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
452b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            try {
453b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                while (is.available() > 0) {
454b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    count = is.read(buf, read, length-read);
455b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    read += count;
456b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
457b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } catch (IOException e) {
458b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                log("IOException from readData");
459b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
460b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            return read;
461b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
462b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
463b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
464b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Read the status line from the input stream extracting method
465b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * information.
466b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param is Inputstream to read
467b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @return number of bytes read
468b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
469b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private int parseStatusLine(InputStream is) {
470b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int index;
471b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int nread = 0;
472b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
473b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("Parse status line");
474b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Check for status line first
475b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            nread = readOneLine(is);
476b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Bomb out if stream closes prematurely
477b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if (nread == -1) {
478b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                requestMethod = UNKNOWN_METHOD;
479b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                return -1;
480b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
481b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
482b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if (buf[0] == (byte)'G' &&
483b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                buf[1] == (byte)'E' &&
484b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                buf[2] == (byte)'T' &&
485b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                buf[3] == (byte)' ') {
486b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                requestMethod = GET_METHOD;
487b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                log("GET request");
488b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                index = 4;
489b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } else if (buf[0] == (byte)'H' &&
490b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       buf[1] == (byte)'E' &&
491b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       buf[2] == (byte)'A' &&
492b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       buf[3] == (byte)'D' &&
493b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       buf[4] == (byte)' ') {
494b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                requestMethod = HEAD_METHOD;
495b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                log("HEAD request");
496b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                index = 5;
497b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } else if (buf[0] == (byte)'P' &&
498b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       buf[1] == (byte)'O' &&
499b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       buf[2] == (byte)'S' &&
500b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       buf[3] == (byte)'T' &&
501b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       buf[4] == (byte)' ') {
502b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                requestMethod = POST_METHOD;
503b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                log("POST request");
504b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                index = 5;
505b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } else {
506b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // Unhandled request
507b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                requestMethod = UNKNOWN_METHOD;
508b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                return -1;
509b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
510b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
511b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // A valid method we understand
512b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if (requestMethod > UNKNOWN_METHOD) {
513b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // Read file name
514b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                int i = index;
515b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                while (buf[i] != (byte)' ') {
516b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    // There should be HTTP/1.x at the end
517b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if ((buf[i] == (byte)'\n') || (buf[i] == (byte)'\r')) {
518b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        requestMethod = UNKNOWN_METHOD;
519b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        return -1;
520b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
521b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    i++;
522b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
523b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
52428f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson                path = new String(buf, 0, index, i-index);
52528f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson                testID = path.substring(1);
526b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
527b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                return nread;
528b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
529b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            return -1;
530b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
531b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
532b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
533b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Read a header from the input stream
534b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param is Inputstream to read
535b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @return number of bytes read
536b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
537b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private int parseHeader(InputStream is) {
538b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int index = 0;
539b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int nread = 0;
540b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("Parse a header");
541b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Check for status line first
542b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            nread = readOneLine(is);
543b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Bomb out if stream closes prematurely
544b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if (nread == -1) {
545b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                requestMethod = UNKNOWN_METHOD;
546b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                return -1;
547b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
548b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Read header entry 'Header: data'
549b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int i = index;
550b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            while (buf[i] != (byte)':') {
551b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // There should be an entry after the header
552b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
553b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                if ((buf[i] == (byte)'\n') || (buf[i] == (byte)'\r')) {
554b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    return UNKNOWN_METHOD;
555b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
556b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                i++;
557b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
558b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
559b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            String headerName = new String(buf, 0, i);
560b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            i++; // Over ':'
561b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            while (buf[i] == ' ') {
562b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                i++;
563b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
564854d06e7c3a4d8a013cc2f1bfec333f13e9f47efJesse Wilson            String headerValue = new String(buf, i, nread - i - 2); // drop \r\n
565b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
566b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            headers.put(headerName, headerValue);
567b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            return nread;
568b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
569b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
570b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
571b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Read all headers from the input stream
572b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param is Inputstream to read
573b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @return number of bytes read
574b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
575b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private int readHeaders(InputStream is) {
576b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int nread = 0;
577b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("Read headers");
578b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Headers should be terminated by empty CRLF line
579b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            while (true) {
580b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                int headerLen = 0;
581b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                headerLen = parseHeader(is);
582b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                if (headerLen == -1)
583b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    return -1;
584b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                nread += headerLen;
585b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                if (headerLen <= 2) {
586b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    return nread;
587b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
588b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
589b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
590b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
591b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
592b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Read content data from the input stream
593b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param is Inputstream to read
594b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @return number of bytes read
595b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
596b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        private int readContent(InputStream is) {
597b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int nread = 0;
598b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("Read content");
599b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            String lengthString = headers.get(requestHeaders[REQ_CONTENT_LENGTH]);
600b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int length = new Integer(lengthString).intValue();
601b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
602b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Read content
603b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            length = readData(is, length);
604b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            return length;
605b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
606b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
607b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
608b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * The main loop, reading requests.
609b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
610b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        void handleClient() throws IOException {
611b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            InputStream is = new BufferedInputStream(s.getInputStream());
612b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            PrintStream ps = new PrintStream(s.getOutputStream());
613b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            int nread = 0;
614b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
615b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            /* we will only block in read for this many milliseconds
616b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob             * before we fail with java.io.InterruptedIOException,
617b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob             * at which point we will abandon the connection.
618b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob             */
619b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            s.setSoTimeout(mTimeout);
620b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            s.setTcpNoDelay(true);
621b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
622b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            do {
623b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                nread = parseStatusLine(is);
624b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                if (requestMethod != UNKNOWN_METHOD) {
625b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
626b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    // If status line found, read any headers
627b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    nread = readHeaders(is);
628b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
62928f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson                    pathToRequest().put(path, new Request(path, headers));
63028f3e3ac54fe2be7d42fa681df6b3efcf0a9221eJesse Wilson
631b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    // Then read content (if any)
632b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    // TODO handle chunked encoding from the client
633b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (headers.get(requestHeaders[REQ_CONTENT_LENGTH]) != null) {
634b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        nread = readContent(is);
635b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
636b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                } else {
637b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (nread > 0) {
638b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        /* we don't support this method */
639b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        ps.print(HTTP_VERSION_STRING + " " + HTTP_BAD_METHOD +
640b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                                 " unsupported method type: ");
641b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        ps.write(buf, 0, 5);
642b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        ps.write(EOL);
643b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        ps.flush();
644b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    } else {
645b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
646b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (!keepAlive || nread <= 0) {
647b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        headers.clear();
648b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        readStarted = false;
649b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
650b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        log("SOCKET CLOSED");
651b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        s.close();
652b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        return;
653b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
654b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
655b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
656b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // Reset test number prior to outputing data
657b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                testNum = -1;
658b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
659b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // Write out the data
660b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                printStatus(ps);
661b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                printHeaders(ps);
662b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
663b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // Write line between headers and body
664b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psWriteEOL(ps);
665b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
666b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // Write the body
667b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                if (redirectCode == -1) {
668b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    switch (requestMethod) {
669b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        case GET_METHOD:
670b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            if ((testNum < -1) || (testNum > Support_TestWebData.tests.length - 1)) {
671b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                                send404(ps);
672b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            } else {
673b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                                sendFile(ps);
674b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            }
675b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
676b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        case HEAD_METHOD:
677b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            // Nothing to do
678b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
679b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        case POST_METHOD:
680b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            // Post method write body data
681b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            if ((testNum > 0) || (testNum < Support_TestWebData.tests.length - 1)) {
682b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                                sendFile(ps);
683b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            }
684b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
685b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
686b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        default:
687b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
688b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
689b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                } else { // Redirecting
690b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    switch (redirectCode) {
691b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        case 301:
692b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            // Seems 301 needs a body by neon (although spec
693b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            // says SHOULD).
694b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            psPrint(ps, Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_301]);
695b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
696b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        case 302:
697b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            //
698b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            psPrint(ps, Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_302]);
699b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
700b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        case 303:
701b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            psPrint(ps, Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_303]);
702b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
703b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        case 307:
704b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            psPrint(ps, Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_307]);
705b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
706b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        default:
707b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            break;
708b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
709b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
710b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
711b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                ps.flush();
712b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
713b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // Reset for next request
714b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                readStarted = false;
715b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                headers.clear();
716b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
717b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } while (keepAlive);
718b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
719b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("SOCKET CLOSED");
720b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            s.close();
721b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
722b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
723b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        // Print string to log and output stream
724b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        void psPrint(PrintStream ps, String s) throws IOException {
725b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log(s);
726b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            ps.print(s);
727b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
728b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
729b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        // Print bytes to log and output stream
73068d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes        void psWrite(PrintStream ps, byte[] bytes, int offset, int count) throws IOException {
731b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log(new String(bytes));
73268d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes            ps.write(bytes, offset, count);
733b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
734b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
735b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        // Print CRLF to log and output stream
736b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        void psWriteEOL(PrintStream ps) throws IOException {
737b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("CRLF");
738b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            ps.write(EOL);
739b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
740b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
741b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
742b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        // Print status to log and output stream
743b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        void printStatus(PrintStream ps) throws IOException {
744b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Handle redirects first.
745b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if (redirectCode != -1) {
746b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                log("REDIRECTING TO "+redirectHost+" status "+redirectCode);
747b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psPrint(ps, HTTP_VERSION_STRING + " " + redirectCode +" Moved permanently");
748b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psWriteEOL(ps);
749b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psPrint(ps, "Location: " + redirectHost);
750b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psWriteEOL(ps);
751b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                return;
752b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
753b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
754b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
755b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if (testID.startsWith("test")) {
756b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                testNum = Integer.valueOf(testID.substring(4))-1;
757b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
758b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
759b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if ((testNum < -1) || (testNum > Support_TestWebData.tests.length - 1)) {
760b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psPrint(ps, HTTP_VERSION_STRING + " " + HTTP_NOT_FOUND + " not found");
761b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psWriteEOL(ps);
762b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }  else {
763b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psPrint(ps, HTTP_VERSION_STRING + " " + HTTP_OK+" OK");
764b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psWriteEOL(ps);
765b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
766b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
767b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("Status sent");
768b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
769b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
770b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Create the server response and output to the stream
771b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param ps The PrintStream to output response headers and data to
772b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
773b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        void printHeaders(PrintStream ps) throws IOException {
774b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if ((testNum < -1) || (testNum > Support_TestWebData.tests.length - 1)) {
775b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // 404 status already sent
776b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                return;
777b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
778b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            SimpleDateFormat df = new SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss");
779b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
780b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            psPrint(ps,"Server: TestWebServer"+mPort);
781b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            psWriteEOL(ps);
782b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            psPrint(ps, "Date: " + df.format(new Date()));
783b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            psWriteEOL(ps);
784b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            psPrint(ps, "Connection: " + ((keepAlive) ? "Keep-Alive" : "Close"));
785b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            psWriteEOL(ps);
786b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
787b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            // Yuk, if we're not redirecting, we add the file details
788b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if (redirectCode == -1) {
789b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
790b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                if (testNum == -1) {
791b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (!Support_TestWebData.test0DataAvailable) {
792b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        log("testdata was not initilaized");
793b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        return;
794b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
795b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (chunked) {
796b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        psPrint(ps, "Transfer-Encoding: chunked");
797b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    } else {
798b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        psPrint(ps, "Content-length: "
799b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                                + Support_TestWebData.test0Data.length);
800b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
801b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
802b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
803b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psPrint(ps, "Last Modified: " + (new Date(
804b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            Support_TestWebData.test0Params.testLastModified)));
805b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
806b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
807b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psPrint(ps, "Content-type: "
808b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                            + Support_TestWebData.test0Params.testType);
809b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
810b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
811b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (Support_TestWebData.testParams[testNum].testExp > 0) {
812b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        long exp;
813b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        exp = Support_TestWebData.testParams[testNum].testExp;
814b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        psPrint(ps, "expires: "
815b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                                + df.format(exp) + " GMT");
816b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        psWriteEOL(ps);
817b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
818b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                } else if (!Support_TestWebData.testParams[testNum].testDir) {
819b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (chunked) {
820b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        psPrint(ps, "Transfer-Encoding: chunked");
821b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    } else {
822b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        psPrint(ps, "Content-length: "+Support_TestWebData.testParams[testNum].testLength);
823b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
824b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
825b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
826b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psPrint(ps,"Last Modified: " + (new
827b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                                                    Date(Support_TestWebData.testParams[testNum].testLastModified)));
828b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
829b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
830b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psPrint(ps, "Content-type: " + Support_TestWebData.testParams[testNum].testType);
831b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
832b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
833b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    if (Support_TestWebData.testParams[testNum].testExp > 0) {
834b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        long exp;
835b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        exp = Support_TestWebData.testParams[testNum].testExp;
836b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        psPrint(ps, "expires: "
837b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                                + df.format(exp) + " GMT");
838b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                        psWriteEOL(ps);
839b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    }
840b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                } else {
841b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psPrint(ps, "Content-type: text/html");
842b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
843b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
844b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } else {
845b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                // Content-length of 301, 302, 303, 307 are the same.
846b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psPrint(ps, "Content-length: "+(Support_TestWebData.testServerResponse[Support_TestWebData.REDIRECT_301]).length());
847b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psWriteEOL(ps);
848b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                psWriteEOL(ps);
849b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
850b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            log("Headers sent");
851b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
852b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
853b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
854b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
855b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Sends the 404 not found message
856b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param ps The PrintStream to write to
857b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
858b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        void send404(PrintStream ps) throws IOException {
859b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            ps.println("Not Found\n\n"+
860b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                       "The requested resource was not found.\n");
861b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
862b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob
863b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        /**
864b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * Sends the data associated with the headers
865b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         * @param ps The PrintStream to write to
866b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob         */
867b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        void sendFile(PrintStream ps) throws IOException {
868b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            if (testNum == -1) {
869b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                if (!Support_TestWebData.test0DataAvailable) {
87068d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                    log("test data was not initialized");
871b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    return;
872b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
87368d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                sendFile(ps, Support_TestWebData.test0Data);
874b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            } else {
87568d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                sendFile(ps, Support_TestWebData.tests[testNum]);
87668d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes            }
87768d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes        }
87868d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes
87968d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes        void sendFile(PrintStream ps, byte[] bytes) throws IOException {
88068d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes            if (chunked) {
88168d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                int offset = 0;
88268d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                while (offset < bytes.length) {
88368d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                    int chunkSize = Math.min(bytes.length - offset, maxChunkSize);
88468d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                    psPrint(ps, Integer.toHexString(chunkSize));
885b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
88668d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                    psWrite(ps, bytes, offset, chunkSize);
887b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                    psWriteEOL(ps);
88868d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                    offset += chunkSize;
889b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob                }
89068d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                psPrint(ps, "0");
89168d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                psWriteEOL(ps);
89268d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                psWriteEOL(ps);
89368d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes            } else {
89468d262d2c56fb4bb0d8253383aacaba8f36bc691Elliott Hughes                psWrite(ps, bytes, 0, bytes.length);
895b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob            }
896b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob        }
897b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob    }
898b72a4718fca6b1ccde71c785cea317f5b3c2c2e9Urs Grob}
899