Author: costin
Date: Thu Feb 23 07:03:52 2012
New Revision: 1292671

URL: http://svn.apache.org/viewvc?rev=1292671&view=rev
Log:
Add decompress support and the npn handler. 

Assuming you compile the right jni library it should (basicaly) work with 
chrome and firefox.
Lots of missing features: async/comet/websocket, recycle of various objects, 
etc.


Added:
    tomcat/trunk/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java
    tomcat/trunk/java/org/apache/tomcat/spdy/CompressDeflater6.java
Modified:
    tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java

Added: tomcat/trunk/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java?rev=1292671&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java (added)
+++ tomcat/trunk/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java Thu Feb 23 
07:03:52 2012
@@ -0,0 +1,247 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.coyote.spdy;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.coyote.Adapter;
+import org.apache.coyote.http11.Http11AprProtocol;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.Error;
+import org.apache.tomcat.jni.SSLExt;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.spdy.CompressDeflater6;
+import org.apache.tomcat.spdy.SpdyConnection;
+import org.apache.tomcat.spdy.SpdyContext;
+import org.apache.tomcat.spdy.SpdyStream;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+/**
+ * Plugin for APR connector providing SPDY support via NPN negotiation.
+ * 
+ * Example:
+ * <Connector port="9443" 
+ *            npnHandler="org.apache.coyote.spdy.SpdyAprNpnHandler"
+ *            protocol="HTTP/1.1" 
+ *            SSLEnabled="true"
+ *            maxThreads="150" 
+ *            scheme="https" 
+ *            secure="true"
+ *            sslProtocol="TLS" 
+ *            SSLCertificateFile="conf/localhost-cert.pem" 
+ *            SSLCertificateKeyFile="conf/localhost-key.pem"/>
+ * 
+ * This requires APR library ( libtcnative-1 ) to be present and compiled
+ * with a recent openssl or a openssl patched with npn support.
+ * 
+ * Because we need to auto-detect SPDY and fallback to HTTP ( based on SSL next
+ * proto ) this is implemented in tomcat a special way:
+ * Http11AprProtocol will delegate to Spdy.process if spdy is
+ * negotiated by TLS.
+ * 
+ */
+public class SpdyAprNpnHandler implements Http11AprProtocol.NpnHandler {
+
+    private static final Log log = LogFactory.getLog(AprEndpoint.class);
+
+    private SpdyContext spdyContext;
+
+    boolean ssl = true;
+
+    @Override
+    public void init(final AbstractEndpoint ep, long sslContext, 
+            final Adapter adapter) {
+        if (sslContext == 0) {
+            // Apr endpoint without SSL.
+            ssl = false;
+            spdyContext = new SpdyContextApr(ep, adapter);
+            spdyContext.setExecutor(ep.getExecutor());
+            return;
+        }
+        if (0 == SSLExt.setNPN(sslContext, SpdyContext.SPDY_NPN_OUT)) {
+            spdyContext = new SpdyContextApr(ep, adapter);
+            spdyContext.setExecutor(ep.getExecutor());
+        } else {
+            log.warn("SPDY/NPN not supported");
+        }
+    }
+    
+    
+    private final class SpdyContextApr extends SpdyContext {
+        private final AbstractEndpoint ep;
+
+        private final Adapter adapter;
+
+        private SpdyContextApr(AbstractEndpoint ep, Adapter adapter) {
+            this.ep = ep;
+            this.adapter = adapter;
+        }
+
+        @Override
+        protected void onSynStream(SpdyConnection con, SpdyStream ch) throws 
IOException {
+            SpdyProcessor sp = new SpdyProcessor(con, ep);
+            sp.setAdapter(adapter);
+            sp.onSynStream(ch);
+        }
+    }
+
+    public static class SpdyConnectionApr extends SpdyConnection {
+        long socket;
+
+        public SpdyConnectionApr(SocketWrapper<Long> socketW,
+                SpdyContext spdyContext, boolean ssl) {
+            super(spdyContext);
+            this.socket = socketW.getSocket().longValue();
+            if (ssl) {
+                setCompressSupport(new CompressDeflater6());
+            }
+        }
+        
+        // TODO: write/read should go to SocketWrapper.
+        @Override
+        public int write(byte[] data, int off, int len) {
+            if (socket == 0 || inClosed) {
+                return -1;
+            }
+            int rem = len;
+            while (rem > 0) {
+                int sent = org.apache.tomcat.jni.Socket.send(socket, data, off,
+                        rem);
+                if (sent < 0) {
+                    inClosed = true;
+                    return -1;
+                }
+                if (sent == 0) {
+                    return len - rem;
+                }
+                rem -= sent;
+                off += sent;
+            }
+            return len;
+        }
+
+        /**
+         */
+        @Override
+        public int read(byte[] data, int off, int len) throws IOException {
+            if (socket == 0 || inClosed) {
+                return 0;
+            }
+            int rd = org.apache.tomcat.jni.Socket.recv(socket, data, off, len);
+            if (rd == -Status.APR_EOF) {
+                inClosed = true;
+                return -1;
+            }
+            if (rd == -Status.TIMEUP) {
+                rd = 0;
+            }
+            if (rd == -Status.EAGAIN) {
+                rd = 0;
+            }
+            if (rd < 0) {
+                // all other errors
+                inClosed = true;
+                throw new IOException("Error: " + rd + " "
+                        + Error.strerror((int) -rd));
+            }
+            off += rd;
+            len -= rd;
+            return rd;
+        }
+    }
+    
+    // apr normally creates a new object on each poll.
+    // For 'upgraded' protocols we need to remember it's handled differently.
+    Map<Long, SpdyConnectionApr> lightProcessors = 
+            new HashMap<Long, SpdyConnectionApr>();
+
+    @Override
+    public SocketState process(SocketWrapper<Long> socketO, SocketStatus 
status,
+            Http11AprProtocol proto, AbstractEndpoint endpoint) {
+        
+        SocketWrapper<Long> socketW = socketO;
+        long socket = ((Long) socketW.getSocket()).longValue();
+
+        SpdyConnectionApr lh = lightProcessors.get(socket);
+        // Are we getting an HTTP request ? 
+        if (lh == null && status != SocketStatus.OPEN) {
+            return null;
+        }
+
+        log.info("Status: " + status);
+
+        SocketState ss = null;
+        if (lh != null) {
+            // STOP, ERROR, DISCONNECT, TIMEOUT -> onClose
+            if (status == SocketStatus.TIMEOUT) {
+                // Called from maintain - we're removed from the poll
+                ((AprEndpoint) endpoint).getCometPoller().add(
+                        socketO.getSocket().longValue(), false); 
+                return SocketState.LONG;
+            }
+            if (status == SocketStatus.STOP || status == 
SocketStatus.DISCONNECT ||
+                    status == SocketStatus.ERROR) {
+                SpdyConnectionApr wrapper = lightProcessors.remove(socket);
+                if (wrapper != null) {
+                    wrapper.onClose();
+                }
+                return SocketState.CLOSED;
+            }
+            int rc = lh.onBlockingSocket();
+            ss = (rc == SpdyConnection.LONG) ? SocketState.LONG
+                    : SocketState.CLOSED;
+        } else {
+            // OPEN, no existing socket
+            if (!ssl || SSLExt.checkNPN(socket, SpdyContext.SPDY_NPN)) {
+                // NPN negotiated or not ssl
+                lh = new SpdyConnectionApr(socketW, spdyContext, ssl); 
+                
+                int rc = lh.onBlockingSocket();
+                ss = (rc == SpdyConnection.LONG) ? SocketState.LONG
+                        : SocketState.CLOSED;
+                if (ss == SocketState.LONG) {
+                    lightProcessors.put(socketO.getSocket().longValue(), lh);
+                }
+            } else {
+                return null;
+            }
+        }
+        
+        // OPEN is used for both 'first time' and 'new connection'
+        // In theory we shouldn't get another open while this is in 
+        // progress ( only after we add back to the poller )
+
+        if (ss == SocketState.LONG) {
+            log.info("Long poll: " + status);
+            ((AprEndpoint) endpoint).getCometPoller().add(
+                    socketO.getSocket().longValue(), false); 
+        }
+        return ss;
+    }
+    
+    public void onClose(SocketWrapper<Long> socketWrapper) {
+    }
+
+    
+}

