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;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.OutputStream;
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;
22 * Monitor server I/O thread.
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;
30 private byte[] readBuffer = new byte[BUFFER];
33 protected byte[] buffer = new byte[0];
34 protected int bufferIndex = 0;
36 protected InputStream in;
37 protected OutputStream out;
38 protected HTTPConnection conn;
39 protected boolean isRequest;
40 protected Connection conn2;
42 protected HTTPThread request;
43 protected boolean isWaiting;
45 // user to translate the Host: header
46 protected String host;
49 protected int contentLength = -1;
50 protected byte transferEncoding = -1;
51 protected String responseType = null;
52 protected boolean keepAlive = false;
54 protected static final String[] ENCODING_STRING = new String[] {
55 "chunked", "identity", "gzip", "compressed", "deflate"};
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;
64 Referer: http://localhost:8081/index.html
67 /* The Connection header has the following grammar:
69 Connection = "Connection" ":" 1#(connection-token)
70 connection-token = token
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
78 * MonitorThread constructor comment.
80 public HTTPThread(Connection conn2, InputStream in, OutputStream out, HTTPConnection conn, boolean isRequest, String host, int port) {
86 this.isRequest = isRequest;
90 setName("HTTP (" + host + ":" + port + ") " + (isRequest ? "REQUEST" : "RESPONSE") + " " + (threadCount++));
91 setPriority(Thread.NORM_PRIORITY + 1);
94 Trace.trace(Trace.PARSING, "Started: " + this);
98 * MonitorThread constructor comment.
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);
103 this.request = request;
107 * Add a line feed to the end of the byte array.
111 protected static byte[] convert(byte[] b) {
112 if (b == null || b.length == 0)
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
124 * Read more data into the buffer.
128 protected void fillBuffer() throws IOException {
129 int n = in.read(readBuffer);
132 throw new IOException("End of input");
134 // add to full buffer
135 int len = buffer.length - bufferIndex;
138 byte[] x = new byte[n + len];
139 System.arraycopy(buffer, bufferIndex, x, 0, len);
140 System.arraycopy(readBuffer, 0, x, len, n);
146 * Returns the first location of a CRLF.
150 protected int getFirstCRLF() {
151 int size = buffer.length;
152 int i = bufferIndex + 1;
154 if (buffer[i - 1] == CR && buffer[i] == LF)
162 * Output the given bytes.
165 protected void outputBytes(byte[] b, boolean isNew) throws IOException {
168 conn.addRequest(b, isNew);
170 conn.addResponse(b, isNew);
174 * Parse the HTTP body.
176 public void parseBody() throws IOException {
177 Trace.trace(Trace.PARSING, "Parsing body for: " + this);
180 if (contentLength != -1) {
181 byte[] b = readBytes(contentLength);
183 conn.addRequest(b, false);
185 } else if (transferEncoding != -1 && transferEncoding != ENCODING_IDENTITY) {
189 Trace.trace(Trace.PARSING, "Done parsing request body for: " + this);
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];
200 Trace.trace(Trace.PARSING, "Bytes read: " + n + " " + this);
201 if (b != null && n > 0) {
207 System.arraycopy(b, 0, x, 0, n);
209 outputBytes(x, false);
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);
217 if (b.length < BUFFER)
218 b = new byte[BUFFER];
228 if (responseType != null && (responseType.startsWith("1") || "204".equals(responseType) || "304".equals(responseType))) {
229 setHTTPBody(new byte[0]);
234 if (transferEncoding != -1 && transferEncoding != ENCODING_IDENTITY) {
240 if (contentLength != -1) {
241 byte[] b = readBytes(contentLength);
244 conn.addRequest(b, false);
246 conn.addResponse(b, false);
253 Trace.trace(Trace.PARSING, "Unknown body for: " + this);
257 * Parse an HTTP chunk.
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];
266 byte[] b = readLine();
268 String s = new String(b);
269 int index = s.indexOf(" ");
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);
280 outputBytes(b, false);
285 // read and output chunk data plus CRLF
286 b = readBytes(length + 2);
287 outputBytes(b, false);
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);
298 byte[] b = readLine();
299 while (b.length > 2) {
300 outputBytes(b, false);
304 outputBytes(b, false);
309 * Parse an HTTP header.
311 public void parseHeader() throws IOException {
312 Trace.trace(Trace.PARSING, "Parsing header for: " + this);
314 // read until first blank line
315 boolean isFirstLine = true;
316 boolean isNew = true;
318 byte[] b = readLine();
319 while (b.length > 5) {
320 Trace.trace(Trace.PARSING, "Parsing header line: '" + new String(b) + "'");
323 String s = new String(b);
330 int index1 = s.indexOf(' ');
331 int index2 = s.indexOf(' ', index1 + 1);
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);
339 if (responseType != null && responseType.equals("100")) {
340 outputBytes(b, isNew);
344 outputBytes(b, false);
348 index1 = s.indexOf(' ');
349 index2 = s.indexOf(' ', index1 + 1);
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);
363 b = translateHeaderLine(b);
365 outputBytes(b, isNew);
371 Trace.trace(Trace.PARSING, "Parsing final header line: '" + new String(b) + "'");
373 outputBytes(b, false);
375 IRequest rr = conn.getRequestResponse(isRequest);
376 Trace.trace(Trace.PARSING, "Setting header length: " + rr.getRequest(IRequest.ALL).length);
382 * Read bytes from the stream.
385 protected byte[] readBytes(int n) throws IOException {
386 Trace.trace(Trace.PARSING, "readBytes() " + n + " for: " + this);
387 while (buffer.length - bufferIndex < n)
390 return removeFromBuffer(bufferIndex + n);
394 * Read and return the next full line.
398 protected byte[] readLine() throws IOException {
399 Trace.trace(Trace.PARSING, "readLine() for: " + this);
401 int n = getFirstCRLF();
406 return removeFromBuffer(n + 1);
410 * Remove data from the buffer up to the absolute index n.
411 * Return the data from between bufferIndex and n.
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);
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);
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.
444 transferEncoding = -1;
450 if (isRequest && keepAlive)
453 IRequest r = conn.getRequestResponse(true);
454 r.fireChangedEvent();
456 Trace.trace(Trace.PARSING, "Done HTTP request for " + this + " " + keepAlive);
457 if (!isRequest && !keepAlive) {
467 } catch (IOException e) {
468 // reached end of input
469 Trace.trace(Trace.PARSING, "End of buffer for: " + this + " " + e.getMessage());
472 // send rest of buffer
473 out.write(buffer, bufferIndex, buffer.length - bufferIndex);
475 } catch (IOException e) {
476 Trace.trace(Trace.PARSING, "Error in: " + this, e);
478 Trace.trace(Trace.PARSING, "Closing thread " + this);
482 * Sets the title of the call.
484 * @param s java.lang.String
486 protected void setLabel(String s) {
488 int index1 = s.indexOf(' ');
489 if (index1 < 0 || index1 > 15)
491 int index2 = s.indexOf(' ', index1 + 1);
495 conn.setLabel(s.substring(index1 + 1, index2), true);
496 } catch (Exception e) { }
500 * Translate the header line.
505 protected byte[] translateHeaderLine(byte[] b) {
506 String s = new String(b);
508 if (isRequest && s.startsWith("Host: ")) {
509 String t = "Host: " + host;
512 return convert(t.getBytes());
513 } else if (s.startsWith("Content-Length: ")) {
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);
520 } else if (s.startsWith("Connection: ")) {
522 String t = s.substring(11).trim();
523 if (t.equalsIgnoreCase("Keep-Alive"))
525 Trace.trace(Trace.PARSING, "Keep alive: " + keepAlive);
526 } catch (Exception e) {
527 Trace.trace(Trace.PARSING, "Error getting Connection: from header", e);
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]);
543 protected void close() {
546 } catch (Exception e) {
547 Trace.trace(Trace.PARSING, "Error closing connection " + this + " " + e.getMessage());
551 protected void waitForResponse() {
552 Trace.trace(Trace.PARSING, "Waiting for response " + this);
553 synchronized (this) {
557 } catch (Exception e) {
558 Trace.trace(Trace.PARSING, "Error in waitForResponse() " + this + " " + e.getMessage());
562 Trace.trace(Trace.PARSING, "Done waiting for response " + this);
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);
571 } catch (Exception e) { }
573 synchronized (request) {
576 } catch (Exception e) {
577 Trace.trace(Trace.PARSING, "Error in notifyRequest() " + this + " " + e.getMessage());
580 Trace.trace(Trace.PARSING, "Done notifying request " + this);
583 protected void setHTTPHeader(IRequest rr) {
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);
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);
597 protected void setHTTPBody(byte[] b) {
598 IRequest rr = conn.getRequestResponse(isRequest);
600 rr.addProperty(HTTPRequest.HTTP_REQUEST_BODY, b);
602 rr.addProperty(HTTPRequest.HTTP_RESPONSE_BODY, b);