X-Git-Url: http://git.phpeclipse.com diff --git a/archive/net.sourceforge.phpeclipse.monitor.core/src/net/sourceforge/phpdt/monitor/core/internal/http/HTTPThread.java b/archive/net.sourceforge.phpeclipse.monitor.core/src/net/sourceforge/phpdt/monitor/core/internal/http/HTTPThread.java new file mode 100644 index 0000000..1530d89 --- /dev/null +++ b/archive/net.sourceforge.phpeclipse.monitor.core/src/net/sourceforge/phpdt/monitor/core/internal/http/HTTPThread.java @@ -0,0 +1,602 @@ +/********************************************************************** + * Copyright (c) 2003 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Common Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/cpl-v10.html + * + * Contributors: + * IBM - Initial API and implementation + **********************************************************************/ +package net.sourceforge.phpdt.monitor.core.internal.http; + +import java.io.*; + +import net.sourceforge.phpdt.monitor.core.IRequest; +import net.sourceforge.phpdt.monitor.core.internal.Connection; +import net.sourceforge.phpdt.monitor.core.internal.Trace; + +/** + * Monitor server I/O thread. + */ +public class HTTPThread extends Thread { + private static final int BUFFER = 2048; + private static final byte CR = (byte) '\r'; + private static final byte LF = (byte) '\n'; + protected static int threadCount = 0; + + private byte[] readBuffer = new byte[BUFFER]; + + // buffer and index + protected byte[] buffer = new byte[0]; + protected int bufferIndex = 0; + + protected InputStream in; + protected OutputStream out; + protected HTTPConnection conn; + protected boolean isRequest; + protected Connection conn2; + + protected HTTPThread request; + protected boolean isWaiting; + + // user to translate the Host: header + protected String host; + protected int port; + + protected int contentLength = -1; + protected byte transferEncoding = -1; + protected String responseType = null; + protected boolean keepAlive = false; + + protected static final String[] ENCODING_STRING = new String[] { + "chunked", "identity", "gzip", "compressed", "deflate"}; + + protected static final byte ENCODING_CHUNKED = 0; + protected static final byte ENCODING_IDENTITY = 1; + protected static final byte ENCODING_GZIP = 2; + protected static final byte ENCODING_COMPRESSED = 3; + protected static final byte ENCODING_DEFLATE = 4; + +/* change: +Referer: http://localhost:8081/index.html +Host: localhost:8081 +*/ +/* The Connection header has the following grammar: + + Connection = "Connection" ":" 1#(connection-token) + connection-token = token + + HTTP/1.1 proxies MUST parse the Connection header field before a + message is forwarded and, for each connection-token in this field, + remove any header field(s) from the message with the same name as the + connection-token. */ + + /** + * MonitorThread constructor comment. + */ + public HTTPThread(Connection conn2, InputStream in, OutputStream out, HTTPConnection conn, boolean isRequest, String host, int port) { + super(); + this.conn2 = conn2; + this.in = in; + this.out = out; + this.conn = conn; + this.isRequest = isRequest; + this.host = host; + this.port = port; + + setName("HTTP (" + host + ":" + port + ") " + (isRequest ? "REQUEST" : "RESPONSE") + " " + (threadCount++)); + setPriority(Thread.NORM_PRIORITY + 1); + setDaemon(true); + + Trace.trace(Trace.PARSING, "Started: " + this); + } + + /** + * MonitorThread constructor comment. + */ + public HTTPThread(Connection conn2, InputStream in, OutputStream out, HTTPConnection conn, boolean isRequest, String host, int port, HTTPThread request) { + this(conn2, in, out, conn, isRequest, host, port); + + this.request = request; + } + + /** + * Add a line feed to the end of the byte array. + * @return byte[] + * @param b byte[] + */ + protected static byte[] convert(byte[] b) { + if (b == null || b.length == 0) + return b; + + int size = b.length; + byte[] x = new byte[size + 2]; + System.arraycopy(b, 0, x, 0, size); + x[size] = (byte) '\r'; // CR + x[size + 1] = (byte) '\n'; // LF + return x; + } + + /** + * Read more data into the buffer. + * + * @return byte[] + */ + protected void fillBuffer() throws IOException { + int n = in.read(readBuffer); + + if (n <= 0) + throw new IOException("End of input"); + + // add to full buffer + int len = buffer.length - bufferIndex; + if (len < 0) + len = 0; + byte[] x = new byte[n + len]; + System.arraycopy(buffer, bufferIndex, x, 0, len); + System.arraycopy(readBuffer, 0, x, len, n); + bufferIndex = 0; + buffer = x; + } + + /** + * Returns the first location of a CRLF. + * + * @return int + */ + protected int getFirstCRLF() { + int size = buffer.length; + int i = bufferIndex + 1; + while (i < size) { + if (buffer[i - 1] == CR && buffer[i] == LF) + return i; + i++; + } + return -1; + } + + /** + * Output the given bytes. + * @param b byte[] + */ + protected void outputBytes(byte[] b, boolean isNew) throws IOException { + out.write(b); + if (isRequest) + conn.addRequest(b, isNew); + else + conn.addResponse(b, isNew); + } + + /** + * Parse the HTTP body. + */ + public void parseBody() throws IOException { + Trace.trace(Trace.PARSING, "Parsing body for: " + this); + + if (isRequest) { + if (contentLength != -1) { + byte[] b = readBytes(contentLength); + out.write(b); + conn.addRequest(b, false); + setHTTPBody(b); + } else if (transferEncoding != -1 && transferEncoding != ENCODING_IDENTITY) { + parseChunk(); + } + + Trace.trace(Trace.PARSING, "Done parsing request body for: " + this); + return; + } + + // just return body for HTTP 1.0 responses + if (!isRequest && !keepAlive && contentLength == -1 && transferEncoding == -1) { + Trace.trace(Trace.PARSING, "Assuming HTTP 1.0 for: " + this); + int n = buffer.length - bufferIndex; + byte[] b = readBytes(n); + byte[] body = new byte[0]; + while (n >= 0) { + Trace.trace(Trace.PARSING, "Bytes read: " + n + " " + this); + if (b != null && n > 0) { + byte[] x = null; + if (n == b.length) + x = b; + else { + x = new byte[n]; + System.arraycopy(b, 0, x, 0, n); + } + outputBytes(x, false); + + // copy to HTTP body + byte[] temp = new byte[body.length + x.length]; + System.arraycopy(body, 0, temp, 0, body.length); + System.arraycopy(x, 0, temp, body.length, x.length); + body = temp; + } + if (b.length < BUFFER) + b = new byte[BUFFER]; + n = in.read(b); + Thread.yield(); + } + out.flush(); + setHTTPBody(body); + return; + } + + // spec 4.4.1 + if (responseType != null && (responseType.startsWith("1") || "204".equals(responseType) || "304".equals(responseType))) { + setHTTPBody(new byte[0]); + return; + } + + // spec 4.4.2 + if (transferEncoding != -1 && transferEncoding != ENCODING_IDENTITY) { + parseChunk(); + return; + } + + // spec 4.4.3 + if (contentLength != -1) { + byte[] b = readBytes(contentLength); + out.write(b); + if (isRequest) + conn.addRequest(b, false); + else + conn.addResponse(b, false); + setHTTPBody(b); + return; + } + + // spec 4.4.4 (?) + + Trace.trace(Trace.PARSING, "Unknown body for: " + this); + } + + /** + * Parse an HTTP chunk. + */ + public void parseChunk() throws IOException { + Trace.trace(Trace.PARSING, "Parsing chunk for: " + this); + boolean done = false; + byte[] body = new byte[0]; + + while (!done) { + // read chunk size + byte[] b = readLine(); + + String s = new String(b); + int index = s.indexOf(" "); + int length = -1; + try { + if (index > 0) + s = s.substring(0, index); + length = Integer.parseInt(s.trim(), 16); + } catch (Exception e) { + Trace.trace(Trace.PARSING, "Error chunk for: " + this, e); + } + + // output bytes + outputBytes(b, false); + + if (length <= 0) + done = true; + else { + // read and output chunk data plus CRLF + b = readBytes(length + 2); + outputBytes(b, false); + + // copy to HTTP body + byte[] temp = new byte[body.length + b.length - 2]; + System.arraycopy(body, 0, temp, 0, body.length); + System.arraycopy(b, 0, temp, body.length, b.length - 2); + body = temp; + } + } + + // read trailer + byte[] b = readLine(); + while (b.length > 2) { + outputBytes(b, false); + b = readLine(); + } + + outputBytes(b, false); + setHTTPBody(body); + } + + /** + * Parse an HTTP header. + */ + public void parseHeader() throws IOException { + Trace.trace(Trace.PARSING, "Parsing header for: " + this); + + // read until first blank line + boolean isFirstLine = true; + boolean isNew = true; + + byte[] b = readLine(); + while (b.length > 5) { + Trace.trace(Trace.PARSING, "Parsing header line: '" + new String(b) + "'"); + + if (isFirstLine) { + String s = new String(b); + if (isRequest) { + setLabel(s); + isNew = false; + } + + if (!isRequest) { + int index1 = s.indexOf(' '); + int index2 = s.indexOf(' ', index1 + 1); + + try { + responseType = s.substring(index1 + 1, index2).trim(); + Trace.trace(Trace.PARSING, "Response Type: " + this + " " + responseType); + } catch (Exception e) { + Trace.trace(Trace.PARSING, "Error parsing response type for: " + this, e); + } + if (responseType != null && responseType.equals("100")) { + outputBytes(b, isNew); + isNew = false; + + b = readLine(); + outputBytes(b, false); + + b = readLine(); + + index1 = s.indexOf(' '); + index2 = s.indexOf(' ', index1 + 1); + + try { + responseType = s.substring(index1 + 1, index2).trim(); + Trace.trace(Trace.PARSING, "Response Type: " + this + " " + responseType); + } catch (Exception e) { + Trace.trace(Trace.PARSING, "Error parsing response type for: " + this, e); + } + } + } + isFirstLine = false; + } + + // translate + b = translateHeaderLine(b); + + outputBytes(b, isNew); + isNew = false; + + b = readLine(); + } + + Trace.trace(Trace.PARSING, "Parsing final header line: '" + new String(b) + "'"); + + outputBytes(b, false); + + IRequest rr = conn.getRequestResponse(isRequest); + Trace.trace(Trace.PARSING, "Setting header length: " + rr.getRequest(IRequest.ALL).length); + + setHTTPHeader(rr); + } + + /** + * Read bytes from the stream. + * @return byte[] + */ + protected byte[] readBytes(int n) throws IOException { + Trace.trace(Trace.PARSING, "readBytes() " + n + " for: " + this); + while (buffer.length - bufferIndex < n) + fillBuffer(); + + return removeFromBuffer(bufferIndex + n); + } + + /** + * Read and return the next full line. + * + * @return byte[] + */ + protected byte[] readLine() throws IOException { + Trace.trace(Trace.PARSING, "readLine() for: " + this); + + int n = getFirstCRLF(); + while (n < 0) { + fillBuffer(); + n = getFirstCRLF(); + } + return removeFromBuffer(n + 1); + } + + /** + * Remove data from the buffer up to the absolute index n. + * Return the data from between bufferIndex and n. + * + * @return byte[] + * @param index int + */ + protected byte[] removeFromBuffer(int n) { + // copy line out of buffer + byte[] b = new byte[n - bufferIndex]; + System.arraycopy(buffer, bufferIndex, b, 0, n - bufferIndex); + + if (buffer.length > BUFFER * 2 || bufferIndex > BUFFER) { + // remove line from buffer + int size = buffer.length; + byte[] x = new byte[size - n]; + System.arraycopy(buffer, n, x, 0, size - n); + buffer = x; + bufferIndex = 0; + } else + bufferIndex = n; + + return b; + } + + /** + * Listen for input, save it, and pass to the output stream. + * Philosophy: Read a single line separately and translate. + * When blank line is reached, just pass all other data through. + */ + public void run() { + try { + try { + while (true) { + contentLength = -1; + transferEncoding = -1; + keepAlive = false; + + parseHeader(); + parseBody(); + + if (isRequest && keepAlive) + waitForResponse(); + + IRequest r = conn.getRequestResponse(true); + r.fireChangedEvent(); + + Trace.trace(Trace.PARSING, "Done HTTP request for " + this + " " + keepAlive); + if (!isRequest && !keepAlive) { + conn2.close(); + break; + } + + if (!isRequest) + notifyRequest(); + + Thread.yield(); + } + } catch (IOException e) { + // reached end of input + Trace.trace(Trace.PARSING, "End of buffer for: " + this + " " + e.getMessage()); + } + + // send rest of buffer + out.write(buffer, bufferIndex, buffer.length - bufferIndex); + out.flush(); + } catch (IOException e) { + Trace.trace(Trace.PARSING, "Error in: " + this, e); + } + Trace.trace(Trace.PARSING, "Closing thread " + this); + } + + /** + * Sets the title of the call. + * + * @param s java.lang.String + */ + protected void setLabel(String s) { + try { + int index1 = s.indexOf(' '); + if (index1 < 0 || index1 > 15) + return; + int index2 = s.indexOf(' ', index1 + 1); + if (index2 < 0) + return; + + conn.setLabel(s.substring(index1 + 1, index2), true); + } catch (Exception e) { } + } + + /** + * Translate the header line. + * + * @return byte[] + * @param b byte[] + */ + protected byte[] translateHeaderLine(byte[] b) { + String s = new String(b); + + if (isRequest && s.startsWith("Host: ")) { + String t = "Host: " + host; + if (port != 80) + t += ":" + port; + return convert(t.getBytes()); + } else if (s.startsWith("Content-Length: ")) { + try { + contentLength = Integer.parseInt(s.substring(16).trim()); + Trace.trace(Trace.PARSING, "Content length: " + this + " " + contentLength); + } catch (Exception e) { + Trace.trace(Trace.PARSING, "Content length error", e); + } + } else if (s.startsWith("Connection: ")) { + try { + String t = s.substring(11).trim(); + if (t.equalsIgnoreCase("Keep-Alive")) + keepAlive = true; + Trace.trace(Trace.PARSING, "Keep alive: " + keepAlive); + } catch (Exception e) { + Trace.trace(Trace.PARSING, "Error getting Connection: from header", e); + } + } else if (s.startsWith("Transfer-Encoding: ")) { + String t = s.substring(19).trim(); + int size = ENCODING_STRING.length; + for (int i = 0; i < size; i++) { + if (ENCODING_STRING[i].equalsIgnoreCase(t)) { + transferEncoding = (byte) i; + Trace.trace(Trace.PARSING, "Transfer encoding: " + ENCODING_STRING[i]); + } + } + } + + return b; + } + + protected void close() { + try { + out.close(); + } catch (Exception e) { + Trace.trace(Trace.PARSING, "Error closing connection " + this + " " + e.getMessage()); + } + } + + protected void waitForResponse() { + Trace.trace(Trace.PARSING, "Waiting for response " + this); + synchronized (this) { + try { + isWaiting = true; + wait(); + } catch (Exception e) { + Trace.trace(Trace.PARSING, "Error in waitForResponse() " + this + " " + e.getMessage()); + } + isWaiting = false; + } + Trace.trace(Trace.PARSING, "Done waiting for response " + this); + } + + protected void notifyRequest() { + Trace.trace(Trace.PARSING, "Notifying request " + this); + while (request.keepAlive && !request.isWaiting) { + Trace.trace(Trace.PARSING, "Waiting for request " + this); + try { + Thread.sleep(100); + } catch (Exception e) { } + } + synchronized (request) { + try { + request.notify(); + } catch (Exception e) { + Trace.trace(Trace.PARSING, "Error in notifyRequest() " + this + " " + e.getMessage()); + } + } + Trace.trace(Trace.PARSING, "Done notifying request " + this); + } + + protected void setHTTPHeader(IRequest rr) { + if (isRequest) { + byte[] b = rr.getRequest(IRequest.ALL); + byte[] h = new byte[b.length]; + System.arraycopy(b, 0, h, 0, b.length); + rr.addProperty(HTTPRequest.HTTP_REQUEST_HEADER, h); + } else { + byte[] b = rr.getResponse(IRequest.ALL); + byte[] h = new byte[b.length]; + System.arraycopy(b, 0, h, 0, b.length); + rr.addProperty(HTTPRequest.HTTP_RESPONSE_HEADER, h); + } + } + + protected void setHTTPBody(byte[] b) { + IRequest rr = conn.getRequestResponse(isRequest); + if (isRequest) + rr.addProperty(HTTPRequest.HTTP_REQUEST_BODY, b); + else + rr.addProperty(HTTPRequest.HTTP_RESPONSE_BODY, b); + } +} \ No newline at end of file