Issue #6728 - QUIC and HTTP/3

Added http3 Jetty module and distribution test.
Implemented simple logic to send the Alt-Svc header in HTTP/2 responses.
Updated JNA dependency to use jna-jpms.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-12-01 13:26:04 +01:00
parent 3d6578deee
commit 578ae30311
13 changed files with 198 additions and 7 deletions

View File

@ -130,7 +130,7 @@
<configuration>
<includeGroupIds>jakarta.transaction,org.eclipse.jetty</includeGroupIds>
<excludeGroupIds>
org.eclipse.jetty.orbit,org.eclipse.jetty.http2,org.eclipse.jetty.websocket,org.eclipse.jetty.fcgi,org.eclipse.jetty.toolchain,org.apache.taglibs
org.eclipse.jetty.orbit,org.eclipse.jetty.http2,org.eclipse.jetty.http3,org.eclipse.jetty.quic,org.eclipse.jetty.websocket,org.eclipse.jetty.fcgi,org.eclipse.jetty.toolchain,org.apache.taglibs
</excludeGroupIds>
<excludeArtifactIds>
apache-jsp,apache-jstl,jetty-start,jetty-slf4j-impl
@ -148,7 +148,7 @@
<configuration>
<includeGroupIds>jakarta.transaction,org.eclipse.jetty</includeGroupIds>
<excludeGroupIds>
org.eclipse.jetty.orbit,org.eclipse.jetty.http2,org.eclipse.jetty.websocket,org.eclipse.jetty.fcgi,org.eclipse.jetty.toolchain,org.apache.taglibs
org.eclipse.jetty.orbit,org.eclipse.jetty.http2,org.eclipse.jetty.http3,org.eclipse.jetty.quic,org.eclipse.jetty.websocket,org.eclipse.jetty.fcgi,org.eclipse.jetty.toolchain,org.apache.taglibs
</excludeGroupIds>
<excludeArtifactIds>
apache-jsp,apache-jstl,jetty-start
@ -264,6 +264,33 @@
<outputDirectory>${source-assembly-directory}/lib/http2</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-lib-http3-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty.http3,org.eclipse.jetty.quic,org.eclipse.jetty.quic.libquiche</includeGroupIds>
<includeArtifactIds>http3-server,http3-common,http3-qpack,quic-server,quic-common,quic-quiche-common,quic-quiche-jna,quic-quiche-foreign-incubator,jetty-quiche-native</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/http3</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-lib-http3-src-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty.http3</includeGroupIds>
<includeArtifactIds>http3-server</includeArtifactIds>
<includeTypes>jar</includeTypes>
<classifier>sources</classifier>
<outputDirectory>${source-assembly-directory}/lib/http3</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-lib-fcgi-deps</id>
<phase>generate-resources</phase>
@ -690,6 +717,10 @@
<groupId>org.eclipse.jetty.http2</groupId>
<artifactId>http2-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-alpn-server</artifactId>

View File

@ -86,6 +86,7 @@ public enum HttpHeader
*/
ACCEPT_RANGES("Accept-Ranges"),
AGE("Age"),
ALT_SVC("Alt-Svc"),
ETAG("ETag"),
LOCATION("Location"),
PROXY_AUTHENTICATE("Proxy-Authenticate"),

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addConnector">
<Arg>
<New id="http3Connector" class="org.eclipse.jetty.http3.server.HTTP3ServerConnector">
<Arg><Ref refid="Server" /></Arg>
<Arg><Ref refid="sslContextFactory" /></Arg>
<Arg>
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.http3.server.HTTP3ServerConnectionFactory">
<Arg><Ref refid="sslHttpConfig" /></Arg>
<Get name="HTTP3Configuration">
<Set name="streamIdleTimeout" property="jetty.http3.streamIdleTimeout" />
</Get>
</New>
</Item>
</Array>
</Arg>
<Set name="host" property="jetty.quic.host" />
<Set name="port"><Property name="jetty.quic.port" default="8444" /></Set>
<Set name="idleTimeout" property="jetty.quic.idleTimeout" />
<Get name="quicConfiguration">
<Set name="maxBidirectionalRemoteStreams" property="jetty.quic.maxBidirectionalRemoteStreams" />
<Set name="sessionRecvWindow" property="jetty.quic.sessionRecvWindow" />
<Set name="bidirectionalStreamRecvWindow" property="jetty.quic.bidirectionalStreamRecvWindow" />
</Get>
</New>
</Arg>
</Call>
</Configure>

