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