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