tomcat-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cos...@apache.org
Subject svn commit: r534293 [5/11] - in /tomcat/sandbox/tomcat-lite: ./ bin/ external/ java/ java/org/apache/commons/logging/ java/org/apache/tomcat/lite/ java/org/apache/tomcat/lite/ctxmap/ java/org/apache/tomcat/lite/http/ java/org/apache/tomcat/lite/http11/...
Date Wed, 02 May 2007 02:22:50 GMT
Added: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11Connection.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11Connection.java?view=auto&rev=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11Connection.java (added)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11Connection.java Tue May  1 19:22:45 2007
@@ -0,0 +1,813 @@
+/*
+ */
+package org.apache.tomcat.lite.http11;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SocketChannel;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tomcat.lite.TomcatLite;
+import org.apache.tomcat.util.buf.BBuffer;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.FastHttpDateFormat;
+import org.apache.tomcat.util.http.MimeHeaders;
+
+/** 
+ * 
+ * Buffers used ( to be reduced ):
+ *  - 2 header buffers ( one for req, one for response )
+ *  - char[] buffers for headers ( also in MessageBytes, etc )
+ *  - one input buffer, one output buffer
+ * 
+ * TODO: 
+ *  - use direct ByteBuffers 
+ *  - better deal with chunking - leave pre/post space in the buffer
+ *  - head buffer can be thrown away after submitting headers - maybe share 
+ *  with the first out buffer
+ * 
+ * Uses NIO to implment selection.
+ *  In addition to sockets, you can add other kind of objects.
+ *  
+ *  Unlike most NIO examples, this uses multiple threads, each blocking 
+ *  in Selector.select(). It seems to be working - if there are problems
+ *  on some OS/JVMs, we can force one poller thread and use the TP in a
+ *  classical NIO model.
+ *  
+ * @author Costin Manolache
+ */
+
+/** 
+ * One connection (socket/in-process). Connections are registered with the
+ * IO thread(s) 
+ * 
+ * - when input is received buffers are assigned. Keep-alive sockets don't 
+ * have buffers until the first bytes of the next request are received.
+ * 
+ * - when a request is completely received in the buffer, and the request 
+ * makes it to the thread pool - per/thread ServletRequest/ServletResponse
+ * objects are assigned.
+ * 
+ *   
+ * 
+ */
+public class Http11Connection implements Runnable, 
+  ReadableByteChannel
+{
+    private static final byte[] ACK_BYTES = {
+        'H', 'T', 'T', 'P', '/', '1', '.', '1', ' ', '1', '0', '0',
+        ' ', 'C', 'o', 'n', 't', 'i', 'n', 'u', 'e', '\r', '\n',
+        '\r', '\n' };
+    
+    private static final String CONNECTION = "Connection";
+    public static final String KEEPALIVE = "keep-alive";
+    
+    public static final String CLOSE = "close";
+    public static final String TRANSFERENCODING = "Transfer-Encoding";
+    
+    public static final String CHUNKED = "chunked";
+
+    static byte[] END_CHUNK_BYTES = {(byte) '0', (byte) '\r', (byte) '\n', 
+        (byte) '\r', (byte) '\n'};
+
+    private static final byte[] HTTP_11 = 
+        {'H','T','T','P','/','1','.','1'};
+    
+    
+    // A poller thread can process one request at the time, or some IO
+    // 
+    //Request cReq = new Request();
+    //Response http11ResBuffer = new Response();
+
+    //ServletRequestImpl req = new ServletRequestImpl();
+    //ServletResponseImpl res = new ServletResponseImpl();
+
+    protected static Log log =
+        LogFactory.getLog(Http11Connection.class.getName());
+
+    static boolean debugIn = false;
+    static boolean debugOut = false;
+
+    // -------------------------
+    
+    public Http11Buffer reqB = new Http11Buffer();
+    
+    public Http11Buffer resB = new Http11Buffer();
+    
+    
+    // Cached values
+    protected String remoteAddr = null;
+    protected String remoteHost = null;
+    protected String localName = null;
+    protected int localPort = -1;
+    protected int remotePort = -1;
+    protected String localAddr = null;
+    
+    
+    /**
+     * Buffer used for chunk length conversion.
+     */
+    protected byte[] chunkLength = new byte[10];
+
+    TomcatLite facade;
+    NioServlet ioServlet;
+
+    int state;
+    
+    SocketChannel mySocket;
+    Socket s;
+    InputStream is;
+    OutputStream os;
+
+
+    // TODO: move all keepalive logic to filter
+    boolean keepAlive = true;
+    boolean closed = false;
+    boolean error = false;
+
+    boolean expectation = false;
+    boolean swallowInput = false;
+
+    // Controls for output
+    boolean chunkedOutput = false;
+    
+    boolean voidOutput = false;
+    
+    boolean contentDelimitation = false;
+
+    boolean http09 = false;
+    
+    boolean http11 = true;
+    
+    boolean committed = false;
+    
+        
+//    ByteBuffer dst = ByteBuffer.allocateDirect(32 * 1024);
+
+    
+    
+    
+    
+    
+    private int serverPort = -1;
+    
+    private MessageBytes serverNameMB = MessageBytes.newInstance();
+    /** 
+     * Each accepted socket is associated with a Http11Worker object. 
+     * After the connection is closed, the worker will get another socket.
+     * 
+     * Not all Http11Worker objects have a thread, we keep a number ( between
+     * min and max ) of threads listening for select() events. 
+     * 
+     * When a select event is received, the worker associated with the socket
+     * will process the request in the current thread.
+     * @param lite 
+     * 
+     * @param accepted  an already accepted and parsed socket - will 
+     *   service the request ( in blocking mode ), then maybe keep waiting 
+     *   for more activity.
+     *   if null: just a backup thread, waiting for events.
+     */
+    public Http11Connection(TomcatLite lite, SocketChannel accepted,
+                            NioServlet nio) {
+        mySocket = accepted;
+        this.ioServlet = nio;
+        this.facade = lite;
+        
+        // All the magic for coyote, lite req/res objects
+        //cRes.setHook(this);
+        
+        //cReq.setResponse(cRes);
+        //req.setCoyoteRequest(cReq);
+        //res.setRequest(req);
+        //req.setResponse(res);
+        //res.setCoyoteResponse(cRes);
+        
+        initFilters();
+        
+    }
+    
+    public void clientFlush() {
+        forceFlushResponse();
+    }
+    
+    public void finish() {
+        // Close
+
+        // End the processing of the current request, and stop any further
+        // transactions with the client
+
+        try {
+            if (!committed) {
+                commitResponse();
+            }
+            os.flush();
+        } catch (IOException e) {
+            // Set error flag
+            error = true;
+        }
+    }
+    
+    public String getLocalAddr() {
+        if (localAddr == null) {
+            localAddr = s.getLocalAddress().getHostAddress();
+        }
+        return localAddr;
+    }
+
+    public String getLocalName() {
+        if ((localName == null) && (s != null)) {
+            InetAddress inetAddr = s.getLocalAddress();
+            if (inetAddr != null) {
+                localName = inetAddr.getHostName();
+            }
+        }
+        return localName;
+    }
+    
+    public int getLocalPort() {
+        if ((localPort == -1 ) && (s !=null)) {
+            localPort = s.getLocalPort();
+        }
+        return localPort;
+    }
+    
+    public String getRemoteAddr() {
+        if ((remoteAddr == null) && (s != null)) {
+            InetAddress inetAddr = s.getInetAddress();
+            if (inetAddr != null) {
+                if (inetAddr.isLoopbackAddress()) {
+                    // even if it's IPv6
+                    remoteAddr = "127.0.0.1";
+                } else {
+                    remoteAddr = inetAddr.getHostAddress();
+                }
+            }
+        }
+        return remoteAddr;
+    }
+    
+    public String getRemoteHost() {
+        if ((remoteHost == null) && (s != null)) {
+            InetAddress inetAddr = s.getInetAddress();
+            if (inetAddr != null) {
+                remoteHost = inetAddr.getHostName();
+            }
+            if(remoteHost == null) {
+                remoteHost = getRemoteAddr();
+            }
+        }
+        return remoteHost;
+    }
+    
+    public int getRemotePort() {
+        if ((remotePort == -1 ) && (s !=null)) {
+            remotePort = s.getPort();
+        }
+        return remotePort;
+    }
+    
+    public int getServerPort() {
+        return serverPort;
+    }
+
+    public int getState() { 
+        return state;
+    }
+    
+    public boolean isClosed() {
+        return closed;
+    }
+    
+    
+    /**
+     *  Attempt to parse the request line. 
+     *  Reentrant, non-blocking called from IOThread.
+     *  
+     *  If the request is parsed - the s will be set as 
+     *  blocking.
+     * 
+     * @return true if the header has been parsed, false if it need
+     * more input.
+     */
+    public boolean parseRequestHead() throws IOException {
+        // Parse request 
+        try {
+            if (reqB.lastValid == 0) {
+                if (fillBuffer() <= 0) return false;
+            }
+            if (state == 0) {
+                while( ! reqB.parseRequestLine()) {
+                    if (fillBuffer() <= 0) return false;
+                }
+                state = 1;
+            }
+            if (state == 1) {
+                while( ! reqB.parseHeaders()) {
+                    if (fillBuffer() <= 0) return false;
+                }
+                state = 2; // we have a request !!
+            }
+            
+            // Blocking mode while in servlet 
+            // TODO: non blocking IO
+            ioServlet.cancel(mySocket);
+        } catch(ClosedChannelException ex) {
+            log.info( " Closed channel " + this);
+            // clean up ? 
+            keepAlive = false;
+        }
+        return true;
+    }
+    
+    public boolean processOneRequest(Socket s) {
+        // Process the connection
+        try {
+            if (reqB.method().toString() == null) { 
+                log.error( " Empty request " + reqB);
+                return false;
+            }
+            
+            //log.debug( " Req: " + http11Buffer);
+            prepareRequest();
+            
+            reqB.messageWriter.setConnection(this);
+            
+            try {
+                facade.service(this);
+            } catch( Throwable t ) {
+                log.error( " ERROR IN ADAPTER SHOULD BE HANDLED IN TOMCAT", t);
+            }
+            
+            reqB.messageWriter.doFlush(true, true);
+
+            endResponse();
+            
+            //req.recycle();
+            //res.recycle();
+            // Final processing
+            //MessageWriter.getWriter(cReq, cRes, 0).flush();
+            //cRes.finish();
+            reqB.recycle();
+            //cRes.recycle();
+            
+            // this.recycle():
+            committed = false;
+            resB.recycle();
+            
+            reqB.nextRequest();
+
+            return keepAlive;
+        } catch (SocketException se) {
+            log.error("Socket error " + s.getInetAddress(), se);
+        } catch (IOException t) {
+            log.error( " IOException " + t);
+        } catch (Throwable t) {    
+            log.error( " Unexpected error", t);
+        } finally {
+        }
+        return false;
+    }
+
+    /** Called when a request is available, or on new READ on an async servlet.
+     */
+    public void processRequest() {
+        if (state == 2) {
+            // new request
+            keepAlive = true;
+            try {
+                s = mySocket.socket();
+                is = s.getInputStream();
+                os = s.getOutputStream();
+                while (keepAlive) {
+                    keepAlive = processOneRequest(s);
+                    if (closed) {
+                        ioServlet.done(this);
+                        return;
+                    }
+                    state = 0; // waiting for the next request.
+
+                    mySocket.configureBlocking(false);
+                    
+                    // Maybe we have it ? 
+                    if (keepAlive && parseRequestHead()) {
+                        // yes, it's already there
+                        if (debugIn)
+                            log.debug("Another request is in buffer " + mySocket);
+                        continue;
+                    } else if (keepAlive){
+                        // need more input.
+                        
+                        ioServlet.needData(this, mySocket);
+                        return;
+                    }
+                    // No more keepalive
+                }
+            } catch(IOException ex) {
+                if (debugIn)
+                log.debug("IO Exception, no more keepalive for you");
+                keepAlive = false;
+            } 
+        } else {
+            if (debugIn)
+                log.debug( " processRequest " + state + " " + mySocket);
+        }
+        
+        // Request done, close socket
+        reqB.recycle();
+        
+        try {
+            if (s != null) {
+                s.shutdownOutput();
+                s.close();
+            }
+        } catch (IOException ex) {
+            log.error( " Error closing socket " + ex);
+        }
+        ioServlet.done(this);
+    }
+
+    public void realWriteBytes(byte[] buf, int off, int cnt) throws IOException {
+        if (!committed) {
+            commitResponse();
+        }
+        if (debugOut)
+            log.debug( " write " + buf + "  off=" + off + " l=" + cnt + 
+                    " chunk=" + chunkedOutput + " void=" + voidOutput);
+        if (voidOutput) 
+            return;
+        if (chunkedOutput) {
+            writeChunked(buf, off, cnt);
+        } else {
+            os.write(buf, off, cnt);
+        }
+    }
+    
+    // Not used right now - new connection objects created
+    public void recycle() {
+        remoteAddr = null;
+        remoteHost = null;
+        localName = null;
+
+        localPort = -1;
+        remotePort = -1;
+    }
+
+    public void reset() {
+        // Reset response
+
+        // Note: This must be called before the response is committed
+
+        if (committed) {
+            throw new IllegalStateException();
+        }
+        //outputBuffer.reset();
+        // clean any buffers
+        
+        resB.recycle();
+    }
+
+    public void run() {
+        processRequest();
+    }
+    
+    public void sendAck() {
+        // Acknowlege request
+
+        // Send a 100 status back if it makes sense (response not committed
+        // yet, and client specified an expectation for 100-continue)
+
+        if ((resB.isCommitted()) || !expectation)
+            return;
+
+        swallowInput = true;
+        try {
+            if (!committed)
+                os.write(ACK_BYTES);
+        } catch (IOException e) {
+            // Set error flag
+            error = true;
+        }
+    }
+    
+    /** Signal that we're done with the headers, and body will follow.
+     *  Any implementation needs to notify ContextManager, to allow
+     *  interceptors to fix headers.
+     */
+    public void sendHeaders() throws IOException {
+        commitResponse();
+        resB.setCommitted(true);
+    }
+    
+    /** 
+     * Return the buffer holding the server name, if
+     * any. Use isNull() to check if there is no value
+     * set.
+     * This is the "virtual host", derived from the
+     * Host: header.
+     */
+    public MessageBytes serverName() {
+        return serverNameMB;
+    }
+    
+    public void setLocalPort(int port){
+        this.localPort = port;
+    }
+    
+    public void setOutputStream(OutputStream os) {
+        this.os = os;
+    }
+    public void setRemotePort(int port){
+        this.remotePort = port;
+    }
+    public void setServerPort(int serverPort ) {
+        this.serverPort=serverPort;
+    }
+
+    public String toString() {
+        if ( state  == 1 || state == 2 ) {
+            return "HTTP: " + state + " " + reqB.requestURI();
+        }
+        return "HTTP: " + state;
+    }
+    public void writeChunked(byte data[], int start, int len) 
+            throws IOException {
+        int pos = 7;
+        int current = len;
+        while (current > 0) {
+            int digit = current % 16;
+            current = current / 16;
+            chunkLength[pos--] = HexUtils.HEX[digit];
+        }
+        // TODO: use writev or leave space in the ByteBuffer before and after
+        os.write(chunkLength, pos+1, 9-pos);
+
+        os.write(data, start, len);
+
+        os.write(chunkLength, 8, 2);
+    }
+
+    
+    protected void prepareRequest() {
+        keepAlive = false;
+        chunkedOutput = false;
+        voidOutput = false;
+        
+        if (reqB.protocol().equals(HTTP_11)) {
+            keepAlive = true;
+        }
+        
+        String conHdr = reqB.getMimeHeaders().getHeader(CONNECTION);
+        if (conHdr != null) {
+            //log.debug( " Close connection requested " + conHdr + " " + 
+            //          http11Buffer);
+            if (CLOSE.equals(conHdr)) {
+                keepAlive = false;
+            }
+            if (KEEPALIVE.equals(conHdr)) {
+                // on http/1.0 - it must be explicitely requested
+                keepAlive = true;
+            }
+        }
+        String kaHdr = reqB.getMimeHeaders().getHeader(KEEPALIVE);
+        if (kaHdr != null) {
+            
+        }
+        
+    }
+
+    /**
+     * When committing the response, we have to validate the set of headers,
+     *  as well as setup the response filters.
+     */
+    protected void prepareResponse() {
+        boolean entityBody = true;
+        contentDelimitation = false;
+
+        // TODO: identity filter: enforce content-length
+        
+        if (http09 == true) {
+            return;
+        }
+
+        int statusCode = resB.getStatus();
+        if (statusCode == 0) {
+            statusCode = 200;
+        }
+            
+        if ((statusCode == 204) || (statusCode == 205)
+                || (statusCode == 304)) {
+            // No entity body
+            voidOutput = true;
+            entityBody = false;
+            contentDelimitation = true;
+        }
+
+        MessageBytes methodMB = reqB.method();
+        if (methodMB.equals("HEAD")) {
+            voidOutput = true;
+            contentDelimitation = true;
+        }
+
+        MimeHeaders headers = resB.getMimeHeaders();
+        if (!entityBody) {
+            resB.setContentLength(-1);
+        } else {
+            // Have content body. 
+            // TODO: just put them in the buffer, no need to copy around
+            String contentType = resB.getContentType();
+            if (contentType != null) {
+                headers.setValue("Content-Type").setString(contentType);
+            }
+            String contentLanguage = resB.getContentLanguage();
+            if (contentLanguage != null) {
+                headers.setValue("Content-Language").setString(contentLanguage);
+            }
+        }
+
+        // TODO: http/1.0 with keep alive doesn't support chunked !
+        long contentLength = resB.getContentLengthLong();
+        if (contentLength != -1) {
+            headers.setValue("Content-Length").setLong(contentLength);
+            contentDelimitation = true;
+        } else {
+            if (entityBody && http11 && keepAlive) {
+                // Do chunked encoding
+                chunkedOutput = true;
+                contentDelimitation = true;
+                headers.addValue(TRANSFERENCODING).setString(CHUNKED);
+            }
+        }
+
+        // Add date header
+        // TODO: to buffer directly
+        String date = null;
+        date = FastHttpDateFormat.getCurrentDate();
+        headers.setValue("Date").setString(date);
+
+        // FIXME: Add transfer encoding header
+
+        if ((entityBody) && (!contentDelimitation)) {
+            // Mark as close the connection after the request, and add the
+            // connection: close header
+            keepAlive = false;
+        }
+
+        // If we know that the request is bad this early, add the
+        // Connection: close header.
+        keepAlive = keepAlive && !statusDropsConnection(statusCode);
+        if (!keepAlive) {
+            headers.addValue(CONNECTION).setString(CLOSE);
+        } else if (!http11 && !error) {
+            // http10 or error 
+            headers.addValue(CONNECTION).setString(KEEPALIVE);
+        }
+
+        // Build the response header
+        resB.sendStatus(resB.getStatus(), resB.getMessage());
+
+        int size = headers.size();
+        for (int i = 0; i < size; i++) {
+            resB.sendHeader(headers.getName(i), headers.getValue(i));
+        }
+        resB.endHeaders();
+    }
+
+    /**
+     * Keep alive: 
+     * Determine if we must drop the connection because of the HTTP status
+     * code.  Use the same list of codes as Apache/httpd.
+     */
+    protected 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 */;
+    }
+
+    //The connector should
+    // validate the headers, send them (using sendHeader) and 
+    // set the filters accordingly.
+    void commitResponse() {
+        if (debugOut) 
+            log.debug( " commitResponse()");
+        if (resB.isCommitted())
+            return;
+        committed = true;
+        // Validate and write response headers
+        prepareResponse();
+        try {
+            // TODO: additional caching
+            if (os != null) {
+                if (debugOut) {
+                    log.debug("Write " + resB.pos);
+                }
+                os.write(resB.buf, 0, resB.pos);
+            }
+        } catch (IOException e) {
+            // Set error flag
+            error = true;
+        }
+    }
+    
+    
+    void endResponse() throws IOException {
+        if (debugOut) {
+            log.debug( " End response ");
+        }
+        
+        if (chunkedOutput) {
+            os.write(END_CHUNK_BYTES, 0, END_CHUNK_BYTES.length);
+        }
+    }
+
+    void forceFlushResponse() {
+        try {
+            if (debugOut)
+                log.debug( " Force flush");
+            if (!committed) {
+                commitResponse();
+            }
+            //outputBuffer.flush();
+            // TODO: force any flush to the stream
+            os.flush();
+        } catch (IOException e) {
+            // Set error flag
+            error = true;
+            resB.setErrorException(e);
+        }            
+    }
+    
+    
+    private int fillBuffer() throws IOException {
+        // TODO: use the buffer in parser
+        ByteBuffer dst = ByteBuffer.wrap(reqB.buf, reqB.lastValid, 
+                reqB.getFreeSpace());
+        int rd = mySocket.read(dst);
+        if (debugIn)
+            log.debug( " ReadL: " + rd + " state:" + state);
+        if (rd == -1) {
+            keepAlive = false;
+            closed = true;
+            return rd;
+        }
+        if (rd == 0) {
+            return rd; // nothing for us 
+        } else {
+//            dst.flip();
+//            dst.get(reqB.buf, 
+//                    reqB.lastValid, 
+//                    reqB.getFreeSpace(rd));
+            reqB.lastValid += rd;
+//            dst.rewind();
+        }
+        return rd;
+    }
+
+    // -------------------- Utils -------------------------
+    
+    private void initFilters() {
+        chunkLength = new byte[10];
+        chunkLength[8] = (byte) '\r';
+        chunkLength[9] = (byte) '\n';
+
+    }
+
+    // TODO: support async 
+    public int read(ByteBuffer dst) throws IOException {
+      if (is instanceof FileInputStream) {
+        return ((FileInputStream)is).getChannel().read(dst);
+      }
+      
+      int len = is.read(dst.array(), dst.position(), 
+          dst.limit() - dst.position());
+      if (len > 0) {
+        dst.position(dst.position() + len);
+      }
+      return len;
+    }
+
+    public void close() throws IOException {
+      // 
+    }
+
+    public boolean isOpen() {
+      return true;
+    }
+
+}

