diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml index 72c99d8ae9e..5e9a7c0b6fd 100644 --- a/jetty-home/pom.xml +++ b/jetty-home/pom.xml @@ -130,7 +130,7 @@ jakarta.transaction,org.eclipse.jetty - 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 apache-jsp,apache-jstl,jetty-start,jetty-slf4j-impl @@ -148,7 +148,7 @@ jakarta.transaction,org.eclipse.jetty - 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 apache-jsp,apache-jstl,jetty-start @@ -264,6 +264,33 @@ ${source-assembly-directory}/lib/http2 + + copy-lib-http3-deps + generate-resources + + copy-dependencies + + + org.eclipse.jetty.http3,org.eclipse.jetty.quic,org.eclipse.jetty.quic.libquiche + http3-server,http3-common,http3-qpack,quic-server,quic-common,quic-quiche-common,quic-quiche-jna,quic-quiche-foreign-incubator,jetty-quiche-native + jar + ${assembly-directory}/lib/http3 + + + + copy-lib-http3-src-deps + generate-resources + + copy-dependencies + + + org.eclipse.jetty.http3 + http3-server + jar + sources + ${source-assembly-directory}/lib/http3 + + copy-lib-fcgi-deps generate-resources @@ -690,6 +717,10 @@ org.eclipse.jetty.http2 http2-server + + org.eclipse.jetty.http3 + http3-server + org.eclipse.jetty jetty-alpn-server diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java index 752f0dcfe8e..78632d1e84e 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java @@ -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"), diff --git a/jetty-http3/http3-server/src/main/config/etc/jetty-http3.xml b/jetty-http3/http3-server/src/main/config/etc/jetty-http3.xml new file mode 100644 index 00000000000..ca37ce84f07 --- /dev/null +++ b/jetty-http3/http3-server/src/main/config/etc/jetty-http3.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jetty-http3/http3-server/src/main/config/modules/http3.mod b/jetty-http3/http3-server/src/main/config/modules/http3.mod new file mode 100644 index 00000000000..57c30db8744 --- /dev/null +++ b/jetty-http3/http3-server/src/main/config/modules/http3.mod @@ -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@ diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java index 212bcab30d7..0cb608a8158 100644 --- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java +++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java @@ -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 diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnector.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnector.java index 54f8a9de5c9..d5669c17944 100644 --- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnector.java +++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnector.java @@ -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; + } } diff --git a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java index 5dad9518210..7e0ac019674 100644 --- a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java +++ b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java @@ -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 diff --git a/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml b/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml index 79e06bd2ba8..3dc909d32fd 100644 --- a/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml +++ b/jetty-quic/quic-quiche/quic-quiche-jna/pom.xml @@ -38,7 +38,7 @@ net.java.dev.jna - jna + jna-jpms org.eclipse.jetty diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java index 172f7fefa91..72f1ef75166 100644 --- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java +++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/QuicServerConnector.java @@ -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 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"))); diff --git a/pom.xml b/pom.xml index 6c52d0076d3..5b480a20bc0 100644 --- a/pom.xml +++ b/pom.xml @@ -1126,7 +1126,7 @@ net.java.dev.jna - jna + jna-jpms ${jna.version} diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index 8fd6b8c0c47..c05454f4d13 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -84,6 +84,11 @@ http2-http-client-transport test + + org.eclipse.jetty.http3 + http3-http-client-transport + test + org.eclipse.jetty.demos demo-simple-webapp diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 9439b897d7c..fcc2a53660c 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -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()); + } + } + } } diff --git a/tests/test-distribution/src/test/resources/jetty-logging.properties b/tests/test-distribution/src/test/resources/jetty-logging.properties index cb96a22a250..ef1afe43964 100644 --- a/tests/test-distribution/src/test/resources/jetty-logging.properties +++ b/tests/test-distribution/src/test/resources/jetty-logging.properties @@ -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