inital plugin from webtools project
[phpeclipse.git] / archive / net.sourceforge.phpeclipse.monitor.core / src / net / sourceforge / phpdt / monitor / core / internal / http / HTTPThread.java
1 /**********************************************************************
2  * Copyright (c) 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  *
8  * Contributors:
9  *    IBM - Initial API and implementation
10  **********************************************************************/
11 package net.sourceforge.phpdt.monitor.core.internal.http;
12
13 import java.io.*;
14
15 import net.sourceforge.phpdt.monitor.core.IRequest;
16 import net.sourceforge.phpdt.monitor.core.internal.Connection;
17 import net.sourceforge.phpdt.monitor.core.internal.Trace;
18
19 /**
20  * Monitor server I/O thread.
21  */
22 public class HTTPThread extends Thread {
23         private static final int BUFFER = 2048;
24         private static final byte CR = (byte) '\r';
25         private static final byte LF = (byte) '\n';
26         protected static int threadCount = 0;
27
28         private byte[] readBuffer = new byte[BUFFER];
29
30         // buffer and index
31         protected byte[] buffer = new byte[0];
32         protected int bufferIndex = 0;
33
34         protected InputStream in;
35         protected OutputStream out;
36         protected HTTPConnection conn;
37         protected boolean isRequest;
38         protected Connection conn2;
39         
40         protected HTTPThread request;
41         protected boolean isWaiting;
42         
43         // user to translate the Host: header
44         protected String host;
45         protected int port;
46
47         protected int contentLength = -1;
48         protected byte transferEncoding = -1;
49         protected String responseType = null;
50         protected boolean keepAlive = false;
51
52         protected static final String[] ENCODING_STRING = new String[] {
53                 "chunked", "identity", "gzip", "compressed", "deflate"};
54
55         protected static final byte ENCODING_CHUNKED = 0;
56         protected static final byte ENCODING_IDENTITY = 1;
57         protected static final byte ENCODING_GZIP = 2;
58         protected static final byte ENCODING_COMPRESSED = 3;
59         protected static final byte ENCODING_DEFLATE = 4;
60
61 /* change:
62 Referer: http://localhost:8081/index.html
63 Host: localhost:8081
64 */
65 /* The Connection header has the following grammar:
66
67            Connection = "Connection" ":" 1#(connection-token)
68            connection-token  = token
69
70    HTTP/1.1 proxies MUST parse the Connection header field before a
71    message is forwarded and, for each connection-token in this field,
72    remove any header field(s) from the message with the same name as the
73    connection-token. */
74
75         /**
76          * MonitorThread constructor comment.
77          */
78         public HTTPThread(Connection conn2, InputStream in, OutputStream out, HTTPConnection conn, boolean isRequest, String host, int port) {
79                 super();
80                 this.conn2 = conn2;
81                 this.in = in;
82                 this.out = out;
83                 this.conn = conn;
84                 this.isRequest = isRequest;
85                 this.host = host;
86                 this.port = port;
87         
88                 setName("HTTP (" + host + ":" + port + ") " + (isRequest ? "REQUEST" : "RESPONSE") + " " + (threadCount++));
89                 setPriority(Thread.NORM_PRIORITY + 1);
90                 setDaemon(true);
91                 
92                 Trace.trace(Trace.PARSING, "Started: " + this);
93         }
94         
95         /**
96          * MonitorThread constructor comment.
97          */
98         public HTTPThread(Connection conn2, InputStream in, OutputStream out, HTTPConnection conn, boolean isRequest, String host, int port, HTTPThread request) {
99                 this(conn2, in, out, conn, isRequest, host, port);
100                 
101                 this.request = request;
102         }
103
104         /**
105          * Add a line feed to the end of the byte array.
106          * @return byte[]
107          * @param b byte[]
108          */
109         protected static byte[] convert(byte[] b) {
110                 if (b == null || b.length == 0)
111                         return b;
112         
113                 int size = b.length;
114                 byte[] x = new byte[size + 2];
115                 System.arraycopy(b, 0, x, 0, size);
116                 x[size] = (byte) '\r';     // CR
117                 x[size + 1] = (byte) '\n'; // LF
118                 return x;
119         }
120
121         /**
122          * Read more data into the buffer.
123          *
124          * @return byte[]
125          */
126         protected void fillBuffer() throws IOException {
127                 int n = in.read(readBuffer);
128         
129                 if (n <= 0)
130                         throw new IOException("End of input");
131         
132                 // add to full buffer
133                 int len = buffer.length - bufferIndex;
134                 if (len < 0)
135                         len = 0;
136                 byte[] x = new byte[n + len];
137                 System.arraycopy(buffer, bufferIndex, x, 0, len);
138                 System.arraycopy(readBuffer, 0, x, len, n);
139                 bufferIndex = 0;
140                 buffer = x;
141         }
142
143         /**
144          * Returns the first location of a CRLF.
145          *
146          * @return int
147          */
148         protected int getFirstCRLF() {
149                 int size = buffer.length;
150                 int i = bufferIndex + 1;
151                 while (i < size) {
152                         if (buffer[i - 1] == CR && buffer[i] == LF)
153                                 return i;
154                         i++;
155                 }
156                 return -1;
157         }
158
159         /**
160          * Output the given bytes.
161          * @param b byte[]
162          */
163         protected void outputBytes(byte[] b, boolean isNew) throws IOException {
164                 out.write(b);
165                 if (isRequest)
166                         conn.addRequest(b, isNew);
167                 else
168                         conn.addResponse(b, isNew);
169         }
170
171         /**
172          * Parse the HTTP body.
173          */
174         public void parseBody() throws IOException {
175                 Trace.trace(Trace.PARSING, "Parsing body for: " + this);
176                 
177                 if (isRequest) {
178                         if (contentLength != -1) {
179                                 byte[] b = readBytes(contentLength);
180                                 out.write(b);
181                                 conn.addRequest(b, false);
182                                 setHTTPBody(b);
183                         } else if (transferEncoding != -1 && transferEncoding != ENCODING_IDENTITY) {
184                                 parseChunk();
185                         }
186                         
187                         Trace.trace(Trace.PARSING, "Done parsing request body for: " + this);
188                         return;
189                 }
190         
191                 // just return body for HTTP 1.0 responses
192                 if (!isRequest && !keepAlive && contentLength == -1 && transferEncoding == -1) {
193                         Trace.trace(Trace.PARSING, "Assuming HTTP 1.0 for: " + this);
194                         int n = buffer.length - bufferIndex;
195                         byte[] b = readBytes(n);
196                         byte[] body = new byte[0];
197                         while (n >= 0) {
198                                 Trace.trace(Trace.PARSING, "Bytes read: " + n + " " + this);
199                                 if (b != null && n > 0) {
200                                         byte[] x = null;
201                                         if (n == b.length)
202                                                 x = b;
203                                         else {
204                                                 x = new byte[n];
205                                                 System.arraycopy(b, 0, x, 0, n);
206                                         }
207                                         outputBytes(x, false);
208                                         
209                                         // copy to HTTP body
210                                         byte[] temp = new byte[body.length + x.length];
211                                         System.arraycopy(body, 0, temp, 0, body.length);
212                                         System.arraycopy(x, 0, temp, body.length, x.length);
213                                         body = temp;
214                                 }
215                                 if (b.length < BUFFER)
216                                         b = new byte[BUFFER];
217                                 n = in.read(b);
218                                 Thread.yield();
219                         }
220                         out.flush();
221                         setHTTPBody(body);
222                         return;
223                 }
224         
225                 // spec 4.4.1
226                 if (responseType != null &&     (responseType.startsWith("1") || "204".equals(responseType) || "304".equals(responseType))) {
227                         setHTTPBody(new byte[0]);
228                         return;
229                 }
230         
231                 // spec 4.4.2
232                 if (transferEncoding != -1 && transferEncoding != ENCODING_IDENTITY) {
233                         parseChunk();
234                         return;
235                 }
236         
237                 // spec 4.4.3
238                 if (contentLength != -1) {
239                         byte[] b = readBytes(contentLength);
240                         out.write(b);
241                         if (isRequest)
242                                 conn.addRequest(b, false);
243                         else
244                                 conn.addResponse(b, false);
245                         setHTTPBody(b);
246                         return;
247                 }
248                 
249                 // spec 4.4.4 (?)
250                 
251                 Trace.trace(Trace.PARSING, "Unknown body for: " + this);
252         }
253
254         /**
255          * Parse an HTTP chunk.
256          */
257         public void parseChunk() throws IOException {
258                 Trace.trace(Trace.PARSING, "Parsing chunk for: " + this);
259                 boolean done = false;
260                 byte[] body = new byte[0];
261         
262                 while (!done) {
263                         // read chunk size
264                         byte[] b = readLine();
265         
266                         String s = new String(b);
267                         int index = s.indexOf(" ");
268                         int length = -1;
269                         try {
270                                 if (index > 0)
271                                         s = s.substring(0, index);
272                                 length = Integer.parseInt(s.trim(), 16);
273                         } catch (Exception e) {
274                                 Trace.trace(Trace.PARSING, "Error chunk for: " + this, e);
275                         }
276         
277                         // output bytes
278                         outputBytes(b, false);
279         
280                         if (length <= 0)
281                                 done = true;
282                         else {
283                                 // read and output chunk data plus CRLF
284                                 b = readBytes(length + 2);
285                                 outputBytes(b, false);
286                                 
287                                 // copy to HTTP body
288                                 byte[] temp = new byte[body.length + b.length - 2];
289                                 System.arraycopy(body, 0, temp, 0, body.length);
290                                 System.arraycopy(b, 0, temp, body.length, b.length - 2);
291                                 body = temp;
292                         }
293                 }
294         
295                 // read trailer
296                 byte[] b = readLine();
297                 while (b.length > 2) {
298                         outputBytes(b, false);
299                         b = readLine();
300                 }
301         
302                 outputBytes(b, false);
303                 setHTTPBody(body);
304         }
305
306         /**
307          * Parse an HTTP header.
308          */
309         public void parseHeader() throws IOException {
310                 Trace.trace(Trace.PARSING, "Parsing header for: " + this);
311         
312                 // read until first blank line
313                 boolean isFirstLine = true;
314                 boolean isNew = true;
315         
316                 byte[] b = readLine();
317                 while (b.length > 5) {
318                         Trace.trace(Trace.PARSING, "Parsing header line: '" + new String(b) + "'");
319                         
320                         if (isFirstLine) {
321                                 String s = new String(b);
322                                 if (isRequest) {
323                                         setLabel(s);
324                                         isNew = false;
325                                 }
326         
327                                 if (!isRequest) {
328                                         int index1 = s.indexOf(' ');
329                                         int index2 = s.indexOf(' ', index1 + 1);
330         
331                                         try {
332                                                 responseType = s.substring(index1 + 1, index2).trim();
333                                                 Trace.trace(Trace.PARSING, "Response Type: " + this + " " + responseType);
334                                         } catch (Exception e) {
335                                                 Trace.trace(Trace.PARSING, "Error parsing response type for: " + this, e);
336                                         }
337                                         if (responseType != null && responseType.equals("100")) {
338                                                 outputBytes(b, isNew);
339                                                 isNew = false;
340
341                                                 b = readLine();
342                                                 outputBytes(b, false);
343
344                                                 b = readLine();
345
346                                                 index1 = s.indexOf(' ');
347                                                 index2 = s.indexOf(' ', index1 + 1);
348
349                                                 try {
350                                                         responseType = s.substring(index1 + 1, index2).trim();
351                                                         Trace.trace(Trace.PARSING, "Response Type: " + this + " " + responseType);
352                                                 } catch (Exception e) {
353                                                         Trace.trace(Trace.PARSING, "Error parsing response type for: " + this, e);
354                                                 }
355                                         }
356                                 }
357                                 isFirstLine = false;
358                         }
359         
360                         // translate
361                         b = translateHeaderLine(b);
362                         
363                         outputBytes(b, isNew);
364                         isNew = false;
365         
366                         b = readLine();
367                 }
368
369                 Trace.trace(Trace.PARSING, "Parsing final header line: '" + new String(b) + "'");
370
371                 outputBytes(b, false);
372
373                 IRequest rr = conn.getRequestResponse(isRequest);
374                 Trace.trace(Trace.PARSING, "Setting header length: " + rr.getRequest(IRequest.ALL).length);
375                 
376                 setHTTPHeader(rr);
377         }
378
379         /**
380          * Read bytes from the stream.
381          * @return byte[]
382          */
383         protected byte[] readBytes(int n) throws IOException {
384                 Trace.trace(Trace.PARSING, "readBytes() " + n + " for: " + this);
385                 while (buffer.length - bufferIndex < n)
386                         fillBuffer();
387         
388                 return removeFromBuffer(bufferIndex + n);
389         }
390
391         /**
392          * Read and return the next full line.
393          *
394          * @return byte[]
395          */
396         protected byte[] readLine() throws IOException {
397                 Trace.trace(Trace.PARSING, "readLine() for: " + this);
398         
399                 int n = getFirstCRLF();
400                 while (n < 0) {
401                         fillBuffer();
402                         n = getFirstCRLF();
403                 }
404                 return removeFromBuffer(n + 1);
405         }
406
407         /**
408          * Remove data from the buffer up to the absolute index n.
409          * Return the data from between bufferIndex and n.
410          *
411          * @return byte[]
412          * @param index int
413          */
414         protected byte[] removeFromBuffer(int n) {
415                 // copy line out of buffer
416                 byte[] b = new byte[n - bufferIndex];
417                 System.arraycopy(buffer, bufferIndex, b, 0, n - bufferIndex);
418         
419                 if (buffer.length > BUFFER * 2 || bufferIndex > BUFFER) {
420                         // remove line from buffer
421                         int size = buffer.length;
422                         byte[] x = new byte[size - n];
423                         System.arraycopy(buffer, n, x, 0, size - n);
424                         buffer = x;
425                         bufferIndex = 0;
426                 } else
427                         bufferIndex = n;
428         
429                 return b;
430         }
431
432         /**
433          * Listen for input, save it, and pass to the output stream.
434          * Philosophy: Read a single line separately and translate.
435          * When blank line is reached, just pass all other data through.
436          */
437         public void run() {
438                 try {
439                         try {
440                                 while (true) {
441                                         contentLength = -1;
442                                         transferEncoding = -1;
443                                         keepAlive = false;
444
445                                         parseHeader();
446                                         parseBody();
447                                         
448                                         if (isRequest && keepAlive)
449                                                 waitForResponse();
450                                         
451                                         IRequest r = conn.getRequestResponse(true);
452                                         r.fireChangedEvent();
453
454                                         Trace.trace(Trace.PARSING, "Done HTTP request for " + this + " " + keepAlive);
455                                         if (!isRequest && !keepAlive) {
456                                                 conn2.close();
457                                                 break;
458                                         }
459                                         
460                                         if (!isRequest)
461                                                 notifyRequest();
462                                         
463                                         Thread.yield();
464                                 }
465                         } catch (IOException e) {
466                                 // reached end of input
467                                 Trace.trace(Trace.PARSING, "End of buffer for: " + this + " " + e.getMessage());
468                         }
469
470                         // send rest of buffer
471                         out.write(buffer, bufferIndex, buffer.length - bufferIndex);
472                         out.flush();
473                 } catch (IOException e) {
474                         Trace.trace(Trace.PARSING, "Error in: " + this, e);
475                 }
476                 Trace.trace(Trace.PARSING, "Closing thread " + this);
477         }
478
479         /**
480          * Sets the title of the call.
481          *
482          * @param s java.lang.String
483          */
484         protected void setLabel(String s) {
485                 try {
486                         int index1 = s.indexOf(' ');
487                         if (index1 < 0 || index1 > 15)
488                                 return;
489                         int index2 = s.indexOf(' ', index1 + 1);
490                         if (index2 < 0)
491                                 return;
492         
493                         conn.setLabel(s.substring(index1 + 1, index2), true);
494                 } catch (Exception e) { }
495         }
496
497         /**
498          * Translate the header line.
499          * 
500          * @return byte[]
501          * @param b byte[]
502          */
503         protected byte[] translateHeaderLine(byte[] b) {
504                 String s = new String(b);
505         
506                 if (isRequest && s.startsWith("Host: ")) {
507                         String t = "Host: " + host;
508                         if (port != 80)
509                                 t += ":" + port;
510                         return convert(t.getBytes());
511                 } else if (s.startsWith("Content-Length: ")) {
512                         try {
513                                 contentLength = Integer.parseInt(s.substring(16).trim());
514                                 Trace.trace(Trace.PARSING, "Content length: " + this + " " + contentLength);
515                         } catch (Exception e) {
516                                 Trace.trace(Trace.PARSING, "Content length error", e);
517                         }
518                 } else if (s.startsWith("Connection: ")) {
519                         try {
520                                 String t = s.substring(11).trim();
521                                 if (t.equalsIgnoreCase("Keep-Alive"))
522                                         keepAlive = true;
523                                 Trace.trace(Trace.PARSING, "Keep alive: " + keepAlive);
524                         } catch (Exception e) {
525                                 Trace.trace(Trace.PARSING, "Error getting Connection: from header", e);
526                         }
527                 } else if (s.startsWith("Transfer-Encoding: ")) {
528                         String t = s.substring(19).trim();
529                         int size = ENCODING_STRING.length;
530                         for (int i = 0; i < size; i++) {
531                                 if (ENCODING_STRING[i].equalsIgnoreCase(t)) {
532                                         transferEncoding = (byte) i;
533                                         Trace.trace(Trace.PARSING, "Transfer encoding: " + ENCODING_STRING[i]);
534                                 }
535                         }
536                 }
537         
538                 return b;
539         }
540         
541         protected void close() {
542                 try {
543                         out.close();
544                 } catch (Exception e) {
545                         Trace.trace(Trace.PARSING, "Error closing connection " + this + " " + e.getMessage());
546                 }
547         }
548         
549         protected void waitForResponse() {
550                 Trace.trace(Trace.PARSING, "Waiting for response " + this);
551                 synchronized (this) {
552                         try {
553                                 isWaiting = true;
554                                 wait();
555                         } catch (Exception e) {
556                                 Trace.trace(Trace.PARSING, "Error in waitForResponse() " + this + " " + e.getMessage());
557                         }
558                         isWaiting = false;
559                 }
560                 Trace.trace(Trace.PARSING, "Done waiting for response " + this);
561         }
562
563         protected void notifyRequest() {
564                 Trace.trace(Trace.PARSING, "Notifying request " + this);
565                 while (request.keepAlive && !request.isWaiting) {
566                         Trace.trace(Trace.PARSING, "Waiting for request " + this);
567                         try {
568                                 Thread.sleep(100);
569                         } catch (Exception e) { }
570                 }
571                 synchronized (request) {
572                         try {
573                                 request.notify();
574                         } catch (Exception e) {
575                                 Trace.trace(Trace.PARSING, "Error in notifyRequest() " + this + " " + e.getMessage());
576                         }
577                 }
578                 Trace.trace(Trace.PARSING, "Done notifying request " + this);
579         }
580         
581         protected void setHTTPHeader(IRequest rr) {
582                 if (isRequest) {
583                         byte[] b = rr.getRequest(IRequest.ALL);
584                         byte[] h = new byte[b.length];
585                         System.arraycopy(b, 0, h, 0, b.length);
586                         rr.addProperty(HTTPRequest.HTTP_REQUEST_HEADER, h);
587                 } else {
588                         byte[] b = rr.getResponse(IRequest.ALL);
589                         byte[] h = new byte[b.length];
590                         System.arraycopy(b, 0, h, 0, b.length);
591                         rr.addProperty(HTTPRequest.HTTP_RESPONSE_HEADER, h);
592                 }
593         }
594         
595         protected void setHTTPBody(byte[] b) {
596                 IRequest rr = conn.getRequestResponse(isRequest);
597                 if (isRequest)
598                         rr.addProperty(HTTPRequest.HTTP_REQUEST_BODY, b);
599                 else
600                         rr.addProperty(HTTPRequest.HTTP_RESPONSE_BODY, b);
601         }
602 }