Added: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11NioServlet.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11NioServlet.java?view=auto&rev=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11NioServlet.java (added)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/Http11NioServlet.java Tue May  1 19:22:45 2007
@@ -0,0 +1,213 @@
+package org.apache.tomcat.lite.http11;
+
+import java.io.IOException;
+import java.net.BindException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.Channel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.spi.SelectorProvider;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tomcat.lite.ServletContextImpl;
+import org.apache.tomcat.lite.TomcatLite;
+
+/** Main adapter - top mapping and adaptation.
+ * 
+ * This handles host and context mapping - the data structures are stored
+ * in this class. 
+ * 
+ * All context-specific mapping is done in the Mapper class, one per
+ * context. Mapper should also handle filters and authentication.
+ */
+public class Http11NioServlet extends HttpServlet {
+    TomcatLite facade;
+    protected static Log log=
+        LogFactory.getLog(Http11NioServlet.class.getName() );
+
+    // Socket properties
+    protected static final int BACKLOG = 100;
+    protected static final int TIMEOUT = 5000;
+
+    // Server socket options
+    protected int backlog = BACKLOG;
+    protected int serverTimeout = TIMEOUT;
+    protected InetAddress inet;
+    protected int port = 8080;
+    // Accepted socket options
+    protected boolean tcpNoDelay=false;
+    protected int linger=100;
+    protected int socketTimeout=-1;
+
+    // thread pool params
+    int maxThreads = 500; // never go above this
+    int minAcceptorThreads = 2; // at least one spare thread
+    int maxAcceptorThreads = 4; // max spare threads
+    
+    NioServlet nio;
+    
+    public Http11NioServlet() {
+    }
+    
+    public void init() throws ServletException {
+        try {
+            
+            // let's play with the simple one for now, to debug jamvm
+            //proto = new Http11Protocol(new NioEndpoint());
+            //proto.setAdapter(this);
+            ServletContextImpl ctx = (ServletContextImpl)getServletContext();
+            facade = ctx.getEngine();
+
+            nio = NioServlet.getNioServlet(facade);
+
+            String portS = getServletConfig().getInitParameter("port");
+            if (portS != null) {
+                setPort(Integer.parseInt(portS));
+            } 
+            initEndpoint();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServletException(e);
+        }
+    }
+    
+    public void destroy() {
+        try {
+            nio.destroy();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port ) {
+        this.port=port;
+    }
+
+    public InetAddress getAddress() {
+            return inet;
+    }
+
+    public void setAddress(InetAddress inet) {
+            this.inet=inet;
+    }
+    
+    /**
+     * Allows the server developer to specify the backlog that
+     * should be used for server sockets. By default, this value
+     * is 100.
+     */
+    public void setBacklog(int backlog) {
+        if( backlog>0)
+            this.backlog = backlog;
+    }
+
+    public int getBacklog() {
+        return backlog;
+    }
+
+    /**
+     * Sets the timeout in ms of the server sockets created by this
+     * server. This method allows the developer to make servers
+     * more or less responsive to having their server sockets
+     * shut down.
+     *
+     * <p>By default this value is 1000ms.
+     */
+    public void setServerTimeout(int timeout) {
+        this.serverTimeout = timeout;
+    }
+
+    public boolean getTcpNoDelay() {
+        return tcpNoDelay;
+    }
+    
+    public void setTcpNoDelay( boolean b ) {
+        tcpNoDelay=b;
+    }
+
+    public int getSoLinger() {
+        return linger;
+    }
+    
+    public void setSoLinger( int i ) {
+        linger=i;
+    }
+
+    public int getSoTimeout() {
+        return socketTimeout;
+    }
+    
+    public void setSoTimeout( int i ) {
+        socketTimeout=i;
+    }
+    
+    public int getServerSoTimeout() {
+        return serverTimeout;
+    }  
+    
+    public void setServerSoTimeout( int i ) {
+        serverTimeout=i;
+    }
+
+    // -------------------- Processing methods --------------------
+    ServerSocketChannel ssc;
+    
+    public void initEndpoint() throws IOException, InstantiationException {
+        ServerSocket serverSocket = null;
+        if(ssc==null) {
+            try {
+                ssc=ServerSocketChannel.open();
+                serverSocket = ssc.socket();
+                SocketAddress sa = null;
+                if (inet == null) {
+                    sa = new InetSocketAddress( port );
+                } else {
+                    sa = new InetSocketAddress(inet, port);
+                }
+                serverSocket.bind( sa , backlog);
+                if( serverTimeout >= 0 )
+                    serverSocket.setSoTimeout( serverTimeout );
+
+                ssc.configureBlocking(false);
+                nio.registerAcceptor(ssc, this);
+
+                log("Listen on " + port);
+            } catch ( BindException be ) {
+                throw new BindException(be.getMessage() + ":" + port);
+            }
+        }
+    }
+
+    // --------------- Other utils ----------------------
+
+    void setSocketOptions(Socket socket)
+            throws SocketException {
+        if(linger >= 0 ) 
+            socket.setSoLinger( true, linger);
+        if( tcpNoDelay )
+            socket.setTcpNoDelay(tcpNoDelay);
+        if( socketTimeout > 0 )
+            socket.setSoTimeout( socketTimeout );
+    }
+    
+}
\ No newline at end of file

Added: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/NioServlet.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/NioServlet.java?view=auto&rev=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/NioServlet.java (added)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/NioServlet.java Tue May  1 19:22:45 2007
@@ -0,0 +1,305 @@
+package org.apache.tomcat.lite.http11;
+
+import java.io.IOException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tomcat.lite.TomcatLite;
+
+/**
+ * Handle NIO. 
+ * 
+ * Must be bound as "_iothread" in "/__engine_"
+ * 
+ */
+public class NioServlet extends HttpServlet {
+    TomcatLite tomcat;
+    
+    protected static Log log=
+        LogFactory.getLog(NioServlet.class.getName() );
+
+    boolean daemon = false;
+    
+    // state
+    protected volatile boolean running = false;
+    protected volatile boolean paused = false;
+    protected boolean initialized = false;
+    protected boolean reinitializing = false;
+
+    int acceptorThreads = 0; // active threads - will be updated
+    
+    IOThread ioThread;
+
+    // must be done in the io thread
+    List/*<Http11Connection>*/ toRegister = new ArrayList();
+    List/*<Http11NioServlet>*/ acceptorsToRegister = 
+        new ArrayList();
+    
+    // TODO: load it from context, configure separately
+    ThreadPoolSimple tp = new ThreadPoolSimple();
+    
+    static NioServlet mainServlet;
+    
+    public NioServlet() {
+        if (mainServlet == null) {
+            mainServlet = this;
+        }
+    }
+    
+    public static NioServlet getNioServlet(TomcatLite tomcat) {
+        return mainServlet;
+    }
+    
+    public void init() throws ServletException {
+        try {
+            ioThread = new IOThread(this);
+            tomcat = 
+                (TomcatLite)getServletContext().getAttribute(TomcatLite.A_ENGINE);
+            start();
+            System.err.println("Starting IO thread " + ioThread.selector );
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServletException(e);
+        }
+    }
+    
+    public void destroy() {
+        try {
+            stop();
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    public boolean isRunning() {
+        return running;
+    }
+    
+    public boolean isPaused() {
+        return paused;
+    }
+    
+    public void setDaemon(boolean b) {
+        daemon = b;
+    }
+    
+    public void pause() {
+        if (running && !paused) {
+            paused = true;
+        }
+    }
+
+    public void resume() {
+        if (running) {
+            paused = false;
+        }
+    }
+
+    public void stop() {
+        running = false;
+        ioThread.selector.wakeup(); 
+    }
+    
+    // -------------------- Processing methods --------------------
+    
+    public void start() throws IOException, InstantiationException {
+        running = true;
+        ioThread.setDaemon(daemon);
+        ioThread.start();
+        
+    }
+
+    public void registerAcceptor(ServerSocketChannel ssc, 
+                                 Http11NioServlet httpServlet) 
+            throws IOException {
+        // TODO: to register
+        acceptorsToRegister.add(httpServlet);
+        ioThread.selector.wakeup();
+        System.err.println("Waking up " + ioThread.selector);
+    }
+
+    /**
+     *  Called from a random thread - con needs more async data.
+
+     * @param mySocket 
+     * @throws IOException 
+     */
+    public void needData(Http11Connection con, SocketChannel mySocket) 
+            throws IOException {
+        toRegister.add(con);
+        ioThread.selector.wakeup(); // register is blockin
+    }
+    
+    public void stopServer(ServerSocketChannel ssc) {
+        if( ssc!=null) {
+            try {
+                ssc.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        ssc = null;
+    }
+    
+    public void startServer(ServerSocketChannel ssc) {
+        
+    }
+    
+    /** 
+     * We could use more threads for higher loads, one for accept and 
+     * few for read/write.
+     */
+    class IOThread extends Thread {
+        NioServlet master;
+        Selector selector;
+        
+        IOThread(NioServlet master) {
+            this.master = master;
+            try {
+                selector = Selector.open();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        
+        public void run() {
+            while( running ) {
+                try {
+                    if (acceptorsToRegister.size() > 0) {
+                        Http11NioServlet httpServlet = 
+                            (Http11NioServlet) acceptorsToRegister.remove(0);
+                        httpServlet.ssc.register(ioThread.selector, 
+                                SelectionKey.OP_ACCEPT, httpServlet);
+                    }
+
+                    // Process waiting interests ( since they have to be
+                    // in IO thread
+                    // TODO
+                    setName("IO: select");                    
+                    int selRes = selector.select();
+                    
+                    master.acceptorThreads--;
+
+                    if (!master.running) {
+                        return; // done
+                    }
+                    
+                    Set selected = selector.selectedKeys();
+
+                    if (debugSelect)
+                        log.debug("Selected, Active:" + master.tp.getActiveCount() + 
+                                " selSize: " + selected.size() + " selRes: " +
+                                selRes);
+
+                    setName("IO: " + master.acceptorThreads);                    
+
+                    if (toRegister.size() > 0 ) {
+                        Http11Connection con = (Http11Connection) toRegister.remove(0);
+                        con.mySocket.configureBlocking(false);
+                        con.mySocket.register( ioThread.selector, SelectionKey.OP_READ, con);
+                    }
+
+                    if (selRes == 0) {
+                        // multiple threads selected by event - we need to 
+                        // sync on selected, otherwise it goes looping ( not
+                        // sure why )
+                        continue;
+                    }
+                    
+                    Iterator selI = selected.iterator();
+                    
+                    while( selI.hasNext() ) {
+                        SelectionKey sk = (SelectionKey)selI.next();
+                        selI.remove();
+                        if (!sk.isValid()) {
+                            log.info("Invalid selection key " + 
+                                    sk.interestOps());
+                            continue;
+                        }
+                        handleSelect(sk);
+                    }
+                } catch (IOException e) {
+                    log.error("Ignoring IOE" + e.toString());
+                }
+            }
+        }
+    }
+    
+    static boolean debugSelect;
+
+    /** Process a selection key - called from IO thread
+     */
+    void handleSelect(SelectionKey sk) throws IOException {
+        if( sk.isAcceptable() ) {
+            SelectableChannel sc = sk.channel();
+            ServerSocketChannel ssc=(ServerSocketChannel)sc;
+            SocketChannel sockC = ssc.accept();
+            if (sockC == null) return;
+
+            sockC.socket().setTcpNoDelay(true); // for http 
+            sockC.configureBlocking(false);
+            
+            // If some data is available - parse it.
+            Http11NioServlet httpServlet = (Http11NioServlet) sk.attachment();
+            Http11Connection con = new Http11Connection(this.tomcat, sockC, this);
+            
+            boolean hasReq = con.parseRequestHead();
+            if (hasReq ) {
+                if (debugSelect)
+                    log.debug("IO: Req parsed in accept" + con);
+                tp.execute(con); // when done, it will return it to us
+            } else {
+                // wait for more data
+                sockC.register(ioThread.selector, SelectionKey.OP_READ, con);
+            }
+        } else  if( sk.isReadable() ) {
+            // TODO: make sure it has the right state to jump to 
+            // processRequest()
+            Http11Connection con = (Http11Connection)sk.attachment();
+            if (debugSelect)
+                log.debug("Read on " + con);
+            boolean hasReq = con.parseRequestHead();
+            if (hasReq ) {
+                if (debugSelect)
+                    log.debug("IO: Req parsed in isReadable " + con);
+                tp.execute(con); // when done, it will return it to us
+            } else {
+                // nothing.
+            }
+            if (con.isClosed()) {
+                sk.channel().close();
+            }
+        } else {
+            log.debug("Select: " + sk.readyOps());
+        }
+    }
+
+    public void registerAsyncRead(SocketChannel ch, boolean active) {
+        
+    }
+
+    /** Reuse the connection object ( maybe )
+     */
+    void done(Runnable r) {
+        
+    }
+
+    public void cancel(SocketChannel mySocket) throws IOException {
+        SelectionKey key = mySocket.keyFor(ioThread.selector);
+        if (key != null) key.cancel();
+        mySocket.configureBlocking(true);
+    }
+}
\ No newline at end of file

Added: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/ThreadPoolSimple.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/ThreadPoolSimple.java?view=auto&rev=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/ThreadPoolSimple.java (added)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/http11/ThreadPoolSimple.java Tue May  1 19:22:45 2007
@@ -0,0 +1,171 @@
+package org.apache.tomcat.lite.http11;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.LinkedList;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tomcat.lite.ServletContextImpl;
+
+/** Main adapter - top mapping and adaptation.
+ * 
+ * This handles host and context mapping - the data structures are stored
+ * in this class. 
+ * 
+ * All context-specific mapping is done in the Mapper class, one per
+ * context. Mapper should also handle filters and authentication.
+ */
+public class ThreadPoolSimple extends HttpServlet {
+    
+    private static final int spareTimeout = 10000;
+
+    protected static Log log =
+        LogFactory.getLog(ThreadPoolSimple.class.getName() );
+    
+    // state
+    protected volatile boolean running = false;
+    protected volatile boolean paused = false;
+    protected boolean initialized = false;
+    protected boolean reinitializing = false;
+    int spareThreads = 0; // waiting for work
+    int busy;
+
+    LinkedList/*<Runnable>*/ pending = 
+        new LinkedList();
+    
+
+    public ThreadPoolSimple() {
+    }
+    
+    public void init() throws ServletException {
+        try {
+            ServletContextImpl ctx = (ServletContextImpl)getServletContext();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServletException(e);
+        }
+    }
+
+    public void destroy() {
+    }
+
+    protected void doGet(HttpServletRequest req, HttpServletResponse res) 
+            throws ServletException, IOException {
+        PrintWriter out = res.getWriter();
+        out.println(", 'active': " + spareThreads + " , 'busy': " + busy +
+                "}");
+    }
+
+    // TP management
+    public void execute(Runnable con) {
+        if(spareThreads == 0) {
+            new Http11Worker(con).start();
+            return;
+        }
+        synchronized(this) {
+            pending.addLast(con);
+            // log.debug("TP: execute() " + con + " P:" + pending.size() + " A:" + 
+            //                    spareThreads);
+            this.notify(); // wake a thread waiting on get()
+        }
+    }
+
+    public int getActiveCount() {
+        return busy;
+    }
+
+    public boolean isRunning() {
+        return running;
+    }
+    
+    public boolean isPaused() {
+        return paused;
+    }
+    
+    // -------------------- Processing methods --------------------
+    
+    public void initEndpoint() throws IOException, InstantiationException {
+        initialized = true;
+    }
+
+    public void startEndpoint() throws IOException, InstantiationException {
+        running = true;
+    }
+
+    /**
+     * The actual worker controlled by the ThreadPool. 
+     * All 'interesting' methods are in ThreadPoolSimple
+     */
+    class Http11Worker extends Thread {
+        Runnable run;
+        Http11Worker() {
+            super();
+        }
+        // Simple optimization - first task will not do a get
+        Http11Worker(Runnable initial) {
+            super();
+            this.run = initial;
+        }
+        
+        public String toString() {
+            return "TP " + spareThreads + " " + pending.size() + " " + run;
+        }
+        
+        public void run() {
+            while(true) {
+                if ( run!= null) {
+                    try {
+                        setName("TP: starting " + spareThreads + " " + run);
+                        run.run();
+                    } catch(Throwable t) {
+                        log.error("Error: ", t);
+                    }
+                }
+                spareThreads++;
+                setName("TP: waiting " + spareThreads);
+                
+                run = get();  // blocking
+                
+                spareThreads--;
+                setName("TP: spare: " + spareThreads + " run: " + run);
+                if (run == null) {
+                    //log.info("TP: stop spare");
+                    return;
+                }
+                done(run);
+            }
+        }
+    }
+    
+    void done(Runnable r) {
+        
+    }
+    
+    synchronized Runnable get() {
+        // get one
+        synchronized(this) {
+            if (pending.size() > 0) {
+                //log.debug("Got a thread " + pending.size());
+                return (Runnable) pending.removeFirst();
+            }
+        }
+        //log.debug("Waiting...");
+        try {
+            wait(spareTimeout);
+        } catch(InterruptedException ex) {
+        }
+        
+        if (pending.size() == 0) {
+            //log.debug("Timeout ");
+            return null;
+        }
+        //log.debug("Got a thread after wait " + pending.size());
+        return (Runnable) pending.removeFirst();
+    }
+}
\ No newline at end of file

Added: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/tomcat-lite-web.xml
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/tomcat-lite-web.xml?view=auto&rev=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/tomcat-lite-web.xml (added)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/tomcat-lite-web.xml Tue May  1 19:22:45 2007
@@ -0,0 +1,19 @@
+<web-app>
+
+   <!-- Not really needed - after testing can be removed -->
+   <!-- This whole file should be loaded programmatically -->
+   <filter><filter-name>simpleIP</filter-name><filter-class>org.apache.tomcat.servlets.sec.SimpleIPFilter</filter-class></filter>
+   <filter><filter-name>_tc_log</filter-name><filter-class>org.apache.tomcat.servlets.log.LogFilter</filter-class></filter>
+   <filter-mapping><filter-name>simpleIP</filter-name><url-pattern>/*</url-pattern></filter-mapping>    
+   <filter-mapping><filter-name>_tc_log</filter-name><url-pattern>/*</url-pattern></filter-mapping>    
+
+   <servlet><servlet-name>__x_iothread</servlet-name><servlet-class>org.apache.tomcat.lite.http11.NioServlet</servlet-class>
+     <load-on-startup>1</load-on-startup>
+   </servlet>
+
+   <servlet><servlet-name>__x_connector</servlet-name><servlet-class>org.apache.tomcat.lite.http11.Http11NioServlet</servlet-class>
+     <init-param><param-name>port</param-name><param-value>8802</param-value></init-param>
+     <load-on-startup>2</load-on-startup>
+   </servlet>
+
+</web-app>
\ No newline at end of file

Added: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/CompressedWriter.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/CompressedWriter.java?view=auto&rev=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/CompressedWriter.java (added)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/CompressedWriter.java Tue May  1 19:22:45 2007
@@ -0,0 +1,122 @@
+/*
+ */
+package org.apache.tomcat.lite.util;
+
+import org.apache.tomcat.lite.http11.Http11Buffer;
+import org.apache.tomcat.util.buf.MessageBytes;
+
+/**
+ * Message writer with compression support.
+ * Moved out of http worker
+ * 
+ * @author Costin Manolache
+ */
+public class CompressedWriter {
+    Http11Buffer cReq;
+    Http11Buffer cRes;
+    int compressionLevel = 0;
+    protected int compressionMinSize = 2048;
+    protected String[] compressableMimeTypes = { "text/html", "text/xml", 
+            "text/plain" };
+
+    
+    /**
+     * Check for compression
+     */
+    private boolean isCompressable() {
+
+        // Nope Compression could works in HTTP 1.0 also
+        // cf: mod_deflate
+
+        // Compression only since HTTP 1.1
+        // if (! http11)
+        //    return false;
+
+        // Check if browser support gzip encoding
+        MessageBytes acceptEncodingMB =
+            cReq.getMimeHeaders().getValue("accept-encoding");
+
+        if ((acceptEncodingMB == null)
+                || (acceptEncodingMB.indexOf("gzip") == -1))
+            return false;
+
+        // Check if content is not allready gzipped
+        MessageBytes contentEncodingMB =
+            cRes.getMimeHeaders().getValue("Content-Encoding");
+
+        if ((contentEncodingMB != null)
+                && (contentEncodingMB.indexOf("gzip") != -1))
+            return false;
+
+        // If force mode, allways compress (test purposes only)
+        if (compressionLevel == 2)
+            return true;
+
+        // Check for incompatible Browser
+//      if (noCompressionUserAgents != null) {
+//      MessageBytes userAgentValueMB =
+//      request.getMimeHeaders().getValue("user-agent");
+//      if(userAgentValueMB != null) {
+//      String userAgentValue = userAgentValueMB.toString();
+
+//      // If one Regexp rule match, disable compression
+//      for (int i = 0; i < noCompressionUserAgents.length; i++)
+//      if (noCompressionUserAgents[i].matcher(userAgentValue).matches())
+//      return false;
+//      }
+//      }
+
+        // Check if suffisant len to trig the compression
+        long contentLength = cRes.getContentLengthLong();
+        if ((contentLength == -1)
+                || (contentLength > compressionMinSize)) {
+            // Check for compatible MIME-TYPE
+            if (compressableMimeTypes != null) {
+                return (startsWithStringArray(compressableMimeTypes,
+                        cRes.getContentType()));
+            }
+        }
+
+        return false;
+    }
+
+    private boolean startsWithStringArray(String sArray[], String value) {
+        if (value == null)
+            return false;
+        for (int i = 0; i < sArray.length; i++) {
+            if (value.startsWith(sArray[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    boolean gzipOutput = false;
+
+
+
+    public void prepareResponse() { 
+        boolean entityBody = false;
+        boolean gzipOutput; 
+        
+        // Check for compression
+        boolean useCompression = false;
+        if (entityBody && (compressionLevel > 0)) {
+            useCompression = isCompressable();
+            // Change content-length to -1 to force chunking
+            if (useCompression) {
+                cRes.setContentLength(-1);
+            }
+        }
+
+        if (useCompression) {
+            gzipOutput = true; // TODO: this should be external, other transfors as well
+            cRes.getMimeHeaders().setValue("Content-Encoding").setString("gzip");
+            // Make Proxies happy via Vary (from mod_deflate)
+            cRes.getMimeHeaders().setValue("Vary").setString("Accept-Encoding");
+        }
+
+
+    }
+
+}

Modified: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/MappingData.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/MappingData.java?view=diff&rev=534293&r1=534292&r2=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/MappingData.java (original)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/MappingData.java Tue May  1 19:22:45 2007
@@ -16,10 +16,11 @@
 
 package org.apache.tomcat.lite.util;
 
+import org.apache.tomcat.util.buf.CBuffer;
 import org.apache.tomcat.util.buf.MessageBytes;
 
 /**
- * Mapping data.
+ * Mapping data. Used to perform the request mapping
  *
  * @author Remy Maucherat
  */
@@ -37,6 +38,11 @@
 
     public MessageBytes redirectPath = MessageBytes.newInstance();
 
+    // Temp buffer used in RequestDispatcher to pass the mapping param
+    public MessageBytes localURIBytes = MessageBytes.newInstance();
+    // Buffer used to hold temporary paths in RequestDispatcher
+    public CBuffer charBuffer = new CBuffer();
+    
     public void recycle() {
         host = null;
         context = null;

Modified: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/MessageWriter.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/MessageWriter.java?view=diff&rev=534293&r1=534292&r2=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/MessageWriter.java (original)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/lite/util/MessageWriter.java Tue May  1 19:22:45 2007
@@ -18,26 +18,27 @@
 
 
 import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
 import java.io.Writer;
 import java.security.AccessController;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.HashMap;
 
-import org.apache.coyote.ActionCode;
-import org.apache.coyote.Request;
-import org.apache.coyote.Response;
-import org.apache.tomcat.util.buf.ByteChunk;
-import org.apache.tomcat.util.buf.C2BConverter;
-import org.apache.tomcat.util.buf.CharChunk;
-import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.lite.http11.Http11Buffer;
+import org.apache.tomcat.lite.http11.Http11Connection;
+import org.apache.tomcat.util.buf.BBuffer;
+import org.apache.tomcat.util.buf.CBuffer;
+import org.apache.tomcat.util.buf.CBuffer.CharOutputChannel;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.WritableByteChannel;
 
 /*
  * Refactoring: original code in catalina.connector.
  * - renamed to OutputWriter to avoid confusion with coyote OutputBuffer
- * - 
- * TODO: move it to coyote, add Response.getWriter 
- * 
  */
 
 /**
@@ -49,16 +50,10 @@
  * @author Remy Maucherat
  */
 public class MessageWriter extends Writer
-    implements ByteChunk.ByteOutputChannel, CharChunk.CharOutputChannel {
-
-    // used in getWriter, until a method is added to res.
-    private static final int WRITER_NOTE = 9;
+    implements WritableByteChannel, CharOutputChannel {
 
     // -------------------------------------------------------------- Constants
 
-
-    public static final String DEFAULT_ENCODING = 
-        org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
     public static final int DEFAULT_BUFFER_SIZE = 8*1024;
 
 
@@ -76,13 +71,13 @@
     /**
      * The byte buffer.
      */
-    private ByteChunk bb;
+    private BBuffer bb;
 
 
     /**
      * The chunk buffer.
      */
-    private CharChunk cb;
+    private CBuffer cb;
 
 
     /**
@@ -114,14 +109,6 @@
      */
     private boolean doFlush = false;
 
-
-    /**
-     * Byte chunk used to output bytes. This is just used to wrap the byte[]
-     * to match the coyote OutputBuffer interface
-     */
-    private ByteChunk outputChunk = new ByteChunk();
-
-
     /**
      * Encoding to use. 
      * TODO: isn't it redundant ? enc, gotEnc, conv plus the enc in the bb
@@ -147,12 +134,8 @@
      */
     protected C2BConverter conv;
 
-
-    /**
-     * Associated Coyote response.
-     */
-    private Response coyoteResponse;
-
+    Http11Connection httpConnection;
+    
 
     /**
      * Suspended flag. All output bytes will be swallowed if this is true.
@@ -180,10 +163,10 @@
      */
     public MessageWriter(int size) {
 
-        bb = new ByteChunk(size);
+        bb = new BBuffer(size);
         bb.setLimit(size);
         bb.setByteOutputChannel(this);
-        cb = new CharChunk(size);
+        cb = new CBuffer(size);
         cb.setCharOutputChannel(this);
         cb.setLimit(size);
 
@@ -198,19 +181,19 @@
      * 
      * @param coyoteResponse Associated Coyote response
      */
-    public void setResponse(Response coyoteResponse) {
-	this.coyoteResponse = coyoteResponse;
-    }
-
-
-    /**
-     * Get associated Coyote response.
-     * 
-     * @return the associated Coyote response
-     */
-    public Response getResponse() {
-        return this.coyoteResponse;
-    }
+//    public void setResponse(Response coyoteResponse) {
+//	this.coyoteResponse = coyoteResponse;
+//    }
+//
+//
+//    /**
+//     * Get associated Coyote response.
+//     * 
+//     * @return the associated Coyote response
+//     */
+//    public Response getResponse() {
+//        return this.coyoteResponse;
+//    }
 
 
     /**
@@ -274,8 +257,8 @@
         if (suspended)
             return;
 
-        if ((!coyoteResponse.isCommitted()) 
-            && (coyoteResponse.getContentLengthLong() == -1)) {
+        if ((!httpConnection.resB.isCommitted()) 
+            && (httpConnection.resB.getContentLengthLong() == -1)) {
             // Flushing the char buffer
             if (state == CHAR_STATE) {
                 cb.flushBuffer();
@@ -283,15 +266,15 @@
             }
             // If this didn't cause a commit of the response, the final content
             // length can be calculated
-            if (!coyoteResponse.isCommitted()) {
-                coyoteResponse.setContentLength(bb.getLength());
+            if (!httpConnection.resB.isCommitted()) {
+                httpConnection.resB.setContentLength(bb.getLength());
             }
         }
 
-        doFlush(false);
+        doFlush(false, false);
         closed = true;
 
-        coyoteResponse.finish();
+        httpConnection.finish();
 
     }
 
@@ -303,7 +286,7 @@
      */
     public void flush()
         throws IOException {
-        doFlush(true);
+        doFlush(true, false);
     }
 
 
@@ -312,10 +295,10 @@
      * 
      * @throws IOException An underlying IOException occurred
      */
-    protected void doFlush(boolean realFlush)
+    public void doFlush(boolean realFlush, boolean endReq)
         throws IOException {
 
-        if (suspended)
+        if (suspended && !endReq)
             return;
 
         doFlush = true;
@@ -327,18 +310,17 @@
             bb.flushBuffer();
         } else if (state == INITIAL_STATE) {
             // If the buffers are empty, commit the response header
-            coyoteResponse.sendHeaders();
+            httpConnection.sendHeaders();
         }
         doFlush = false;
 
         if (realFlush) {
-            coyoteResponse.action(ActionCode.ACTION_CLIENT_FLUSH, 
-                                  coyoteResponse);
+            httpConnection.clientFlush();
             // If some exception occurred earlier, or if some IOE occurred
             // here, notify the servlet with an IOE
-            if (coyoteResponse.isExceptionPresent()) {
+            if (httpConnection.resB.isExceptionPresent()) {
                 throw new ClientAbortException
-                    (coyoteResponse.getErrorException());
+                    (httpConnection.resB.getErrorException());
             }
         }
 
@@ -348,41 +330,6 @@
     // ------------------------------------------------- Bytes Handling Methods
 
 
-    /** 
-     * Sends the buffer data to the client output, checking the
-     * state of Response and calling the right interceptors.
-     * 
-     * @param buf Byte buffer to be written to the response
-     * @param off Offset
-     * @param cnt Length
-     * 
-     * @throws IOException An underlying IOException occurred
-     */
-    public void realWriteBytes(byte buf[], int off, int cnt)
-	throws IOException {
-
-        if (closed)
-            return;
-        if (coyoteResponse == null)
-            return;
-
-        // If we really have something to write
-        if (cnt > 0) {
-            // real write to the adapter
-            outputChunk.setBytes(buf, off, cnt);
-            try {
-                coyoteResponse.doWrite(outputChunk);
-            } catch (IOException e) {
-                // An IOException on a write is almost always due to
-                // the remote client aborting the request.  Wrap this
-                // so that it can be handled better by the error dispatcher.
-                throw new ClientAbortException(e);
-            }
-        }
-
-    }
-
-
     public void write(byte b[], int off, int len) throws IOException {
 
         if (suspended)
@@ -563,12 +510,12 @@
     protected void setConverter() 
         throws IOException {
 
-        if (coyoteResponse != null)
-            enc = coyoteResponse.getCharacterEncoding();
+        if (httpConnection.resB != null)
+            enc = httpConnection.resB.getCharacterEncoding();
 
         gotEnc = true;
         if (enc == null)
-            enc = DEFAULT_ENCODING;
+            enc = Http11Buffer.DEFAULT_CHARACTER_ENCODING;
         conv = (C2BConverter) encoders.get(enc);
         if (conv == null) {
             
@@ -660,21 +607,285 @@
 	return bb.getLimit();
     }
 
+    public void setConnection(Http11Connection worker) {
+        this.httpConnection = worker;
+    }
+    
+    
+    /** Efficient conversion of character to bytes.
+     *  
+     *  This uses the standard JDK mechansim - a writer - but provides mechanisms
+     *  to recycle all the objects that are used. It is compatible with JDK1.1 and up,
+     *  ( nio is better, but it's not available even in 1.2 or 1.3 )
+     * 
+     * @deprecated Use CharsetEncoder on the ByteBuffer
+     */
+    public final class C2BConverter {
+        
+        private org.apache.commons.logging.Log log=
+            org.apache.commons.logging.LogFactory.getLog(C2BConverter.class );
+        
+        private IntermediateOutputStream ios;
+        private WriteConvertor conv;
+        private BBuffer bb;
+        private String enc;
+        
+        /** Create a converter, with bytes going to a byte buffer
+         */
+        public C2BConverter(BBuffer output, String encoding) throws IOException {
+        this.bb=output;
+        ios=new IntermediateOutputStream( output );
+        conv=new WriteConvertor( ios, encoding );
+            this.enc=encoding;
+        }
 
-    public static MessageWriter getWriter(Request req, Response res, int size) 
-    {        
-        MessageWriter out=(MessageWriter)req.getNote(MessageWriter.WRITER_NOTE);
-        if( out == null ) {
-            if( size<=0 ) {
-                out=new MessageWriter();
-            } else {
-                out=new MessageWriter(size);
-            }
-            out.setResponse(res);
-            req.setNote(MessageWriter.WRITER_NOTE, out );
+        /** Create a converter
+         * Not used.
+         */
+        public C2BConverter(String encoding) throws IOException {
+        this( new BBuffer(1024), encoding );
+        }
+
+        // Not used
+//        public ByteChunk getByteChunk() {
+//      return bb;
+//        }
+
+        // not used
+        public String getEncoding() {
+            return enc;
+        }
+
+        // internal use only
+        public void setByteChunk(BBuffer bb) {
+        this.bb=bb;
+        ios.setByteChunk( bb );
+        }
+
+        /** Reset the internal state, empty the buffers.
+         *  The encoding remain in effect, the internal buffers remain allocated.
+         */
+        public  final void recycle() {
+        conv.recycle();
+        bb.recycle();
+        }
+
+        /** Generate the bytes using the specified encoding
+         */
+        public  final void convert(char c[], int off, int len ) throws IOException {
+        conv.write( c, off, len );
+        }
+
+        /** Generate the bytes using the specified encoding
+         */
+        public  final void convert(String s ) throws IOException {
+        conv.write( s );
+        }
+
+        /** Generate the bytes using the specified encoding
+         */
+        public  final void convert(char c ) throws IOException {
+        conv.write( c );
+        }
+
+        /** Convert a message bytes chars to bytes
+         */
+//        public final void convert(MessageBytes mb ) throws IOException {
+//            int type=mb.getType();
+//            if( type==MessageBytes.T_BYTES )
+//                return; // why ?
+//            ByteChunk orig=bb;
+//            setByteChunk( mb.getByteChunk());
+//            bb.recycle();
+//            bb.allocate( 32, -1 );
+//            
+//            if( type==MessageBytes.T_STR ) {
+//                convert( mb.getString() );
+//                // System.out.println("XXX Converting " + mb.getString() );
+//            } else if( type==MessageBytes.T_CHARS ) {
+//                CharChunk charC=mb.getCharChunk();
+//                convert( charC.getBuffer(),
+//                                    charC.getOffset(), charC.getLength());
+//                //System.out.println("XXX Converting " + mb.getCharChunk() );
+//            } else {
+//                if (log.isDebugEnabled())
+//                    log.debug("XXX unknowon type " + type );
+//            }
+//            flushBuffer();
+//            //System.out.println("C2B: XXX " + bb.getBuffer() + bb.getLength()); 
+//            setByteChunk(orig);
+//        }
+
+        /** Flush any internal buffers into the ByteOutput or the internal
+         *  byte[]
+         */
+        public  final void flushBuffer() throws IOException {
+        conv.flush();
+        }
+
+    }
+
+//     -------------------- Private implementation --------------------
+
+
+
+    /**
+     *  Special writer class, where close() is overritten. The default implementation
+     *  would set byteOutputter to null, and the writter can't be recycled. 
+     *
+     *  Note that the flush method will empty the internal buffers _and_ call
+     *  flush on the output stream - that's why we use an intermediary output stream
+     *  that overrides flush(). The idea is to  have full control: flushing the
+     *  char->byte converter should be independent of flushing the OutputStream.
+     * 
+     *  When a WriteConverter is created, it'll allocate one or 2 byte buffers,
+     *  with a 8k size that can't be changed ( at least in JDK1.1 -> 1.4 ). It would
+     *  also allocate a ByteOutputter or equivalent - again some internal buffers.
+     *
+     *  It is essential to keep  this object around and reuse it. You can use either
+     *  pools or per thread data - but given that in most cases a converter will be
+     *  needed for every thread and most of the time only 1 ( or 2 ) encodings will
+     *  be used, it is far better to keep it per thread and eliminate the pool 
+     *  overhead too.
+     * 
+     */
+     final class        WriteConvertor extends OutputStreamWriter {
+        // stream with flush() and close(). overriden.
+        private IntermediateOutputStream ios;
+        
+        // Has a private, internal byte[8192]
+        
+        /** Create a converter.
+         */
+        public WriteConvertor( IntermediateOutputStream out, String enc )
+        throws UnsupportedEncodingException
+        {
+        super( out, enc );
+        ios=out;
+        }
+        
+        /** Overriden - will do nothing but reset internal state.
+         */
+        public  final void close() throws IOException {
+        // NOTHING
+        // Calling super.close() would reset out and cb.
+        }
+        
+        /**
+         *  Flush the characters only
+         */ 
+        public  final void flush() throws IOException {
+        // Will flushBuffer and out()
+        // flushBuffer put any remaining chars in the byte[] 
+        super.flush();
+        }
+        
+        public  final void write(char cbuf[], int off, int len) throws IOException {
+        // will do the conversion and call write on the output stream
+        super.write( cbuf, off, len );
+        }
+        
+        /** Reset the buffer
+         */
+        public  final void recycle() {
+        ios.disable();
+        try {
+            //      System.out.println("Reseting writer");
+            flush();
+        } catch( Exception ex ) {
+            ex.printStackTrace();
+        }
+        ios.enable();
+        }
+        
+    }
+
+
+    /** Special output stream where close() is overriden, so super.close()
+        is never called.
+        
+        This allows recycling. It can also be disabled, so callbacks will
+        not be called if recycling the converter and if data was not flushed.
+    */
+    final class IntermediateOutputStream extends OutputStream {
+        private BBuffer tbuff;
+        private boolean enabled=true;
+        
+        public IntermediateOutputStream(BBuffer tbuff) {
+            this.tbuff=tbuff;
+        }
+        
+        public  final void close() throws IOException {
+        // shouldn't be called - we filter it out in writer
+        throw new IOException("close() called - shouldn't happen ");
+        }
+        
+        public  final void flush() throws IOException {
+        // nothing - write will go directly to the buffer,
+        // we don't keep any state
+        }
+        
+        public  final  void write(byte cbuf[], int off, int len) throws IOException {
+        // will do the conversion and call write on the output stream
+        if( enabled ) {
+            tbuff.append( cbuf, off, len );
+        }
+        }
+        
+        public  final void write( int i ) throws IOException {
+        throw new IOException("write( int ) called - shouldn't happen ");
+        }
+
+        // -------------------- Internal methods --------------------
+
+        void setByteChunk( BBuffer bb ) {
+        tbuff=bb;
+        }
+        
+        /** Temporary disable - this is used to recycle the converter without
+         *  generating an output if the buffers were not flushed
+         */
+        final void disable() {
+        enabled=false;
+        }
+
+        /** Reenable - used to recycle the converter
+         */
+        final void enable() {
+        enabled=true;
         }
-        return out;
     }
 
 
+    /** 
+     * Sends the buffer data to the client output, checking the
+     * state of Response and calling the right interceptors.
+     * 
+     * @throws IOException An underlying IOException occurred
+     */
+    public int write(ByteBuffer src) throws IOException {
+        if (closed)
+            return 0;
+        if (httpConnection.resB == null)
+            return 0;
+
+        // If we really have something to write
+        int cnt = src.limit() - src.position();
+        
+        if (cnt > 0) {
+            httpConnection.realWriteBytes(src.array(), src.arrayOffset() + 
+                src.position(), cnt);
+            src.position(src.position() + cnt);
+        }
+
+        return cnt;
+    }
+
+
+
+
+    public boolean isOpen() {
+      return true;
+    }
+    
 }

Modified: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/servlets/config/FilterMappingData.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/servlets/config/FilterMappingData.java?view=diff&rev=534293&r1=534292&r2=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/servlets/config/FilterMappingData.java (original)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/servlets/config/FilterMappingData.java Tue May  1 19:22:45 2007
@@ -10,5 +10,5 @@
     public String filterName;
     public String urlPattern;
     public String servletName;
-    public ArrayList<String> dispatcher = new ArrayList();
+    public ArrayList/*<String>*/ dispatcher = new ArrayList();
 }

Modified: tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/servlets/config/WebAppData.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/servlets/config/WebAppData.java?view=diff&rev=534293&r1=534292&r2=534293
==============================================================================
--- tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/servlets/config/WebAppData.java (original)
+++ tomcat/sandbox/tomcat-lite/java/org/apache/tomcat/servlets/config/WebAppData.java Tue May  1 19:22:45 2007
@@ -13,21 +13,21 @@
     public long timestamp;
 
     public boolean full;
-    public HashMap<String, String> contextParam = new HashMap();
-    public HashMap<String, String> mimeMapping = new HashMap(); // extension -> mime-type
+    public HashMap/*<String, String>*/ contextParam = new HashMap();
+    public HashMap/*<String, String>*/ mimeMapping = new HashMap(); // extension -> mime-type
     
-    public ArrayList<String> listenerClass = new ArrayList();
+    public ArrayList/*<String>*/ listenerClass = new ArrayList();
     
-    public ArrayList<String> welcomeFileList = new ArrayList();
-    public HashMap<String, String> errorPageCode= new HashMap(); // code -> location 
-    public HashMap<String, String> errorPageException= new HashMap(); // exception -> location
-    public HashMap<String, String> localeEncodingMapping= new HashMap(); // locale -> encoding
+    public ArrayList/*<String>*/ welcomeFileList = new ArrayList();
+    public HashMap/*<String, String>*/ errorPageCode= new HashMap(); // code -> location 
+    public HashMap/*<String, String>*/ errorPageException= new HashMap(); // exception -> location
+    public HashMap/*<String, String>*/ localeEncodingMapping= new HashMap(); // locale -> encoding
     
     // public HashMap tagLibs; // uri->location
     // jsp-property-group
 
     // securityConstraint
-    public ArrayList<SecurityConstraintData> securityConstraint = new ArrayList();
+    public ArrayList/*<SecurityConstraintData>*/ securityConstraint = new ArrayList();
     // loginConfig
     public String authMethod;
     public String realmName;
@@ -35,10 +35,10 @@
     public String formErrorPage;
     
     // securityRole
-    public ArrayList<String> securityRole = new ArrayList();
+    public ArrayList/*<String>*/ securityRole = new ArrayList();
     
     // envEntry
-    public ArrayList<EnvEntryData> envEntry = new ArrayList();
+    public ArrayList/*<EnvEntryData>*/ envEntry = new ArrayList();
     
     // ejbRef
     // ejbLocalRef
@@ -47,12 +47,12 @@
     // resourceEnvRef
     // message-destination
     // message-destinationRef
-    public HashMap<String, FilterData> filters = new HashMap();
-    public HashMap<String, ServletData> servlets = new HashMap();
+    public HashMap/*<String, FilterData>*/ filters = new HashMap();
+    public HashMap/*<String, ServletData>*/ servlets = new HashMap();
 
     public int sessionTimeout;
     public boolean distributable;
     
-    public HashMap<String, String> servletMapping = new HashMap(); // url -> servlet
-    public ArrayList<FilterMappingData> filterMappings = new ArrayList();
+    public HashMap/*<String, String>*/ servletMapping = new HashMap(); // url -> servlet
+    public ArrayList/*<FilterMappingData>*/ filterMappings = new ArrayList();
 }



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


Mime
View raw message