You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@apache.org on 2009/12/04 08:17:04 UTC
svn commit: r887087 [1/3] - in /tomcat/trunk/modules/tomcat-lite:
java/org/apache/tomcat/lite/http/ java/org/apache/tomcat/lite/io/
java/org/apache/tomcat/lite/proxy/ java/org/apache/tomcat/lite/servlet/
test/org/apache/coyote/lite/ test/org/apache/tom...
Author: costin
Date: Fri Dec 4 07:16:59 2009
New Revision: 887087
URL: http://svn.apache.org/viewvc?rev=887087&view=rev
Log:
One more iteration:
- added few more tests
- moved the http/1.x code to HttpConnection - easier to test, allows protocol upgrade
- added an (experimental, hello-world-style) implementation of spdy ( a new binary protocol
and possible replacement for jk ). Tested with chrome and the unit tests - the tricky part
seems to work - detecting and 'upgrading' the wire transport.
Added:
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java (with props)
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java (with props)
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/ClientTest.java (with props)
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java (with props)
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreq0 (with props)
Removed:
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpBody.java
Modified:
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/DefaultHttpConnector.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Dispatcher.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBuffer.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/CBuffer.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslChannel.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/HttpProxyService.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/StaticContentService.java
tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/servlet/TomcatLite.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpChannelInMemoryTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpChannelTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/services/EchoCallback.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/http/services/SleepCallback.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/io/OneTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/ProxyTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/SmallProxyTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/lite/servlet/TomcatLiteNoConnectorTest.java
tomcat/trunk/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java
Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/DefaultHttpConnector.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/DefaultHttpConnector.java?rev=887087&r1=887086&r2=887087&view=diff
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/DefaultHttpConnector.java (original)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/DefaultHttpConnector.java Fri Dec 4 07:16:59 2009
@@ -11,13 +11,13 @@
}
public synchronized static HttpConnector get() {
- if (DefaultHttpConnector.defaultHttpConnector == null) {
- DefaultHttpConnector.defaultHttpConnector =
- new HttpConnector(new SocketConnector());
+ if (DefaultHttpConnector.socketConnector == null) {
+ socketConnector =
+ new SocketConnector();
}
- return DefaultHttpConnector.defaultHttpConnector;
+ return new HttpConnector(socketConnector);
}
- private static HttpConnector defaultHttpConnector;
+ private static SocketConnector socketConnector;
}
Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Dispatcher.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Dispatcher.java?rev=887087&r1=887086&r2=887087&view=diff
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Dispatcher.java (original)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Dispatcher.java Fri Dec 4 07:16:59 2009
@@ -54,6 +54,10 @@
}
public void runService(HttpChannel ch) {
+ runService(ch, true);
+ }
+
+ public void runService(HttpChannel ch, boolean recycle) {
MappingData mapRes = ch.getRequest().getMappingData();
HttpService h = (HttpService) mapRes.getServiceObject();
try {
@@ -61,11 +65,12 @@
h.service(ch.getRequest(), ch.getResponse());
if (!ch.getRequest().isAsyncStarted()) {
ch.complete();
- ch.release(); // recycle objects.
+ if (recycle) {
+ ch.release(); // recycle objects.
+ }
} else {
// Nothing - complete must be called when done.
}
-
} catch (IOException e) {
e.printStackTrace();
} catch( Throwable t ) {
@@ -75,11 +80,24 @@
@Override
public void service(HttpRequest httpReq, HttpResponse httpRes) throws IOException {
- service(httpReq, httpRes, false);
+ service(httpReq, httpRes, false, true);
+ }
+
+ /**
+ * Process the request/response in the current thread, without
+ * release ( recycle ) at the end.
+ *
+ * For use by tests and/or in-memory running of servlets.
+ *
+ * If no connection is associated with the request - the
+ * output will remain in the out buffer.
+ */
+ public void run(HttpRequest httpReq, HttpResponse httpRes) throws IOException {
+ service(httpReq, httpRes, true, false);
}
- public void service(HttpRequest httpReq, HttpResponse httpRes, boolean noThread)
+ public void service(HttpRequest httpReq, HttpResponse httpRes, boolean noThread, boolean recycle)
throws IOException {
long t0 = System.currentTimeMillis();
HttpChannel http = httpReq.getHttpChannel();
@@ -104,7 +122,7 @@
}
if (mapRes.service.selectorThread || noThread) {
- runService(http);
+ runService(http, recycle);
} else {
tp.execute(httpReq.getHttpChannel().dispatcherRunnable);
}
Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java?rev=887087&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java Fri Dec 4 07:16:59 2009
@@ -0,0 +1,1381 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.http.HttpMessage.HttpMessageBytes;
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.FastHttpDateFormat;
+import org.apache.tomcat.lite.io.Hex;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+
+public class Http11Connection extends HttpConnection {
+ public static final String CHUNKED = "chunked";
+
+ public static final String CLOSE = "close";
+
+ public static final String KEEPALIVE_S = "keep-alive";
+
+ public static final String CONNECTION = "connection";
+
+ public static final String TRANSFERENCODING = "transfer-encoding";
+
+
+ protected static Logger log = Logger.getLogger("Http11Connection");
+ static final byte COLON = (byte) ':';
+
+ // net is the 'socket' connector
+
+ HttpChannel activeHttp;
+ boolean debug;
+ BBuffer line = BBuffer.wrapper();
+ boolean endSent = false;
+
+ BodyState receiveBodyState = new BodyState();
+ BodyState sendBodyState = new BodyState();
+
+ BBuffer headW = BBuffer.wrapper();
+
+ boolean headersReceived = false;
+ boolean bodyReceived = false;
+
+ /**
+ * Close connection when done writting, no content-length/chunked,
+ * or no keep-alive ( http/1.0 ) or error.
+ *
+ * ServerMode: set if HTTP/0.9 &1.0 || !keep-alive
+ * ClientMode: not currently used
+ */
+ boolean keepAlive = true;
+
+ protected boolean http11 = true;
+ protected boolean http10 = false;
+ protected boolean http09 = false;
+
+ HttpConnection switchedProtocol = null;
+
+ public Http11Connection(HttpConnector httpConnector) {
+ this.httpConnector = httpConnector;
+ debug = true; //httpConnector.debug;
+ }
+
+ public void beforeRequest() {
+ log.info("Before request");
+ activeHttp = null;
+ endSent = false;
+ keepAlive = true;
+ receiveBodyState.recycle();
+ sendBodyState.recycle();
+ http11 = true;
+ http09 = false;
+ http10 = false;
+ headersReceived = false;
+ bodyReceived = false;
+ headRecvBuf.recycle();
+ }
+
+ public Http11Connection serverMode() {
+ serverMode = true;
+ return this;
+ }
+
+ private boolean readHead() throws IOException {
+ while (true) {
+ int read;
+ if (headRecvBuf.remaining() < 4) {
+ // requests have at least 4 bytes - detect protocol
+ read = net.getIn().read(headRecvBuf, 4);
+ if (read < 0) {
+ return closeInHead();
+ }
+ if (read < 4) {
+ return false; // need more
+ }
+ // we have at least 4 bytes
+ if (headRecvBuf.get(0) == 0x80 &&
+ headRecvBuf.get(1) == 0x01) {
+ // SPDY signature ( experimental )
+ switchedProtocol = new SpdyConnection(httpConnector);
+ if (serverMode) {
+ switchedProtocol.serverMode = true;
+ }
+ switchedProtocol.withExtraBuffer(headRecvBuf);
+ // Will also call handleReceived
+ switchedProtocol.setSink(net);
+ return false;
+ }
+
+ }
+
+ // we know we have one
+ read = net.getIn().readLine(headRecvBuf);
+ // Remove starting empty lines.
+ headRecvBuf.skipEmptyLines();
+
+ // Do we have another full line in the input ?
+ if (BBuffer.hasLFLF(headRecvBuf)) {
+ break; // done
+ }
+ if (read == 0) { // no more data
+ return false;
+ }
+ if (read < 0) {
+ return closeInHead();
+ }
+ }
+ return true;
+ }
+
+ private boolean closeInHead() throws IOException {
+ if (debug) {
+ trace("CLOSE while reading HEAD");
+ }
+ // too early - we don't have the head
+ abort("Close in head");
+ return false;
+ }
+
+ // Unit tests use this to access the HttpChannel
+ protected HttpChannel checkHttpChannel() throws IOException {
+ if (switchedProtocol != null) {
+ return switchedProtocol.checkHttpChannel();
+ }
+ if (activeHttp == null) {
+ if (serverMode) {
+ activeHttp = httpConnector.getServer();
+ activeHttp.setConnection(this);
+ if (httpConnector.defaultService != null) {
+ activeHttp.setHttpService(httpConnector.defaultService);
+ }
+ } else {
+ }
+ }
+ return activeHttp;
+ }
+
+ @Override
+ public void handleReceived(IOChannel netx) throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.handleReceived(netx);
+ return;
+ }
+
+ if (!checkKeepAliveClient()) {
+ return; // we were in client keep alive mode
+ }
+
+ if (!headersReceived) {
+ if (!readHead()) {
+ return;
+ }
+ }
+
+ // We have a header
+ if (activeHttp == null) {
+ if (checkHttpChannel() == null) {
+ return;
+ }
+ }
+
+ IOBuffer receiveBody = activeHttp.receiveBody;
+
+ if (!headersReceived) {
+ headRecvBuf.wrapTo(headW);
+ parseMessage(activeHttp, headW);
+ if (serverMode && activeHttp.httpReq.decodedUri.remaining() == 0) {
+ abort(activeHttp, "Invalid url");
+ }
+
+ headersReceived = true;
+ // Send header callbacks - we process any incoming data
+ // first, so callbacks have more info
+ activeHttp.handleHeadersReceived(activeHttp.inMessage);
+ }
+
+ // any remaining data will be processed as part of the
+ // body - or left in the channel until endSendReceive()
+
+ if (!bodyReceived) {
+ // Will close receiveBody when it consummed enough
+ rawDataReceived(activeHttp, receiveBody, net.getIn());
+ // Did we process anything ?
+ if (receiveBody.getBufferCount() > 0) {
+ activeHttp.sendHandleReceivedCallback(); // callback
+ }
+
+ // Receive has marked the body as closed
+ if (receiveBody.isAppendClosed()) {
+ activeHttp.handleEndReceive();
+ bodyReceived = true;
+ }
+ }
+
+
+ if (net.getIn().isClosedAndEmpty()) {
+ // If not already closed.
+ closeStreamOnEnd("closed after body");
+ }
+
+ }
+
+ /**
+ * We got data while in client keep alive ( no activeHttp )
+ *
+ * @return false if there is an error
+ */
+ private boolean checkKeepAliveClient() throws IOException {
+ // Client, no active connection ( keep alive )
+ if (!serverMode && activeHttp == null) {
+ if (net.getIn().isClosedAndEmpty() || !net.isOpen()) {
+ // server disconnected, fine
+ httpConnector.cpool.stopKeepAlive(this);
+ return false;
+ }
+ if (net.getIn().available() == 0) {
+ return true;
+ }
+ log.warning("Unexpected message from server in client keep alive "
+ + net.getIn());
+ if (net.isOpen()) {
+ net.close();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private void processProtocol(CBuffer protocolMB) throws IOException {
+ http11 = false;
+ http09 = false;
+ http10 = false;
+
+ if (protocolMB.equals(HttpChannel.HTTP_11)) {
+ http11 = true;
+ } else if (protocolMB.equals(HttpChannel.HTTP_10)) {
+ http10 = true;
+ } else if (protocolMB.equals("")) {
+ http09 = true;
+ } else {
+ http11 = true; // hopefully will be backward compat
+ }
+ }
+
+ void closeStreamOnEnd(String cause) {
+ if (debug)
+ log.info("Not reusing connection because: " + cause);
+ keepAlive = false;
+ }
+
+ boolean keepAlive() {
+ if (httpConnector != null) {
+ if (serverMode && !httpConnector.serverKeepAlive) {
+ keepAlive = false;
+ }
+ if (!serverMode && !httpConnector.clientKeepAlive) {
+ keepAlive = false;
+ }
+ }
+ if (http09) {
+ keepAlive = false;
+ }
+ if (!net.isOpen()) {
+ keepAlive = false;
+ }
+ return keepAlive;
+ }
+
+ @Override
+ protected void endSendReceive(HttpChannel http) throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.endSendReceive(http);
+ return;
+ }
+
+ activeHttp = null;
+ if (!keepAlive()) {
+ if (debug) {
+ log.info("--- Close socket, no keepalive " + net);
+ }
+ if (net != null) {
+ net.close();
+// net.getOut().close(); // shutdown output if not done
+// net.getIn().close(); // this should close the socket
+ net.startSending();
+
+ }
+ beforeRequest();
+ return;
+ }
+
+ beforeRequest(); // will clear head buffer
+
+ if (serverMode) {
+ handleReceived(net); // will attempt to read next req
+ if (debug) {
+ log.info(">>> server socket KEEP_ALIVE " + net.getTarget() +
+ " " + net);
+ }
+
+ } else {
+ if (debug) {
+ log.info(">>> client socket KEEP_ALIVE " + net.getTarget() +
+ " " + net);
+ }
+ httpConnector.cpool.returnChannel(this);
+ }
+ }
+
+ private void trace(String s) {
+ if(debug) {
+ log.info(this.toString() + " " + activeHttp + " " + s);
+ }
+ }
+
+ private boolean isDone(BodyState bodys, IOBuffer body) {
+ if (bodys.noBody) {
+ return true;
+ }
+ if (bodys.isContentDelimited()) {
+ if (!bodys.chunked && bodys.remaining == 0) {
+ return true;
+ } else if (bodys.chunked && body.isAppendClosed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void parseMessage(HttpChannel http, BBuffer headB) throws IOException {
+ //Parse the response
+ line.recycle();
+ headB.readLine(line);
+
+ HttpMessageBytes msgBytes;
+
+ if (serverMode) {
+ msgBytes = http.httpReq.getMsgBytes();
+ parseRequestLine(line, msgBytes.method(),
+ msgBytes.url(),
+ msgBytes.query(),
+ msgBytes.protocol());
+ } else {
+ msgBytes = http.httpRes.getMsgBytes();
+ parseResponseLine(line, msgBytes.protocol(),
+ msgBytes.status(), msgBytes.message());
+ }
+
+ parseHeaders(http, msgBytes, headB);
+
+ http.inMessage.state = HttpMessage.State.BODY_DATA;
+
+ http.inMessage.processReceivedHeaders();
+
+ // TODO: hook to allow specific charsets ( can be done later )
+ processProtocol(http.inMessage.protocol());
+
+ if (serverMode) {
+ // requested connection:close/keepAlive and proto
+ updateKeepAlive(http.getRequest().getMimeHeaders(), true);
+
+ processExpectation(http);
+
+ processContentDelimitation(receiveBodyState, http.getRequest());
+ // Spec:
+ // The presence of a message-body in a request is signaled by the
+ // inclusion of a Content-Length or Transfer-Encoding header field in
+ // the request's message-headers
+ // Server should read - but ignore ..
+ receiveBodyState.noBody = !receiveBodyState.isContentDelimited();
+
+ updateCloseOnEnd(receiveBodyState, http, http.receiveBody);
+
+ /*
+ * The presence of a message-body in a request is signaled by the
+ * inclusion of a Content-Length or Transfer-Encoding header field in
+ * the request's message-headers. A message-body MUST NOT be included
+ * in a request if the specification of the request method
+ * (section 5.1.1) does not allow sending an entity-body in requests.
+ * A server SHOULD read and forward a message-body on any request; if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request.
+ */
+ if (!receiveBodyState.isContentDelimited()) {
+ // No body
+ http.getIn().close();
+ }
+
+ } else {
+ receiveBodyState.noBody = http.getResponse().hasBody();
+
+ updateKeepAlive(http.getResponse().getMimeHeaders(), false);
+
+ if (statusDropsConnection(http.getResponse().getStatus())) {
+ closeStreamOnEnd("response status drops connection");
+ }
+ IOBuffer body = http.receiveBody;
+ processContentDelimitation(receiveBodyState, http.getResponse());
+
+ if (isDone(receiveBodyState, body)) {
+ body.close();
+ }
+
+ if (!receiveBodyState.isContentDelimited()) {
+ closeStreamOnEnd("not content delimited");
+ }
+ }
+
+ }
+
+ private void processExpectation(HttpChannel http) throws IOException {
+ http.expectation = false;
+ MultiMap headers = http.getRequest().getMimeHeaders();
+
+ CBuffer expect = headers.getHeader("expect");
+ if ((expect != null)
+ && (expect.indexOf("100-continue") != -1)) {
+ http.expectation = true;
+
+ // TODO: configure, use the callback or the servlet 'read'.
+ net.getOut().append("HTTP/1.1 100 Continue\r\n\r\n");
+ net.startSending();
+ }
+ }
+
+
+
+ /**
+ * Updates chunked, contentLength, remaining - based
+ * on headers
+ */
+ private void processContentDelimitation(BodyState bodys,
+ HttpMessage httpMsg) {
+
+ bodys.contentLength = httpMsg.getContentLength();
+ if (bodys.contentLength >= 0) {
+ bodys.remaining = bodys.contentLength;
+ }
+
+ // TODO: multiple transfer encoding headers, only process the last
+ String transferEncodingValue = httpMsg.getHeader(TRANSFERENCODING);
+ if (transferEncodingValue != null) {
+ int startPos = 0;
+ int commaPos = transferEncodingValue.indexOf(',');
+ String encodingName = null;
+ while (commaPos != -1) {
+ encodingName = transferEncodingValue.substring
+ (startPos, commaPos).toLowerCase().trim();
+ if ("chunked".equalsIgnoreCase(encodingName)) {
+ bodys.chunked = true;
+ }
+ startPos = commaPos + 1;
+ commaPos = transferEncodingValue.indexOf(',', startPos);
+ }
+ encodingName = transferEncodingValue.substring(startPos)
+ .toLowerCase().trim();
+ if ("chunked".equals(encodingName)) {
+ bodys.chunked = true;
+ httpMsg.chunked = true;
+ } else {
+ System.err.println("TODO: ABORT 501");
+ //return 501; // Currently only chunked is supported for
+ // transfer encoding.
+ }
+ }
+
+ if (bodys.chunked) {
+ bodys.remaining = 0;
+ }
+ }
+
+ /**
+ * Read the request line. This function is meant to be used during the
+ * HTTP request header parsing. Do NOT attempt to read the request body
+ * using it.
+ *
+ * @throws IOException If an exception occurs during the underlying socket
+ * read operations, or if the given buffer is not big enough to accomodate
+ * the whole line.
+ */
+ boolean parseRequestLine(BBuffer line,
+ BBuffer methodMB, BBuffer requestURIMB,
+ BBuffer queryMB,
+ BBuffer protoMB)
+ throws IOException {
+
+ line.readToSpace(methodMB);
+ line.skipSpace();
+
+ line.readToDelimOrSpace(HttpChannel.QUESTION, requestURIMB);
+ if (line.remaining() > 0 && line.get(0) == HttpChannel.QUESTION) {
+ // Has query
+ line.readToSpace(queryMB);
+ // don't include '?'
+ queryMB.position(queryMB.position() + 1);
+ } else {
+ queryMB.setBytes(line.array(), line.position(), 0);
+ }
+ line.skipSpace();
+
+ line.readToSpace(protoMB);
+
+ // proto is optional ( for 0.9 )
+ return requestURIMB.remaining() > 0;
+ }
+
+ boolean parseResponseLine(BBuffer line,
+ BBuffer protoMB, BBuffer statusCode, BBuffer status)
+ throws IOException {
+ line.skipEmptyLines();
+
+ line.readToSpace(protoMB);
+ line.skipSpace();
+ line.readToSpace(statusCode);
+ line.skipSpace();
+ line.wrapTo(status);
+
+ // message may be empty
+ return statusCode.remaining() > 0;
+ }
+
+ private void parseHeaders(HttpChannel http, HttpMessageBytes msgBytes,
+ BBuffer head)
+ throws IOException {
+
+ head.readLine(line);
+
+ int idx = 0;
+ while(line.remaining() > 0) {
+ // not empty..
+ idx = msgBytes.addHeader();
+ BBuffer nameBuf = msgBytes.getHeaderName(idx);
+ BBuffer valBuf = msgBytes.getHeaderValue(idx);
+ parseHeader(http, head, line, nameBuf, valBuf);
+
+ // TODO: process 'interesting' headers here.
+ }
+ }
+
+ /**
+ * Parse one header.
+ * Line must be populated. On return line will be populated
+ * with the next header:
+ *
+ * @param line current header line, not empty.
+ */
+ int parseHeader(HttpChannel http, BBuffer head,
+ BBuffer line, BBuffer name, BBuffer value)
+ throws IOException {
+
+ int newPos = line.readToDelimOrSpace(COLON, name);
+ line.skipSpace();
+ if (line.readByte() != COLON) {
+ throw new IOException("Missing ':' in header name " + line);
+ }
+ line.skipSpace();
+ line.read(value); // remaining of the line
+
+ while (true) {
+ head.readLine(line);
+ if (line.remaining() == 0) {
+ break;
+ }
+ int first = line.get(0);
+ if (first != BBuffer.SP && first != BBuffer.HT) {
+ break;
+ }
+ // continuation line - append it to value
+ value.setEnd(line.getEnd());
+ line.position(line.limit());
+ }
+
+ // We may want to keep the original and use separate buffer ?
+ http.normalizeHeader(value);
+ return 1;
+ }
+
+ private int receiveDone(HttpChannel http, IOBuffer body, boolean frameError) throws IOException {
+ // Content-length case, we're done reading
+ body.close();
+
+ http.error = frameError;
+ if (frameError) {
+ closeStreamOnEnd("frame error");
+ }
+
+ return DONE;
+ }
+
+ /**
+ * Called when raw body data is received.
+ * Callback should not consume past the end of the body.
+ * @param rawReceiveBuffers
+ *
+ */
+ private void rawDataReceived(HttpChannel http, IOBuffer body,
+ IOBuffer rawReceiveBuffers) throws IOException {
+ // TODO: Make sure we don't process more than we need ( eat next req ).
+ // If we read too much: leave it in readBuf, the finalzation code
+ // should skip KeepAlive and start processing it.
+ // we need to read at least something - to detect -1 ( we could
+ // suspend right away, but seems safer
+ BodyState bodys = receiveBodyState;
+
+ while (http.inMessage.state == HttpMessage.State.BODY_DATA) {
+ if (receiveBodyState.noBody) {
+ receiveDone(http, body, false);
+ return;
+ }
+ if (rawReceiveBuffers.isClosedAndEmpty()) {
+ if (receiveBodyState.isContentDelimited()) {
+ if (receiveBodyState.contentLength >= 0 && receiveBodyState.remaining == 0) {
+ receiveDone(http, body, false);
+ } else {
+ // End of input - other side closed, no more data
+ //log.info("CLOSE while reading " + this);
+ // they're not supposed to close !
+ receiveDone(http, body, true);
+ }
+ } else {
+ receiveDone(http, body, false); // ok
+ }
+ // input connection closed ?
+ closeStreamOnEnd("Closed input");
+ return;
+ }
+ BBucket rawBuf = rawReceiveBuffers.peekFirst();
+ if (rawBuf == null) {
+ return; // need more data
+ }
+
+ if (!bodys.isContentDelimited()) {
+ while (true) {
+ BBucket first = rawReceiveBuffers.popFirst();
+ if (first == null) {
+ break; // will go back to check if done.
+ } else {
+ body.queue(first);
+ }
+ }
+ } else {
+
+ if (bodys.contentLength >= 0 && bodys.remaining == 0) {
+ receiveDone(http, body, false);
+ return;
+ }
+
+ if (bodys.chunked && bodys.remaining == 0) {
+ int rc = NEED_MORE;
+ // TODO: simplify, use readLine()
+ while (rc == NEED_MORE) {
+ rc = chunk.parseChunkHeader(rawReceiveBuffers);
+ if (rc == ERROR) {
+ http.abort("Chunk error");
+ receiveDone(http, body, true);
+ return;
+ } else if (rc == NEED_MORE) {
+ return;
+ }
+ }
+ if (rc == 0) { // last chunk
+ receiveDone(http, body, false);
+ return;
+ } else {
+ bodys.remaining = rc;
+ }
+ }
+
+ rawBuf = (BBucket) rawReceiveBuffers.peekFirst();
+ if (rawBuf == null) {
+ return; // need more data
+ }
+
+
+ if (bodys.remaining < rawBuf.remaining()) {
+ // To buffer has more data than we need.
+ int lenToConsume = (int) bodys.remaining;
+ BBucket sb = rawReceiveBuffers.popLen(lenToConsume);
+ body.queue(sb);
+ //log.info("Queue received buffer " + this + " " + lenToConsume);
+ bodys.remaining = 0;
+ } else {
+ BBucket first = rawReceiveBuffers.popFirst();
+ bodys.remaining -= first.remaining();
+ body.queue(first);
+ //log.info("Queue full received buffer " + this + " RAW: " + rawReceiveBuffers);
+ }
+ if (bodys.contentLength >= 0 && bodys.remaining == 0) {
+ // Content-Length, all done
+ body.close();
+ receiveDone(http, body, false);
+ }
+ }
+ }
+ }
+
+
+
+ protected void sendRequest(HttpChannel http)
+ throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.sendRequest(http);
+ return;
+ }
+
+ this.activeHttp = http;
+
+ // Update transfer fields based on headers.
+ processProtocol(http.getRequest().protocol());
+ updateKeepAlive(http.getRequest().getMimeHeaders(), true);
+
+ // Update Host header
+ if (http.getRequest().getMimeHeaders().getHeader("Host") == null) {
+ String target = http.getTarget();
+ if (target == null) {
+ throw new IOException("Missing host header");
+ }
+ CBuffer hostH = http.getRequest().getMimeHeaders().addValue("Host");
+ if (target.endsWith(":80")) {
+ hostH.set(target.substring(0, target.length() - 3));
+ } else {
+ hostH.set(target);
+ }
+ }
+
+ processContentDelimitation(sendBodyState,
+ http.getRequest());
+
+
+ // 1.0: The presence of an entity body in a request is signaled by
+ // the inclusion of a Content-Length header field in the request
+ // message headers. HTTP/1.0 requests containing an entity body
+ // must include a valid Content-Length header field.
+ if (http10 && !sendBodyState.isContentDelimited()) {
+ // Will not close connection - just flush and mark the body
+ // as sent
+ sendBodyState.noBody = true;
+ }
+
+ if (sendBodyState.noBody) {
+ http.getRequest().getMimeHeaders().remove(HttpChannel.CONTENT_LENGTH);
+ http.getRequest().getMimeHeaders().remove(TRANSFERENCODING);
+ http.getOut().close();
+ } else {
+ long contentLength =
+ http.getRequest().getContentLength();
+ if (contentLength < 0) {
+ http.getRequest().getMimeHeaders().addValue("Transfer-Encoding").
+ set(CHUNKED);
+ }
+ }
+
+ updateCloseOnEnd(sendBodyState, http, http.sendBody);
+
+ try {
+ serialize(http.getRequest(), net.getOut());
+ if (http.debug) {
+ http.trace("S: \n" + net.getOut());
+ }
+
+ if (http.outMessage.state == HttpMessage.State.HEAD) {
+ http.outMessage.state = HttpMessage.State.BODY_DATA;
+ }
+
+
+ // TODO: add any body and flush. More body can be added later -
+ // including 'end'.
+
+ http.startSending();
+ } catch (Throwable t) {
+ log.log(Level.SEVERE, "Error sending request", t);
+ abort(t.getMessage());
+ }
+
+ }
+
+
+ /**
+ * Determine if we must drop the connection because of the HTTP status
+ * code. Use the same list of codes as Apache/httpd.
+ */
+ private boolean statusDropsConnection(int status) {
+ return status == 400 /* SC_BAD_REQUEST */ ||
+ status == 408 /* SC_REQUEST_TIMEOUT */ ||
+ status == 411 /* SC_LENGTH_REQUIRED */ ||
+ status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||
+ status == 414 /* SC_REQUEST_URI_TOO_LARGE */ ||
+ status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||
+ status == 503 /* SC_SERVICE_UNAVAILABLE */ ||
+ status == 501 /* SC_NOT_IMPLEMENTED */;
+ }
+
+ protected void sendResponseHeaders(HttpChannel http)
+ throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.sendResponseHeaders(http);
+ return;
+ }
+
+ if (!serverMode) {
+ throw new IOException("Only in server mode");
+ }
+ endSent = false;
+ IOBuffer sendBody = http.sendBody;
+ HttpResponse res = http.getResponse();
+ if (res.isCommitted()) {
+ return;
+ }
+ res.setCommitted(true);
+
+ sendBodyState.noBody = !res.hasBody();
+
+ if (statusDropsConnection(res.getStatus())) {
+ closeStreamOnEnd("status drops connection");
+ }
+ if (http.error) {
+ closeStreamOnEnd("error");
+ }
+
+ MultiMap headers = res.getMimeHeaders();
+
+ // Add date header
+ if (headers.getHeader("Date") == null) {
+ headers.setValue("Date").set(FastHttpDateFormat.getCurrentDate());
+ }
+
+ // Add server header
+ if (http.serverHeader.length() > 0) {
+ headers.setValue("Server").set(http.serverHeader);
+ }
+
+ // Decide on a transfer encoding for out.
+ if (keepAlive()) { // request and user allows keep alive
+ int cl = res.getContentLength();
+
+ if (http10) {
+ if (cl < 0 && !sendBodyState.noBody &&
+ sendBody.isAppendClosed()) {
+ // We can generate content-lenght
+ cl = sendBody.available();
+ res.setContentLength(cl);
+ }
+ if (cl < 0 && !sendBodyState.noBody) {
+ closeStreamOnEnd("HTTP/1.0 without content length");
+ } else {
+ headers.setValue(CONNECTION).set(KEEPALIVE_S);
+ }
+ } else { // http11
+ if (!sendBodyState.noBody) {
+ if (cl < 0) {
+ res.getMimeHeaders().setValue(TRANSFERENCODING).set(CHUNKED);
+ }
+ }
+ }
+ } else {
+ headers.setValue(CONNECTION).set(CLOSE);
+ // since we close the connection - don't bother with
+ // transfer encoding
+ headers.remove(TRANSFERENCODING);
+ }
+
+ // Update our internal state based on headers we just set.
+ processContentDelimitation(sendBodyState, res);
+ updateCloseOnEnd(sendBodyState, http, sendBody);
+
+
+ if (http.debug) {
+ http.trace("Send response headers " + net);
+ }
+ if (net != null) {
+ serialize(res, net.getOut());
+ }
+
+ if (http.outMessage.state == HttpMessage.State.HEAD) {
+ http.outMessage.state = HttpMessage.State.BODY_DATA;
+ }
+
+ if (isDone(sendBodyState, sendBody)) {
+ http.getOut().close();
+ }
+
+ if (net != null) {
+ net.startSending();
+ }
+ }
+
+ private void abort(String t) throws IOException {
+ abort(activeHttp, t);
+ }
+
+ private void updateCloseOnEnd(BodyState bodys, HttpChannel http, IOBuffer body) {
+ if (!bodys.isContentDelimited() && !bodys.noBody) {
+ closeStreamOnEnd("not content delimited");
+ }
+ }
+
+ /**
+ * Disconnect abruptly - client closed, frame errors, etc
+ * @param t
+ * @throws IOException
+ */
+ public void abort(HttpChannel http, String t) throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.abort(http, t);
+ return;
+ }
+ keepAlive = false;
+ if (net != null ) {
+ if (net.isOpen()) {
+ net.close();
+ net.startSending();
+ }
+ }
+ if (http != null) {
+ http.abort(t);
+ }
+ }
+
+ /**
+ * Update keepAlive based on Connection header and protocol.
+ */
+ private void updateKeepAlive(MultiMap headers, boolean request) {
+ if (http09) {
+ closeStreamOnEnd("http 0.9");
+ return;
+ }
+
+ // TODO: also need to remove headers matching connection
+ // ( like 'upgrade')
+
+ CBuffer value = headers.getHeader(CONNECTION);
+ String conHeader = (value == null) ? null : value.toString();
+ if (conHeader != null) {
+ if (CLOSE.equalsIgnoreCase(conHeader)) {
+ // 1.1 ( but we accept it for 1.0 too )
+ closeStreamOnEnd("connection close");
+ }
+ if (http10 && conHeader.indexOf(KEEPALIVE_S) < 0) {
+ // Keep-Alive required for http/1.0
+ closeStreamOnEnd("connection != keep alive");
+ }
+ // we have connection: keepalive, good
+ } else {
+ // no connection header - for 1.1 default is keepAlive,
+ // for 10 it's close
+ if (http10) {
+ closeStreamOnEnd("http1.0 no connection header");
+ }
+ }
+ }
+
+ @Override
+ public void startSending() throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.startSending();
+ return;
+ }
+
+ }
+
+ @Override
+ public void startSending(HttpChannel http) throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.startSending(http);
+ return;
+ }
+ http.send(); // if needed
+
+ if (net == null) {
+ return; // not connected yet.
+ }
+
+ if (net.getOut().isAppendClosed()) {
+ abort("Net closed");
+ } else {
+ flushToNext(http.sendBody, net.getOut());
+ net.startSending();
+ }
+
+ }
+
+ protected void outClosed(HttpChannel http) throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.outClosed(http);
+ return;
+ }
+ // TODO: move it ?
+ if (sendBodyState.isContentDelimited() && !http.error) {
+ if (!sendBodyState.chunked &&
+ sendBodyState.remaining - http.getOut().available() > 0) {
+ http.abort("CLOSE CALLED WITHOUT FULL LEN");
+ }
+ }
+
+ }
+
+ @Override
+ public void handleFlushed(IOChannel net) throws IOException {
+ if (switchedProtocol != null) {
+ switchedProtocol.handleFlushed(net);
+ return;
+ }
+ if (activeHttp != null) {
+ activeHttp.flushLock.signal(this);
+ activeHttp.handleFlushed(this);
+ if (activeHttp.sendBody.isClosedAndEmpty()) {
+ activeHttp.handleEndSent();
+ }
+ }
+ }
+
+
+
+ private void flushToNext(IOBuffer body, IOBuffer out) throws IOException {
+
+ synchronized (this) {
+ // TODO: better head support
+ if (sendBodyState.noBody) {
+ for (int i = 0; i < body.getBufferCount(); i++) {
+ Object bc = body.peekBucket(i);
+ if (bc instanceof BBucket) {
+ ((BBucket) bc).release();
+ }
+ }
+ body.clear();
+ return;
+ }
+
+ // TODO: only send < remainingWrite, if buffer
+ // keeps changing after startWrite() is called (shouldn't)
+
+ if (sendBodyState.chunked) {
+ sendChunked(sendBodyState, body, out);
+ } else if (sendBodyState.contentLength >= 0) {
+ // content-length based
+ sendContentLen(sendBodyState, body, out);
+ } else {
+ sendCloseDelimited(body, out);
+ }
+ }
+ }
+
+ private void sendCloseDelimited(IOBuffer body, IOBuffer out) throws IOException {
+ // Close delimitation
+ while (true) {
+ Object bc = body.popFirst();
+ if (bc == null) {
+ break;
+ }
+ out.queue(bc);
+ }
+ if (body.isClosedAndEmpty()) {
+ out.close(); // no content-delimitation
+ }
+ }
+
+ /**
+ * Convert the request to bytes, ready to send.
+ */
+ public static void serialize(HttpRequest req, IOBuffer rawSendBuffers2) throws IOException {
+ rawSendBuffers2.append(req.method());
+ rawSendBuffers2.append(BBuffer.SP);
+
+ // TODO: encode or use decoded
+ rawSendBuffers2.append(req.requestURI());
+ if (req.queryString().length() > 0) {
+ rawSendBuffers2.append("?");
+ rawSendBuffers2.append(req.queryString());
+ }
+
+ rawSendBuffers2.append(BBuffer.SP);
+ rawSendBuffers2.append(req.protocol());
+ rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+
+ serializeHeaders(req.getMimeHeaders(), rawSendBuffers2);
+ }
+
+ /**
+ * Convert the response to bytes, ready to send.
+ */
+ public static void serialize(HttpResponse res, IOBuffer rawSendBuffers2) throws IOException {
+
+ rawSendBuffers2.append(res.protocol()).append(' ');
+ String status = Integer.toString(res.getStatus());
+ rawSendBuffers2.append(status).append(' ');
+ if (res.getMessageBuffer().length() > 0) {
+ rawSendBuffers2.append(res.getMessage());
+ } else {
+ rawSendBuffers2
+ .append(res.getMessage(res.getStatus()));
+ }
+ rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+ // Headers
+ serializeHeaders(res.getMimeHeaders(), rawSendBuffers2);
+ }
+
+ public static void serializeHeaders(MultiMap mimeHeaders, IOBuffer rawSendBuffers2) throws IOException {
+ for (int i = 0; i < mimeHeaders.size(); i++) {
+ CBuffer name = mimeHeaders.getName(i);
+ CBuffer value = mimeHeaders.getValue(i);
+ if (name.length() == 0 || value.length() == 0) {
+ continue;
+ }
+ rawSendBuffers2.append(name);
+ rawSendBuffers2.append(Http11Connection.COLON);
+ rawSendBuffers2.append(value);
+ rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+ }
+ rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+ }
+
+
+ private boolean sendContentLen(BodyState bodys, IOBuffer body, IOBuffer out) throws IOException {
+ while (true) {
+ BBucket bucket = body.peekFirst();
+ if (bucket == null) {
+ break;
+ }
+ int len = bucket.remaining();
+ if (len <= bodys.remaining) {
+ bodys.remaining -= len;
+ bucket = body.popFirst();
+ out.queue(bucket);
+ } else {
+ // Write over the end of the buffer !
+ log.severe("write more than Content-Length");
+ len = (int) bodys.remaining;
+ // data between position and limit
+ bucket = body.popLen((int) bodys.remaining);
+ out.queue(bucket);
+ while (bucket != null) {
+ bucket = body.popFirst();
+ if (bucket != null) {
+ bucket.release();
+ }
+ }
+
+ // forced close
+ //close();
+ bodys.remaining = 0;
+ return true;
+ }
+ }
+ if (body.isClosedAndEmpty()) {
+ //http.rawSendBuffers.queue(IOBrigade.MARK);
+ if (bodys.remaining > 0) {
+ closeStreamOnEnd("sent more than content-length");
+ log.severe("Content-Length > body");
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean sendChunked(BodyState bodys, IOBuffer body, IOBuffer out) throws IOException {
+ int len = body.available();
+
+ if (len > 0) {
+ ByteBuffer sendChunkBuffer = chunk.prepareChunkHeader(len);
+ bodys.remaining = len;
+ out.queue(sendChunkBuffer);
+ while (bodys.remaining > 0) {
+ BBucket bc = body.popFirst();
+ bodys.remaining -= bc.remaining();
+ out.queue(bc);
+ }
+ }
+
+ if (body.isClosedAndEmpty()) {
+ if (!endSent) {
+ out.append(chunk.endChunk());
+ endSent = true;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // used for chunk parsing/end
+ ChunkState chunk = new ChunkState();
+ static final int NEED_MORE = -1;
+ static final int ERROR = -4;
+ static final int DONE = -5;
+
+
+ static class ChunkState {
+ static byte[] END_CHUNK_BYTES = {
+ (byte) '\r', (byte) '\n',
+ (byte) '0',
+ (byte) '\r', (byte) '\n',
+ (byte) '\r', (byte) '\n'};
+
+
+ int partialChunkLen;
+ boolean readDigit = false;
+ boolean trailer = false;
+ protected boolean needChunkCrlf = false;
+
+ // Buffer used for chunk length conversion.
+ protected byte[] sendChunkLength = new byte[10];
+
+ /** End chunk marker - will include chunked end or empty */
+ protected BBuffer endSendBuffer = BBuffer.wrapper();
+
+ public ChunkState() {
+ sendChunkLength[8] = (byte) '\r';
+ sendChunkLength[9] = (byte) '\n';
+ }
+
+ void recycle() {
+ partialChunkLen = 0;
+ readDigit = false;
+ trailer = false;
+ needChunkCrlf = false;
+ endSendBuffer.recycle();
+ }
+
+ /**
+ * Parse the header of a chunk.
+ * A chunk header can look like
+ * A10CRLF
+ * F23;chunk-extension to be ignoredCRLF
+ * The letters before CRLF but after the trailer mark, must be valid hex digits,
+ * we should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid header
+ * according to spec
+ */
+ int parseChunkHeader(IOBuffer buffer) throws IOException {
+ if (buffer.peekFirst() == null) {
+ return NEED_MORE;
+ }
+ if (needChunkCrlf) {
+ // TODO: Trailing headers
+ int c = buffer.read();
+ if (c == BBuffer.CR) {
+ if (buffer.peekFirst() == null) {
+ return NEED_MORE;
+ }
+ c = buffer.read();
+ }
+ if (c == BBuffer.LF) {
+ needChunkCrlf = false;
+ } else {
+ System.err.println("Bad CRLF " + c);
+ return ERROR;
+ }
+ }
+
+ while (true) {
+ if (buffer.peekFirst() == null) {
+ return NEED_MORE;
+ }
+ int c = buffer.read();
+
+ if (c == BBuffer.CR) {
+ continue;
+ } else if (c == BBuffer.LF) {
+ break;
+ } else if (c == HttpChannel.SEMI_COLON) {
+ trailer = true;
+ } else if (c == BBuffer.SP) {
+ // ignore
+ } else if (trailer) {
+ // ignore
+ } else {
+ //don't read data after the trailer
+ if (Hex.DEC[c] != -1) {
+ readDigit = true;
+ partialChunkLen *= 16;
+ partialChunkLen += Hex.DEC[c];
+ } else {
+ //we shouldn't allow invalid, non hex characters
+ //in the chunked header
+ log.info("Chunk parsing error1 " + c + " " + buffer);
+ //http.abort("Chunk error");
+ return ERROR;
+ }
+ }
+ }
+
+ if (!readDigit) {
+ log.info("Chunk parsing error2 " + buffer);
+ return ERROR;
+ }
+
+ needChunkCrlf = true; // next time I need to parse CRLF
+ int result = partialChunkLen;
+ partialChunkLen = 0;
+ trailer = false;
+ readDigit = false;
+ return result;
+ }
+
+
+ ByteBuffer prepareChunkHeader(int current) {
+ int pos = 7; // 8, 9 are CRLF
+ while (current > 0) {
+ int digit = current % 16;
+ current = current / 16;
+ sendChunkLength[pos--] = Hex.HEX[digit];
+ }
+ if (needChunkCrlf) {
+ sendChunkLength[pos--] = (byte) '\n';
+ sendChunkLength[pos--] = (byte) '\r';
+ } else {
+ needChunkCrlf = true;
+ }
+ // TODO: pool - this may stay in the queue while we flush more
+ ByteBuffer chunkBB = ByteBuffer.allocate(16);
+ chunkBB.put(sendChunkLength, pos + 1, 9 - pos);
+ chunkBB.flip();
+ return chunkBB;
+ }
+
+ public BBuffer endChunk() {
+ if (! needChunkCrlf) {
+ endSendBuffer.setBytes(END_CHUNK_BYTES, 2,
+ END_CHUNK_BYTES.length - 2); // CRLF
+ } else { // 0
+ endSendBuffer.setBytes(END_CHUNK_BYTES, 0,
+ END_CHUNK_BYTES.length);
+ }
+ return endSendBuffer;
+ }
+ }
+
+ static class BodyState {
+ /** response: HEAD or 1xx, 204, 304 status
+ * req: missing content-length or transfer-encoding
+ */
+ protected boolean noBody = false;
+ protected boolean chunked = false;
+ protected long contentLength = -1; // C-L header
+ /** Bytes remaining in the current chunk or body ( if CL ) */
+ protected long remaining = 0; // both chunked and C-L
+
+ public void recycle() {
+ chunked = false;
+ remaining = 0;
+ contentLength = -1;
+ }
+ public boolean isContentDelimited() {
+ return chunked || contentLength >= 0;
+ }
+
+ }
+
+ public String toString() {
+ if (switchedProtocol != null) {
+ return switchedProtocol.toString();
+ }
+
+ return (serverMode ? "S11 " : "C11 ") +
+ (keepAlive() ? " KA " : "");
+ }
+
+}
Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java
------------------------------------------------------------------------------
svn:eol-style = native
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org