Issue #6728 - QUIC and HTTP/3
- Continued implementation of HTTP/3 APIs, frames and parsing, client and server. - Figured out creation of streams, to support special HTTP/3 streams. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
3cbbb47742
commit
7c09800fe1
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.http3</groupId>
|
||||
<artifactId>http3-parent</artifactId>
|
||||
<version>10.0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>http3-client</artifactId>
|
||||
<name>Jetty :: HTTP3 :: Client</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http3</groupId>
|
||||
<artifactId>http3-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-io</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -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;
|
||||
}
|
|
@ -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<String> protocols = List.of("h3");
|
||||
|
||||
public HTTP3Client()
|
||||
{
|
||||
this.connector = new ClientConnector(new QuicClientConnectorConfigurator());
|
||||
addBean(connector);
|
||||
}
|
||||
|
||||
@ManagedAttribute("The ALPN protocol list")
|
||||
public List<String> getProtocols()
|
||||
{
|
||||
return protocols;
|
||||
}
|
||||
|
||||
public void setProtocols(List<String> protocols)
|
||||
{
|
||||
this.protocols = protocols;
|
||||
}
|
||||
|
||||
public CompletableFuture<Session> connect(SocketAddress address, Session.Listener listener)
|
||||
{
|
||||
Promise.Completable<Session> completable = new Promise.Completable<>();
|
||||
ClientConnectionFactory factory = new HTTP3ClientConnectionFactory();
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
}
|
|
@ -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<String, Object> 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<String, Object> context) throws IOException
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>http3-parent</artifactId>
|
||||
<groupId>org.eclipse.jetty.http3</groupId>
|
||||
<artifactId>http3-parent</artifactId>
|
||||
<version>10.0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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<Stream> newStream(HeadersFrame frame, Stream.Listener listener);
|
||||
|
||||
public interface Listener {}
|
||||
}
|
||||
|
|
|
@ -15,4 +15,5 @@ package org.eclipse.jetty.http3.api;
|
|||
|
||||
public interface Stream
|
||||
{
|
||||
public interface Listener {}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -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<Stream> newStream(HeadersFrame frame, Stream.Listener listener)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>http3-parent</artifactId>
|
||||
<groupId>org.eclipse.jetty.http3</groupId>
|
||||
<artifactId>http3-parent</artifactId>
|
||||
<version>10.0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-common</artifactId>
|
||||
<artifactId>quic-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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<String, Object> 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;
|
||||
}
|
||||
}
|
|
@ -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<Instruction> instructions) throws QpackException
|
||||
{
|
||||
// TODO: feed the Instruction to QuicSession.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Instruction> queue = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public void onInstructions(List<Instruction> instructions)
|
||||
{
|
||||
try (AutoLock l = lock.lock())
|
||||
{
|
||||
queue.addAll(instructions);
|
||||
}
|
||||
iterate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Action process()
|
||||
{
|
||||
List<Instruction> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty.http3</groupId>
|
||||
<artifactId>http3-parent</artifactId>
|
||||
<version>10.0.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>http3-tests</artifactId>
|
||||
<name>Jetty :: HTTP3 :: Tests</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http3</groupId>
|
||||
<artifactId>http3-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.http3</groupId>
|
||||
<artifactId>http3-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.quic</groupId>
|
||||
<artifactId>quic-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-slf4j-impl</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
Binary file not shown.
|
@ -16,6 +16,8 @@
|
|||
<module>http3-qpack</module>
|
||||
<module>http3-common</module>
|
||||
<module>http3-server</module>
|
||||
<module>http3-client</module>
|
||||
<module>http3-tests</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -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<SocketAddress, QuicSession> 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<String> protocols = (List<String>)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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<QuicStreamEndPoint> consumer)
|
||||
{
|
||||
return session.getOrCreateStreamEndPoint(streamId, consumer);
|
||||
}
|
||||
|
||||
private void processWritableStreams()
|
||||
{
|
||||
List<Long> 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<Long> 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<String, Object> context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Runnable> strategyQueue = new ArrayDeque<>();
|
||||
private final ConcurrentMap<Long, QuicStreamEndPoint> 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<Long> getWritableStreamIds()
|
||||
{
|
||||
List<Long> 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<Long> 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<Long> 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<QuicStreamEndPoint> 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()
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> 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()
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue