Author: markt Date: Fri May 29 15:48:20 2015 New Revision: 1682507 URL: http://svn.apache.org/r1682507 Log: First pass at adding h2c support The requirement to process the initial HTTP/1.1 request as HTTP/2 stream 1 makes for some interesting code paths. The main motivation behind this is so I can start writing some unit tests for the HTTP/2 protocol implementation and it will be a lot easier to write and debug these if I can do it in the clear.
Modified: tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java tomcat/trunk/java/org/apache/coyote/http2/ConnectionPrefaceParser.java tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java tomcat/trunk/java/org/apache/coyote/http2/Stream.java tomcat/trunk/test/org/apache/coyote/http2/TestAbstractStream.java Modified: tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java?rev=1682507&r1=1682506&r2=1682507&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/UpgradeProtocol.java Fri May 29 15:48:20 2015 @@ -16,6 +16,7 @@ */ package org.apache.coyote; +import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; import org.apache.tomcat.util.net.SocketWrapperBase; public interface UpgradeProtocol { @@ -66,4 +67,14 @@ public interface UpgradeProtocol { * protocol. */ public Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter adapter); + + + /** + * @param adapter The Adapter to use to configure the new upgrade handler + * @param request A copy (may be incomplete) of the request that triggered + * the upgrade + * + * @return An instance of the HTTP upgrade handler for this protocol + */ + public InternalHttpUpgradeHandler getInteralUpgradeHandler(Adapter adapter, Request request); } Modified: tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java?rev=1682507&r1=1682506&r2=1682507&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/AbstractHttp11Protocol.java Fri May 29 15:48:20 2015 @@ -293,6 +293,7 @@ public abstract class AbstractHttp11Prot * The upgrade protocol instances configured. */ private final List<UpgradeProtocol> upgradeProtocols = new ArrayList<>(); + @Override public void addUpgradeProtocol(UpgradeProtocol upgradeProtocol) { upgradeProtocols.add(upgradeProtocol); } @@ -650,7 +651,7 @@ public abstract class AbstractHttp11Prot Http11Processor processor = new Http11Processor( proto.getMaxHttpHeaderSize(), proto.getEndpoint(), proto.getMaxTrailerSize(), proto.allowedTrailerHeaders, proto.getMaxExtensionSize(), - proto.getMaxSwallowSize()); + proto.getMaxSwallowSize(), proto.httpUpgradeProtocols); proto.configureProcessor(processor); register(processor); return processor; Modified: tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java?rev=1682507&r1=1682506&r2=1682507&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java (original) +++ tomcat/trunk/java/org/apache/coyote/http11/Http11Processor.java Fri May 29 15:48:20 2015 @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; @@ -32,7 +33,9 @@ import org.apache.coyote.AbstractProcess import org.apache.coyote.ActionCode; import org.apache.coyote.AsyncContextCallback; import org.apache.coyote.ErrorState; +import org.apache.coyote.Request; import org.apache.coyote.RequestInfo; +import org.apache.coyote.UpgradeProtocol; import org.apache.coyote.http11.filters.BufferedInputFilter; import org.apache.coyote.http11.filters.ChunkedInputFilter; import org.apache.coyote.http11.filters.ChunkedOutputFilter; @@ -42,6 +45,7 @@ import org.apache.coyote.http11.filters. import org.apache.coyote.http11.filters.SavedRequestInputFilter; import org.apache.coyote.http11.filters.VoidInputFilter; import org.apache.coyote.http11.filters.VoidOutputFilter; +import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.ExceptionUtils; @@ -238,8 +242,15 @@ public class Http11Processor extends Abs protected SSLSupport sslSupport; + /** + * UpgradeProtocol information + */ + private final Map<String,UpgradeProtocol> httpUpgradeProtocols; + + public Http11Processor(int maxHttpHeaderSize, AbstractEndpoint<?> endpoint,int maxTrailerSize, - Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize) { + Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize, + Map<String,UpgradeProtocol> httpUpgradeProtocols) { super(endpoint); userDataHelper = new UserDataHelper(log); @@ -271,6 +282,8 @@ public class Http11Processor extends Abs outputBuffer.addFilter(new GzipOutputFilter()); pluggableFilterIndex = inputBuffer.getFilters().length; + + this.httpUpgradeProtocols = httpUpgradeProtocols; } @@ -1014,6 +1027,25 @@ public class Http11Processor extends Abs getAdapter().log(request, response, 0); } + // Has an upgrade been requested? + String connection = request.getHeader(Constants.CONNECTION); + if (connection != null && connection.toLowerCase().contains("upgrade")) { + // Check the protocol + String requestedProtocol = request.getHeader("Upgrade"); + + UpgradeProtocol upgradeProtocol = httpUpgradeProtocols.get(requestedProtocol); + if (upgradeProtocol != null) { + // TODO Figure out how to handle request bodies at this + // point. + + InternalHttpUpgradeHandler upgradeHandler = + upgradeProtocol.getInteralUpgradeHandler( + getAdapter(), cloneRequest(request)); + action(ActionCode.UPGRADE, upgradeHandler); + return SocketState.UPGRADING; + } + } + if (!getErrorState().isError()) { // Setting up filters, and parse some request headers rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE); @@ -1143,6 +1175,20 @@ public class Http11Processor extends Abs } + private Request cloneRequest(Request source) throws IOException { + Request dest = new Request(); + + // Transfer the minimal information required for the copy of the Request + // that is passed to the HTTP upgrade process + + dest.decodedURI().duplicate(source.decodedURI()); + dest.method().duplicate(source.method()); + dest.getMimeHeaders().duplicate(source.getMimeHeaders()); + dest.requestURI().duplicate(source.requestURI()); + + return dest; + + } private boolean handleIncompleteRequestLineRead() { // Haven't finished reading the request so keep the socket // open Modified: tomcat/trunk/java/org/apache/coyote/http2/ConnectionPrefaceParser.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/ConnectionPrefaceParser.java?rev=1682507&r1=1682506&r2=1682507&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/ConnectionPrefaceParser.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/ConnectionPrefaceParser.java Fri May 29 15:48:20 2015 @@ -41,10 +41,10 @@ public class ConnectionPrefaceParser { private volatile boolean error = false; - public boolean parse(SocketWrapperBase<?> socketWrapper) { + public boolean parse(SocketWrapperBase<?> socketWrapper, boolean block) { int read = 0; try { - read = socketWrapper.read(false, data, pos, EXPECTED.length - pos); + read = socketWrapper.read(block, data, pos, EXPECTED.length - pos); } catch (IOException ioe) { log.error(sm.getString("connectionPrefaceParser.ioError"), ioe); error = true; Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java?rev=1682507&r1=1682506&r2=1682507&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java Fri May 29 15:48:20 2015 @@ -20,7 +20,9 @@ import java.nio.charset.StandardCharsets import org.apache.coyote.Adapter; import org.apache.coyote.Processor; +import org.apache.coyote.Request; import org.apache.coyote.UpgradeProtocol; +import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; import org.apache.coyote.http11.upgrade.UpgradeProcessorInternal; import org.apache.tomcat.util.net.SocketWrapperBase; @@ -51,8 +53,15 @@ public class Http2Protocol implements Up @Override public Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter adapter) { - UpgradeProcessorInternal processor = - new UpgradeProcessorInternal(socketWrapper, null, new Http2UpgradeHandler(adapter)); + UpgradeProcessorInternal processor = new UpgradeProcessorInternal(socketWrapper, null, + getInteralUpgradeHandler(adapter, null)); return processor; } + + + @Override + public InternalHttpUpgradeHandler getInteralUpgradeHandler(Adapter adapter, + Request coyoteRequest) { + return new Http2UpgradeHandler(adapter, coyoteRequest); + } } Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1682507&r1=1682506&r2=1682507&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Fri May 29 15:48:20 2015 @@ -32,11 +32,13 @@ import java.util.concurrent.atomic.Atomi import javax.servlet.http.WebConnection; import org.apache.coyote.Adapter; +import org.apache.coyote.Request; import org.apache.coyote.Response; import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler; import org.apache.coyote.http2.HpackEncoder.State; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.codec.binary.Base64; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; import org.apache.tomcat.util.net.SSLSupport; @@ -92,6 +94,10 @@ public class Http2UpgradeHandler extends private static final byte[] GOAWAY = { 0x07, 0x00, 0x00, 0x00, 0x00 }; + private static final String HTTP2_SETTINGS_HEADER = "HTTP2-Settings"; + private static final byte[] HTTP2_UPGRADE_ACK = ("HTTP/1.1 101 Switching Protocols\r\n" + + "Connection: Upgrade\r\nUpgrade: h2c\r\n\r\n").getBytes(StandardCharsets.ISO_8859_1); + private final int connectionId; private final Adapter adapter; @@ -119,28 +125,85 @@ public class Http2UpgradeHandler extends private long backLogSize = 0; - public Http2UpgradeHandler(Adapter adapter) { + public Http2UpgradeHandler(Adapter adapter, Request coyoteRequest) { super (STREAM_ID_ZERO); this.adapter = adapter; this.connectionId = connectionIdGenerator.getAndIncrement(); + + // Initial HTTP request becomes stream 0. + if (coyoteRequest != null) { + Integer key = Integer.valueOf(1); + Stream stream = new Stream(key, this, coyoteRequest); + streams.put(key, stream); + maxRemoteStreamId = 1; + } } @Override - public void init(WebConnection unused) { + public void init(WebConnection webConnection) { if (log.isDebugEnabled()) { log.debug(sm.getString("upgradeHandler.init", Long.toString(connectionId))); } initialized = true; + Stream stream = null; + + if (webConnection != null) { + // HTTP/2 started via HTTP upgrade. + // The initial HTTP/1.1 request is available as Stream 1. + + try { + // Acknowledge the upgrade request + socketWrapper.write(true, HTTP2_UPGRADE_ACK, 0, HTTP2_UPGRADE_ACK.length); + socketWrapper.flush(true); + + // Process the initial settings frame + stream = getStream(1); + String base64Settings = stream.getCoyoteRequest().getHeader(HTTP2_SETTINGS_HEADER); + byte[] settings = Base64.decodeBase64(base64Settings); + + if (settings.length % 6 != 0) { + // Invalid payload length for settings + // TODO i18n + throw new IllegalStateException(); + } + + for (int i = 0; i < settings.length % 6; i++) { + int id = ByteUtil.getTwoBytes(settings, i * 6); + int value = ByteUtil.getFourBytes(settings, (i * 6) + 2); + remoteSettings.set(id, value); + } + + firstFrame = false; + hpackDecoder = new HpackDecoder(remoteSettings.getHeaderTableSize()); + hpackEncoder = new HpackEncoder(localSettings.getHeaderTableSize()); + + if (!connectionPrefaceParser.parse(socketWrapper, true)) { + // TODO i18n + throw new IllegalStateException(); + } + } catch (IOException ioe) { + // TODO i18n + throw new IllegalStateException(); + } + } // Send the initial settings frame try { socketWrapper.write(true, SETTINGS_EMPTY, 0, SETTINGS_EMPTY.length); socketWrapper.flush(true); + } catch (IOException ioe) { throw new IllegalStateException(sm.getString("upgradeHandler.sendPrefaceFail"), ioe); } + + if (webConnection != null) { + // Process the initial request on a container thread + StreamProcessor streamProcessor = new StreamProcessor(stream, adapter, socketWrapper); + streamProcessor.setSslSupport(sslSupport); + socketWrapper.getEndpoint().getExecutor().execute(streamProcessor); + } } @@ -175,7 +238,7 @@ public class Http2UpgradeHandler extends // Gets set to null once the connection preface has been // successfully parsed. if (connectionPrefaceParser != null) { - if (!connectionPrefaceParser.parse(socketWrapper)) { + if (!connectionPrefaceParser.parse(socketWrapper, false)) { if (connectionPrefaceParser.isError()) { // Any errors will have already been logged. close(); Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1682507&r1=1682506&r2=1682507&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Fri May 29 15:48:20 2015 @@ -38,19 +38,26 @@ public class Stream extends AbstractStre private volatile int weight = Constants.DEFAULT_WEIGHT; private final Http2UpgradeHandler handler; - private final Request coyoteRequest = new Request(); + private final Request coyoteRequest; private final Response coyoteResponse = new Response(); private final StreamInputBuffer inputBuffer = new StreamInputBuffer(); private final StreamOutputBuffer outputBuffer = new StreamOutputBuffer(); public Stream(Integer identifier, Http2UpgradeHandler handler) { + this(identifier, handler, new Request()); + } + + + public Stream(Integer identifier, Http2UpgradeHandler handler, Request coyoteRequest) { super(identifier); this.handler = handler; setParentStream(handler); setWindowSize(handler.getRemoteSettings().getInitialWindowSize()); - coyoteRequest.setInputBuffer(inputBuffer); - coyoteResponse.setOutputBuffer(outputBuffer); + this.coyoteRequest = coyoteRequest; + this.coyoteRequest.setInputBuffer(inputBuffer); + this.coyoteResponse.setOutputBuffer(outputBuffer); + this.coyoteRequest.setResponse(coyoteResponse); } Modified: tomcat/trunk/test/org/apache/coyote/http2/TestAbstractStream.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/TestAbstractStream.java?rev=1682507&r1=1682506&r2=1682507&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/coyote/http2/TestAbstractStream.java (original) +++ tomcat/trunk/test/org/apache/coyote/http2/TestAbstractStream.java Fri May 29 15:48:20 2015 @@ -28,7 +28,7 @@ public class TestAbstractStream { @Test public void testDependenciesFig3() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(null); + Http2UpgradeHandler handler = new Http2UpgradeHandler(null, null); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -59,7 +59,7 @@ public class TestAbstractStream { @Test public void testDependenciesFig4() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(null); + Http2UpgradeHandler handler = new Http2UpgradeHandler(null, null); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -90,7 +90,7 @@ public class TestAbstractStream { @Test public void testDependenciesFig5NonExclusive() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(null); + Http2UpgradeHandler handler = new Http2UpgradeHandler(null, null); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -132,7 +132,7 @@ public class TestAbstractStream { @Test public void testDependenciesFig5Exclusive() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(null); + Http2UpgradeHandler handler = new Http2UpgradeHandler(null, null); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org