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
9 * IBM - Initial API and implementation
10 **********************************************************************/
11 package net.sourceforge.phpdt.monitor.core.internal.http;
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;
20 * Monitor server I/O thread.
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;
28 private byte[] readBuffer = new byte[BUFFER];
31 protected byte[] buffer = new byte[0];
32 protected int bufferIndex = 0;
34 protected InputStream in;
35 protected OutputStream out;
36 protected HTTPConnection conn;
37 protected boolean isRequest;
38 protected Connection conn2;
40 protected HTTPThread request;
41 protected boolean isWaiting;
43 // user to translate the Host: header
44 protected String host;
47 protected int contentLength = -1;
48 protected byte transferEncoding = -1;
49 protected String responseType = null;
50 protected boolean keepAlive = false;
52 protected static final String[] ENCODING_STRING = new String[] {
53 "chunked", "identity", "gzip", "compressed", "deflate"};
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;
62 Referer: http://localhost:8081/index.html
65 /* The Connection header has the following grammar:
67 Connection = "Connection" ":" 1#(connection-token)
68 connection-token = token
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
76 * MonitorThread constructor comment.
78 public HTTPThread(Connection conn2, InputStream in, OutputStream out, HTTPConnection conn, boolean isRequest, String host, int port) {
84 this.isRequest = isRequest;
88 setName("HTTP (" + host + ":" + port + ") " + (isRequest ? "REQUEST" : "RESPONSE") + " " + (threadCount++));
89 setPriority(Thread.NORM_PRIORITY + 1);
92 Trace.trace(Trace.PARSING, "Started: " + this);
96 * MonitorThread constructor comment.
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);
101 this.request = request;
105 * Add a line feed to the end of the byte array.
109 protected static byte[] convert(byte[] b) {
110 if (b == null || b.length == 0)
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
122 * Read more data into the buffer.
126 protected void fillBuffer() throws IOException {
127 int n = in.read(readBuffer);
130 throw new IOException("End of input");
132 // add to full buffer
133 int len = buffer.length - bufferIndex;
136 byte[] x = new byte[n + len];
137 System.arraycopy(buffer, bufferIndex, x, 0, len);
138 System.arraycopy(readBuffer, 0, x, len, n);
144 * Returns the first location of a CRLF.
148 protected int getFirstCRLF() {
149 int size = buffer.length;
150 int i = bufferIndex + 1;
152 if (buffer[i - 1] == CR && buffer[i] == LF)
160 * Output the given bytes.
163 protected void outputBytes(byte[] b, boolean isNew) throws IOException {
166 conn.addRequest(b, isNew);
168 conn.addResponse(b, isNew);
172 * Parse the HTTP body.
174 public void parseBody() throws IOException {
175 Trace.trace(Trace.PARSING, "Parsing body for: " + this);
178 if (contentLength != -1) {
179 byte[] b = readBytes(contentLength);
181 conn.addRequest(b, false);
183 } else if (transferEncoding != -1 && transferEncoding != ENCODING_IDENTITY) {
187 Trace.trace(Trace.PARSING, "Done parsing request body for: " + this);
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];
198 Trace.trace(Trace.PARSING, "Bytes read: " + n + " " + this);
199 if (b != null && n > 0) {
205 System.arraycopy(b, 0, x, 0, n);
207 outputBytes(x, false);
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);
215 if (b.length < BUFFER)
216 b = new byte[BUFFER];
226 if (responseType != null && (responseType.startsWith("1") || "204".equals(responseType) || "304".equals(responseType))) {
227 setHTTPBody(new byte[0]);
232 if (transferEncoding != -1 && transferEncoding != ENCODING_IDENTITY) {
238 if (contentLength != -1) {
239 byte[] b = readBytes(contentLength);
242 conn.addRequest(b, false);
244 conn.addResponse(b, false);
251 Trace.trace(Trace.PARSING, "Unknown body for: " + this);
255 * Parse an HTTP chunk.
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];
264 byte[] b = readLine();
266 String s = new String(b);
267 int index = s.indexOf(" ");
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);
278 outputBytes(b, false);
283 // read and output chunk data plus CRLF
284 b = readBytes(length + 2);
285 outputBytes(b, false);
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);
296 byte[] b = readLine();
297 while (b.length > 2) {
298 outputBytes(b, false);
302 outputBytes(b, false);
307 * Parse an HTTP header.
309 public void parseHeader() throws IOException {
310 Trace.trace(Trace.PARSING, "Parsing header for: " + this);
312 // read until first blank line
313 boolean isFirstLine = true;
314 boolean isNew = true;
316 byte[] b = readLine();
317 while (b.length > 5) {
318 Trace.trace(Trace.PARSING, "Parsing header line: '" + new String(b) + "'");
321 String s = new String(b);
328 int index1 = s.indexOf(' ');
329 int index2 = s.indexOf(' ', index1 + 1);
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);
337 if (responseType != null && responseType.equals("100")) {
338 outputBytes(b, isNew);
342 outputBytes(b, false);
346 index1 = s.indexOf(' ');
347 index2 = s.indexOf(' ', index1 + 1);
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);
361 b = translateHeaderLine(b);
363 outputBytes(b, isNew);
369 Trace.trace(Trace.PARSING, "Parsing final header line: '" + new String(b) + "'");
371 outputBytes(b, false);
373 IRequest rr = conn.getRequestResponse(isRequest);
374 Trace.trace(Trace.PARSING, "Setting header length: " + rr.getRequest(IRequest.ALL).length);
380 * Read bytes from the stream.
383 protected byte[] readBytes(int n) throws IOException {
384 Trace.trace(Trace.PARSING, "readBytes() " + n + " for: " + this);
385 while (buffer.length - bufferIndex < n)
388 return removeFromBuffer(bufferIndex + n);
392 * Read and return the next full line.
396 protected byte[] readLine() throws IOException {
397 Trace.trace(Trace.PARSING, "readLine() for: " + this);
399 int n = getFirstCRLF();
404 return removeFromBuffer(n + 1);
408 * Remove data from the buffer up to the absolute index n.
409 * Return the data from between bufferIndex and n.
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);
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);
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.
442 transferEncoding = -1;
448 if (isRequest && keepAlive)
451 IRequest r = conn.getRequestResponse(true);
452 r.fireChangedEvent();
454 Trace.trace(Trace.PARSING, "Done HTTP request for " + this + " " + keepAlive);
455 if (!isRequest && !keepAlive) {
465 } catch (IOException e) {
466 // reached end of input
467 Trace.trace(Trace.PARSING, "End of buffer for: " + this + " " + e.getMessage());
470 // send rest of buffer
471 out.write(buffer, bufferIndex, buffer.length - bufferIndex);
473 } catch (IOException e) {
474 Trace.trace(Trace.PARSING, "Error in: " + this, e);
476 Trace.trace(Trace.PARSING, "Closing thread " + this);
480 * Sets the title of the call.
482 * @param s java.lang.String
484 protected void setLabel(String s) {
486 int index1 = s.indexOf(' ');
487 if (index1 < 0 || index1 > 15)
489 int index2 = s.indexOf(' ', index1 + 1);
493 conn.setLabel(s.substring(index1 + 1, index2), true);
494 } catch (Exception e) { }
498 * Translate the header line.
503 protected byte[] translateHeaderLine(byte[] b) {
504 String s = new String(b);
506 if (isRequest && s.startsWith("Host: ")) {
507 String t = "Host: " + host;
510 return convert(t.getBytes());
511 } else if (s.startsWith("Content-Length: ")) {
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);
518 } else if (s.startsWith("Connection: ")) {
520 String t = s.substring(11).trim();
521 if (t.equalsIgnoreCase("Keep-Alive"))
523 Trace.trace(Trace.PARSING, "Keep alive: " + keepAlive);
524 } catch (Exception e) {
525 Trace.trace(Trace.PARSING, "Error getting Connection: from header", e);
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]);
541 protected void close() {
544 } catch (Exception e) {
545 Trace.trace(Trace.PARSING, "Error closing connection " + this + " " + e.getMessage());
549 protected void waitForResponse() {
550 Trace.trace(Trace.PARSING, "Waiting for response " + this);
551 synchronized (this) {
555 } catch (Exception e) {
556 Trace.trace(Trace.PARSING, "Error in waitForResponse() " + this + " " + e.getMessage());
560 Trace.trace(Trace.PARSING, "Done waiting for response " + this);
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);
569 } catch (Exception e) { }
571 synchronized (request) {
574 } catch (Exception e) {
575 Trace.trace(Trace.PARSING, "Error in notifyRequest() " + this + " " + e.getMessage());
578 Trace.trace(Trace.PARSING, "Done notifying request " + this);
581 protected void setHTTPHeader(IRequest rr) {
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);
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);
595 protected void setHTTPBody(byte[] b) {
596 IRequest rr = conn.getRequestResponse(isRequest);
598 rr.addProperty(HTTPRequest.HTTP_REQUEST_BODY, b);
600 rr.addProperty(HTTPRequest.HTTP_RESPONSE_BODY, b);