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