diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
index f2d312a21fc..5b9c8f773a4 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
@@ -60,17 +60,17 @@ public class HTTP2Connection extends AbstractConnection
return session;
}
-
+
protected Parser getParser()
{
return parser;
}
-
- protected void prefill(ByteBuffer buffer)
+
+ protected void setInputBuffer(ByteBuffer buffer)
{
- producer.buffer=buffer;
+ producer.buffer = buffer;
}
-
+
@Override
public void onOpen()
{
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/PrefaceFrame.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/PrefaceFrame.java
index 2bfa9af5467..2589f3d2d8d 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/PrefaceFrame.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/frames/PrefaceFrame.java
@@ -18,14 +18,28 @@
package org.eclipse.jetty.http2.frames;
+import java.nio.charset.StandardCharsets;
+
public class PrefaceFrame extends Frame
{
- public static final byte[] PREFACE_BYTES = new byte[]
- {
- 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
- 0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
- 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
- };
+ /**
+ * The bytes of the HTTP/2 preface that form a legal HTTP/1.1
+ * request, used in the direct upgrade.
+ */
+ public static final byte[] PREFACE_PREAMBLE_BYTES = (
+ "PRI * HTTP/2.0\r\n" +
+ "\r\n"
+ ).getBytes(StandardCharsets.US_ASCII);
+
+ /**
+ * The HTTP/2 preface bytes.
+ */
+ public static final byte[] PREFACE_BYTES = (
+ "PRI * HTTP/2.0\r\n" +
+ "\r\n" +
+ "SM\r\n" +
+ "\r\n"
+ ).getBytes(StandardCharsets.US_ASCII);
public PrefaceFrame()
{
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PrefaceParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PrefaceParser.java
index f511f0891b6..71caff341e5 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PrefaceParser.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/PrefaceParser.java
@@ -38,20 +38,19 @@ public class PrefaceParser
this.listener = listener;
}
- /* ------------------------------------------------------------ */
- /** Unsafe upgrade is an unofficial upgrade from HTTP/1.0 to HTTP/2.0
- * initiated when a the org.eclipse.jetty.server.HttpConnection
sees a PRI * HTTP/2.0 prefix
- * that indicates a HTTP/2.0 client is attempting a h2c direct connection.
- * This is not a standard HTTP/1.1 Upgrade path.
+ /**
+ *
Advances this parser after the {@link PrefaceFrame#PREFACE_PREAMBLE_BYTES}.
+ *This allows the HTTP/1.1 parser to parse the preamble of the preface, + * which is a legal HTTP/1.1 request, and this parser will parse the remaining + * bytes, that are not parseable by a HTTP/1.1 parser.
*/ - public void directUpgrade() + protected void directUpgrade() { - if (cursor!=0) + if (cursor != 0) throw new IllegalStateException(); - cursor=18; + cursor = PrefaceFrame.PREFACE_PREAMBLE_BYTES.length; } - - + public boolean parse(ByteBuffer buffer) { while (buffer.hasRemaining()) diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java index 5338b0f878d..fa18a8d6c93 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/ServerParser.java @@ -41,20 +41,25 @@ public class ServerParser extends Parser this.prefaceParser = new PrefaceParser(listener); } - /* ------------------------------------------------------------ */ - /** Unsafe upgrade is an unofficial upgrade from HTTP/1.0 to HTTP/2.0 - * initiated when a theorg.eclipse.jetty.server.HttpConnection
sees a PRI * HTTP/2.0 prefix
- * that indicates a HTTP/2.0 client is attempting a h2c direct connection.
- * This is not a standard HTTP/1.1 Upgrade path.
+ /**
+ * A direct upgrade is an unofficial upgrade from HTTP/1.1 to HTTP/2.0.
+ *A direct upgrade is initiated when {@code org.eclipse.jetty.server.HttpConnection} + * sees a request with these bytes:
+ *+ * PRI * HTTP/2.0\r\n + * \r\n + *+ *
This request is part of the HTTP/2.0 preface, indicating that a + * HTTP/2.0 client is attempting a h2c direct connection.
+ *This is not a standard HTTP/1.1 Upgrade path.
*/ public void directUpgrade() { - if (state!=State.PREFACE) + if (state != State.PREFACE) throw new IllegalStateException(); prefaceParser.directUpgrade(); } - - + @Override public void parse(ByteBuffer buffer) { diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java index c9aeb362666..e9bbb49728f 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java @@ -25,7 +25,6 @@ import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.HTTP2Connection; import org.eclipse.jetty.http2.api.server.ServerSessionListener; import org.eclipse.jetty.http2.generator.Generator; -import org.eclipse.jetty.http2.parser.Parser; import org.eclipse.jetty.http2.parser.ServerParser; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; @@ -40,12 +39,12 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne private int initialStreamSendWindow = FlowControlStrategy.DEFAULT_WINDOW_SIZE; private int maxConcurrentStreams = -1; private final HttpConfiguration httpConfiguration; - + public AbstractHTTP2ServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration) { - this(httpConfiguration,"h2-17","h2-16","h2-15","h2-14","h2"); + this(httpConfiguration,"h2","h2-17","h2-16","h2-15","h2-14"); } - + protected AbstractHTTP2ServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration, String... protocols) { super(protocols); @@ -86,7 +85,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne { return httpConfiguration; } - + @Override public Connection newConnection(Connector connector, EndPoint endPoint) { @@ -102,8 +101,8 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne // the typical case is that the connection will be busier and the // stream idle timeout will expire earlier that the connection's. session.setStreamIdleTimeout(endPoint.getIdleTimeout()); - - Parser parser = newServerParser(connector, session); + + ServerParser parser = newServerParser(connector, session); HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener); diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java index b9ba2d4e7a4..e9276a805f7 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2CServerConnectionFactory.java @@ -34,33 +34,33 @@ import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ /** HTTP2 Clear Text Connection factory. - *This extension of HTTP2ServerConnection Factory sets the + *
This extension of HTTP2ServerConnection Factory sets the * protocol name to "h2c" as used by the clear text upgrade mechanism * for HTTP2 and marks all TLS ciphers as unacceptable. *
- *If used in combination with a {@link HttpConnectionFactory} as the + *
If used in combination with a {@link HttpConnectionFactory} as the
* default protocol, this factory can support the non-standard direct
- * update mechanism, where a HTTP1 request of the form "PRI * HTTP/2.0"
+ * update mechanism, where a HTTP1 request of the form "PRI * HTTP/2.0"
* is used to trigger a switch to a HTTP2 connection. This approach
- * allows a single port to accept either HTTP/1 or HTTP/2 direct
+ * allows a single port to accept either HTTP/1 or HTTP/2 direct
* connections.
*/
-public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory implements ConnectionFactory.Upgrading
+public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory implements ConnectionFactory.Upgrading
{
private static final Logger LOG = Log.getLogger(HTTP2CServerConnectionFactory.class);
public HTTP2CServerConnectionFactory(@Name("config") HttpConfiguration httpConfiguration)
{
- super(httpConfiguration,"h2c","h2c-14");
+ super(httpConfiguration,"h2c","h2c-17","h2c-16","h2c-15","h2c-14");
}
-
+
@Override
public boolean isAcceptable(String protocol, String tlsProtocol, String tlsCipher)
{
// Never use TLS with h2c
return false;
}
-
+
@Override
public Connection upgradeConnection(Connector connector, EndPoint endPoint, Request request, HttpFields response101) throws BadMessageException
{
@@ -69,9 +69,9 @@ public class HTTP2CServerConnectionFactory extends HTTP2ServerConnectionFactory
if (request.getContentLength() > 0)
return null;
-
+
HTTP2ServerConnection connection = (HTTP2ServerConnection)newConnection(connector, endPoint);
- if (connection.upgrade(request, response101))
+ if (connection.upgrade(request))
return connection;
return null;
}
diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java
index 795b038b288..07ffd987ada 100644
--- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java
+++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java
@@ -23,7 +23,7 @@ import java.util.Queue;
import java.util.concurrent.Executor;
import org.eclipse.jetty.http.BadMessageException;
-import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.MetaData;
@@ -35,7 +35,6 @@ import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
-import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.http2.parser.ServerParser;
import org.eclipse.jetty.http2.parser.SettingsBodyParser;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -56,7 +55,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
private final HttpConfiguration httpConfig;
private HeadersFrame upgradeRequest;
- public HTTP2ServerConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, HttpConfiguration httpConfig, Parser parser, ISession session, int inputBufferSize, ServerSessionListener listener)
+ public HTTP2ServerConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, HttpConfiguration httpConfig, ServerParser parser, ISession session, int inputBufferSize, ServerSessionListener listener)
{
super(byteBufferPool, executor, endPoint, parser, session, inputBufferSize);
this.listener = listener;
@@ -64,11 +63,17 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
}
@Override
- public void onUpgradeTo(ByteBuffer prefilled)
+ protected ServerParser getParser()
+ {
+ return (ServerParser)super.getParser();
+ }
+
+ @Override
+ public void onUpgradeTo(ByteBuffer buffer)
{
if (LOG.isDebugEnabled())
- LOG.debug("HTTP2 onUpgradeTo {} {}", this, BufferUtil.toDetailString(prefilled));
- prefill(prefilled);
+ LOG.debug("HTTP2 onUpgradeTo {} {}", this, BufferUtil.toDetailString(buffer));
+ setInputBuffer(buffer);
}
@Override
@@ -143,16 +148,19 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
return channel;
}
- public boolean upgrade(Request request, HttpFields response101)
+ public boolean upgrade(Request request)
{
if (HttpMethod.PRI.is(request.getMethod()))
{
- ((ServerParser)getParser()).directUpgrade();
+ getParser().directUpgrade();
}
else
{
- String value = request.getFields().getField(HttpHeader.HTTP2_SETTINGS).getValue();
- final byte[] settings = B64Code.decodeRFC4648URL(value);
+ HttpField settingsField = request.getFields().getField(HttpHeader.HTTP2_SETTINGS);
+ if (settingsField == null)
+ throw new BadMessageException("Missing " + HttpHeader.HTTP2_SETTINGS + " header");
+ String value = settingsField.getValue();
+ final byte[] settings = B64Code.decodeRFC4648URL(value == null ? "" : value);
if (LOG.isDebugEnabled())
LOG.debug("{} settings {}",this,TypeUtil.toHexString(settings));
diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServer.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServer.java
index 2c66cbfbfd9..7c4723c69b4 100644
--- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServer.java
+++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServer.java
@@ -31,35 +31,29 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
public class HTTP2CServer extends Server
{
public HTTP2CServer(int port)
{
- // HTTP connector
HttpConfiguration config = new HttpConfiguration();
+ // HTTP + HTTP/2 connector
ServerConnector http = new ServerConnector(this,new HttpConnectionFactory(config), new HTTP2CServerConnectionFactory(config));
- http.setHost("localhost");
http.setPort(port);
- http.setIdleTimeout(30000);
-
- // Set the connector
addConnector(http);
- // Set a handler
+ ((QueuedThreadPool)getThreadPool()).setName("server");
+
setHandler(new SimpleHandler());
}
-
+
public static void main(String... args ) throws Exception
{
- // The Server
HTTP2CServer server = new HTTP2CServer(8080);
-
- // Start the server
server.start();
- server.join();
}
-
+
private static class SimpleHandler extends AbstractHandler
{
@Override
@@ -69,15 +63,14 @@ public class HTTP2CServer extends Server
String code=request.getParameter("code");
if (code!=null)
response.setStatus(Integer.parseInt(code));
-
+
response.setHeader("Custom","Value");
response.setContentType("text/plain");
String content = "Hello from Jetty using "+request.getProtocol() +"\n";
content+="uri="+request.getRequestURI()+"\n";
content+="date="+new Date()+"\n";
response.setContentLength(content.length());
- response.getOutputStream().print(content);
+ response.getOutputStream().print(content);
}
-
}
}
diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java
index 05a0782a21d..ac5d6aeb9f0 100644
--- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java
+++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java
@@ -18,9 +18,7 @@
package org.eclipse.jetty.http2.server;
-import static org.hamcrest.Matchers.containsString;
-import static org.junit.Assert.assertThat;
-
+import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
@@ -45,30 +43,35 @@ import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Utf8StringBuilder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
public class HTTP2CServerTest extends AbstractServerTest
{
- HTTP2CServer _server;
- int _port;
-
+ private HTTP2CServer _server;
+ private int _port;
+
@Before
public void before() throws Exception
{
- _server=new HTTP2CServer(0);
+ _server = new HTTP2CServer(0);
_server.start();
- _port=((NetworkConnector)_server.getConnectors()[0]).getLocalPort();
+ _port = ((NetworkConnector)_server.getConnectors()[0]).getLocalPort();
}
-
+
@After
public void after() throws Exception
{
_server.stop();
}
-
+
@Test
public void testHTTP_1_0_Simple() throws Exception
{
@@ -76,14 +79,14 @@ public class HTTP2CServerTest extends AbstractServerTest
{
client.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
client.getOutputStream().flush();
-
+
String response = IO.toString(client.getInputStream());
-
- assertThat(response,containsString("HTTP/1.1 200 OK"));
- assertThat(response,containsString("Hello from Jetty using HTTP/1.0"));
+
+ assertThat(response, containsString("HTTP/1.1 200 OK"));
+ assertThat(response, containsString("Hello from Jetty using HTTP/1.0"));
}
}
-
+
@Test
public void testHTTP_1_1_Simple() throws Exception
{
@@ -92,27 +95,131 @@ public class HTTP2CServerTest extends AbstractServerTest
client.getOutputStream().write("GET /one HTTP/1.1\r\nHost: localhost\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
client.getOutputStream().write("GET /two HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
client.getOutputStream().flush();
-
+
String response = IO.toString(client.getInputStream());
-
- assertThat(response,containsString("HTTP/1.1 200 OK"));
- assertThat(response,containsString("Hello from Jetty using HTTP/1.1"));
- assertThat(response,containsString("uri=/one"));
- assertThat(response,containsString("uri=/two"));
+
+ assertThat(response, containsString("HTTP/1.1 200 OK"));
+ assertThat(response, containsString("Hello from Jetty using HTTP/1.1"));
+ assertThat(response, containsString("uri=/one"));
+ assertThat(response, containsString("uri=/two"));
}
}
@Test
- public void testHTTP_2_0_Simple() throws Exception
+ public void testHTTP_1_1_Upgrade() throws Exception
+ {
+ try (Socket client = new Socket("localhost", _port))
+ {
+ OutputStream output = client.getOutputStream();
+ output.write(("" +
+ "GET /one HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: Upgrade, HTTP2-Settings\r\n" +
+ "Upgrade: h2c\r\n" +
+ "HTTP2-Settings: \r\n" +
+ "\r\n").getBytes(StandardCharsets.ISO_8859_1));
+ output.flush();
+
+ InputStream input = client.getInputStream();
+ Utf8StringBuilder upgrade = new Utf8StringBuilder();
+ int crlfs = 0;
+ while (true)
+ {
+ int read = input.read();
+ if (read == '\r' || read == '\n')
+ ++crlfs;
+ else
+ crlfs = 0;
+ upgrade.append((byte)read);
+ if (crlfs == 4)
+ break;
+ }
+
+ assertTrue(upgrade.toString().startsWith("HTTP/1.1 101 "));
+
+ byteBufferPool = new MappedByteBufferPool();
+ generator = new Generator(byteBufferPool);
+
+ final AtomicReference