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: [email protected]
For additional commands, e-mail: [email protected]