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