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