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);