diff --git a/jetty-http3/http3-client/pom.xml b/jetty-http3/http3-client/pom.xml
new file mode 100644
index 00000000000..98879df330a
--- /dev/null
+++ b/jetty-http3/http3-client/pom.xml
@@ -0,0 +1,36 @@
+
+
+
+ org.eclipse.jetty.http3
+ http3-parent
+ 10.0.7-SNAPSHOT
+
+
+ 4.0.0
+ http3-client
+ Jetty :: HTTP3 :: Client
+
+
+
+ org.eclipse.jetty.http3
+ http3-common
+ ${project.version}
+
+
+ org.eclipse.jetty.quic
+ quic-client
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-io
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-util
+ ${project.version}
+
+
+
+
diff --git a/jetty-http3/http3-client/src/main/java/module-info.java b/jetty-http3/http3-client/src/main/java/module-info.java
new file mode 100644
index 00000000000..1b5fd4c7ea2
--- /dev/null
+++ b/jetty-http3/http3-client/src/main/java/module-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+module org.eclipse.jetty.http3.client
+{
+ exports org.eclipse.jetty.http3.client;
+
+ requires transitive org.eclipse.jetty.http3.common;
+ requires transitive org.eclipse.jetty.io;
+ requires transitive org.eclipse.jetty.quic.common;
+ requires org.eclipse.jetty.quic.client;
+ requires transitive org.eclipse.jetty.util;
+}
diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java
new file mode 100644
index 00000000000..abaad158302
--- /dev/null
+++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java
@@ -0,0 +1,71 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.client;
+
+import java.net.SocketAddress;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.http3.api.Session;
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.quic.client.ClientQuicConnection;
+import org.eclipse.jetty.quic.client.QuicClientConnectorConfigurator;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+
+public class HTTP3Client extends ContainerLifeCycle
+{
+ public static final String CLIENT_CONTEXT_KEY = HTTP3Client.class.getName();
+ private static final String SESSION_LISTENER_CONTEXT_KEY = CLIENT_CONTEXT_KEY + ".listener";
+ private static final String SESSION_PROMISE_CONTEXT_KEY = CLIENT_CONTEXT_KEY + ".promise";
+
+ private final ClientConnector connector;
+ private List protocols = List.of("h3");
+
+ public HTTP3Client()
+ {
+ this.connector = new ClientConnector(new QuicClientConnectorConfigurator());
+ addBean(connector);
+ }
+
+ @ManagedAttribute("The ALPN protocol list")
+ public List getProtocols()
+ {
+ return protocols;
+ }
+
+ public void setProtocols(List protocols)
+ {
+ this.protocols = protocols;
+ }
+
+ public CompletableFuture connect(SocketAddress address, Session.Listener listener)
+ {
+ Promise.Completable completable = new Promise.Completable<>();
+ ClientConnectionFactory factory = new HTTP3ClientConnectionFactory();
+ Map context = new ConcurrentHashMap<>();
+ context.put(CLIENT_CONTEXT_KEY, this);
+ context.put(SESSION_LISTENER_CONTEXT_KEY, listener);
+ context.put(SESSION_PROMISE_CONTEXT_KEY, completable);
+ context.put(ClientQuicConnection.APPLICATION_PROTOCOLS, getProtocols());
+ context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, factory);
+ context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, completable::failed));
+ connector.connect(address, context);
+ return completable;
+ }
+}
diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientConnectionFactory.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientConnectionFactory.java
new file mode 100644
index 00000000000..b371a422bc8
--- /dev/null
+++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientConnectionFactory.java
@@ -0,0 +1,41 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.client;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.eclipse.jetty.io.ClientConnectionFactory;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.quic.client.ClientQuicSession;
+import org.eclipse.jetty.quic.common.ProtocolQuicSession;
+import org.eclipse.jetty.quic.common.QuicSession;
+
+public class HTTP3ClientConnectionFactory implements ClientConnectionFactory, ProtocolQuicSession.Factory
+{
+ @Override
+ public ProtocolQuicSession newProtocolQuicSession(QuicSession quicSession, Map context)
+ {
+ HTTP3Client http3Client = (HTTP3Client)context.get(HTTP3Client.CLIENT_CONTEXT_KEY);
+ // TODO: configure the QpackDecoder.maxHeaderSize from HTTP3Client
+ return new HTTP3ClientQuicSession((ClientQuicSession)quicSession);
+ }
+
+ @Override
+ public Connection newConnection(EndPoint endPoint, Map context) throws IOException
+ {
+ return null;
+ }
+}
diff --git a/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientQuicSession.java b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientQuicSession.java
new file mode 100644
index 00000000000..04df0257469
--- /dev/null
+++ b/jetty-http3/http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3ClientQuicSession.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.client;
+
+import org.eclipse.jetty.http3.internal.ControlConnection;
+import org.eclipse.jetty.http3.internal.DecoderConnection;
+import org.eclipse.jetty.http3.internal.EncoderConnection;
+import org.eclipse.jetty.quic.client.ClientQuicSession;
+import org.eclipse.jetty.quic.client.ProtocolClientQuicSession;
+import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
+
+public class HTTP3ClientQuicSession extends ProtocolClientQuicSession
+{
+ private QuicStreamEndPoint decoderEndPoint;
+ private QuicStreamEndPoint encoderEndPoint;
+ private QuicStreamEndPoint controlEndPoint;
+
+ public HTTP3ClientQuicSession(ClientQuicSession session)
+ {
+ super(session);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ long decoderStreamId = getQuicSession().newClientUnidirectionalStreamId();
+ decoderEndPoint = configureDecoderEndPoint(decoderStreamId);
+
+ long encoderStreamId = getQuicSession().newClientUnidirectionalStreamId();
+ encoderEndPoint = configureEncoderEndPoint(encoderStreamId);
+
+ long controlStreamId = getQuicSession().newClientBidirectionalStreamId();
+ controlEndPoint = configureControlEndPoint(controlStreamId);
+ }
+
+ private QuicStreamEndPoint configureDecoderEndPoint(long streamId)
+ {
+ return getOrCreateStreamEndPoint(streamId, endPoint ->
+ {
+ DecoderConnection connection = new DecoderConnection(endPoint, getQuicSession().getExecutor());
+ endPoint.setConnection(connection);
+ endPoint.onOpen();
+ connection.onOpen();
+ });
+ }
+
+ private QuicStreamEndPoint configureEncoderEndPoint(long streamId)
+ {
+ return getOrCreateStreamEndPoint(streamId, endPoint ->
+ {
+ EncoderConnection connection = new EncoderConnection(endPoint, getQuicSession().getExecutor());
+ endPoint.setConnection(connection);
+ endPoint.onOpen();
+ connection.onOpen();
+ });
+ }
+
+ private QuicStreamEndPoint configureControlEndPoint(long streamId)
+ {
+ return getOrCreateStreamEndPoint(streamId, endPoint ->
+ {
+ ControlConnection connection = new ControlConnection(endPoint, getQuicSession().getExecutor());
+ endPoint.setConnection(connection);
+ endPoint.onOpen();
+ connection.onOpen();
+ });
+ }
+}
diff --git a/jetty-http3/http3-common/pom.xml b/jetty-http3/http3-common/pom.xml
index 94e9d40de8e..6c3a2e205c2 100644
--- a/jetty-http3/http3-common/pom.xml
+++ b/jetty-http3/http3-common/pom.xml
@@ -1,8 +1,8 @@
- http3-parent
org.eclipse.jetty.http3
+ http3-parent
10.0.7-SNAPSHOT
diff --git a/jetty-http3/http3-common/src/main/java/module-info.java b/jetty-http3/http3-common/src/main/java/module-info.java
new file mode 100644
index 00000000000..5496d2ad6b4
--- /dev/null
+++ b/jetty-http3/http3-common/src/main/java/module-info.java
@@ -0,0 +1,30 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+module org.eclipse.jetty.http3.common
+{
+ exports org.eclipse.jetty.http3;
+ exports org.eclipse.jetty.http3.api;
+ exports org.eclipse.jetty.http3.api.server;
+ exports org.eclipse.jetty.http3.frames;
+
+ exports org.eclipse.jetty.http3.internal.parser to org.eclipse.jetty.http3.server;
+ exports org.eclipse.jetty.http3.internal;
+
+ requires transitive org.eclipse.jetty.http;
+ requires org.eclipse.jetty.http3.qpack;
+ requires org.eclipse.jetty.io;
+ requires org.eclipse.jetty.quic.common;
+ requires org.eclipse.jetty.util;
+ requires org.slf4j;
+}
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java
index b080b532b9d..99bc8628c72 100644
--- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Session.java
@@ -13,6 +13,13 @@
package org.eclipse.jetty.http3.api;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.jetty.http3.frames.HeadersFrame;
+
public interface Session
{
+ public CompletableFuture newStream(HeadersFrame frame, Stream.Listener listener);
+
+ public interface Listener {}
}
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java
index a8f110ad05d..b70d44adff6 100644
--- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java
@@ -15,4 +15,5 @@ package org.eclipse.jetty.http3.api;
public interface Stream
{
+ public interface Listener {}
}
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/server/ServerSessionListener.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/server/ServerSessionListener.java
new file mode 100644
index 00000000000..ee66bcead4a
--- /dev/null
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/api/server/ServerSessionListener.java
@@ -0,0 +1,20 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.api.server;
+
+import org.eclipse.jetty.http3.api.Session;
+
+public interface ServerSessionListener extends Session.Listener
+{
+}
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/ControlConnection.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/ControlConnection.java
new file mode 100644
index 00000000000..2cecc9da45a
--- /dev/null
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/ControlConnection.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.internal;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+
+public class ControlConnection extends AbstractConnection
+{
+ public ControlConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onFillable()
+ {
+
+ }
+}
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/DecoderConnection.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/DecoderConnection.java
new file mode 100644
index 00000000000..384d544df98
--- /dev/null
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/DecoderConnection.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.internal;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+
+public class DecoderConnection extends AbstractConnection
+{
+ public DecoderConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onFillable()
+ {
+
+ }
+}
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/EncoderConnection.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/EncoderConnection.java
new file mode 100644
index 00000000000..5bebb9962a5
--- /dev/null
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/EncoderConnection.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.internal;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.EndPoint;
+
+public class EncoderConnection extends AbstractConnection
+{
+ public EncoderConnection(EndPoint endPoint, Executor executor)
+ {
+ super(endPoint, executor);
+ }
+
+ @Override
+ public void onFillable()
+ {
+
+ }
+}
diff --git a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java
index c4d46ddb9a7..45db8c4e9bb 100644
--- a/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java
+++ b/jetty-http3/http3-common/src/main/java/org/eclipse/jetty/http3/internal/HTTP3Session.java
@@ -13,9 +13,18 @@
package org.eclipse.jetty.http3.internal;
+import java.util.concurrent.CompletableFuture;
+
import org.eclipse.jetty.http3.api.Session;
+import org.eclipse.jetty.http3.api.Stream;
+import org.eclipse.jetty.http3.frames.HeadersFrame;
import org.eclipse.jetty.http3.internal.parser.Parser;
public class HTTP3Session implements Session, Parser.Listener
{
+ @Override
+ public CompletableFuture newStream(HeadersFrame frame, Stream.Listener listener)
+ {
+ return null;
+ }
}
diff --git a/jetty-http3/http3-server/pom.xml b/jetty-http3/http3-server/pom.xml
index 0a87ae0da77..f3b36bda37e 100644
--- a/jetty-http3/http3-server/pom.xml
+++ b/jetty-http3/http3-server/pom.xml
@@ -1,8 +1,8 @@
- http3-parent
org.eclipse.jetty.http3
+ http3-parent
10.0.7-SNAPSHOT
@@ -23,7 +23,7 @@
org.eclipse.jetty.quic
- quic-common
+ quic-server
${project.version}
diff --git a/jetty-http3/http3-server/src/main/java/module-info.java b/jetty-http3/http3-server/src/main/java/module-info.java
new file mode 100644
index 00000000000..944ce5749e8
--- /dev/null
+++ b/jetty-http3/http3-server/src/main/java/module-info.java
@@ -0,0 +1,23 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+module org.eclipse.jetty.http3.server
+{
+ exports org.eclipse.jetty.http3.server;
+
+ requires transitive org.eclipse.jetty.http3.common;
+ requires transitive org.eclipse.jetty.http3.qpack;
+ requires transitive org.eclipse.jetty.io;
+ requires transitive org.eclipse.jetty.quic.server;
+ requires transitive org.eclipse.jetty.server;
+}
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java
new file mode 100644
index 00000000000..1ed542c4c73
--- /dev/null
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/AbstractHTTP3ServerConnectionFactory.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.server;
+
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.jetty.http3.internal.HTTP3Connection;
+import org.eclipse.jetty.http3.internal.HTTP3Session;
+import org.eclipse.jetty.http3.internal.parser.Parser;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.quic.common.ProtocolQuicSession;
+import org.eclipse.jetty.quic.common.QuicSession;
+import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
+import org.eclipse.jetty.quic.server.ServerQuicSession;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+
+public abstract class AbstractHTTP3ServerConnectionFactory extends AbstractConnectionFactory implements ProtocolQuicSession.Factory
+{
+ private final HttpConfiguration httpConfiguration;
+
+ public AbstractHTTP3ServerConnectionFactory(HttpConfiguration httpConfiguration)
+ {
+ super("h3");
+ this.httpConfiguration = Objects.requireNonNull(httpConfiguration);
+ addBean(httpConfiguration);
+ }
+
+ public HttpConfiguration getHttpConfiguration()
+ {
+ return httpConfiguration;
+ }
+
+ @Override
+ public ProtocolQuicSession newProtocolQuicSession(QuicSession quicSession, Map context)
+ {
+ return new HTTP3ServerQuicSession((ServerQuicSession)quicSession, getHttpConfiguration().getResponseHeaderSize());
+ }
+
+ @Override
+ public Connection newConnection(Connector connector, EndPoint endPoint)
+ {
+ // TODO: can the downcasts be removed?
+ long streamId = ((QuicStreamEndPoint)endPoint).getStreamId();
+ HTTP3ServerQuicSession http3QuicSession = (HTTP3ServerQuicSession)((QuicStreamEndPoint)endPoint).getQuicSession().getProtocolQuicSession();
+
+ HTTP3Session session = new HTTP3Session();
+
+ Parser parser = new Parser(streamId, session, http3QuicSession.getQpackDecoder());
+
+ HTTP3Connection connection = new HTTP3Connection(endPoint, connector.getExecutor(), parser);
+ return connection;
+ }
+}
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3QuicSession.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3QuicSession.java
deleted file mode 100644
index 9e5c2819eda..00000000000
--- a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3QuicSession.java
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under the
-// terms of the Eclipse Public License v. 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
-// which is available at https://www.apache.org/licenses/LICENSE-2.0.
-//
-// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
-// ========================================================================
-//
-
-package org.eclipse.jetty.http3.server;
-
-import java.util.List;
-
-import org.eclipse.jetty.http3.qpack.Instruction;
-import org.eclipse.jetty.http3.qpack.QpackDecoder;
-import org.eclipse.jetty.http3.qpack.QpackException;
-import org.eclipse.jetty.quic.common.ProtocolQuicSession;
-import org.eclipse.jetty.quic.common.QuicSession;
-
-public class HTTP3QuicSession extends ProtocolQuicSession
-{
- private final QpackDecoder decoder;
- private final Instruction.Handler decoderHandler = new QpackDecoderInstructionHandler();
-
- public HTTP3QuicSession(QuicSession session, int maxHeaderSize)
- {
- super(session);
- decoder = new QpackDecoder(decoderHandler, maxHeaderSize);
- // TODO: create a streamId for the Instruction stream.
- }
-
- public QpackDecoder getQpackDecoder()
- {
- return decoder;
- }
-
- private class QpackDecoderInstructionHandler implements Instruction.Handler
- {
- @Override
- public void onInstructions(List instructions) throws QpackException
- {
- // TODO: feed the Instruction to QuicSession.
- }
- }
-}
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 74800f17133..2530cdf5ea5 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
@@ -13,24 +13,10 @@
package org.eclipse.jetty.http3.server;
-import java.util.Objects;
-
-import org.eclipse.jetty.http3.internal.HTTP3Connection;
-import org.eclipse.jetty.http3.internal.HTTP3Session;
-import org.eclipse.jetty.http3.internal.parser.Parser;
-import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.quic.common.ProtocolQuicSession;
-import org.eclipse.jetty.quic.common.QuicSession;
-import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
-import org.eclipse.jetty.server.AbstractConnectionFactory;
-import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
-public class HTTP3ServerConnectionFactory extends AbstractConnectionFactory implements ProtocolQuicSession.Factory
+public class HTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionFactory
{
- private final HttpConfiguration httpConfiguration;
-
public HTTP3ServerConnectionFactory()
{
this(new HttpConfiguration());
@@ -38,29 +24,6 @@ public class HTTP3ServerConnectionFactory extends AbstractConnectionFactory impl
public HTTP3ServerConnectionFactory(HttpConfiguration configuration)
{
- super("h3");
- this.httpConfiguration = Objects.requireNonNull(configuration);
- addBean(httpConfiguration);
- }
-
- @Override
- public ProtocolQuicSession newProtocolQuicSession(QuicSession quicSession)
- {
- return new HTTP3QuicSession(quicSession, httpConfiguration.getResponseHeaderSize());
- }
-
- @Override
- public Connection newConnection(Connector connector, EndPoint endPoint)
- {
- // TODO: can the downcasts be removed?
- long streamId = ((QuicStreamEndPoint)endPoint).getStreamId();
- HTTP3QuicSession http3QuicSession = (HTTP3QuicSession)((QuicStreamEndPoint)endPoint).getQuicSession().getProtocolQuicSession();
-
- HTTP3Session session = new HTTP3Session();
-
- Parser parser = new Parser(streamId, session, http3QuicSession.getQpackDecoder());
-
- HTTP3Connection connection = new HTTP3Connection(endPoint, connector.getExecutor(), parser);
- return connection;
+ super(configuration);
}
}
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerQuicSession.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerQuicSession.java
new file mode 100644
index 00000000000..a32d95fc9b7
--- /dev/null
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerQuicSession.java
@@ -0,0 +1,142 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.server;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+
+import org.eclipse.jetty.http3.internal.ControlConnection;
+import org.eclipse.jetty.http3.internal.DecoderConnection;
+import org.eclipse.jetty.http3.internal.EncoderConnection;
+import org.eclipse.jetty.http3.qpack.Instruction;
+import org.eclipse.jetty.http3.qpack.QpackDecoder;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
+import org.eclipse.jetty.quic.server.ProtocolServerQuicSession;
+import org.eclipse.jetty.quic.server.ServerQuicSession;
+import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.thread.AutoLock;
+
+public class HTTP3ServerQuicSession extends ProtocolServerQuicSession
+{
+ private final QpackDecoder decoder;
+ private QuicStreamEndPoint decoderEndPoint;
+ private QuicStreamEndPoint encoderEndPoint;
+ private QuicStreamEndPoint controlEndPoint;
+
+ public HTTP3ServerQuicSession(ServerQuicSession session, int maxHeaderSize)
+ {
+ super(session);
+ decoder = new QpackDecoder(new QpackDecoderInstructionHandler(), maxHeaderSize);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ long decoderStreamId = getQuicSession().newServerUnidirectionalStreamId();
+ decoderEndPoint = configureDecoderEndPoint(decoderStreamId);
+
+ long encoderStreamId = getQuicSession().newServerUnidirectionalStreamId();
+ encoderEndPoint = configureEncoderEndPoint(encoderStreamId);
+
+ long controlStreamId = getQuicSession().newServerBidirectionalStreamId();
+ controlEndPoint = configureControlEndPoint(controlStreamId);
+ }
+
+ private QuicStreamEndPoint configureDecoderEndPoint(long streamId)
+ {
+ return getOrCreateStreamEndPoint(streamId, endPoint ->
+ {
+ DecoderConnection connection = new DecoderConnection(endPoint, getQuicSession().getExecutor());
+ endPoint.setConnection(connection);
+ endPoint.onOpen();
+ connection.onOpen();
+ });
+ }
+
+ private QuicStreamEndPoint configureEncoderEndPoint(long streamId)
+ {
+ return getOrCreateStreamEndPoint(streamId, endPoint ->
+ {
+ EncoderConnection connection = new EncoderConnection(endPoint, getQuicSession().getExecutor());
+ endPoint.setConnection(connection);
+ endPoint.onOpen();
+ connection.onOpen();
+ });
+ }
+
+ private QuicStreamEndPoint configureControlEndPoint(long streamId)
+ {
+ return getOrCreateStreamEndPoint(streamId, endPoint ->
+ {
+ ControlConnection connection = new ControlConnection(endPoint, getQuicSession().getExecutor());
+ endPoint.setConnection(connection);
+ endPoint.onOpen();
+ connection.onOpen();
+ });
+ }
+
+ public QpackDecoder getQpackDecoder()
+ {
+ return decoder;
+ }
+
+ private class QpackDecoderInstructionHandler extends IteratingCallback implements Instruction.Handler
+ {
+ private final AutoLock lock = new AutoLock();
+ private final ByteBufferPool.Lease lease = new ByteBufferPool.Lease(getQuicSession().getByteBufferPool());
+ private final Queue queue = new ArrayDeque<>();
+
+ @Override
+ public void onInstructions(List instructions)
+ {
+ try (AutoLock l = lock.lock())
+ {
+ queue.addAll(instructions);
+ }
+ iterate();
+ }
+
+ @Override
+ protected Action process()
+ {
+ List instructions;
+ try (AutoLock l = lock.lock())
+ {
+ if (queue.isEmpty())
+ return Action.IDLE;
+ instructions = new ArrayList<>(queue);
+ }
+ instructions.forEach(i -> i.encode(lease));
+ decoderEndPoint.write(this, getQuicSession().getRemoteAddress(), lease.getByteBuffers().toArray(ByteBuffer[]::new));
+ return Action.SCHEDULED;
+ }
+
+ @Override
+ public void succeeded()
+ {
+ lease.recycle();
+ super.succeeded();
+ }
+
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return InvocationType.NON_BLOCKING;
+ }
+ }
+}
diff --git a/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/RawHTTP3ServerConnectionFactory.java b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/RawHTTP3ServerConnectionFactory.java
new file mode 100644
index 00000000000..4bc83997261
--- /dev/null
+++ b/jetty-http3/http3-server/src/main/java/org/eclipse/jetty/http3/server/RawHTTP3ServerConnectionFactory.java
@@ -0,0 +1,33 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.server;
+
+import org.eclipse.jetty.http3.api.server.ServerSessionListener;
+import org.eclipse.jetty.server.HttpConfiguration;
+
+public class RawHTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionFactory
+{
+ private final ServerSessionListener listener;
+
+ public RawHTTP3ServerConnectionFactory(ServerSessionListener listener)
+ {
+ this(new HttpConfiguration(), listener);
+ }
+
+ public RawHTTP3ServerConnectionFactory(HttpConfiguration httpConfiguration, ServerSessionListener listener)
+ {
+ super(httpConfiguration);
+ this.listener = listener;
+ }
+}
diff --git a/jetty-http3/http3-tests/pom.xml b/jetty-http3/http3-tests/pom.xml
new file mode 100644
index 00000000000..62d12503c8c
--- /dev/null
+++ b/jetty-http3/http3-tests/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ org.eclipse.jetty.http3
+ http3-parent
+ 10.0.7-SNAPSHOT
+
+
+ 4.0.0
+ http3-tests
+ Jetty :: HTTP3 :: Tests
+
+
+
+ org.eclipse.jetty.http3
+ http3-client
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty.http3
+ http3-server
+ ${project.version}
+ test
+
+
+ org.eclipse.jetty.quic
+ quic-server
+ ${project.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+
+
+ org.eclipse.jetty
+ jetty-slf4j-impl
+
+
+
+
diff --git a/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HTTP3ClientServerTest.java b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HTTP3ClientServerTest.java
new file mode 100644
index 00000000000..76dfc82eaaa
--- /dev/null
+++ b/jetty-http3/http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HTTP3ClientServerTest.java
@@ -0,0 +1,68 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.http3.tests;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.http.MetaData;
+import org.eclipse.jetty.http3.api.Session;
+import org.eclipse.jetty.http3.api.Stream;
+import org.eclipse.jetty.http3.api.server.ServerSessionListener;
+import org.eclipse.jetty.http3.client.HTTP3Client;
+import org.eclipse.jetty.http3.frames.HeadersFrame;
+import org.eclipse.jetty.http3.server.RawHTTP3ServerConnectionFactory;
+import org.eclipse.jetty.quic.server.ServerQuicConnector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.jupiter.api.Test;
+
+public class HTTP3ClientServerTest
+{
+ @Test
+ public void testGETThenResponseWithoutContent() throws Exception
+ {
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
+ sslContextFactory.setKeyStorePassword("storepwd");
+
+ QueuedThreadPool serverThreads = new QueuedThreadPool();
+ serverThreads.setName("server");
+ Server server = new Server(serverThreads);
+ ServerQuicConnector connector = new ServerQuicConnector(server, sslContextFactory, new RawHTTP3ServerConnectionFactory(new ServerSessionListener() {}));
+ server.addConnector(connector);
+ server.start();
+
+ HTTP3Client client = new HTTP3Client();
+ client.start();
+
+ Session session = client.connect(new InetSocketAddress("localhost", connector.getLocalPort()), new Session.Listener() {})
+ .get(555, TimeUnit.SECONDS);
+
+ System.err.println("session = " + session);
+
+ HttpURI uri = HttpURI.from("https://localhost:" + connector.getLocalPort());
+ MetaData.Request metaData = new MetaData.Request(HttpMethod.GET.asString(), uri, HttpVersion.HTTP_3, HttpFields.EMPTY);
+ HeadersFrame frame = new HeadersFrame(metaData);
+ Stream stream = session.newStream(frame, new Stream.Listener() {})
+ .get(5, TimeUnit.SECONDS);
+
+ System.err.println("stream = " + stream);
+ }
+}
diff --git a/jetty-http3/http3-tests/src/test/resources/jetty-logging.properties b/jetty-http3/http3-tests/src/test/resources/jetty-logging.properties
new file mode 100644
index 00000000000..ad6a6d51788
--- /dev/null
+++ b/jetty-http3/http3-tests/src/test/resources/jetty-logging.properties
@@ -0,0 +1,4 @@
+#org.eclipse.jetty.LEVEL=DEBUG
+#org.eclipse.jetty.http3.LEVEL=DEBUG
+#org.eclipse.jetty.quic.LEVEL=DEBUG
+org.eclipse.jetty.quic.quiche.LEVEL=DEBUG
diff --git a/jetty-http3/http3-tests/src/test/resources/keystore.p12 b/jetty-http3/http3-tests/src/test/resources/keystore.p12
new file mode 100644
index 00000000000..0b56dd34ee9
Binary files /dev/null and b/jetty-http3/http3-tests/src/test/resources/keystore.p12 differ
diff --git a/jetty-http3/pom.xml b/jetty-http3/pom.xml
index b1937e9c359..6b4e9f19cee 100644
--- a/jetty-http3/pom.xml
+++ b/jetty-http3/pom.xml
@@ -16,6 +16,8 @@
http3-qpack
http3-common
http3-server
+ http3-client
+ http3-tests
diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
index 9c06dca55bb..7b1311be423 100644
--- a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
+++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
@@ -47,6 +47,7 @@ import org.slf4j.LoggerFactory;
*/
public class ClientQuicConnection extends QuicConnection
{
+ public static final String APPLICATION_PROTOCOLS = "org.eclipse.jetty.quic.application.protocols";
private static final Logger LOG = LoggerFactory.getLogger(ClientQuicConnection.class);
private final Map pendingSessions = new ConcurrentHashMap<>();
@@ -61,39 +62,63 @@ public class ClientQuicConnection extends QuicConnection
@Override
public void onOpen()
{
- super.onOpen();
-
try
{
- InetSocketAddress remoteAddress = (InetSocketAddress)context.get(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);
- HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ super.onOpen();
+
+ @SuppressWarnings("unchecked")
+ List protocols = (List)context.get(APPLICATION_PROTOCOLS);
+ if (protocols == null)
+ {
+ HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
+ if (destination != null)
+ protocols = destination.getOrigin().getProtocol().getProtocols();
+ if (protocols == null)
+ throw new IllegalStateException("Missing ALPN protocols");
+ }
// TODO: pull the config settings from somewhere else TBD (context?)
QuicheConfig quicheConfig = new QuicheConfig();
- List protocols = destination.getOrigin().getProtocol().getProtocols();
- quicheConfig.setApplicationProtos(protocols.toArray(new String[0]));
+ quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
quicheConfig.setDisableActiveMigration(true);
quicheConfig.setVerifyPeer(false);
- quicheConfig.setMaxIdleTimeout(5000L);
+ quicheConfig.setMaxIdleTimeout(getEndPoint().getIdleTimeout());
quicheConfig.setInitialMaxData(10_000_000L);
quicheConfig.setInitialMaxStreamDataBidiLocal(10_000_000L);
quicheConfig.setInitialMaxStreamDataUni(10_000_000L);
quicheConfig.setInitialMaxStreamsBidi(100L);
quicheConfig.setInitialMaxStreamsUni(100L);
+ InetSocketAddress remoteAddress = (InetSocketAddress)context.get(ClientConnector.REMOTE_SOCKET_ADDRESS_CONTEXT_KEY);
QuicheConnection quicheConnection = QuicheConnection.connect(quicheConfig, remoteAddress);
QuicSession session = new ClientQuicSession(getExecutor(), getScheduler(), getByteBufferPool(), quicheConnection, this, remoteAddress, context);
pendingSessions.put(remoteAddress, session);
session.flush(); // send the response packet(s) that connect generated.
if (LOG.isDebugEnabled())
LOG.debug("created connecting QUIC session {}", session);
- }
- catch (IOException e)
- {
- throw new RuntimeIOException("Error trying to open connection", e);
- }
- fillInterested();
+ fillInterested();
+ }
+ catch (IOException x)
+ {
+ throw new RuntimeIOException(x);
+ }
+ }
+
+ @Override
+ protected QuicSession createSession(SocketAddress remoteAddress, ByteBuffer cipherBuffer) throws IOException
+ {
+ QuicSession session = pendingSessions.get(remoteAddress);
+ if (session != null)
+ {
+ session.process(remoteAddress, cipherBuffer);
+ if (session.isConnectionEstablished())
+ {
+ pendingSessions.remove(remoteAddress);
+ return session;
+ }
+ }
+ return null;
}
@Override
@@ -109,21 +134,4 @@ public class ClientQuicConnection extends QuicConnection
promise.failed(x);
}
}
-
- @Override
- protected QuicSession createSession(SocketAddress remoteAddress, ByteBuffer cipherBuffer) throws IOException
- {
- QuicSession session = pendingSessions.get(remoteAddress);
- if (session != null)
- {
- session.process(remoteAddress, cipherBuffer);
- if (session.isConnectionEstablished())
- {
- pendingSessions.remove(remoteAddress);
- session.onOpen();
- return session;
- }
- }
- return null;
- }
}
diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicSession.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicSession.java
index 88c9adace45..fabb905a83c 100644
--- a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicSession.java
+++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicSession.java
@@ -22,7 +22,6 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
-import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.quic.common.ProtocolQuicSession;
import org.eclipse.jetty.quic.common.QuicConnection;
import org.eclipse.jetty.quic.common.QuicSession;
@@ -49,25 +48,16 @@ public class ClientQuicSession extends QuicSession
@Override
protected ProtocolQuicSession createProtocolQuicSession()
{
- return new ProtocolQuicSession(this);
+ ClientConnectionFactory connectionFactory = (ClientConnectionFactory)context.get(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY);
+ if (connectionFactory instanceof ProtocolQuicSession.Factory)
+ return ((ProtocolQuicSession.Factory)connectionFactory).newProtocolQuicSession(this, context);
+ return new ProtocolClientQuicSession(this);
}
@Override
- protected QuicStreamEndPoint createQuicStreamEndPoint(long streamId)
+ public Connection newConnection(QuicStreamEndPoint endPoint) throws IOException
{
- try
- {
- ClientConnectionFactory connectionFactory = (ClientConnectionFactory)context.get(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY);
- QuicStreamEndPoint endPoint = new QuicStreamEndPoint(getScheduler(), this, streamId);
- Connection connection = connectionFactory.newConnection(endPoint, context);
- endPoint.setConnection(connection);
- endPoint.onOpen();
- connection.onOpen();
- return endPoint;
- }
- catch (IOException x)
- {
- throw new RuntimeIOException("Error creating new connection", x);
- }
+ ClientConnectionFactory connectionFactory = (ClientConnectionFactory)context.get(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY);
+ return connectionFactory.newConnection(endPoint, context);
}
}
diff --git a/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ProtocolClientQuicSession.java b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ProtocolClientQuicSession.java
new file mode 100644
index 00000000000..f6f8c0528a3
--- /dev/null
+++ b/jetty-quic/quic-client/src/main/java/org/eclipse/jetty/quic/client/ProtocolClientQuicSession.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.quic.client;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.quic.common.ProtocolQuicSession;
+import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProtocolClientQuicSession extends ProtocolQuicSession
+{
+ private static final Logger LOG = LoggerFactory.getLogger(ProtocolClientQuicSession.class);
+
+ public ProtocolClientQuicSession(ClientQuicSession session)
+ {
+ super(session);
+ }
+
+ @Override
+ public ClientQuicSession getQuicSession()
+ {
+ return (ClientQuicSession)super.getQuicSession();
+ }
+
+ @Override
+ public void onOpen()
+ {
+ // Create a single bidirectional, client-initiated,
+ // QUIC stream that plays the role of the TCP stream.
+ configureEndPoint(getQuicSession().newClientBidirectionalStreamId());
+ process();
+ }
+
+ private void configureEndPoint(long streamId)
+ {
+ getOrCreateStreamEndPoint(streamId, endPoint ->
+ {
+ try
+ {
+ Connection connection = getQuicSession().newConnection(endPoint);
+ endPoint.setConnection(connection);
+ endPoint.onOpen();
+ connection.onOpen();
+ }
+ catch (RuntimeException | Error x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("could not open protocol QUIC session", x);
+ throw x;
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("could not open protocol QUIC session", x);
+ throw new RuntimeException(x);
+ }
+ });
+ }
+
+ @Override
+ protected void onReadable(long readableStreamId)
+ {
+ // On the client, we need a get-only semantic in case of reads.
+ QuicStreamEndPoint streamEndPoint = getStreamEndPoint(readableStreamId);
+ if (LOG.isDebugEnabled())
+ LOG.debug("stream {} selected endpoint for read: {}", readableStreamId, streamEndPoint);
+ if (streamEndPoint != null)
+ streamEndPoint.onReadable();
+ }
+}
diff --git a/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java b/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java
index 9684b880b49..39e21045894 100644
--- a/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java
+++ b/jetty-quic/quic-client/src/test/java/org/eclipse/jetty/quic/client/End2EndClientTest.java
@@ -36,6 +36,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -58,12 +59,16 @@ public class End2EndClientTest
@BeforeEach
public void setUp() throws Exception
{
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
+ sslContextFactory.setKeyStorePassword("storepwd");
+
server = new Server();
HttpConfiguration httpConfiguration = new HttpConfiguration();
HttpConnectionFactory http1 = new HttpConnectionFactory(httpConfiguration);
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration);
- connector = new ServerQuicConnector(server, http1, http2);
+ connector = new ServerQuicConnector(server, sslContextFactory, http1, http2);
server.addConnector(connector);
server.setHandler(new AbstractHandler()
diff --git a/jetty-quic/quic-client/src/test/resources/jetty-logging.properties b/jetty-quic/quic-client/src/test/resources/jetty-logging.properties
index 7eafbcf5b9d..2c88f356fc5 100644
--- a/jetty-quic/quic-client/src/test/resources/jetty-logging.properties
+++ b/jetty-quic/quic-client/src/test/resources/jetty-logging.properties
@@ -1,3 +1,3 @@
#org.eclipse.jetty.LEVEL=DEBUG
-org.eclipse.jetty.quic.LEVEL=DEBUG
-org.eclipse.jetty.quic.quiche.LEVEL=DEBUG
+#org.eclipse.jetty.quic.LEVEL=DEBUG
+org.eclipse.jetty.quic.quiche.LEVEL=INFO
diff --git a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolQuicSession.java b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolQuicSession.java
index eb7a939c468..8b8607165d9 100644
--- a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolQuicSession.java
+++ b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolQuicSession.java
@@ -13,10 +13,18 @@
package org.eclipse.jetty.quic.common;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
-public class ProtocolQuicSession
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class ProtocolQuicSession
{
+ private static final Logger LOG = LoggerFactory.getLogger(ProtocolQuicSession.class);
+
private final AtomicLong active = new AtomicLong();
private final QuicSession session;
@@ -30,6 +38,8 @@ public class ProtocolQuicSession
return session;
}
+ public abstract void onOpen();
+
public void process()
{
if (active.getAndIncrement() == 0)
@@ -38,8 +48,8 @@ public class ProtocolQuicSession
{
while (true)
{
- session.processWritableStreams();
- if (session.processReadableStreams())
+ processWritableStreams();
+ if (processReadableStreams())
continue;
// Exit if did not process any stream and we are idle.
if (active.decrementAndGet() == 0)
@@ -49,8 +59,47 @@ public class ProtocolQuicSession
}
}
+ protected QuicStreamEndPoint getStreamEndPoint(long streamId)
+ {
+ return session.getStreamEndPoint(streamId);
+ }
+
+ protected QuicStreamEndPoint getOrCreateStreamEndPoint(long streamId, Consumer consumer)
+ {
+ return session.getOrCreateStreamEndPoint(streamId, consumer);
+ }
+
+ private void processWritableStreams()
+ {
+ List writableStreamIds = session.getWritableStreamIds();
+ if (LOG.isDebugEnabled())
+ LOG.debug("writable stream ids: {}", writableStreamIds);
+ writableStreamIds.forEach(this::onWritable);
+ }
+
+ protected void onWritable(long writableStreamId)
+ {
+ // For both client and server, we only need a get-only semantic in case of writes.
+ QuicStreamEndPoint streamEndPoint = session.getStreamEndPoint(writableStreamId);
+ if (LOG.isDebugEnabled())
+ LOG.debug("stream {} selected endpoint for write: {}", writableStreamId, streamEndPoint);
+ if (streamEndPoint != null)
+ streamEndPoint.onWritable();
+ }
+
+ private boolean processReadableStreams()
+ {
+ List readableStreamIds = session.getReadableStreamIds();
+ if (LOG.isDebugEnabled())
+ LOG.debug("readable stream ids: {}", readableStreamIds);
+ readableStreamIds.forEach(this::onReadable);
+ return !readableStreamIds.isEmpty();
+ }
+
+ protected abstract void onReadable(long readableStreamId);
+
public interface Factory
{
- public ProtocolQuicSession newProtocolQuicSession(QuicSession quicSession);
+ public ProtocolQuicSession newProtocolQuicSession(QuicSession quicSession, Map context);
}
}
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 28a3182dc9d..6e099c09922 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
@@ -23,8 +23,11 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.CyclicTimeout;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.quic.quiche.QuicheConnection;
@@ -54,6 +57,7 @@ public abstract class QuicSession
{
private static final Logger LOG = LoggerFactory.getLogger(QuicSession.class);
+ private final AtomicLong ids = new AtomicLong();
private final AutoLock strategyQueueLock = new AutoLock();
private final Queue strategyQueue = new ArrayDeque<>();
private final ConcurrentMap endpoints = new ConcurrentHashMap<>();
@@ -66,7 +70,6 @@ public abstract class QuicSession
private final ExecutionStrategy strategy;
private SocketAddress remoteAddress;
private ProtocolQuicSession protocolSession;
- // TODO make it final?
private QuicheConnectionId quicheConnectionId;
protected QuicSession(Executor executor, Scheduler scheduler, ByteBufferPool byteBufferPool, QuicheConnection quicheConnection, QuicConnection connection, SocketAddress remoteAddress)
@@ -92,6 +95,11 @@ public abstract class QuicSession
return scheduler;
}
+ public ByteBufferPool getByteBufferPool()
+ {
+ return byteBufferPool;
+ }
+
public ProtocolQuicSession getProtocolQuicSession()
{
return protocolSession;
@@ -102,9 +110,46 @@ public abstract class QuicSession
return quicheConnection.getNegotiatedProtocol();
}
+ /**
+ * @return a new unidirectional, client-initiated, stream ID
+ */
+ public long newClientUnidirectionalStreamId()
+ {
+ return newStreamId() + 0x02;
+ }
+
+ /**
+ * @return a new bidirectional, client-initiated, stream ID
+ */
+ public long newClientBidirectionalStreamId()
+ {
+ return newStreamId();
+ }
+
+ /**
+ * @return a new unidirectional, server-initiated, stream ID
+ */
+ public long newServerUnidirectionalStreamId()
+ {
+ return newStreamId() + 0x03;
+ }
+
+ /**
+ * @return a new bidirectional, server-initiated, stream ID
+ */
+ public long newServerBidirectionalStreamId()
+ {
+ return newStreamId() + 0x01;
+ }
+
+ private long newStreamId()
+ {
+ return ids.getAndIncrement() << 2;
+ }
+
public void onOpen()
{
- getOrCreateStreamEndPoint(0);
+ protocolSession.onOpen();
}
public int fill(long streamId, ByteBuffer buffer) throws IOException
@@ -182,19 +227,32 @@ public abstract class QuicSession
// client1 -- sockEP1 -> H2Connection - HEADERSParser - H2Session -* RequestStreams -# HTTP Handler
// client2 -- sockEP2 -> H2Connection - HEADERSParser - H2Session -* RequestStreams -# HTTP Handler
+ // HTTP/1 on QUIC
+ // client1
+ // \
+ // dataEP - QuicConnection -* QuicSession -# ProtocolQuicSession -* RequestStreamN - HttpConnection - HTTP Handler
+ // /
+ // client2
+
// HTTP/3
// client1
- // \ /- ConnectionStream0 - ConnectionParser for SETTINGS frames, etc.
- // dataEP - QuicConnection -* QuicSession -# ProtocolQuicSession -* RequestStreamsEP - H3Connection - HEADERSParser -# HTTP Handler
- // / `- InstructionStream - InstructionConnection/Parser
+ // \ /- ConnectionStream0 - ConnectionParser for SETTINGS frames, etc.
+ // dataEP - QuicConnection -* QuicSession -# H3QuicSession -* RequestStreamsEP - H3Connection - HEADERSParser -# HTTP Handler
+ // / `- InstructionStream - InstructionConnection/Parser
// client2
// H3ProtoSession - QpackEncoder
// H3ProtoSession - QpackDecoder
// H3ProtoSession -* request streams
if (protocolSession == null)
+ {
protocolSession = createProtocolQuicSession();
- protocolSession.process();
+ onOpen();
+ }
+ else
+ {
+ protocolSession.process();
+ }
}
else
{
@@ -204,38 +262,22 @@ public abstract class QuicSession
protected abstract ProtocolQuicSession createProtocolQuicSession();
- void processWritableStreams()
+ List getWritableStreamIds()
{
- List writableStreamIds = quicheConnection.writableStreamIds();
- if (LOG.isDebugEnabled())
- LOG.debug("writable stream ids: {}", writableStreamIds);
- writableStreamIds.forEach(this::onWritable);
+ return quicheConnection.writableStreamIds();
}
- private void onWritable(long writableStreamId)
+ List getReadableStreamIds()
{
- QuicStreamEndPoint streamEndPoint = getOrCreateStreamEndPoint(writableStreamId);
- if (LOG.isDebugEnabled())
- LOG.debug("selected endpoint for write: {}", streamEndPoint);
- streamEndPoint.onWritable();
+ return quicheConnection.readableStreamIds();
}
- boolean processReadableStreams()
+ QuicStreamEndPoint getStreamEndPoint(long streamId)
{
- List readableStreamIds = quicheConnection.readableStreamIds();
- if (LOG.isDebugEnabled())
- LOG.debug("readable stream ids: {}", readableStreamIds);
- readableStreamIds.forEach(this::onReadable);
- return !readableStreamIds.isEmpty();
+ return endpoints.get(streamId);
}
- private void onReadable(long readableStreamId)
- {
- QuicStreamEndPoint streamEndPoint = getOrCreateStreamEndPoint(readableStreamId);
- if (LOG.isDebugEnabled())
- LOG.debug("selected endpoint for read: {}", streamEndPoint);
- streamEndPoint.onReadable();
- }
+ public abstract Connection newConnection(QuicStreamEndPoint endPoint) throws IOException;
private void dispatch(Runnable runnable)
{
@@ -253,15 +295,16 @@ public abstract class QuicSession
flusher.iterate();
}
- private QuicStreamEndPoint getOrCreateStreamEndPoint(long streamId)
+ public QuicStreamEndPoint getOrCreateStreamEndPoint(long streamId, Consumer consumer)
{
QuicStreamEndPoint endPoint = endpoints.compute(streamId, (sid, quicStreamEndPoint) ->
{
if (quicStreamEndPoint == null)
{
- quicStreamEndPoint = createQuicStreamEndPoint(streamId);
if (LOG.isDebugEnabled())
LOG.debug("creating endpoint for stream {}", sid);
+ quicStreamEndPoint = newQuicStreamEndPoint(streamId);
+ consumer.accept(quicStreamEndPoint);
}
return quicStreamEndPoint;
});
@@ -270,7 +313,10 @@ public abstract class QuicSession
return endPoint;
}
- protected abstract QuicStreamEndPoint createQuicStreamEndPoint(long streamId);
+ private QuicStreamEndPoint newQuicStreamEndPoint(long streamId)
+ {
+ return new QuicStreamEndPoint(getScheduler(), this, streamId);
+ }
public void close()
{
diff --git a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java
index 78c57990282..226b9aedd12 100644
--- a/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java
+++ b/jetty-quic/quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicStreamEndPoint.java
@@ -184,4 +184,10 @@ public class QuicStreamEndPoint extends AbstractEndPoint
// No need to do anything.
// See QuicSession.process().
}
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x#%d[%s]->[%s]", getClass().getSimpleName(), hashCode(), getStreamId(), toEndPointString(), toConnectionString());
+ }
}
diff --git a/jetty-quic/quic-quiche/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnectionId.java b/jetty-quic/quic-quiche/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnectionId.java
index decfb5c78d0..9fdd5e7262b 100644
--- a/jetty-quic/quic-quiche/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnectionId.java
+++ b/jetty-quic/quic-quiche/src/main/java/org/eclipse/jetty/quic/quiche/QuicheConnectionId.java
@@ -44,9 +44,11 @@ public class QuicheConnectionId
this.string = bytesToHex(dcid);
}
- private static String bytesToHex(byte[] bytes) {
+ private static String bytesToHex(byte[] bytes)
+ {
byte[] hexChars = new byte[bytes.length * 2];
- for (int j = 0; j < bytes.length; j++) {
+ for (int j = 0; j < bytes.length; j++)
+ {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
@@ -60,9 +62,9 @@ public class QuicheConnectionId
return new QuicheConnectionId(sizedDcid);
}
- static QuicheConnectionId fromCid(byte[] dcid, size_t_pointer dcid_len)
+ static QuicheConnectionId fromCid(byte[] dcid, size_t_pointer dcidLen)
{
- byte[] sizedDcid = resizeIfNeeded(dcid, (int)dcid_len.getValue());
+ byte[] sizedDcid = resizeIfNeeded(dcid, (int)dcidLen.getValue());
return new QuicheConnectionId(sizedDcid);
}
@@ -76,23 +78,23 @@ public class QuicheConnectionId
// Source Connection ID
byte[] scid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
- size_t_pointer scid_len = new size_t_pointer(scid.length);
+ size_t_pointer scidLen = new size_t_pointer(scid.length);
// Destination Connection ID
byte[] dcid = new byte[LibQuiche.QUICHE_MAX_CONN_ID_LEN];
- size_t_pointer dcid_len = new size_t_pointer(dcid.length);
+ size_t_pointer dcidLen = new size_t_pointer(dcid.length);
byte[] token = new byte[32];
- size_t_pointer token_len = new size_t_pointer(token.length);
+ size_t_pointer tokenLen = new size_t_pointer(token.length);
int rc = LibQuiche.INSTANCE.quiche_header_info(packet, new size_t(packet.remaining()), new size_t(LibQuiche.QUICHE_MAX_CONN_ID_LEN),
version, type,
- scid, scid_len,
- dcid, dcid_len,
- token, token_len);
+ scid, scidLen,
+ dcid, dcidLen,
+ token, tokenLen);
if (rc < 0)
return null;
- return fromCid(dcid, dcid_len);
+ return fromCid(dcid, dcidLen);
}
private static byte[] resizeIfNeeded(byte[] buffer, int length)
diff --git a/jetty-quic/quic-server/src/main/java/module-info.java b/jetty-quic/quic-server/src/main/java/module-info.java
index 2ef324b25e1..01fb2092373 100644
--- a/jetty-quic/quic-server/src/main/java/module-info.java
+++ b/jetty-quic/quic-server/src/main/java/module-info.java
@@ -15,7 +15,7 @@ module org.eclipse.jetty.quic.server
{
exports org.eclipse.jetty.quic.server;
- requires org.eclipse.jetty.quic.common;
+ requires transitive org.eclipse.jetty.quic.common;
requires org.eclipse.jetty.quic.quiche;
requires org.eclipse.jetty.io;
requires org.eclipse.jetty.server;
diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ProtocolServerQuicSession.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ProtocolServerQuicSession.java
new file mode 100644
index 00000000000..7db808b6d73
--- /dev/null
+++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ProtocolServerQuicSession.java
@@ -0,0 +1,60 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.quic.server;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.quic.common.ProtocolQuicSession;
+import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProtocolServerQuicSession extends ProtocolQuicSession
+{
+ private static final Logger LOG = LoggerFactory.getLogger(ProtocolServerQuicSession.class);
+
+ public ProtocolServerQuicSession(ServerQuicSession session)
+ {
+ super(session);
+ }
+
+ @Override
+ public ServerQuicSession getQuicSession()
+ {
+ return (ServerQuicSession)super.getQuicSession();
+ }
+
+ @Override
+ public void onOpen()
+ {
+ process();
+ }
+
+ @Override
+ protected void onReadable(long readableStreamId)
+ {
+ // On the server, we need a get-or-create semantic in case of reads.
+ QuicStreamEndPoint streamEndPoint = getOrCreateStreamEndPoint(readableStreamId, this::configureEndPoint);
+ if (LOG.isDebugEnabled())
+ LOG.debug("stream {} selected endpoint for read: {}", readableStreamId, streamEndPoint);
+ streamEndPoint.onReadable();
+ }
+
+ private void configureEndPoint(QuicStreamEndPoint endPoint)
+ {
+ Connection connection = getQuicSession().newConnection(endPoint);
+ endPoint.setConnection(connection);
+ endPoint.onOpen();
+ connection.onOpen();
+ }
+}
diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
index 3b36326e229..239d6309dc2 100644
--- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
+++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
@@ -88,7 +88,8 @@ public class ServerQuicConnection extends QuicConnection
else
{
QuicSession session = new ServerQuicSession(getExecutor(), getScheduler(), byteBufferPool, quicheConnection, this, remoteAddress, connector);
- session.flush(); // send the response packet(s) that tryAccept() generated.
+ // Send the response packet(s) that tryAccept() generated.
+ session.flush();
return session;
}
}
diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnector.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnector.java
index 3a9c006bd4f..efdf71c06a4 100644
--- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnector.java
+++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnector.java
@@ -35,64 +35,29 @@ import org.eclipse.jetty.server.AbstractNetworkConnector;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
-// TODO: add configuration and figure out interaction with SslContextFactory.
public class ServerQuicConnector extends AbstractNetworkConnector
{
private final ServerDatagramSelectorManager _manager;
- private final QuicheConfig _quicheConfig;
+ private final SslContextFactory.Server _sslContextFactory;
+ private final QuicheConfig _quicheConfig = new QuicheConfig();
private volatile DatagramChannel _datagramChannel;
private volatile int _localPort = -1;
- public ServerQuicConnector(
- @Name("server") Server server,
- @Name("executor") Executor executor,
- @Name("scheduler") Scheduler scheduler,
- @Name("bufferPool") ByteBufferPool bufferPool,
- @Name("selectors") int selectors,
- @Name("factories") ConnectionFactory... factories)
+ public ServerQuicConnector(Server server, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
- super(server, executor, scheduler, bufferPool, 0, factories);
- _manager = new ServerDatagramSelectorManager(getExecutor(), getScheduler(), selectors);
- addBean(_manager, true);
- setAcceptorPriorityDelta(-2);
-
- File[] files;
- try
- {
- SSLKeyPair keyPair;
- keyPair = new SSLKeyPair(new File("src/test/resources/keystore.p12"), "PKCS12", "storepwd".toCharArray(), "mykey", "storepwd".toCharArray());
- files = keyPair.export(new File(System.getProperty("java.io.tmpdir")));
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
-
- // TODO make the QuicheConfig configurable
- _quicheConfig = new QuicheConfig();
- _quicheConfig.setPrivKeyPemPath(files[0].getPath());
- _quicheConfig.setCertChainPemPath(files[1].getPath());
- _quicheConfig.setVerifyPeer(false);
- _quicheConfig.setMaxIdleTimeout(5000L);
- _quicheConfig.setInitialMaxData(10000000L);
- _quicheConfig.setInitialMaxStreamDataBidiLocal(10000000L);
- _quicheConfig.setInitialMaxStreamDataBidiRemote(10000000L);
- _quicheConfig.setInitialMaxStreamDataUni(10000000L);
- _quicheConfig.setInitialMaxStreamsBidi(100L);
- _quicheConfig.setCongestionControl(QuicheConfig.CongestionControl.RENO);
- List protocols = getProtocols();
- protocols.add(0, "http/0.9"); // TODO this is only needed for Quiche example clients
- _quicheConfig.setApplicationProtos(protocols.toArray(new String[0]));
+ this(server, null, null, null, sslContextFactory, factories);
}
- public ServerQuicConnector(
- @Name("server") Server server,
- @Name("factories") ConnectionFactory... factories)
+ public ServerQuicConnector(Server server, Executor executor, Scheduler scheduler, ByteBufferPool bufferPool, SslContextFactory.Server sslContextFactory, ConnectionFactory... factories)
{
- this(server, null, null, null, 1, factories);
+ super(server, executor, scheduler, bufferPool, 0, factories);
+ _manager = new ServerDatagramSelectorManager(getExecutor(), getScheduler(), 1);
+ addBean(_manager);
+ _sslContextFactory = sslContextFactory;
+ addBean(_sslContextFactory);
}
@Override
@@ -101,6 +66,13 @@ public class ServerQuicConnector extends AbstractNetworkConnector
return _localPort;
}
+ @Override
+ public boolean isOpen()
+ {
+ DatagramChannel channel = _datagramChannel;
+ return channel != null && channel.isOpen();
+ }
+
@Override
protected void doStart() throws Exception
{
@@ -108,21 +80,34 @@ public class ServerQuicConnector extends AbstractNetworkConnector
_manager.addEventListener(l);
super.doStart();
_manager.accept(_datagramChannel);
- }
- @Override
- protected void doStop() throws Exception
- {
- super.doStop();
- for (EventListener l : getBeans(EventListener.class))
- _manager.removeEventListener(l);
- }
+ String alias = _sslContextFactory.getCertAlias();
+ char[] keyStorePassword = _sslContextFactory.getKeyStorePassword().toCharArray();
+ String keyManagerPassword = _sslContextFactory.getKeyManagerPassword();
+ SSLKeyPair keyPair = new SSLKeyPair(
+ _sslContextFactory.getKeyStoreResource().getFile(),
+ _sslContextFactory.getKeyStoreType(),
+ keyStorePassword,
+ alias == null ? "mykey" : alias,
+ keyManagerPassword == null ? keyStorePassword : keyManagerPassword.toCharArray()
+ );
+ File[] pemFiles = keyPair.export(new File(System.getProperty("java.io.tmpdir")));
- @Override
- public boolean isOpen()
- {
- DatagramChannel channel = _datagramChannel;
- return channel != null && channel.isOpen();
+ // TODO: make the QuicheConfig configurable.
+ _quicheConfig.setPrivKeyPemPath(pemFiles[0].getPath());
+ _quicheConfig.setCertChainPemPath(pemFiles[1].getPath());
+ _quicheConfig.setVerifyPeer(false);
+ _quicheConfig.setMaxIdleTimeout(getIdleTimeout());
+ _quicheConfig.setInitialMaxData(10000000L);
+ _quicheConfig.setInitialMaxStreamDataBidiLocal(10000000L);
+ _quicheConfig.setInitialMaxStreamDataBidiRemote(10000000L);
+ _quicheConfig.setInitialMaxStreamDataUni(10000000L);
+ _quicheConfig.setInitialMaxStreamsBidi(100L);
+ _quicheConfig.setCongestionControl(QuicheConfig.CongestionControl.RENO);
+ List protocols = getProtocols();
+ // This is only needed for Quiche example clients.
+ protocols.add(0, "http/0.9");
+ _quicheConfig.setApplicationProtos(protocols.toArray(String[]::new));
}
@Override
@@ -134,44 +119,18 @@ public class ServerQuicConnector extends AbstractNetworkConnector
_datagramChannel.configureBlocking(false);
_localPort = _datagramChannel.socket().getLocalPort();
if (_localPort <= 0)
- throw new IOException("Datagram channel not bound");
+ throw new IOException("DatagramChannel not bound");
addBean(_datagramChannel);
}
}
- @Override
- public void close()
- {
- super.close();
-
- DatagramChannel datagramChannel = _datagramChannel;
- _datagramChannel = null;
- if (datagramChannel != null)
- {
- removeBean(datagramChannel);
-
- if (datagramChannel.isOpen())
- {
- try
- {
- datagramChannel.close();
- }
- catch (IOException e)
- {
- LOG.warn("Unable to close {}", datagramChannel, e);
- }
- }
- }
- _localPort = -2;
- }
-
protected DatagramChannel openDatagramChannel() throws IOException
{
InetSocketAddress bindAddress = getHost() == null ? new InetSocketAddress(getPort()) : new InetSocketAddress(getHost(), getPort());
DatagramChannel datagramChannel = DatagramChannel.open();
try
{
- datagramChannel.socket().bind(bindAddress);
+ datagramChannel.bind(bindAddress);
}
catch (Throwable e)
{
@@ -181,6 +140,28 @@ public class ServerQuicConnector extends AbstractNetworkConnector
return datagramChannel;
}
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ for (EventListener l : getBeans(EventListener.class))
+ _manager.removeEventListener(l);
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+ DatagramChannel datagramChannel = _datagramChannel;
+ _datagramChannel = null;
+ if (datagramChannel != null)
+ {
+ removeBean(datagramChannel);
+ IO.close(datagramChannel);
+ }
+ _localPort = -2;
+ }
+
@Override
public Object getTransport()
{
diff --git a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicSession.java b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicSession.java
index 2cf743ff1cd..fa4bd1c433e 100644
--- a/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicSession.java
+++ b/jetty-quic/quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicSession.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.quic.server;
import java.net.SocketAddress;
+import java.util.Map;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
@@ -50,20 +51,15 @@ public class ServerQuicSession extends QuicSession
{
ConnectionFactory connectionFactory = findConnectionFactory(getNegotiatedProtocol());
if (connectionFactory instanceof ProtocolQuicSession.Factory)
- return ((ProtocolQuicSession.Factory)connectionFactory).newProtocolQuicSession(this);
- return new ProtocolQuicSession(this);
+ return ((ProtocolQuicSession.Factory)connectionFactory).newProtocolQuicSession(this, Map.of());
+ return new ProtocolServerQuicSession(this);
}
@Override
- protected QuicStreamEndPoint createQuicStreamEndPoint(long streamId)
+ public Connection newConnection(QuicStreamEndPoint endPoint)
{
ConnectionFactory connectionFactory = findConnectionFactory(getNegotiatedProtocol());
- QuicStreamEndPoint endPoint = new QuicStreamEndPoint(getScheduler(), this, streamId);
- Connection connection = connectionFactory.newConnection(connector, endPoint);
- endPoint.setConnection(connection);
- endPoint.onOpen();
- connection.onOpen();
- return endPoint;
+ return connectionFactory.newConnection(connector, endPoint);
}
private ConnectionFactory findConnectionFactory(String negotiatedProtocol)
diff --git a/jetty-quic/quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java b/jetty-quic/quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java
index 1f311708357..59647accc11 100644
--- a/jetty-quic/quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java
+++ b/jetty-quic/quic-server/src/test/java/org/eclipse/jetty/quic/server/ServerQuicConnectorTest.java
@@ -25,6 +25,7 @@ import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -34,13 +35,17 @@ public class ServerQuicConnectorTest
@Test
public void testSmall() throws Exception
{
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
+ sslContextFactory.setKeyStorePassword("storepwd");
+
Server server = new Server();
HttpConfiguration config = new HttpConfiguration();
config.setHttpCompliance(HttpCompliance.LEGACY); // enable HTTP/0.9
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);
- ServerQuicConnector connector = new ServerQuicConnector(server, connectionFactory);
+ ServerQuicConnector connector = new ServerQuicConnector(server, sslContextFactory, connectionFactory);
connector.setPort(8443);
server.addConnector(connector);
@@ -71,13 +76,17 @@ public class ServerQuicConnectorTest
@Test
public void testBig() throws Exception
{
+ SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
+ sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
+ sslContextFactory.setKeyStorePassword("storepwd");
+
Server server = new Server();
HttpConfiguration config = new HttpConfiguration();
config.setHttpCompliance(HttpCompliance.LEGACY); // enable HTTP/0.9
HttpConnectionFactory connectionFactory = new HttpConnectionFactory(config);
- ServerQuicConnector connector = new ServerQuicConnector(server, connectionFactory);
+ ServerQuicConnector connector = new ServerQuicConnector(server, sslContextFactory, connectionFactory);
connector.setPort(8443);
server.addConnector(connector);