View File

@ -0,0 +1,49 @@
[description]
Enables the support for the HTTP/3 protocol.
[tags]
connector
http3
http
quic
[depend]
http2
[files]
maven://net.java.dev.jna/jna-jpms/${jna.version}|lib/http3/jna-jpms-${jna.version}.jar
maven://org.mortbay.jetty.quic.libquiche/jetty-quiche-native/${jetty-quiche-native.version}|lib/http3/jetty-quiche-native-${jetty-quiche-native.version}.jar
[lib]
lib/http3/*.jar
[xml]
etc/jetty-http3.xml
[ini-template]
# tag::documentation[]
## The host/address to bind the connector to.
# jetty.quic.host=0.0.0.0
## The port the connector listens on.
# jetty.quic.port=8444
## The connector idle timeout, in milliseconds.
# jetty.quic.idleTimeout=30000
## Specifies the maximum number of concurrent requests per session.
# jetty.quic.maxBidirectionalRemoteStreams=128
## Specifies the session receive window (client to server) in bytes.
# jetty.quic.sessionRecvWindow=4194304
## Specifies the stream receive window (client to server) in bytes.
# jetty.quic.bidirectionalStreamRecvWindow=2097152
## Specifies the stream idle timeout, in milliseconds.
# jetty.http3.streamIdleTimeout=30000
# end::documentation[]
[ini]
jna.version?=@jna.version@
jetty-quiche-native.version?=@jetty-quiche-native.version@

View File

@ -15,6 +15,8 @@ package org.eclipse.jetty.http3.server;
import java.util.Objects;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http3.api.Session;
import org.eclipse.jetty.http3.api.Stream;
import org.eclipse.jetty.http3.frames.HeadersFrame;
@ -38,6 +40,16 @@ public class HTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionF
public HTTP3ServerConnectionFactory(HttpConfiguration configuration)
{
super(configuration, new HTTP3SessionListener());
configuration.addCustomizer((connector, httpConfig, request) ->
{
HTTP3ServerConnector http3Connector = connector.getServer().getBean(HTTP3ServerConnector.class);
if (http3Connector != null && HttpVersion.HTTP_2.is(request.getHttpVersion().asString()))
{
HttpField altSvc = http3Connector.getAltSvcHttpField();
if (altSvc != null)
request.getResponse().getHttpFields().add(altSvc);
}
});
}
private static class HTTP3SessionListener implements Session.Server.Listener

View File

@ -15,6 +15,9 @@ package org.eclipse.jetty.http3.server;
import java.util.concurrent.Executor;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.quic.server.QuicServerConnector;
import org.eclipse.jetty.server.ConnectionFactory;
@ -27,6 +30,8 @@ import org.eclipse.jetty.util.thread.Scheduler;
*/
public class HTTP3ServerConnector extends QuicServerConnector
{
private HttpField altSvcHttpField;
public HTTP3ServerConnector(Server server, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
this(server, null, null, null, sslContextFactory, factories);
@ -41,4 +46,16 @@ public class HTTP3ServerConnector extends QuicServerConnector
getQuicConfiguration().setMaxUnidirectionalRemoteStreams(8);
getQuicConfiguration().setUnidirectionalStreamRecvWindow(1024 * 1024);
}
@Override
protected void doStart() throws Exception
{
super.doStart();
altSvcHttpField = new PreEncodedHttpField(HttpHeader.ALT_SVC, String.format("h3=\":%d\"", getLocalPort()));
}
public HttpField getAltSvcHttpField()
{
return altSvcHttpField;
}
}

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.quic.common;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.Arrays;
import java.util.Collection;
import java.util.EventListener;
@ -514,7 +515,7 @@ public abstract class QuicSession extends ContainerLifeCycle
if (LOG.isDebugEnabled())
LOG.debug("connection closed {}", QuicSession.this);
byteBufferPool.release(cipherBuffer);
finishOutwardClose(null);
finishOutwardClose(new ClosedChannelException());
}
@Override

