OSDN Git Service

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