Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/Vertx.java
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,22 @@ default NetClient createNetClient() {
*/
HttpServer createHttpServer(HttpServerOptions options);

/**
* Create an HTTP3 client using the specified options
*
* @param options the options to use
* @return the server
*/
HttpClientAgent createHttpClient(Http3ClientOptions options);

/**
* Create an HTTP3 server using the specified options
*
* @param options the options to use
* @return the server
*/
HttpServer createHttpServer(Http3ServerOptions options);

/**
* Create an HTTP/HTTPS server using default options
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.vertx.core.http;

import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.net.QLogConfig;
import io.vertx.core.net.QuicClientOptions;

@DataObject
public class Http3ClientOptions extends QuicClientOptions {

public Http3ClientOptions() {
}

public Http3ClientOptions(Http3ClientOptions other) {
super(other);
}

@Override
public Http3ClientOptions setQLogConfig(QLogConfig qLogConfig) {
return (Http3ClientOptions)super.setQLogConfig(qLogConfig);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.vertx.core.http;

import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.QLogConfig;
import io.vertx.core.net.QuicClientAddressValidation;
import io.vertx.core.net.QuicServerOptions;

import java.time.Duration;

@DataObject
public class Http3ServerOptions extends QuicServerOptions {

public Http3ServerOptions() {
}

public Http3ServerOptions(Http3ServerOptions other) {
super(other);
}

@Override
public Http3ServerOptions setQLogConfig(QLogConfig qLogConfig) {
return (Http3ServerOptions)super.setQLogConfig(qLogConfig);
}

@Override
public Http3ServerOptions setLoadBalanced(boolean loadBalanced) {
return (Http3ServerOptions)super.setLoadBalanced(loadBalanced);
}

@Override
public Http3ServerOptions setClientAddressValidation(QuicClientAddressValidation clientAddressValidation) {
return (Http3ServerOptions)super.setClientAddressValidation(clientAddressValidation);
}

@Override
public Http3ServerOptions setClientAddressValidationTimeWindow(Duration clientAddressValidationTimeWindow) {
return (Http3ServerOptions)super.setClientAddressValidationTimeWindow(clientAddressValidationTimeWindow);
}

@Override
public Http3ServerOptions setClientAddressValidationKey(KeyCertOptions validationKey) {
return (Http3ServerOptions)super.setClientAddressValidationKey(validationKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2011-2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://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 io.vertx.core.http.impl;

import io.netty.handler.codec.http3.Http3;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.Http3ClientOptions;
import io.vertx.core.http.impl.http3.Http3ClientConnection;
import io.vertx.core.internal.ContextInternal;
import io.vertx.core.internal.VertxInternal;
import io.vertx.core.internal.quic.QuicConnectionInternal;
import io.vertx.core.net.*;
import io.vertx.core.spi.metrics.ClientMetrics;

import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Http3ChannelConnector implements HttpChannelConnector {

private final VertxInternal vertx;
private final Lock lock;
private Future<QuicClient> clientFuture;
private final Http3ClientOptions options;

public Http3ChannelConnector(VertxInternal vertxInternal, Http3ClientOptions options) {

options = new Http3ClientOptions(options);
options.getSslOptions().setApplicationLayerProtocols(Arrays.asList(Http3.supportedApplicationProtocols()));
options.getTransportOptions().setInitialMaxData(10000000L);
options.getTransportOptions().setInitialMaxStreamDataBidirectionalLocal(1000000L);
options.getTransportOptions().setInitialMaxStreamDataBidirectionalRemote(1000000L);
options.getTransportOptions().setInitialMaxStreamDataUnidirectional(1000000L);
options.getTransportOptions().setInitialMaxStreamsBidirectional(100L);
options.getTransportOptions().setInitialMaxStreamsUnidirectional(100L);

this.vertx = vertxInternal;
this.lock = new ReentrantLock();
this.options = options;
}

@Override
public Future<HttpClientConnection> httpConnect(ContextInternal context, SocketAddress server, HostAndPort authority, HttpConnectParams params, long maxLifetimeMillis, ClientMetrics<?, ?, ?> metrics) {

lock.lock();
Future<QuicClient> fut = clientFuture;
if (fut == null) {
QuicClient client = QuicClient.create(vertx, this.options);
fut = client.bind(SocketAddress.inetSocketAddress(0, "localhost")).map(client);
clientFuture = fut;
lock.unlock();
} else {
lock.unlock();
}
Promise<HttpClientConnection> promise = context.promise();

fut.onComplete((res, err) -> {
if (err == null) {
Future<QuicConnection> f = res.connect(server);
f.onComplete((res2, err2) -> {
if (err2 == null) {
Http3ClientConnection c = new Http3ClientConnection((QuicConnectionInternal) res2);
c.init();
promise.complete(c);
} else {
promise.fail(err2);
}
});
} else {
promise.fail(err);
}
});

return promise.future();
}

@Override
public Future<Void> shutdown(Duration timeout) {
if (clientFuture == null) {
return vertx.getOrCreateContext().succeededFuture();
} else {
return clientFuture.compose(client -> client.shutdown(timeout));
}
}

@Override
public Future<Void> close() {
if (clientFuture == null) {
return vertx.getOrCreateContext().succeededFuture();
} else {
return clientFuture.compose(QuicEndpoint::close);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@
*/
public class HttpClientImpl extends HttpClientBase implements HttpClientInternal, MetricsProvider {

static class Config {
List<String> nonProxyHosts;
boolean verifyHost;
boolean defaultSsl;
String defaultHost;
int defaultPort;
int maxRedirects;
int initialPoolKind;
public static class Config {
public List<String> nonProxyHosts;
public boolean verifyHost;
public boolean defaultSsl;
public String defaultHost;
public int defaultPort;
public int maxRedirects;
public int initialPoolKind;
}

// Pattern to check we are not dealing with an absoluate URI
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package io.vertx.core.http.impl.http3;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http3.DefaultHttp3Headers;
import io.netty.handler.codec.http3.Http3ClientConnectionHandler;
import io.netty.handler.codec.http3.Http3RequestStreamInitializer;
import io.netty.handler.codec.quic.QuicStreamChannel;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.http.*;
import io.vertx.core.http.impl.HttpClientConnection;
import io.vertx.core.http.impl.HttpClientStream;
import io.vertx.core.http.impl.headers.HttpRequestHeaders;
import io.vertx.core.internal.ContextInternal;
import io.vertx.core.internal.quic.QuicConnectionInternal;
import io.vertx.core.internal.quic.QuicStreamInternal;
import io.vertx.core.net.HostAndPort;

import java.util.function.Consumer;
import java.util.function.Function;

public class Http3ClientConnection extends Http3Connection implements HttpClientConnection {

public Http3ClientConnection(QuicConnectionInternal connection) {
super(connection);
}

public void init() {

super.init();

Http3ClientConnectionHandler http3Handler = new Http3ClientConnectionHandler();

ChannelPipeline pipeline = connection.channelHandlerContext().pipeline();

pipeline.addBefore("handler", "http3", http3Handler);



}

@Override
public MultiMap newHttpRequestHeaders() {
return new HttpRequestHeaders(new DefaultHttp3Headers());
}

@Override
public long activeStreams() {
return 0;
}

@Override
public long concurrency() {
// For now hardcode
return 10;
}

@Override
public HostAndPort authority() {
return HostAndPort.authority("localhost", 8443);
}

@Override
public HttpClientConnection evictionHandler(Handler<Void> handler) {
return null;
}

@Override
public HttpClientConnection invalidMessageHandler(Handler<Object> handler) {
return null;
}

@Override
public HttpClientConnection concurrencyChangeHandler(Handler<Long> handler) {
return null;
}

@Override
public ChannelHandlerContext channelHandlerContext() {
return null;
}

@Override
public Future<HttpClientStream> createStream(ContextInternal context) {
return connection.createStream(context, true, new Function<Consumer<QuicStreamChannel>, ChannelInitializer<QuicStreamChannel>>() {
@Override
public ChannelInitializer<QuicStreamChannel> apply(Consumer<QuicStreamChannel> quicStreamChannelConsumer) {
return new Http3RequestStreamInitializer() {
@Override
protected void initRequestStream(QuicStreamChannel ch) {
quicStreamChannelConsumer.accept(ch);
}
};
}
}).map(stream -> {
QuicStreamInternal streamInternal = (QuicStreamInternal) stream;
Http3ClientStream http3Stream = new Http3ClientStream(this, streamInternal, context);
http3Stream.init();
return http3Stream;
});
}

@Override
public ContextInternal context() {
return context;
}

@Override
public boolean isValid() {
return false;
}

@Override
public Object metric() {
return null;
}

@Override
public long lastResponseReceivedTimestamp() {
return 0;
}

@Override
public String indicatedServerName() {
return "";
}

}
Loading
Loading