Modified: tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java?rev=1292671&r1=1292670&r2=1292671&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java (original)
+++ tomcat/trunk/java/org/apache/tomcat/jni/SSLExt.java Thu Feb 23 07:03:52 2012
@@ -67,22 +67,22 @@ public final class SSLExt {
     public static native int setSessionData(long tcsock, byte[] data, int len);
 
 
-//    /**
-//     * Client: get the ticket received from server, if tickets are supported.
-//     */
-//    public static native int getTicket(long tcsock, byte[] resBuf);
-//
-//    /**
-//     * Client: set the previously received ticket.
-//     */
-//    public static native int setTicket(long tcsock, byte[] data, int len);
-//
-//    /**
-//     * Set the key used by server to generate tickets.
-//     * Key must be 48 bytes.
-//     */
-//    public static native int setTicketKeys(long ctx, byte[] data, int len);
-//
+    /**
+     * Client: get the ticket received from server, if tickets are supported.
+     */
+    public static native int getTicket(long tcsock, byte[] resBuf);
+
+    /**
+     * Client: set the previously received ticket.
+     */
+    public static native int setTicket(long tcsock, byte[] data, int len);
+
+    /**
+     * Set the key used by server to generate tickets.
+     * Key must be 48 bytes.
+     */
+    public static native int setTicketKeys(long ctx, byte[] data, int len);
+
     /**
      * For client side calls. Data should be a \0 terminated string
      */

Added: tomcat/trunk/java/org/apache/tomcat/spdy/CompressDeflater6.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/tomcat/spdy/CompressDeflater6.java?rev=1292671&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/tomcat/spdy/CompressDeflater6.java (added)
+++ tomcat/trunk/java/org/apache/tomcat/spdy/CompressDeflater6.java Thu Feb 23 
07:03:52 2012
@@ -0,0 +1,181 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.tomcat.spdy;
+
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.apache.tomcat.spdy.SpdyConnection.CompressSupport;
+
+/**
+ * Java6 Deflater with the workaround from tomcat http filters.
+ * 
+ */
+public class CompressDeflater6 implements CompressSupport {
+    public static long DICT_ID = 3751956914L;
+
+    // Make sure to use the latest from net/spdy/spdy_framer.cc, not from spec
+    private static String SPDY_DICT_S = 
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
+            + 
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
+            + 
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
+            + 
"-agent10010120020120220320420520630030130230330430530630740040140240340440"
+            + 
"5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
+            + 
"glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
+            + 
"ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
+            + 
"sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
+            + 
"oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
+            + 
"ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
+            + 
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
+            + 
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
+            + ".1statusversionurl ";
+
+    public static byte[] SPDY_DICT = SPDY_DICT_S.getBytes();
+    // C code uses this - not in the spec
+    static {
+        SPDY_DICT[SPDY_DICT.length - 1] = (byte) 0;
+    }
+
+    Deflater zipOut;
+    Inflater zipIn;
+
+    byte[] dict;
+
+    long dictId;
+
+    byte[] decompressBuffer;
+    int decOff;
+    int decMax;
+
+    byte[] compressBuffer;
+    int compressOff;
+
+    public CompressDeflater6() {
+    }
+
+    public void recycle() {
+        // TODO
+    }
+
+    public void init() {
+        if (zipOut != null) {
+            return;
+        }
+        try {
+            // false is important - otherwise 'bad method'
+            zipOut = new Deflater(Deflater.DEFAULT_COMPRESSION, false);
+            zipOut.setDictionary(SPDY_DICT);
+            zipIn = new Inflater();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public synchronized void compress(SpdyFrame frame, int start)
+            throws IOException {
+        init();
+        
+        if (compressBuffer == null) {
+            compressBuffer = new byte[frame.data.length];
+        }
+
+        // last byte for flush ?
+        zipOut.setInput(frame.data, start, frame.endData - start - 1);
+        int coff = start;
+        zipOut.setLevel(Deflater.DEFAULT_COMPRESSION);
+        while (true) {
+            int rd = zipOut.deflate(compressBuffer, coff, 
compressBuffer.length - coff);
+            if (rd == 0) {
+                // needsInput needs to be called - we're done with this frame ?
+                zipOut.setInput(frame.data, frame.endData - 1, 1);
+                zipOut.setLevel(Deflater.BEST_SPEED);
+                while (true) {
+                    rd = zipOut.deflate(compressBuffer, coff, 
compressBuffer.length - coff);
+                    coff += rd;
+                    if (rd == 0) {
+                        break;
+                    }
+                    byte[] b = new byte[compressBuffer.length * 2];
+                    System.arraycopy(compressBuffer, 0, b, 0, coff);
+                    compressBuffer = b;
+                }
+                zipOut.setLevel(Deflater.DEFAULT_COMPRESSION);
+                break;
+            }
+            coff += rd;
+        }
+
+        byte[] tmp = frame.data;
+        frame.data = compressBuffer;
+        compressBuffer = tmp;
+        frame.endData = coff;
+    }
+
+    @Override
+    public synchronized void decompress(SpdyFrame frame, int start)
+            throws IOException {
+        // stream id ( 4 ) + unused ( 2 )
+        // nvCount is compressed in impl - spec is different
+        init();
+
+        
+        if (decompressBuffer == null) {
+            decompressBuffer = new byte[frame.data.length];
+        }
+        
+        // will read from dec buffer to frame.data
+        decMax = frame.endData;
+        decOff = start;
+        
+        int off = start;
+        int max = frame.data.length;
+        
+        zipIn.setInput(frame.data, start, decMax - start);
+        
+        while (true) {
+            int rd;
+            try {
+                rd = zipIn.inflate(decompressBuffer, off, 
decompressBuffer.length - off);
+                if (rd == 0 && zipIn.needsDictionary()) {
+                    zipIn.setDictionary(SPDY_DICT);
+                    continue;
+                }
+            } catch (DataFormatException e) {
+                throw new IOException(e);
+            }
+            if (rd == 0) {
+                break;
+            }
+            if (rd == -1) {
+                break;
+            }
+            off += rd;
+            byte[] b = new byte[decompressBuffer.length * 2];
+            System.arraycopy(decompressBuffer, 0, b, 0, off);
+            decompressBuffer = b;
+            
+        }
+        byte[] tmpBuf = decompressBuffer;
+        decompressBuffer = frame.data;
+        frame.data = tmpBuf;
+        
+        frame.off = start;
+        frame.endData = off;
+    }
+}



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

Reply via email to