View File

@ -38,7 +38,7 @@
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<artifactId>jna-jpms</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -22,6 +22,7 @@ import java.nio.channels.SelectionKey;
import java.nio.file.Files;
import java.util.EventListener;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
@ -157,14 +158,19 @@ public class QuicServerConnector extends AbstractNetworkConnector
super.doStart();
selectorManager.accept(datagramChannel);
Set<String> aliases = sslContextFactory.getAliases();
if (aliases.isEmpty())
throw new IllegalStateException("Invalid KeyStore: no aliases");
String alias = sslContextFactory.getCertAlias();
if (alias == null)
alias = aliases.stream().findFirst().orElse("mykey");
char[] keyStorePassword = sslContextFactory.getKeyStorePassword().toCharArray();
String keyManagerPassword = sslContextFactory.getKeyManagerPassword();
SSLKeyPair keyPair = new SSLKeyPair(
sslContextFactory.getKeyStoreResource().getFile(),
sslContextFactory.getKeyStoreType(),
keyStorePassword,
alias == null ? "mykey" : alias,
alias,
keyManagerPassword == null ? keyStorePassword : keyManagerPassword.toCharArray()
);
File[] pemFiles = keyPair.export(new File(System.getProperty("java.io.tmpdir")));

View File

@ -1126,7 +1126,7 @@
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<artifactId>jna-jpms</artifactId>
<version>${jna.version}</version>
</dependency>
<dependency>

View File

@ -84,6 +84,11 @@
<artifactId>http2-http-client-transport</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http3</groupId>
<artifactId>http3-http-client-transport</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.demos</groupId>
<artifactId>demo-simple-webapp</artifactId>

View File

@ -33,9 +33,12 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
import org.eclipse.jetty.http3.client.HTTP3Client;
import org.eclipse.jetty.http3.client.http.HttpClientTransportOverHTTP3;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.start.FS;
import org.eclipse.jetty.toolchain.test.PathAssert;
@ -1106,4 +1109,38 @@ public class DistributionTests extends AbstractJettyHomeTest
}
}
}
@Test
public void testH3() throws Exception
{
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
try (JettyHomeTester.Run run1 = distribution.start("--add-modules=http3,test-keystore"))
{
assertTrue(run1.awaitFor(10, TimeUnit.SECONDS));
assertEquals(0, run1.getExitValue());
int h2Port = distribution.freePort();
int h3Port = distribution.freePort();
try (JettyHomeTester.Run run2 = distribution.start(List.of("jetty.ssl.selectors=1", "jetty.ssl.port=" + h2Port, "jetty.quic.port=" + h3Port)))
{
assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS));
HTTP3Client http3Client = new HTTP3Client();
http3Client.getQuicConfiguration().setVerifyPeerCertificates(false);
this.client = new HttpClient(new HttpClientTransportOverHTTP3(http3Client));
this.client.start();
ContentResponse response = this.client.newRequest("localhost", h3Port)
.scheme(HttpScheme.HTTPS.asString())
.path("/path")
.timeout(15, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus());
}
}
}
}

View File

@ -1,5 +1,4 @@
# Jetty Logging using jetty-slf4j-impl
org.eclipse.jetty.logging.appender.MESSAGE_ESCAPE=false
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.tests.distribution.LEVEL=DEBUG