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