tomcat-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cos...@apache.org
Subject svn commit: r887087 [2/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...
Date Fri, 04 Dec 2009 07:17:04 GMT
Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java?rev=887087&r1=887086&r2=887087&view=diff
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java (original)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java Fri Dec  4 07:16:59 2009
@@ -16,20 +16,15 @@
 package org.apache.tomcat.lite.http;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import org.apache.tomcat.lite.http.HttpMessage.HttpMessageBytes;
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+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.DumpChannel;
-import org.apache.tomcat.lite.io.FastHttpDateFormat;
-import org.apache.tomcat.lite.io.BBucket;
 import org.apache.tomcat.lite.io.IOBuffer;
 import org.apache.tomcat.lite.io.IOChannel;
-import org.apache.tomcat.lite.io.IOConnector;
 
 /**
  * HTTP async client and server, based on tomcat NIO/APR connectors
@@ -48,46 +43,36 @@
 
     static AtomicInteger serCnt = new AtomicInteger();
 
-    protected static Logger log = Logger.getLogger("HttpCh");
+    public static final String CONTENT_LENGTH= "Content-Length";
 
-    boolean debug = false;
-    // Used to receive an entity - headers + maybe some body
-    // read() must first consume extra data from this buffer.
-    // Next reads will be direct from socket.
-    protected BBuffer headRecvBuf = BBuffer.allocate(HEADER_SIZE);
-    BBuffer line = BBuffer.wrapper(); 
+    public static final String HTTP_10 = "HTTP/1.0";
     
-    // ---- Buffers owned by the AsyncHttp object ----
+    public static final String HTTP_11 = "HTTP/1.1";
     
-    BBuffer headB = BBuffer.wrapper();
-    FutureCallbacks<HttpChannel> doneLock = new FutureCallbacks<HttpChannel>();
-    ArrayList<IOChannel> filters = new ArrayList<IOChannel>();
-
-    // ---------- Body read side ------------
-
-    // Set if Exect: 100-continue was set on reqest.
-    // If this is the case - body won't be sent until
-    // server responds ( client ) and server will only
-    // read body after ack() - or skip to next request 
-    // without swallowing the body.
-    protected boolean expectation = false;
+    /**
+     * SEMI_COLON.
+     */
+    public static final byte SEMI_COLON = (byte) ';';
     
-    /** Ready for recycle, if send/receive are done */
-    protected boolean release = false;
+    public static final byte QUESTION = (byte) '?';
     
-    protected boolean sendReceiveDone = false; 
     
+    protected static Logger log = Logger.getLogger("HttpChannel");
+
     
-    // ----------- Body write side ------------
+    boolean debug = false;
     
+    // ---- Callbacks and locks 
     
-    // TODO: setters
+    FutureCallbacks<HttpChannel> doneLock = new FutureCallbacks<HttpChannel>();
+    FutureCallbacks<HttpChannel> headersReceivedLock = 
+            new FutureCallbacks<HttpChannel>();
     /**
      * Called when the incoming headers have been received.
      * ( response for client mode, request for server mode )
      * @throws IOException 
      */
-    HttpService httpService;
+    protected HttpService httpService;
     /** 
      * Called when:
      *  - body sent
@@ -101,104 +86,70 @@
      *  - returned to the pool.
      */
     private RequestCompleted doneAllCallback;
+    protected boolean sendReceiveDone = false; 
     
+    // Will be signalled (open) when the buffer is empty. 
+    FutureCallbacks<IOChannel> flushLock = new FutureCallbacks<IOChannel>();
     
-    HttpMessage inMessage;
-    
-    
-    HttpMessage outMessage;
-    
-    // receive can be for request ( server mode ) or response ( client )
-    HttpBody receiveBody = new HttpBody(this, false);
-    HttpBody sendBody = new HttpBody(this, true);
-    
-    private HttpRequest httpReq;
-    private HttpResponse httpRes;
+    FutureCallbacks<HttpChannel> doneFuture;
+    boolean doneCallbackCalled = false;
 
-    boolean headersDone = false;
-    protected boolean serverMode = false;
-    // ---------- Client only ------------
-    
-    //protected HttpParser parser = new HttpParser();
-    
-    protected String dbgName = this.getClass().getSimpleName();
-    // ----- Pools - sockets, heavy objects -------------
-    // If client mode - what host we are connected to.
-    protected String host;
 
-    protected int port; 
+    // ---------- 
     
-    private HttpConnector httpConnector;
-    // ------ JMX 
-    protected int ser; // id - for jmx registration and logs
-    // Server side only 
-    protected String serverHeader = "TomcatLite";
+    // Set if Exect: 100-continue was set on reqest.
+    // If this is the case - body won't be sent until
+    // server responds ( client ) and server will only
+    // read body after ack() - or skip to next request 
+    // without swallowing the body.
+    protected boolean expectation = false;
+    
+    /** Ready for recycle, if send/receive are done */
+    protected boolean release = false;
     
-    protected boolean http11 = false;
+    // ----------- 
     
-    protected boolean http09 = false;
+    protected boolean headersDone = false;
     protected boolean error = false;
     protected boolean abortDone = false;
-    FutureCallbacks<HttpChannel> doneFuture;
-    boolean doneCallbackCalled = false;
+    
+    
+    protected int ser; // id - for jmx registration and logs
+    protected int channelId;
 
     /** 
-     * 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;
+     * Null after endSendReceive and before sending the request
+     */
+    HttpConnection conn;
 
-    // Will be signalled (open) when the buffer is empty. 
-    private FutureCallbacks<IOChannel> flushLock = new FutureCallbacks<IOChannel>();
+    HttpConnector httpConnector;
 
-    // -- Lifecycle --
-    
-    Runnable dispatcherRunnable = new Runnable() {
-        @Override
-        public void run() {
-            getConnector().getDispatcher().runService(HttpChannel.this);
-        }
+    // Different ways to point to request response (server/client) 
+    HttpRequest httpReq;
+    HttpResponse httpRes;
+    HttpMessage inMessage;
+    HttpMessage outMessage;
+    // receive can be for request ( server mode ) or response ( client )
+    IOBuffer receiveBody = new IOBuffer();
+
+    // notify us that user called close()
+    IOBuffer sendBody = new IOBuffer() {
+        public void close() throws IOException {
+            if (isAppendClosed()) {
+                return;
+            }
+            super.close();
+            outClosed();
+        }        
     };
     
-    long ioTimeout = 30 * 60000; // 30 min seems high enough 
-        
-    public static final String CONTENT_LENGTH= "Content-Length";
 
-    /**
-     * HTTP/1.0.
-     */
-    public static final String HTTP_10 = "HTTP/1.0";
-    
-    public static final String HTTP_11 = "HTTP/1.1";
-    
-    public static final String CHUNKED = "chunked";
-    
-    public static final String CLOSE = "close"; 
+    // Server side only 
+    protected String serverHeader = "TomcatLite";
     
-    public static final String KEEPALIVE_S = "keep-alive";
-
+    long ioTimeout = 30 * 60000; // 30 min seems high enough 
 
-    public static final String CONNECTION = "Connection";
-    
-    public static final String TRANSFERENCODING = "Transfer-Encoding";
-    
-    /**
-     * SEMI_COLON.
-     */
-    public static final byte SEMI_COLON = (byte) ';';
-    
-    static byte[] END_CHUNK_BYTES = {
-    (byte) '\r', (byte) '\n', 
-    (byte) '0', 
-    (byte) '\r', (byte) '\n', 
-    (byte) '\r', (byte) '\n'};
-    
-    public static final byte QUESTION = (byte) '?';
-    
-    static final byte COLON = (byte) ':';
+        
     public HttpChannel() {
         ser = serCnt.incrementAndGet();
         httpReq = new HttpRequest(this);
@@ -230,38 +181,31 @@
         checkRelease();
         trace("abort " + t);
         log.info("Abort connection " + t);
-        if (net != null ) {
-            if (net.isOpen()) {
-                net.close();
-                net.startSending();
-            }
+        if (conn != null) {
+            conn.abort(this, t);
         }
         inMessage.state = HttpMessage.State.DONE;
         outMessage.state = HttpMessage.State.DONE;
         sendReceiveDone = true;
         error = true;
-        close();
         handleEndSendReceive();
     }
     
-    public HttpChannel addFilterAfter(IOChannel filter) {
-        filters.add(filter);
-        return this;
-    }
-
-    
+    /**
+     * If release was called - throw exception, you shouldn't use
+     * the object again.
+     * @throws IOException
+     */
     private void checkRelease() throws IOException {
         if (release && sendReceiveDone) {
             throw new IOException("Object released");
         }        
     }
     
-    void closeStreamOnEnd(String cause) {
-        if (debug) 
-            log.info("Not reusing connection because: " + cause);
-        keepAlive = false;
-    }
-
+    /**
+     * Called when the request is done. Need to send remaining byte.
+     * 
+     */
     public void complete() throws IOException {
         checkRelease();
         if (!getOut().isAppendClosed()) {
@@ -295,21 +239,10 @@
         return read;
     }
     
-    public void flushNet() throws IOException {
-        checkRelease();
-        if (net != null) {
-            net.startSending();
-        }
-    }
-    
     public HttpConnector getConnector() {
         return httpConnector;
     }
 
-    public FutureCallbacks<HttpChannel> getDoneFuture() {
-        return doneLock;
-    }
-
     public boolean getError() {
         return error;
     }
@@ -329,8 +262,12 @@
         return ioTimeout;
     }
 
+    // TODO: replace with getSocketChannel - used for remote addr, etc
     public IOChannel getNet() {
-        return net;
+        if (conn == null) {
+            return null;
+        }
+        return conn.getSink();
     }
     
     
@@ -350,9 +287,8 @@
       
     public String getState() {
         return
-            (serverMode ? "SRV:" : "") + 
-            (keepAlive() ? " KA " : "")  
-            + "RCV=[" + inMessage.state.toString() + " " + 
+            conn + 
+            "RCV=[" + inMessage.state.toString() + " " + 
             receiveBody.toString()  
             + "] SND=[" + outMessage.state.toString() 
             + " " + sendBody.toString() + "]";
@@ -366,10 +302,10 @@
     
  
     public String getTarget() {
-        if (host == null) {
-            return ":" + port;
+        if (target == null) {
+            return ":0"; // server mode ? 
         }
-        return host + ":" + port;
+        return target;
     }
 
 
@@ -378,19 +314,13 @@
      * is completed ( or if there is no req body )
      * @throws IOException 
      */
-    protected void handleEndReceive(boolean frameError) throws IOException {
+    protected void handleEndReceive() throws IOException {
         if (inMessage.state == HttpMessage.State.DONE) {
             return;
         }
         if (debug) {
-            trace("END_RECV " + ((frameError) ? " FRAME_ERROR" : ""));
+            trace("END_RECV");
         }
-        if (frameError) {
-            closeStreamOnEnd("frame error");
-            // TODO: next read() should throw exception !!
-            error = true;
-        }
-
         getIn().close();
 
         inMessage.state = HttpMessage.State.DONE;
@@ -419,31 +349,20 @@
             doneCallbackCalled = true;
         }
         
-        if (!keepAlive() && net != null) {
-            net.getOut().close(); // shutdown output if not done
-            net.getIn().close(); // this should close the socket
-            net.startSending();
-        }
-
+        getIn().close();
+        
         if (doneAllCallback != null) {
             doneAllCallback.handle(this, error ? new Throwable() : null);
         }
         
-        // Remove the net object - will be pooled separtely
-        IOChannel ch = this.net;
-        if (ch != null && keepAlive()) {
-            
-            boolean keepOpen = ch.isOpen(); 
-        
-            resetBuffers(); // net is now NULL - can't send anything more
-            if (getConnector() != null) {
-                getConnector().returnSocket(ch, serverMode, keepOpen);
-            }
+        if (conn != null) {
+            conn.endSendReceive(this);
         }
         
+        conn = null;
+        
         if (debug) {
             trace("END_SEND_RECEIVE" 
-                    + (!keepAlive() ? " CLOSE_ON_END " : "")
                     + (release ? " REL" : ""));
         }
             
@@ -482,67 +401,11 @@
         System.err.println("Error " + type + " " + outMessage.state);
     }
 
-    @Override
-    public void handleFlushed(IOChannel net) throws IOException {
-        flushLock.signal(this);
-        super.handleFlushed(this);
-        if (sendBody.isClosedAndEmpty()) {
-            handleEndSent();
-        }
-    }
-    
-    /**
-     * Called when the net has readable data.
-     */
-    @Override
-    public void handleReceived(IOChannel net) throws IOException {
-        try {
-            if (getConnector() == null) {
-                throw new IOException("Data received after release");
-            }
-            if (net == null) {
-                return; // connection released
-            }
-            if (net.getIn().isClosedAndEmpty()) {
-                // Close received
-                closeStreamOnEnd("close on input 2");
-                if (inMessage.state == HttpMessage.State.HEAD) {
-                    trace("NET CLOSE WHILE READING HEAD");
-                    abort(new IOException("Connection closed"));
-                    return;
-                } else if (inMessage.state == HttpMessage.State.DONE) {
-                    // Close received - make sure we close out
-                    if (sendBody.isClosedAndEmpty()) {
-                        net.getOut().close();
-                    }
-                    return;
-                }
-            }
-            if (debug) {
-                trace("Http data received " + inMessage.state + " " + 
-                        net.getIn() + " headerDone=" + headersDone);
-            }
-
-            if (inMessage.state == HttpMessage.State.HEAD) {
-                headDataReceived();
-                if (inMessage.state == HttpMessage.State.HEAD) {
-                    return; // still parsing head
-                }
-                if (serverMode && httpReq.decodedUri.remaining() == 0) {
-                    abort("Invalid url");
-                }
-            } 
-
-            if (inMessage.state == HttpMessage.State.BODY_DATA) {
-                if (net != null) {
-                    receiveBody.rawDataReceived(net.getIn());
-                }
-            }
-            
-            // Send header callbacks - we process any incoming data 
-            // first, so callbacks have more info
-            if (httpService != null && !headersDone) {
-                headersDone = true;
+    void handleHeadersReceived(HttpMessage in) throws IOException {
+        if (!headersDone) {
+            headersDone = true;
+            headersReceivedLock.signal(this);
+            if (httpService != null) {
                 try {
                     httpService.service(getRequest(), getResponse());
                 } catch (Throwable t) {
@@ -550,122 +413,11 @@
                     abort(t);
                 }
             }
-
-            // If header callback or previous dataReceived() hasn't consumed all 
-            if (receiveBody.getBufferCount() > 0) {
-                // Has data 
-                super.sendHandleReceivedCallback(); // callback
-            }
-
-            // Receive has marked the body as closed
-            if (receiveBody.isAppendClosed() 
-                    && inMessage.state != HttpMessage.State.DONE) {
-                if (net != null && net.getIn().getBufferCount() > 0) {
-                    if (debug) {
-                        trace("Pipelined requests"); // may be a crlf
-                    }
-                }                
-                handleEndReceive(receiveBody.frameError);
-            }
-
-            if (inMessage.state == HttpMessage.State.DONE) {
-                // TCP end ? 
-                if (net == null || net.getIn() == null) {
-                    trace("NO NET");
-                    return;
-                }
-                if (net.getIn().isClosedAndEmpty()) {
-                    // If not already closed.
-                    closeStreamOnEnd("closed on input 3");
-                    if (outMessage.state == HttpMessage.State.DONE) {
-                        // need to flush out.
-                        net.getOut().close();
-                        flushNet();
-                    }
-                } else {
-                    // Next request, ignore it.
-                }
-
-            }
-        } catch (IOException ex) {
-            ex.printStackTrace();
-            abort(ex);
-        }
-    }
- 
-
-    /** 
-     * Read and process a chunk of head, called from dataReceived() if 
-     * in HEAD mode.
-     * 
-     * @return <= 0 - still in head mode. > 0 moved to body mode, some 
-     * body chunk may have been received. 
-     */
-    protected void headDataReceived() throws IOException {
-        while (true) {
-            // we know we have one
-            int read = net.getIn().readLine(headRecvBuf);
-            if (read < 0) {
-                if (debug) {
-                    trace("CLOSE while reading HEAD");    
-                }
-                // too early - we don't have the head
-                abort("Close in head");
-                return;
-            }
-            // Remove starting empty lines.
-            headRecvBuf.skipEmptyLines();
-
-            // Do we have another full line in the input ?
-            if (BBuffer.hasLFLF(headRecvBuf)) {
-                break;
-            }
-            if (read == 0) { // no more data
-                return;
-            }
-        }
-        headRecvBuf.wrapTo(headB);
-
-        
-        parseMessage(headB);
-
-        
-        if (debug) {
-            trace("HEAD_RECV " + getRequest().requestURI() + " " + 
-                    getResponse().getMsgBytes().status() + " " + net.getIn());
-        }
-        
-    }
-    
-    public void parseMessage(BBuffer headB) throws IOException {
-        //Parse the response
-        headB.readLine(line);
-        
-        HttpMessageBytes msgBytes;
-
-        if (serverMode) {
-            msgBytes = httpReq.getMsgBytes();
-            parseRequestLine(line, msgBytes.method(),
-                    msgBytes.url(),
-                    msgBytes.query(),
-                    msgBytes.protocol());
-        } else {
-            msgBytes = httpRes.getMsgBytes();
-            parseResponseLine(line, msgBytes.protocol(), 
-                    msgBytes.status(), msgBytes.message());
         }
-        
-        parseHeaders(msgBytes, headB);
-
-        inMessage.state = HttpMessage.State.BODY_DATA;
-        
-        // TODO: hook to allow specific charsets ( can be done later )
+    }   
 
-        inMessage.processReceivedHeaders();
-    }
 
     private void init() {
-        headRecvBuf.recycle();
         headersDone = false;
         sendReceiveDone = false;
         
@@ -673,25 +425,19 @@
         sendBody.recycle();
         expectation = false;
         
-        http11 = false;
-        http09 = false;
         error = false;
         abortDone = false;
        
         
         getRequest().recycle();
         getResponse().recycle();
-        host = null;
-        filters.clear();
-        
-        line.recycle();
-        headB.recycle();
+        target = null;
         
         doneLock.recycle();
+        headersReceivedLock.recycle();
         flushLock.recycle();
         
         doneCallbackCalled = false;
-        keepAlive = true;
         // Will be set again after pool
         setHttpService(null);
         doneAllCallback = null;
@@ -702,13 +448,6 @@
         return outMessage.state == HttpMessage.State.DONE && inMessage.state == HttpMessage.State.DONE;
     }
     
-    public boolean keepAlive() {
-        if (http09) {
-            return false;
-        }
-        return keepAlive;
-    }
-
     /**
      * Called when all done:
      *  - service finished ( endService was called )
@@ -786,174 +525,6 @@
     }  
     
     
-    /**
-     * Parse one header. 
-     * Line must be populated. On return line will be populated
-     * with the next header:
-     * 
-     * @param line current header line, not empty.
-     */
-    public int parseHeader(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;
-            }
-            byte 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 ?
-        normalizeHeader(value);
-        return 1;
-    }
-    
-    public void parseHeaders(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(head, line, nameBuf, valBuf);
-            
-            // TODO: process 'interesting' headers here.
-        }
-    }
-
-    /**
-     * 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.
-     */
-    public boolean parseRequestLine(BBuffer line, 
-            BBuffer methodMB, BBuffer requestURIMB,
-            BBuffer queryMB,
-            BBuffer protoMB)
-        throws IOException {
-
-        line.readToSpace(methodMB);
-        line.skipSpace();
-        
-        line.readToDelimOrSpace(QUESTION, requestURIMB);
-        if (line.remaining() > 0 && line.get(0) == 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;
-    }
-
-    public 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;
-    }
-
-    /**
-     * Update keepAlive based on Connection header and protocol.
-     */
-    void processConnectionHeader(MultiMap headers) {
-        if (http09) {
-            return;
-        }
-
-        CBuffer value = headers.getHeader(HttpChannel.CONNECTION);
-        String conHeader = (value == null) ? null : value.toString();
-        if (conHeader != null) {
-            if (HttpChannel.CLOSE.equalsIgnoreCase(conHeader)) {
-                closeStreamOnEnd("connection close");
-            }
-            if (!HttpChannel.KEEPALIVE_S.equalsIgnoreCase(conHeader)) {
-                closeStreamOnEnd("connection != keep alive");
-            }
-        } else {
-            // no connection header
-            if (!http11) {
-                closeStreamOnEnd("http1.0 no connection header");
-            }
-        }
-    }
-
-    void processExpectation() throws IOException {
-        expectation = false;
-        MultiMap headers = getRequest().getMimeHeaders();
-
-        CBuffer expect = headers.getHeader("expect");
-        if ((expect != null)
-                && (expect.indexOf("100-continue") != -1)) {
-            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();
-        }
-    }
-
-    void processProtocol() throws IOException {
-        http11 = true;
-        http09 = false;
-        
-        CBuffer protocolMB = getRequest().protocol();
-        if (protocolMB.equals(HttpChannel.HTTP_11)) {
-            http11 = true;
-        } else if (protocolMB.equals(HttpChannel.HTTP_10)) {
-            http11 = false;
-        } else if (protocolMB.equals("")) {
-            http09 = true;
-            http11 = false;
-        } else {
-            // Unsupported protocol
-            http11 = false;
-            error = true;
-            // Send 505; Unsupported HTTP version
-            getResponse().setStatus(505);
-            abort("Invalid protocol");
-        }
-    }
-
     protected void recycle() {
         if (debug) { 
             trace("RECYCLE");
@@ -983,265 +554,49 @@
         }
     }
 
-    public void resetBuffers() {
-        if (net != null) {
-            net.setDataFlushedCallback(null);
-            net.setDataReceivedCallback(null);
-            setSink(null);
-        }
-    }
-
-    public void sendHeaders() throws IOException {
+    public void send() throws IOException {
         checkRelease();
-        if (serverMode) {
-            sendResponseHeaders();
-        } else {
-            sendRequest();
-        }
-    }
-
-    /** 
-     * Can be called from any thread.
-     * 
-     * @param host
-     * @param port
-     * @throws IOException
-     */
-    public void sendRequest() throws IOException {
-        if (getRequest().isCommitted()) {
-            return;
-        }
-        getRequest().setCommitted(true);
-
-        String target = host + ":" + port;
-        
-        if (getRequest().getMimeHeaders().getHeader("Host") == null
-                && host != null) {
-            CBuffer hostH = getRequest().getMimeHeaders().addValue("Host");
-            hostH.set(host); // TODO: port
-        }
-        
-        outMessage.state = HttpMessage.State.HEAD;
-
-        IOChannel ch = getConnector().cpool.getChannel(target);
-
-        if (ch == null) {
-            if (debug) {
-                trace("HTTP_CONNECT: New connection " + target);
-            }
-            IOConnector.ConnectedCallback connected = new IOConnector.ConnectedCallback() {
-                @Override
-                public void handleConnected(IOChannel ch) throws IOException {
-                    if (httpConnector.debugHttp) {
-                        IOChannel ch1 = new DumpChannel("");
-                        ch.addFilterAfter(ch1);
-                        ch = ch1;                        
-                    }
-                    
-                    sendRequestHeaders(ch);
-                }
-            };
-            getConnector().getIOConnector().connect(host, port, connected);
+        if (httpReq == inMessage) {
+            conn.sendResponseHeaders(this);   
         } else {
-            if (debug) {
-                trace("HTTP_CONNECT: Reuse connection " + target + " " + this);
-            }
-            // TODO retry if closed
-            sendRequestHeaders(ch);
-        }
-    }
-
-    /** 
-     * Used in request mode.  
-     * 
-     * @throws IOException
-     */
-    void sendRequestHeaders(IOChannel ch) throws IOException {
-        if (getConnector() == null) {
-            throw new IOException("after release");
-        }
-        if (!ch.isOpen()) {
-            abort("Closed channel");
-            return;
-        }
-        setChannel(ch); // register read/write callbacks
-        
-        // Update transfer fields based on headers.
-        processProtocol();
-        
-        processConnectionHeader(getRequest().getMimeHeaders());
-
-
-        // 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 (!sendBody.isContentDelimited()) {
-            // Will not close connection - just flush and mark the body 
-            // as sent
-            sendBody.noBody = true;
-            getOut().close();
-        }
-
-        if (sendBody.noBody) {
-            getRequest().getMimeHeaders().remove(HttpChannel.CONTENT_LENGTH);
-            getRequest().getMimeHeaders().remove(HttpChannel.TRANSFERENCODING);
-        } else {
-            long contentLength = 
-                getRequest().getContentLength();
-            if (contentLength < 0) {
-                getRequest().getMimeHeaders().addValue(HttpChannel.TRANSFERENCODING).
-                    set(HttpChannel.CHUNKED);
-            }
-            sendBody.processContentDelimitation();
-        }
-
-        sendBody.updateCloseOnEnd();
-
-        try {
-            getRequest().serialize(net.getOut());
-            if (debug) {
-                trace("S: \n" + net.getOut());
+            if (getRequest().isCommitted()) {
+                return;
             }
-
-        } catch (Throwable t) {
-            log.log(Level.SEVERE, "Error sending request", t);
-        }
-
-        if (outMessage.state == HttpMessage.State.HEAD) {
-            outMessage.state = HttpMessage.State.BODY_DATA;
+            getRequest().setCommitted(true);
+            
+            outMessage.state = HttpMessage.State.HEAD;
+            
+            getConnector().connectAndSend(this);
         }
-
-        // TODO: add any body and flush. More body can be added later - 
-        // including 'end'.
-
-        startSending();
-        
     }
-
-    /**
-     * When committing the response, we have to validate the set of headers, as
-     * well as setup the response filters.
-     * Only in server mode.
-     */
-    void sendResponseHeaders() throws IOException {
-        checkRelease();
-        if (!serverMode) {
-            throw new IOException("Only in server mode");
-        }
-
-        if (getResponse().isCommitted()) {
-            return; 
-        }
-        getResponse().setCommitted(true);
-        
-        sendBody.noBody = !getResponse().hasBody();
-
-        if (sendBody.statusDropsConnection(getResponse().getStatus())) {
-            closeStreamOnEnd("status drops connection");
-        }
-        if (error) {
-            closeStreamOnEnd("error");
-        }
-
-        // A header explicitely set.
-        CBuffer transferEncHeader = 
-            getResponse().getMimeHeaders().getHeader(HttpChannel.TRANSFERENCODING);
-        if (!sendBody.noBody 
-                && keepAlive()) {
-            if (getResponse().getContentLength() < 0) {
-                // Use chunked by default, if no c-l
-                if (transferEncHeader == null) {
-                    getResponse().getMimeHeaders().addValue(HttpChannel.TRANSFERENCODING).set(HttpChannel.CHUNKED);
-                } else {
-                    transferEncHeader.set(HttpChannel.CHUNKED);                    
-                }
-            }
-        }
-        
-        sendBody.processContentDelimitation();
-        
-        sendBody.updateCloseOnEnd();
-        
-        MultiMap headers = getResponse().getMimeHeaders();
-
-        // Add date header
-        if (headers.getHeader("Date") == null) {
-            headers.setValue("Date").set(FastHttpDateFormat.getCurrentDate());
-        }
-
-        // Add server header
-        if (serverHeader.length() > 0) {
-            headers.setValue("Server").set(serverHeader);
-        }
-
-        // did the user set a connection header that may override what we have ?
-        processConnectionHeader(headers);
-        
-        if (!keepAlive()) {
-            headers.setValue(HttpChannel.CONNECTION).set(HttpChannel.CLOSE);
-        } else {
-            if (!http11 && !http09) {
-                headers.setValue(HttpChannel.CONNECTION).set(HttpChannel.KEEPALIVE_S);                
-            }
-        }
     
-        if (debug) {
-            trace("Send response headers " + net);
-        }
-        if (net != null) {
-            getResponse().serialize(net.getOut());
-        }
-        
-        if (outMessage.state == HttpMessage.State.HEAD) {
-            outMessage.state = HttpMessage.State.BODY_DATA;
-        }
-        
-        if (sendBody.isDone()) {
-            getOut().close();
-        }
-
-        if (net != null) {
-            net.startSending();
+    /** Called when the outgoing stream is closed:
+     * - by an explicit call to close()
+     * - when all content has been sent. 
+     */
+    protected void outClosed() throws IOException {
+        if (conn != null) {
+            conn.outClosed(this);
         }
     }
 
     public HttpChannel serverMode(boolean enabled) {
         if (enabled) {
-            serverMode = true;
-            dbgName = "AsyncHttpServer";
             httpReq.setBody(receiveBody);
             httpRes.setBody(sendBody);
-            sendBody.setMessage(httpRes);
-            receiveBody.setMessage(httpReq);
             inMessage = httpReq;
             outMessage = httpRes;
         } else {
-            serverMode = false;
-            dbgName = "AsyncHttp";         
             httpReq.setBody(sendBody);
             httpRes.setBody(receiveBody);
-            sendBody.setMessage(httpReq);
-            receiveBody.setMessage(httpRes);
             inMessage = httpRes;
             outMessage = httpReq;
         }
         if (debug) {
-            log = Logger.getLogger(dbgName);
         }
         return this;
     }
     
-    public void setChannel(IOChannel ch) throws IOException {
-        for (IOChannel filter: filters) {
-            ch.addFilterAfter(filter);
-            ch = filter;
-        }
-        
-        withBuffers(ch);
-    }
-    
     public void setCompletedCallback(RequestCompleted doneAllCallback) 
             throws IOException {
         this.doneAllCallback = doneAllCallback;
@@ -1271,16 +626,16 @@
         ioTimeout = timeout;
     }
 
-    public void setTarget(String host, int port) {
-        this.host = host;
-        this.port = port;
+    
+    public void setTarget(String host) {
+        this.target = host;
     }
     
     public void startSending() throws IOException {
         checkRelease();
-        
-        sendBody.flushToNext();
-        flushNet();
+        if (conn != null) {
+            conn.startSending(this);
+        }
     }
     
     public String toString() {
@@ -1305,13 +660,11 @@
         flushLock.waitSignal(timeMs);
     }
  
-    public HttpChannel withBuffers(IOChannel net) {
-        setSink(net);
-        net.setDataFlushedCallback(this);
-        net.setDataReceivedCallback(this);
+    public HttpChannel setConnection(HttpConnection conn) {
+        this.conn = conn;
         return this;
     }
- 
+    
     /**
      * Normalize URI.
      * <p>
@@ -1439,5 +792,13 @@
         void handle(HttpChannel data, Object extraData) throws IOException;
     }
 
+    Runnable dispatcherRunnable = new Runnable() {
+        @Override
+        public void run() {
+            getConnector().getDispatcher().runService(HttpChannel.this);
+        }
+    };
+    
+
     
 }
\ No newline at end of file

Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java?rev=887087&r1=887086&r2=887087&view=diff
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java (original)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java Fri Dec  4 07:16:59 2009
@@ -15,10 +15,14 @@
 import java.util.logging.Logger;
 
 import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.SpdyConnection.SpdyConnectionManager;
+import org.apache.tomcat.lite.io.BBuffer;
 import org.apache.tomcat.lite.io.DumpChannel;
 import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.IOBuffer;
 import org.apache.tomcat.lite.io.IOChannel;
 import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.IOConnector.DataReceivedCallback;
 
 /**
  * Manages HttpChannels and associated socket pool.
@@ -41,6 +45,8 @@
         public void onDestroy(HttpChannel ch, HttpConnector con) throws IOException;
     }
     
+    HttpConnectionManager conManager = new HttpConnectionManager();
+    
     private static Logger log = Logger.getLogger("HttpConnector");
     private int maxHttpPoolSize = 20;
     
@@ -49,7 +55,7 @@
     
     private Queue<HttpChannel> httpChannelPool = new ConcurrentLinkedQueue<HttpChannel>();
 
-    private IOConnector ioConnector;
+    protected IOConnector ioConnector;
     
     boolean debugHttp = false;
     boolean debug = false;
@@ -67,8 +73,13 @@
     public AtomicInteger reusedChannels = new AtomicInteger();
 
     public ConnectionPool cpool = new ConnectionPool();
-    
         
+    // Host + context mapper.
+    Dispatcher dispatcher;
+    protected HttpService defaultService;
+    int port = 8080;
+    
+    
     public HttpConnector(IOConnector ioConnector) {
         this.ioConnector = ioConnector;
         dispatcher = new Dispatcher();
@@ -152,20 +163,31 @@
     }
 
     public HttpChannel get(String host, int port) throws IOException {
-        HttpChannel http = get(false, host, port);
-        http.setTarget(host, port);
+        HttpChannel http = get(false);
+        http.setTarget(host + ":" + port);
         return http;
     }
     
     public HttpChannel getServer() {
         try {
-            return get(true, null, 0);
+            return get(true);
         } catch (IOException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
             return null;
         }
     }
+
+    public HttpRequest request(String host, int port) throws IOException {
+        HttpChannel http = get(false);
+        http.setTarget(host + ":" + port);
+        return http.getRequest();
+        
+    }
+
+    public HttpRequest request(CharSequence urlString) throws IOException {
+        return get(urlString).getRequest();
+    }
     
     /**
      * Get an existing AsyncHttp object. Since it uses many buffers and 
@@ -183,15 +205,15 @@
             port = secure ? 443: 80;
         }
         // TODO: insert SSL filter
-        HttpChannel http = get(false, host, port);
-        http.setTarget(host, port);
+        HttpChannel http = get(false);
+        http.setTarget(host + ":" + port);
         String path = url.getFile(); // path + qry
         // TODO: query string
         http.getRequest().requestURI().set(path);
         return http;
     }
     
-    protected HttpChannel get(boolean server, CharSequence host, int port) throws IOException {
+    protected HttpChannel get(boolean server) throws IOException {
         HttpChannel processor = null;
         synchronized (httpChannelPool) {
             processor = httpChannelPool.poll();
@@ -210,8 +232,7 @@
         }
         processor.serverMode(server);
         if (debug) {
-            log.info((reuse ? "REUSE ": "Create ")
-                    + host + ":" + port + 
+            log.info((reuse ? "REUSE ": "Create ") +
                     (server? " S" : "")
                     + " id=" + processor.ser + 
                     " " + processor +
@@ -238,75 +259,8 @@
                                 boolean keepOpen) 
             throws IOException {
         // Now handle net - note that we could have reused the async object
-        if (serverMode) {
-            BBucket first = ch.getIn().peekFirst();
-            if (first != null) {
-                HttpChannel http = getServer();
-                if (debug) {
-                    http.trace("PIPELINED request " + first + " " + http.httpService); 
-                }
-                http.setChannel(ch);
-                http.setHttpService(defaultService);
-                
-                // In case it was disabled
-                if (ch != null) {
-                    if (ch.isOpen()) {
-                        ch.readInterest(true);
-                    }
-                    // Notify that data was received. The callback must deal with
-                    // pre-existing data.
-                    ch.sendHandleReceivedCallback();
-                }
-                http.handleReceived(http.getSink());
-                return;
-            }
-        }
-        if (serverMode && !serverKeepAlive) {
-            keepOpen = false;
-        }
-        if (!serverMode && !clientKeepAlive) {
-            keepOpen = false;
-        }
         
 
-        if (keepOpen) {
-            // reuse the socket
-            if (serverMode) {
-                if (debug) {
-                    log.info(">>> server socket KEEP_ALIVE " + ch.getTarget() + 
-                            " " + ch);
-                }
-                ch.readInterest(true);
-                ch.setDataReceivedCallback(receiveCallback);
-                ch.setDataFlushedCallback(null);
-                
-                cpool.returnChannel(ch);
-                // TODO: timeout event to close it
-                //                ch.setTimer(10000, new Runnable() {
-                //                    @Override
-                //                    public void run() {
-                //                        System.err.println("Keep alive timeout");
-                //                    }
-                //                });
-            } else {
-                if (debug) {
-                    log.info(">>> client socket KEEP_ALIVE " + ch.getTarget() + 
-                            " " + ch);
-                }
-                ch.readInterest(true);
-                ch.setDataReceivedCallback(clientReceiveCallback);
-                ch.setDataFlushedCallback(null);
-                
-                cpool.returnChannel(ch);
-            }
-        } else { 
-            if (debug) {
-                log.info("--- Close socket, no keepalive " + ch);
-            }
-            if (ch != null) {
-                ch.close();
-            }
-        }
     }
     
     protected void returnToPool(HttpChannel http) throws IOException {
@@ -321,7 +275,7 @@
         
         // No more data - release the object
         synchronized (httpChannelPool) {
-            http.resetBuffers();
+            http.setConnection(null);
             http.setConnector(null);
             if (httpChannelPool.contains(http)) {
                 System.err.println("dup ? ");                
@@ -341,11 +295,6 @@
         return ioConnector;
     }
     
-    // Host + context mapper.
-    Dispatcher dispatcher;
-    HttpService defaultService;
-    int port = 8080;
-    
     
     public void setHttpService(HttpService s) {
         defaultService = s;
@@ -353,7 +302,7 @@
     
     public void start() throws IOException {
         if (ioConnector != null) {
-            ioConnector.acceptor(new AcceptorCallback(this, defaultService), 
+            ioConnector.acceptor(new AcceptorCallback(), 
                     Integer.toString(port), null);
         }
     }
@@ -369,85 +318,214 @@
         }
     }
     
-    private static class AcceptorCallback implements IOConnector.ConnectedCallback {
+    protected void connectAndSend(HttpChannel httpCh) throws IOException {
+        String target = httpCh.getTarget();
+        // TODO: SSL 
+        HttpConnection ch = cpool.getChannel(target);
+
+        if (ch == null) {
+            if (debug) {
+                httpCh.trace("HTTP_CONNECT: New connection " + target);
+            }
+            IOConnector.ConnectedCallback connected =
+                new HttpConnectedCallback(this, httpCh);
+            
+            // will call sendRequestHeaders
+            String[] hostPort = target.split(":");
+            int targetPort = hostPort.length > 1 ? 
+                    Integer.parseInt(hostPort[1]) : 80;
+            getIOConnector().connect(hostPort[0], targetPort,
+                    connected);
+        } else {
+            if (debug) {
+                httpCh.trace("HTTP_CONNECT: Reuse connection " + target + " " + this);
+            }
+            // TODO retry if closed
+            ch.beforeRequest();
+            httpCh.setConnection(ch);
+            ch.sendRequest(httpCh);
+        }
+    }
+    
+    static class HttpConnectedCallback implements IOConnector.ConnectedCallback {
         HttpConnector httpCon;
-        HttpService callback;
+        HttpChannel httpCh;
         
-        public AcceptorCallback(HttpConnector asyncHttpConnector,
-                HttpService headersReceived) {
-            this.httpCon = asyncHttpConnector;
-            this.callback = headersReceived;
+        public HttpConnectedCallback(HttpConnector httpConnector,
+                HttpChannel httpCh2) {
+            this.httpCh = httpCh2;
+            this.httpCon = httpConnector;
         }
 
         @Override
-        public void handleConnected(IOChannel accepted) throws IOException {
-            HttpChannel shttp = httpCon.getServer();
-            if (callback != null) {
-                shttp.setHttpService(callback);
-            }
+        public void handleConnected(IOChannel ch) throws IOException {
             if (httpCon.debugHttp) {
-                IOChannel ch = new DumpChannel("");
-                accepted.addFilterAfter(ch);
-                shttp.setChannel(ch);
-            } else {
-                shttp.setChannel(accepted);
+                IOChannel ch1 = new DumpChannel("");
+                ch.addFilterAfter(ch1);
+                ch = ch1;                        
             }
-            // TODO: JSSE filter
-            
-
-            // Will read any data in the channel.
-            
-            accepted.handleReceived(accepted);
+            httpCon.handleConnected(ch, httpCh);
         }
+    }
 
+    HttpConnection newConnection() {
+        return conManager.newConnection(this);
     }
 
 
-    private IOConnector.DataReceivedCallback receiveCallback = 
-        new IOConnector.DataReceivedCallback() {
-        /** For keepalive - for server
-         * 
-         * @param peer
-         * @throws IOException
-         */
+    private class AcceptorCallback implements IOConnector.ConnectedCallback {
         @Override
-        public void handleReceived(IOChannel net) throws IOException {
-            cpool.stopKeepAlive(net);
-            if (!net.isOpen()) {
-                return;
-            }
-            HttpChannel shttp = getServer();
-            shttp.setChannel(net);
-            shttp.setHttpService(defaultService);
-            net.sendHandleReceivedCallback();
+        public void handleConnected(IOChannel accepted) throws IOException {
+            System.err.println("ACCEPTED " + accepted);
+            handleAccepted(accepted);
         }
-    };
+    }
 
+    public HttpConnection handleAccepted(IOChannel accepted) throws IOException {
+        // TODO: reuse
+        HttpConnection shttp = newConnection();
+        shttp.serverMode = true;
 
-    // Sate-less, just closes the net.
-    private IOConnector.DataReceivedCallback clientReceiveCallback = 
-        new IOConnector.DataReceivedCallback() {
-        
-        @Override
-        public void handleReceived(IOChannel net) throws IOException {
-            if (!net.isOpen()) {
-                cpool.stopKeepAlive(net);
-                return;
-            }
-            log.warning("Unexpected message from server in client keep alive " 
-                    + net.getIn());
-            if (net.isOpen()) {
-                net.close();
-            }
+        if (debugHttp) {
+            log.info("Accepted " + accepted.getFirst().getPort(true));
+            IOChannel ch = new DumpChannel("");
+            accepted.addFilterAfter(ch);
+            shttp.setSink(ch);
+        } else {
+            shttp.setSink(accepted);
         }
+        // TODO: JSSE filter
         
-    };
+
+        // Will read any data in the channel.
+        
+        accepted.handleReceived(accepted);
+        return shttp;
+    }
 
     public HttpConnector setPort(int port2) {
         this.port = port2;
         return this;
     }
     
+    public void handleConnected(IOChannel net, HttpChannel httpCh) 
+            throws IOException {
+        if (!net.isOpen()) {
+            httpCh.abort("Can't connect");
+            return;
+        }
+        HttpConnection httpStream = newConnection();
+        httpStream.setSink(net);
+
+        // TODO: add it to the cpool
+        httpCh.setConnection(httpStream);
+        
+        httpStream.sendRequest(httpCh);
+    }
+
+    public static class HttpConnectionManager {
+        public HttpConnection newConnection(HttpConnector con) {
+            return new Http11Connection(con);
+        }
+        
+        public HttpConnection getFromPool(RemoteServer t) {
+            return t.connections.remove(t.connections.size() - 1);            
+        }
+    }
+    
+    /**
+     * Actual HTTP/1.1 wire protocol. 
+     *  
+     */
+    public static class HttpConnection extends IOChannel
+        implements DataReceivedCallback
+    {
+        protected HttpConnector httpConnector;
+        protected boolean serverMode;
+
+        protected BBuffer headRecvBuf = BBuffer.allocate(8192);
+        
+
+        @Override
+        public void handleReceived(IOChannel ch) throws IOException {
+            dataReceived(ch.getIn());
+        }
+
+        protected HttpChannel checkHttpChannel() throws IOException {
+            return null;
+        }
+        
+        /** 
+         * Called before a new request is sent, on a channel that is 
+         * reused.
+         */
+        public void beforeRequest() {
+        }
+
+        public void setSink(IOChannel ch) throws IOException {
+            this.net = ch;
+            ch.setDataReceivedCallback(this);
+            ch.setDataFlushedCallback(this);
+            // we may have data in the buffer;
+            handleReceived(ch);
+        }
+
+
+        /** 
+         * Incoming data.
+         */
+        public void dataReceived(IOBuffer iob) throws IOException {
+            
+        }
+        
+        /** 
+         * Framing error, client interrupt, etc.
+         */
+        public void abort(HttpChannel http, String t) throws IOException {
+        }
+        
+        protected void sendRequest(HttpChannel http) 
+            throws IOException {
+        }
+        
+        protected void sendResponseHeaders(HttpChannel http) 
+            throws IOException {
+        }
+
+        public void startSending(HttpChannel http) throws IOException {
+        }
+
+        @Override
+        public IOBuffer getIn() {
+            return net.getIn();
+        }
+
+        @Override
+        public IOBuffer getOut() {
+            return net.getOut();
+        }
+
+        @Override
+        public void startSending() throws IOException {
+        }
+        
+        /** Called when the outgoing stream is closed:
+         * - by an explicit call to close()
+         * - when all content has been sent. 
+         */
+        protected void outClosed(HttpChannel http) throws IOException {
+        }
+
+        protected void endSendReceive(HttpChannel httpChannel) throws IOException {
+            return;
+        }
+
+        public void withExtraBuffer(BBuffer received) {
+            return;
+        }
+        
+    }
+
     /** 
      * Connections for one remote host.
      * This should't be restricted by IP:port or even hostname,
@@ -455,10 +533,9 @@
      */
     public static class RemoteServer {
         public ConnectionPool pool;
-        public ArrayList<IOChannel> connections = new ArrayList<IOChannel>();
+        public ArrayList<HttpConnection> connections = new ArrayList<HttpConnection>();
     }
 
-    
     // TODO: add timeouts, limits per host/total, expire old entries 
     // TODO: discover apr and use it
     
@@ -510,7 +587,7 @@
          * are connected to equivalent servers ( LB ) 
          * @throws IOException 
          */
-        public IOChannel getChannel(CharSequence key) throws IOException {
+        public HttpConnection getChannel(CharSequence key) throws IOException {
             RemoteServer t = null;
             synchronized (hosts) {
                 t = hosts.get(key);
@@ -519,32 +596,23 @@
                     return null;
                 }
             }
-            IOChannel res = null;
+            HttpConnection res = null;
             synchronized (t) {
                 if (t.connections.size() == 0) {
                     misses.incrementAndGet();
-                    hosts.remove(key); 
                     return null;
                 } // one may be added - no harm.
-                res = t.connections.remove(t.connections.size() - 1);
-
-                if (t.connections.size() == 0) {
-                    hosts.remove(key); 
-                } 
-                if (res == null) {
-                    log.fine("Null connection ?");
-                    misses.incrementAndGet();
-                    return null;
-                }
+                
+                res = conManager.getFromPool(t);
                 
                 if (!res.isOpen()) {
                     res.setDataReceivedCallback(null);
                     res.close();
                     log.fine("Already closed " + res);
-                    //res.keepAliveServer = null;
                     res = null;
+                    misses.incrementAndGet();
+                    return null;
                 }
-                
                 waitingSockets.decrementAndGet();
             }
             hits.incrementAndGet();
@@ -557,7 +625,7 @@
         /**
          * Must be called in IOThread for the channel
          */
-        public void returnChannel(IOChannel ch) 
+        public void returnChannel(HttpConnection ch) 
                 throws IOException {
             CharSequence key = ch.getTarget(); 
             if (key == null) {
@@ -595,10 +663,7 @@
             
             ch.ts = System.currentTimeMillis();
             synchronized (t) {
-                // sdata.keepAliveServer = t;
                 t.connections.add(ch);      
-                //sdata.ch.setDataCallbacks(readable, null, cc);
-                ch.readInterest(true);
             }
         }
         
@@ -622,4 +687,10 @@
             }
         }
     }
+
+    public HttpConnector withConnectionManager(
+            HttpConnectionManager connectionManager) {
+        this.conManager = connectionManager;
+        return this;
+    }
 }

Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java?rev=887087&r1=887086&r2=887087&view=diff
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java (original)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java Fri Dec  4 07:16:59 2009
@@ -9,6 +9,8 @@
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted;
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
 import org.apache.tomcat.lite.io.BBuffer;
 import org.apache.tomcat.lite.io.BufferedIOReader;
 import org.apache.tomcat.lite.io.CBuffer;
@@ -346,11 +348,6 @@
     public void setCommitted(boolean b) {
         commited = b;
     }
-
-    // Not used in coyote connector ( hack )
-    
-    public void sendHead() throws IOException {
-    }
     
     public HttpChannel getHttpChannel() {
         return httpCh;
@@ -382,6 +379,27 @@
         return reader;
     }
     
+    public BBuffer readAll(BBuffer chunk, long to) throws IOException {
+        return httpCh.readAll(chunk, to);
+    }
+    
+    public BBuffer readAll() throws IOException {
+        return httpCh.readAll(null, httpCh.ioTimeout);
+    }
+    
+    /** 
+     * We're done with this object, it can be recycled.
+     * Any use after this should throw exception or affect an 
+     *  unrelated request.
+     */
+    public void release() throws IOException {
+        httpCh.release();
+    }
+
+    public void setCompletedCallback(RequestCompleted doneAllCallback) throws IOException {
+        httpCh.setCompletedCallback(doneAllCallback);
+    }
+    
     /** 
      * Returns a buffered reader. 
      */
@@ -395,26 +413,6 @@
         return writer;
     }
     
-    //
-    public abstract void serialize(IOBuffer out) throws IOException;
-    
-    
-    public void serializeHeaders(IOBuffer rawSendBuffers2) throws IOException {
-        MultiMap mimeHeaders = getMimeHeaders();
-        
-        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(HttpChannel.COLON);
-            rawSendBuffers2.append(value);
-            rawSendBuffers2.append(BBuffer.CRLF_BYTES);
-        }
-        rawSendBuffers2.append(BBuffer.CRLF_BYTES);
-    }
     
     protected void processMimeHeaders() {
         for (int idx = 0; idx < getMsgBytes().headerCount; idx++) {

Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java?rev=887087&r1=887086&r2=887087&view=diff
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java (original)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java Fri Dec  4 07:16:59 2009
@@ -7,6 +7,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
 import org.apache.tomcat.lite.http.MultiMap.Entry;
 import org.apache.tomcat.lite.io.BBuffer;
 import org.apache.tomcat.lite.io.CBuffer;
@@ -61,6 +62,8 @@
     boolean ssl = false;
     
     boolean async = false;
+
+    CBuffer requestURL = CBuffer.newInstance();
     
     private Map<String, Object> attributes = new HashMap<String, Object>();
 
@@ -97,6 +100,7 @@
         schemeMB.recycle();
         methodMB.set("GET");
         requestURI.recycle();
+        requestURL.recycle();
         queryMB.recycle();
         decodedUriMB.recycle();
         
@@ -298,6 +302,30 @@
         return requestURI;
     }
     
+    public CBuffer requestURL() {
+        CBuffer url = requestURL;
+        url.recycle();
+        
+        String scheme = getScheme();
+        int port = getServerPort();
+        if (port < 0)
+            port = 80; // Work around java.net.URL bug
+
+        url.append(scheme);
+        url.append("://");
+        url.append(getServerName());
+        if ((scheme.equals("http") && (port != 80))
+            || (scheme.equals("https") && (port != 443))) {
+            url.append(':');
+            url.append(port);
+        }
+        // Decoded !!
+        url.append(getRequestURI());
+
+        return (url);
+
+    }
+
     /** 
      * Not decoded - %xx as in original.
      * @return
@@ -427,29 +455,33 @@
         this.localPort = port;
     }
     
-    public void sendHead() throws IOException {
-        httpCh.sendRequestHeaders(httpCh);
+    public HttpResponse waitResponse() throws IOException {
+        return waitResponse(httpCh.ioTimeout);
     }
 
-    /** 
-     * Convert the request to bytes, ready to send.
-     */
-    public void serialize(IOBuffer rawSendBuffers2) throws IOException {
-        rawSendBuffers2.append(method());
-        rawSendBuffers2.append(BBuffer.SP);
-
-        // TODO: encode or use decoded
-        rawSendBuffers2.append(requestURI());
-        if (queryString().length() > 0) {
-            rawSendBuffers2.append("?");
-            rawSendBuffers2.append(queryString());
+    public void send(HttpService headersCallback, long timeout) throws IOException {
+        if (headersCallback != null) {
+            httpCh.setHttpService(headersCallback);
         }
 
-        rawSendBuffers2.append(BBuffer.SP);
-        rawSendBuffers2.append(protocol());
-        rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+        httpCh.send();
+    }
+    
+    public void send(HttpService headersCallback) throws IOException {
+        send(headersCallback, httpCh.ioTimeout);
+    }
+
+    public void send() throws IOException {
+        send(null, httpCh.ioTimeout);
+    }
+    
+    public HttpResponse waitResponse(long timeout) throws IOException {
+        // TODO: close out if post
+        httpCh.send();
         
-        super.serializeHeaders(rawSendBuffers2);
+        httpCh.headersReceivedLock.waitSignal(timeout);
+        
+        return httpCh.getResponse();
     }
 
     /**
@@ -467,10 +499,15 @@
         }
 
         BBuffer valueBC = hostHF.valueB;
+        if (valueBC == null) {
+            valueBC = BBuffer.allocate();
+            hostHF.getValue().toAscii(valueBC);
+        }
         byte[] valueB = valueBC.array();
         int valueL = valueBC.getLength();
         int valueS = valueBC.getStart();
-        int colonPos = -1;
+        
+        int colonPos = valueBC.indexOf(':', 0);
         
         serverNameMB.recycle();
 
@@ -492,10 +529,8 @@
 
         if (colonPos < 0) {
             if (!ssl) {
-                // 80 - Default HTTP port
                 setServerPort(80);
             } else {
-                // 443 - Default HTTPS port
                 setServerPort(443);
             }
         } else {
@@ -823,6 +858,7 @@
 
         // URL decode and normalize
         decodedUri.append(getMsgBytes().url());
+        
         getURLDecoder().urlDecode(decodedUri, false); 
         
         // Need to normalize again - %decoding may decode /
@@ -833,47 +869,8 @@
         }
         decodedURI().set(decodedUri);
 
-        httpCh.processProtocol();
-
         // default response protocol
         httpCh.getResponse().protocol().set(getMsgBytes().protocol());            
-
-        // requested connection:close/keepAlive and proto
-        httpCh.processConnectionHeader(getMimeHeaders());
-
-        httpCh.processExpectation();
-
-        httpCh.receiveBody.processContentDelimitation();
-        // 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
-
-        httpCh.receiveBody.noBody = !httpCh.receiveBody.isContentDelimited();
-
-        httpCh.receiveBody.updateCloseOnEnd();
-
-        /*
-         * 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 (!httpCh.receiveBody.isContentDelimited()) {
-            // No body
-            httpCh.getIn().close();
-        } 
-
-        CBuffer valueMB = getMimeHeaders().getHeader("host");
-        // Check host header
-//        if (httpCh.http11 && (valueMB == null)) {
-//            httpCh.error = true;
-//            // 400 - Bad request
-//            httpCh.getResponse().setStatus(400);
-//        }
     }
 
     
@@ -977,4 +974,13 @@
         return bb;
     }
 
+    public String toString() {
+        IOBuffer out = new IOBuffer();
+        try {
+            Http11Connection.serialize(this, out);
+            return out.readAll(null).toString();
+        } catch (IOException e) {
+            return "Invalid request";
+        }
+    }
 }

Modified: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java?rev=887087&r1=887086&r2=887087&view=diff
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java (original)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java Fri Dec  4 07:16:59 2009
@@ -64,29 +64,6 @@
         return status;
     }
 
-    public void sendHead() throws IOException {
-        httpCh.sendHeaders();
-    }
-
-    /** 
-     * Convert the response to bytes, ready to send.
-     */
-    public void serialize(IOBuffer rawSendBuffers2) throws IOException {
-        
-        rawSendBuffers2.append(protocol()).append(' ');
-        String status = Integer.toString(getStatus());   
-        rawSendBuffers2.append(status).append(' ');
-        if (getMessageBuffer().length() > 0) {
-            rawSendBuffers2.append(getMessage());
-        } else {
-            rawSendBuffers2
-                .append(getMessage(getStatus()));
-        }
-        rawSendBuffers2.append(BBuffer.CRLF_BYTES);
-        // Headers
-        super.serializeHeaders(rawSendBuffers2);
-    }
-
     public HttpRequest getRequest() {
         return getHttpChannel().getRequest();
     }
@@ -96,36 +73,12 @@
         protocol().set(getMsgBytes().protocol());                
         message.set(getMsgBytes().message());
         processMimeHeaders();
-        
-        
         // TODO: if protocol == 1.0 and we requested 1.1, downgrade getHttpChannel().pro
-        int status = 500;
         try {
             status = getStatus();
         } catch (Throwable t) {
             getHttpChannel().log.warning("Invalid status " + getMsgBytes().status() + " " + getMessage());
         }
-        HttpBody body = (HttpBody) getBody();
-        body.noBody = !hasBody();
-
-        // Will parse 'connection:close', set close on end
-        getHttpChannel().processConnectionHeader(getMimeHeaders());
-        
-        body.processContentDelimitation();
-        
-        if (body.statusDropsConnection(status)) {
-            getHttpChannel().closeStreamOnEnd("response status drops connection");
-        }
-        
-        if (body.isDone()) {
-            body.close();
-        }
-
-        if (!body.isContentDelimited()) {
-            getHttpChannel().closeStreamOnEnd("not content delimited");
-        }
-        
-        
     }
 
     /**
@@ -159,7 +112,7 @@
      *  Common messages are cached.
      *
      */
-    private BBucket getMessage( int status ) {
+    BBucket getMessage( int status ) {
         // method from Response.
 
         // Does HTTP requires/allow international messages or
@@ -174,10 +127,15 @@
         case 404:
             return st_404;
         }
-        return stats.get(status);
+        BBucket bb = stats.get(status);
+        if (bb == null) {
+            return st_unknown;
+        }
+        return bb;
     }
     
     
+    static BBucket st_unknown = BBuffer.wrapper("No Message");
     static BBucket st_200 = BBuffer.wrapper("OK");
     static BBucket st_302= BBuffer.wrapper("Moved Temporarily");
     static BBucket st_400= BBuffer.wrapper("Bad Request");

Added: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java?rev=887087&view=auto
==============================================================================
--- tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java (added)
+++ tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java Fri Dec  4 07:16:59 2009
@@ -0,0 +1,548 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.http.HttpConnector.RemoteServer;
+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.IOBuffer;
+
+/*
+ * TODO: expectations ? 
+ * Fix docs - order matters
+ * Crashes in chrome
+ */
+
+public class SpdyConnection extends HttpConnector.HttpConnection  {
+    
+    public static class SpdyConnectionManager 
+        extends HttpConnector.HttpConnectionManager {
+        @Override
+        public HttpConnection newConnection(HttpConnector con) {
+            return new SpdyConnection(con);
+        }
+
+        @Override
+        public HttpConnection getFromPool(RemoteServer t) {
+            // TODO: we may initiate multiple SPDY connections with each server
+            // Sending frames is synchronized, receiving is muxed
+            return t.connections.get(0);
+        }
+        
+    }
+    
+
+    protected static Logger log = Logger.getLogger("SpdyConnection");
+    
+    /**
+     * @param spdyConnector
+     */
+    SpdyConnection(HttpConnector spdyConnector) {
+        this.httpConnector = spdyConnector;
+    }
+
+    AtomicInteger lastInStream = new AtomicInteger();
+    AtomicInteger lastOutStream = new AtomicInteger();
+
+    // TODO: use int map
+    Map<Integer, HttpChannel> channels = new HashMap();
+
+    SpdyConnection.Frame currentInFrame = null;
+
+    SpdyConnection.Frame lastFrame = null; // for debug
+
+    BBuffer outFrameBuffer = BBuffer.allocate();
+    BBuffer inFrameBuffer = BBuffer.allocate();
+
+    BBuffer headW = BBuffer.wrapper();
+    
+    // TODO: detect if it's spdy or http based on bit 8
+
+    @Override
+    public void withExtraBuffer(BBuffer received) {
+        inFrameBuffer = received;
+    }
+    
+    @Override
+    public void dataReceived(IOBuffer iob) throws IOException {
+        int avail = iob.available();
+        while (avail > 0) {
+            if (currentInFrame == null) {
+                if (inFrameBuffer.remaining() + avail < 8) {
+                    return;
+                }
+                if (inFrameBuffer.remaining() < 8) {
+                    int headRest = 8 - inFrameBuffer.remaining();
+                    int rd = iob.read(inFrameBuffer, headRest);
+                    avail -= rd;
+                }
+                currentInFrame = new SpdyConnection.Frame(); // TODO: reuse
+                currentInFrame.parse(inFrameBuffer);
+            }
+            if (avail < currentInFrame.length) {
+                return;
+            }
+            // We have a full frame. Process it.
+            onFrame(iob);
+
+            // TODO: extra checks, make sure the frame is correct and
+            // it consumed all data.
+            avail -= currentInFrame.length;
+            currentInFrame = null;
+        }
+    }
+
+    /**
+     * Frame received. Must consume all data for the frame.
+     * 
+     * @param iob
+     * @throws IOException
+     */
+    protected void onFrame(IOBuffer iob) throws IOException {
+        // TODO: make sure we have enough data.
+        lastFrame = currentInFrame;
+        
+        if (currentInFrame.c) {
+            if (currentInFrame.type == SpdyConnection.Frame.TYPE_HELO) {
+                // receivedHello = currentInFrame;
+            } else if (currentInFrame.type == SpdyConnection.Frame.TYPE_SYN_STREAM) {
+                HttpChannel ch = new HttpChannel(); // TODO: reuse
+                ch.channelId = SpdyConnection.readInt(iob);
+                ch.setConnection(this);
+                ch.httpConnector = this.httpConnector;
+                if (serverMode) {
+                    ch.serverMode(true);
+                }
+                if (this.httpConnector.defaultService != null) {
+                    ch.setHttpService(this.httpConnector.defaultService);
+                }
+
+                channels.put(ch.channelId, ch);
+
+                // pri and unused
+                SpdyConnection.readShort(iob);
+
+                HttpMessageBytes reqBytes = ch.getRequest().getMsgBytes();
+                
+                BBuffer head = processHeaders(iob, ch, reqBytes);
+
+                ch.getRequest().processReceivedHeaders();
+
+                ch.handleHeadersReceived(ch.getRequest());
+
+                if ((currentInFrame.flags & SpdyConnection.Frame.FLAG_HALF_CLOSE) != 0) {
+                    ch.getIn().close();
+                    ch.handleEndReceive();
+                }
+            } else if (currentInFrame.type == SpdyConnection.Frame.TYPE_SYN_REPLY) {
+                int chId = SpdyConnection.readInt(iob);
+                HttpChannel ch = channels.get(chId);
+                
+                SpdyConnection.readShort(iob);
+        
+                HttpMessageBytes resBytes = ch.getResponse().getMsgBytes();
+                
+                BBuffer head = processHeaders(iob, ch, resBytes);
+
+                ch.getResponse().processReceivedHeaders();
+
+                ch.handleHeadersReceived(ch.getResponse());
+
+                if ((currentInFrame.flags & SpdyConnection.Frame.FLAG_HALF_CLOSE) != 0) {
+                    ch.getIn().close();
+                    ch.handleEndReceive();
+                }
+            } else {
+                log.warning("Unknown frame type " + currentInFrame.type);
+                iob.advance(currentInFrame.length);
+            }
+        } else {
+            // data frame - part of an existing stream
+            HttpChannel ch = channels.get(currentInFrame.streamId);
+            if (ch == null) {
+                log.warning("Unknown stream ");
+                net.close();
+                net.startSending();
+                return;
+            }
+            int len = currentInFrame.length;
+            while (len > 0) {
+                BBucket bb = iob.peekFirst();
+                if (len > bb.remaining()) {
+                    ch.getIn().append(bb);
+                    len += bb.remaining();
+                    bb.position(bb.limit());
+                } else {
+                    ch.getIn().append(bb, len);
+                    bb.position(bb.position() + len);
+                    len = 0;
+                }
+            }
+            ch.sendHandleReceivedCallback();
+            
+            if ((currentInFrame.flags & SpdyConnection.Frame.FLAG_HALF_CLOSE) != 0) {
+                ch.getIn().close();
+                ch.handleEndReceive();
+            }
+        }
+    }
+
+    private BBuffer processHeaders(IOBuffer iob, HttpChannel ch,
+            HttpMessageBytes reqBytes) throws IOException {
+        int nvCount = SpdyConnection.readShort(iob);
+        int read = 8;
+
+        iob.read(headRecvBuf, currentInFrame.length - 8);
+
+        // Wrapper - so we don't change position in head
+        headRecvBuf.wrapTo(headW);
+        
+        BBuffer nameBuf = BBuffer.wrapper();
+        BBuffer valBuf = BBuffer.wrapper();
+
+        for (int i = 0; i < nvCount; i++) {
+
+            int nameLen = SpdyConnection.readShort(headW);
+
+            nameBuf
+                    .setBytes(headW.array(), headW.position(),
+                            nameLen);
+            headW.advance(nameLen);
+
+            int valueLen = SpdyConnection.readShort(headW);
+            valBuf
+                    .setBytes(headW.array(), headW.position(),
+                            valueLen);
+            headW.advance(valueLen);
+
+            // TODO: no need to send version, method if default
+
+            if (nameBuf.equals("method")) {
+                valBuf.wrapTo(reqBytes.method());
+            } else if (nameBuf.equals("version")) {
+                valBuf.wrapTo(reqBytes.protocol());
+            } else if (nameBuf.equals("url")) {
+                valBuf.wrapTo(reqBytes.url());
+                // TODO: spdy uses full URL, we may want to trim
+                // also no host header
+            } else {
+                int idx = reqBytes.addHeader();
+                nameBuf.wrapTo(reqBytes.getHeaderName(idx));
+                valBuf.wrapTo(reqBytes.getHeaderValue(idx));
+            }
+
+            // TODO: repeated values are separated by a 0
+            // pretty weird...
+            read += nameLen + valueLen + 4;
+        }
+        return headW;
+    }
+
+    @Override
+    protected void sendRequest(HttpChannel http) throws IOException {
+        if (serverMode) {
+            throw new IOException("Only in client mode");
+        }
+
+        MultiMap mimeHeaders = http.getRequest().getMimeHeaders();
+        BBuffer headBuf = BBuffer.allocate();
+        
+        SpdyConnection.appendShort(headBuf, mimeHeaders.size() + 3);
+        
+        serializeMime(mimeHeaders, headBuf);
+
+        // TODO: url - with host prefix , method
+        // optimize...
+        SpdyConnection.appendAsciiHead(headBuf, "version");
+        SpdyConnection.appendAsciiHead(headBuf, "HTTP/1.1");
+        
+        SpdyConnection.appendAsciiHead(headBuf, "method");
+        SpdyConnection.appendAsciiHead(headBuf, http.getRequest().getMethod());
+        
+        SpdyConnection.appendAsciiHead(headBuf, "url");
+        // TODO: url
+        SpdyConnection.appendAsciiHead(headBuf, http.getRequest().requestURL());
+        
+        
+        BBuffer out = BBuffer.allocate();
+        // Syn-reply 
+        out.putByte(0x80); 
+        out.putByte(0x01); 
+        out.putByte(0x00); 
+        out.putByte(0x01);
+        
+        if (http.getOut().isAppendClosed()) {
+            out.putByte(0x01); // closed
+        } else {
+            out.putByte(0x00); 
+        }
+        SpdyConnection.append24(out, headBuf.remaining() + http.getOut().available() + 4);
+        
+        if (serverMode) {
+            http.channelId = 2 * lastOutStream.incrementAndGet();
+        } else {
+            http.channelId = 2 * lastOutStream.incrementAndGet() + 1;            
+        }
+        SpdyConnection.appendInt(out, http.channelId);
+        
+        channels.put(http.channelId, http);
+        
+        out.putByte(0x00); // no priority 
+        out.putByte(0x00); 
+        
+        sendFrame(out, headBuf); 
+
+        // Any existing data
+        sendData(http);
+    }
+    
+    @Override
+    protected void sendResponseHeaders(HttpChannel http) throws IOException {
+        if (!serverMode) {
+            throw new IOException("Only in server mode");
+        }
+
+        if (http.getResponse().isCommitted()) {
+            return; 
+        }
+        http.getResponse().setCommitted(true);
+
+        MultiMap mimeHeaders = http.getResponse().getMimeHeaders();
+
+        BBuffer headBuf = BBuffer.allocate();
+
+        SpdyConnection.appendInt(headBuf, http.channelId);
+        headBuf.putByte(0);
+        headBuf.putByte(0);
+
+        //mimeHeaders.remove("content-length");
+        
+        SpdyConnection.appendShort(headBuf, mimeHeaders.size() + 2);
+        
+        // chrome will crash if we don't send the header
+        serializeMime(mimeHeaders, headBuf);
+
+        // Must be at the end
+        SpdyConnection.appendAsciiHead(headBuf, "status");
+        SpdyConnection.appendAsciiHead(headBuf, 
+                Integer.toString(http.getResponse().getStatus()));
+
+        SpdyConnection.appendAsciiHead(headBuf, "version");
+        SpdyConnection.appendAsciiHead(headBuf, "HTTP/1.1");
+
+        
+        BBuffer out = BBuffer.allocate();
+        // Syn-reply 
+        out.putByte(0x80); // Control
+        out.putByte(0x01); // version
+        out.putByte(0x00); // 00 02 - SYN_REPLY
+        out.putByte(0x02);
+        
+        // It seems piggibacking data is not allowed
+        out.putByte(0x00); 
+
+        SpdyConnection.append24(out, headBuf.remaining());
+        
+        sendFrame(out, headBuf);
+    }
+    
+    
+    public void startSending(HttpChannel http) throws IOException {
+        http.send(); // if needed
+        
+        if (net != null) {
+            sendData(http);
+            net.startSending();
+        }
+    }
+    
+    private void sendData(HttpChannel http) throws IOException {
+        int avail = http.getOut().available();
+        boolean closed = http.getOut().isAppendClosed();
+        if (avail > 0 || closed) {
+            sendDataFrame(http.getOut(), avail,
+                    http.channelId, closed);
+            if (avail > 0) {
+                getOut().advance(avail);
+            }
+        }
+        if (closed) {
+            http.handleEndSent();
+        }
+    }
+
+    private BBuffer serializeMime(MultiMap mimeHeaders, BBuffer headBuf) 
+            throws IOException {
+
+        // TODO: duplicated headers not allowed
+        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;
+            }
+            SpdyConnection.appendShort(headBuf, name.length());
+            name.toAscii(headBuf);
+            SpdyConnection.appendShort(headBuf, value.length());
+            value.toAscii(headBuf);
+        }
+        return headBuf;
+    }
+
+
+    private synchronized void sendFrame(BBuffer out, BBuffer headBuf)
+            throws IOException {
+        if (net == null) {
+            return; // unit test
+        }
+        net.getOut().append(out);
+        if (headBuf != null) {
+            net.getOut().append(headBuf);
+        }
+        net.startSending();
+    }
+
+    public synchronized void sendDataFrame(IOBuffer out2, int avail,
+            int channelId, boolean last) throws IOException {
+        if (net == null) {
+            return; // unit test
+        }
+        outFrameBuffer.recycle();
+        SpdyConnection.appendInt(outFrameBuffer, channelId); // first bit 0 ?
+        if (last) {
+            outFrameBuffer.putByte(0x01); // closed
+        } else {
+            outFrameBuffer.putByte(0x00);
+        }
+
+        // TODO: chunk if too much data ( at least at 24 bits)
+        SpdyConnection.append24(outFrameBuffer, avail);
+
+        net.getOut().append(outFrameBuffer);
+        if (avail > 0) {
+            net.getOut().append(out2, avail);
+        }
+        net.startSending();
+    }
+
+    static void appendInt(BBuffer headBuf, int length) throws IOException {
+        headBuf.putByte((length & 0xFF000000) >> 24);
+        headBuf.putByte((length & 0xFF0000) >> 16);
+        headBuf.putByte((length & 0xFF00) >> 8);
+        headBuf.putByte((length & 0xFF));
+    }
+
+    static void append24(BBuffer headBuf, int length) throws IOException {
+        headBuf.putByte((length & 0xFF0000) >> 16);
+        headBuf.putByte((length & 0xFF00) >> 8);
+        headBuf.putByte((length & 0xFF));
+    }
+
+    static void appendAsciiHead(BBuffer headBuf, CBuffer s) throws IOException {
+        appendShort(headBuf, s.length());
+        for (int i = 0; i < s.length(); i++) {
+            headBuf.append(s.charAt(i));
+        }
+    }
+
+    static void appendShort(BBuffer headBuf, int length) throws IOException {
+        if (length > 0xFFFF) {
+            throw new IOException("Too long");
+        }
+        headBuf.putByte((length & 0xFF00) >> 8);
+        headBuf.putByte((length & 0xFF));
+    }
+
+    static void appendAsciiHead(BBuffer headBuf, String s) throws IOException {
+        SpdyConnection.appendShort(headBuf, s.length());
+        for (int i = 0; i < s.length(); i++) {
+            headBuf.append(s.charAt(i));
+        }
+    }
+
+    static int readShort(BBuffer iob) throws IOException {
+        int res = iob.readByte();
+        return res << 8 | iob.readByte();
+    }
+
+    static int readShort(IOBuffer iob) throws IOException {
+        int res = iob.read();
+        return res << 8 | iob.read();
+    }
+
+    static int readInt(IOBuffer iob) throws IOException {
+        int res = 0;
+        for (int i = 0; i < 4; i++) {
+            int b0 = iob.read();
+            res = res << 8 | b0;
+        }
+        return res;
+    }
+
+    public static class Frame {
+        int flags;
+    
+        int length;
+    
+        boolean c; // for control
+    
+        int version;
+    
+        int type;
+    
+        int streamId; // for data
+    
+        static int TYPE_HELO = 4;
+    
+        static int TYPE_SYN_STREAM = 1;
+
+        static int TYPE_SYN_REPLY = 2;
+    
+        static int FLAG_HALF_CLOSE = 1;
+    
+        public void parse(BBuffer iob) throws IOException {
+            int b0 = iob.read();
+            if (b0 < 128) {
+                c = false;
+                streamId = b0;
+                for (int i = 0; i < 3; i++) {
+                    b0 = iob.read();
+                    streamId = streamId << 8 | b0;
+                }
+            } else {
+                c = true;
+                b0 -= 128;
+                version = ((b0 << 8) | iob.read());
+                b0 = iob.read();
+                type = ((b0 << 8) | iob.read());
+            }
+    
+            flags = iob.read();
+            for (int i = 0; i < 3; i++) {
+                b0 = iob.read();
+                length = length << 8 | b0;
+            }
+
+            iob.recycle();
+        }
+    
+    }
+    
+    /** 
+     * Framing error, client interrupt, etc.
+     */
+    public void abort(HttpChannel http, String t) throws IOException {
+        // TODO: send interrupt signal
+    }
+
+
+}
\ No newline at end of file

Propchange: tomcat/trunk/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Mime
View raw message