From 35adffac485f226acafe907be2c19d6e1da11fca Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Sun, 16 Jun 2024 14:26:29 +0800 Subject: [PATCH] HTTP/3 support for dubbo triple (#14033) --- .artifacts | 1 + .../apache/dubbo/common/io/StreamUtils.java | 5 +- .../dubbo/config/context/ConfigManager.java | 15 +- .../dubbo/config/nested/TripleConfig.java | 220 ++++++ dubbo-demo/dubbo-demo-triple/pom.xml | 9 + .../org/apache/dubbo/demo/GreeterService.java | 14 + .../apache/dubbo/demo/GreeterServiceImpl.java | 49 +- .../dubbo/demo/consumer/ApiConsumer.java | 70 +- .../demo/consumer/ApiWrapperConsumer.java | 5 + .../dubbo/demo/provider/ApiProvider.java | 9 +- .../demo/provider/ApiWrapperProvider.java | 5 + dubbo-dependencies-bom/pom.xml | 6 + dubbo-distribution/dubbo-all-shaded/pom.xml | 9 + dubbo-distribution/dubbo-all/pom.xml | 17 + dubbo-distribution/dubbo-bom/pom.xml | 5 + dubbo-distribution/dubbo-core-spi/pom.xml | 9 + .../remoting/transport/AbstractClient.java | 8 +- .../AbstractServerHttpChannelObserver.java | 9 + .../remoting/http12/HttpHeaderNames.java | 2 + .../http12/h2/Http2InputMessageFrame.java | 6 +- .../http12/h2/Http2MetadataFrame.java | 8 +- .../http12/h2/Http2OutputMessage.java | 2 +- .../remoting/http12/h2/Http2StreamFrame.java | 2 +- .../message/LengthFieldStreamingDecoder.java | 3 +- .../http12/message/codec/CodecUtils.java | 4 +- .../http12/message/codec/JsonCodec.java | 3 + .../http12/message/codec/CodeUtilsTest.java | 6 +- dubbo-remoting/dubbo-remoting-http3/pom.xml | 56 ++ .../remoting/exchange/Http3Exchanger.java | 85 +++ .../Http3ServerTransportListenerFactory.java | 31 + .../http3/Http3TransportListener.java | 21 + .../http3/netty4/Http2HeadersAdapter.java | 627 ++++++++++++++++++ .../netty4/Http3ChannelAddressAccessor.java | 57 ++ .../http3/netty4/NettyHttp3FrameCodec.java | 155 +++++ .../NettyHttp3ProtocolSelectorHandler.java | 81 +++ .../http3/netty4/NettyHttp3StreamChannel.java | 82 +++ .../remoting/transport/netty4/Helper.java | 75 +++ .../netty4/NettyHttp3ConnectionClient.java | 138 ++++ .../transport/netty4/NettyHttp3Server.java | 207 ++++++ ...ng.transport.netty4.ChannelAddressAccessor | 1 + .../netty4/AbstractNettyConnectionClient.java | 354 ++++++++++ .../transport/netty4/AddressUtils.java | 88 +++ .../netty4/ChannelAddressAccessor.java | 34 + .../transport/netty4/NettyChannel.java | 4 +- .../transport/netty4/NettyChannelHandler.java | 28 +- .../transport/netty4/NettyClientHandler.java | 23 +- .../netty4/NettyConnectionClient.java | 332 +--------- .../netty4/NettyConnectionHandler.java | 52 +- .../transport/netty4/NettyServer.java | 7 +- .../transport/netty4/NettyServerHandler.java | 31 +- dubbo-remoting/pom.xml | 1 + .../java/org/apache/dubbo/rpc/Constants.java | 4 + dubbo-rpc/dubbo-rpc-triple/pom.xml | 7 +- .../rpc/protocol/tri/TripleHttp2Protocol.java | 14 +- .../rpc/protocol/tri/TripleProtocol.java | 21 +- .../protocol/tri/call/TripleClientCall.java | 32 +- .../Http3CreateStreamQueueCommand.java | 61 ++ .../h12/AbstractServerTransportListener.java | 7 + .../GrpcHttp2ServerTransportListener.java | 5 +- .../DefaultHttp11ServerTransportListener.java | 9 + .../GenericHttp2ServerTransportListener.java | 10 + .../h12/http2/Http2ClientStreamFactory.java | 44 ++ .../h12/http2/Http2TripleClientStream.java | 75 +++ .../GenericHttp3ServerTransportListener.java | 45 ++ ...icHttp3ServerTransportListenerFactory.java | 38 ++ .../tri/h3/Http3ClientFrameCodec.java | 107 +++ .../tri/h3/Http3ClientStreamFactory.java | 48 ++ .../tri/h3/Http3TripleClientStream.java | 77 +++ .../GrpcHttp3ServerTransportListener.java | 44 ++ ...pcHttp3ServerTransportListenerFactory.java | 39 ++ ...m.java => AbstractTripleClientStream.java} | 59 +- .../tri/stream/ClientStreamFactory.java | 37 ++ .../tri/stream/TripleStreamChannelFuture.java | 4 +- ....http3.Http3ServerTransportListenerFactory | 2 + ...pc.protocol.tri.stream.ClientStreamFactory | 2 + .../tri/stream/TripleClientStreamTest.java | 3 +- dubbo-test/dubbo-dependencies-all/pom.xml | 5 + 77 files changed, 3386 insertions(+), 484 deletions(-) create mode 100644 dubbo-remoting/dubbo-remoting-http3/pom.xml create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/exchange/Http3Exchanger.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3ServerTransportListenerFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3TransportListener.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/Http2HeadersAdapter.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/Http3ChannelAddressAccessor.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3FrameCodec.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3ProtocolSelectorHandler.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3StreamChannel.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyHttp3ConnectionClient.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyHttp3Server.java create mode 100644 dubbo-remoting/dubbo-remoting-http3/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.transport.netty4.ChannelAddressAccessor create mode 100644 dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/AbstractNettyConnectionClient.java create mode 100644 dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/AddressUtils.java create mode 100644 dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ChannelAddressAccessor.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/command/Http3CreateStreamQueueCommand.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ClientStreamFactory.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2TripleClientStream.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/GenericHttp3ServerTransportListener.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/GenericHttp3ServerTransportListenerFactory.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3ClientFrameCodec.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3ClientStreamFactory.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3TripleClientStream.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/grpc/GrpcHttp3ServerTransportListener.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/grpc/GrpcHttp3ServerTransportListenerFactory.java rename dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/{TripleClientStream.java => AbstractTripleClientStream.java} (88%) create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/ClientStreamFactory.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http3.Http3ServerTransportListenerFactory create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.stream.ClientStreamFactory diff --git a/.artifacts b/.artifacts index 5818a24e9a..ff64e78918 100644 --- a/.artifacts +++ b/.artifacts @@ -75,6 +75,7 @@ dubbo-registry-zookeeper dubbo-remoting dubbo-remoting-api dubbo-remoting-http12 +dubbo-remoting-http3 dubbo-remoting-netty dubbo-remoting-netty4 dubbo-remoting-zookeeper-curator5 diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java index 9e10135d87..6b72eed44d 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/io/StreamUtils.java @@ -27,7 +27,10 @@ import java.nio.charset.StandardCharsets; /** * Stream utils. */ -public class StreamUtils { +public final class StreamUtils { + + public static final ByteArrayInputStream EMPTY = new ByteArrayInputStream(new byte[0]); + private StreamUtils() {} public static InputStream limitedInputStream(final InputStream is, final int limit) throws IOException { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java index e49bf560bf..296f846668 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/context/ConfigManager.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.config.context; +import org.apache.dubbo.common.URL; import org.apache.dubbo.common.context.ApplicationExt; import org.apache.dubbo.common.extension.DisableInject; import org.apache.dubbo.common.logger.Logger; @@ -72,13 +73,14 @@ public class ConfigManager extends AbstractConfigManager implements ApplicationE TracingConfig.class)); } + public static ProtocolConfig getProtocol(URL url) { + return url.getOrDefaultApplicationModel().getApplicationConfigManager().getOrAddProtocol(url.getProtocol()); + } + // ApplicationConfig correlative methods /** * Set application config - * - * @param application - * @return current application config instance */ @DisableInject public void setApplication(ApplicationConfig application) { @@ -147,7 +149,7 @@ public class ConfigManager extends AbstractConfigManager implements ApplicationE if (CollectionUtils.isEmpty(defaults)) { defaults = getConfigCenters(); } - return Optional.ofNullable(defaults); + return ofNullable(defaults); } public Optional getConfigCenter(String id) { @@ -217,6 +219,7 @@ public class ConfigManager extends AbstractConfigManager implements ApplicationE } @Override + @SuppressWarnings("RedundantMethodOverride") public List getDefaultConfigs(Class cls) { return getDefaultConfigs(getConfigsMap(getTagName(cls))); } @@ -289,7 +292,7 @@ public class ConfigManager extends AbstractConfigManager implements ApplicationE // load dubbo.metadata-report.xxx loadConfigsOfTypeFromProps(MetadataReportConfig.class); - // config centers has bean loaded before starting config center + // config centers has been loaded before starting config center // loadConfigsOfTypeFromProps(ConfigCenterConfig.class); refreshAll(); @@ -319,7 +322,7 @@ public class ConfigManager extends AbstractConfigManager implements ApplicationE // check port conflicts Map protocolPortMap = new LinkedHashMap<>(); - for (ProtocolConfig protocol : this.getProtocols()) { + for (ProtocolConfig protocol : getProtocols()) { Integer port = protocol.getPort(); if (port == null || port == -1) { continue; diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java index 0edaf61c36..1524671ed6 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/nested/TripleConfig.java @@ -100,6 +100,99 @@ public class TripleConfig implements Serializable { */ private Integer maxHeaderListSize; + /** + * Enable http3 support + *

The default value is false. + */ + private Boolean enableHttp3; + + /** + * See set_initial_max_data. + *

The default value is 8MiB. + */ + private Integer http3InitialMaxData; + + /** + * If configured this will enable Datagram support. + */ + private Integer http3RecvQueueLen; + + /** + * If configured this will enable Datagram support. + */ + private Integer http3SendQueueLen; + + /** + * See + * set_initial_max_stream_data_bidi_local. + *

The default value is 1MiB. + */ + private Integer http3InitialMaxStreamDataBidiLocal; + + /** + * See + * set_initial_max_stream_data_bidi_remote. + *

The default value is 1MiB. + */ + private Integer http3InitialMaxStreamDataBidiRemote; + + /** + * See + * set_initial_max_stream_data_uni. + *

The default value is 0. + */ + private Integer http3InitialMaxStreamDataUni; + + /** + * See + * set_initial_max_streams_bidi. + *

The default value is 1B(2^30). + */ + private Long http3InitialMaxStreamsBidi; + + /** + * See + * set_initial_max_streams_uni. + *

+ *

The default value is 1B(2^30). + */ + private Long http3InitialMaxStreamsUni; + + /** + * See + * set_ack_delay_exponent. + *

The default value is 3. + */ + private Integer http3MaxAckDelayExponent; + + /** + * See + * set_max_ack_delay. + *

The default value is 25 milliseconds. + */ + private Integer http3MaxAckDelay; + + /** + * See + * set_disable_active_migration. + *

The default value is {@code false}. + */ + private Boolean http3DisableActiveMigration; + + /** + * See + * enable_hystart. + *

The default value is {@code true}. + */ + private Boolean http3EnableHystart; + + /** + * Sets the congestion control algorithm to use. + *

Supported algorithms are {@code "RENO"} or {@code "CUBIC"} or {@code "BBR"}. + *

The default value is {@code "CUBIC"}. + */ + private String http3CcAlgorithm; + public Integer getMaxBodySize() { return maxBodySize; } @@ -196,6 +289,118 @@ public class TripleConfig implements Serializable { this.maxHeaderListSize = maxHeaderListSize; } + public Boolean getEnableHttp3() { + return enableHttp3; + } + + public void setEnableHttp3(Boolean enableHttp3) { + this.enableHttp3 = enableHttp3; + } + + public Integer getHttp3InitialMaxData() { + return http3InitialMaxData; + } + + public void setHttp3InitialMaxData(Integer http3InitialMaxData) { + this.http3InitialMaxData = http3InitialMaxData; + } + + public Integer getHttp3RecvQueueLen() { + return http3RecvQueueLen; + } + + public void setHttp3RecvQueueLen(Integer http3RecvQueueLen) { + this.http3RecvQueueLen = http3RecvQueueLen; + } + + public Integer getHttp3SendQueueLen() { + return http3SendQueueLen; + } + + public void setHttp3SendQueueLen(Integer http3SendQueueLen) { + this.http3SendQueueLen = http3SendQueueLen; + } + + public Integer getHttp3InitialMaxStreamDataBidiLocal() { + return http3InitialMaxStreamDataBidiLocal; + } + + public void setHttp3InitialMaxStreamDataBidiLocal(Integer http3InitialMaxStreamDataBidiLocal) { + this.http3InitialMaxStreamDataBidiLocal = http3InitialMaxStreamDataBidiLocal; + } + + public Integer getHttp3InitialMaxStreamDataBidiRemote() { + return http3InitialMaxStreamDataBidiRemote; + } + + public void setHttp3InitialMaxStreamDataBidiRemote(Integer http3InitialMaxStreamDataBidiRemote) { + this.http3InitialMaxStreamDataBidiRemote = http3InitialMaxStreamDataBidiRemote; + } + + public Integer getHttp3InitialMaxStreamDataUni() { + return http3InitialMaxStreamDataUni; + } + + public void setHttp3InitialMaxStreamDataUni(Integer http3InitialMaxStreamDataUni) { + this.http3InitialMaxStreamDataUni = http3InitialMaxStreamDataUni; + } + + public Long getHttp3InitialMaxStreamsBidi() { + return http3InitialMaxStreamsBidi; + } + + public void setHttp3InitialMaxStreamsBidi(Long http3InitialMaxStreamsBidi) { + this.http3InitialMaxStreamsBidi = http3InitialMaxStreamsBidi; + } + + public Long getHttp3InitialMaxStreamsUni() { + return http3InitialMaxStreamsUni; + } + + public void setHttp3InitialMaxStreamsUni(Long http3InitialMaxStreamsUni) { + this.http3InitialMaxStreamsUni = http3InitialMaxStreamsUni; + } + + public Integer getHttp3MaxAckDelayExponent() { + return http3MaxAckDelayExponent; + } + + public void setHttp3MaxAckDelayExponent(Integer http3MaxAckDelayExponent) { + this.http3MaxAckDelayExponent = http3MaxAckDelayExponent; + } + + public Integer getHttp3MaxAckDelay() { + return http3MaxAckDelay; + } + + public void setHttp3MaxAckDelay(Integer http3MaxAckDelay) { + this.http3MaxAckDelay = http3MaxAckDelay; + } + + public Boolean getHttp3DisableActiveMigration() { + return http3DisableActiveMigration; + } + + public void setHttp3DisableActiveMigration(Boolean http3DisableActiveMigration) { + this.http3DisableActiveMigration = http3DisableActiveMigration; + } + + public Boolean getHttp3EnableHystart() { + return http3EnableHystart; + } + + public void setHttp3EnableHystart(Boolean http3EnableHystart) { + this.http3EnableHystart = http3EnableHystart; + } + + public String getHttp3CcAlgorithm() { + return http3CcAlgorithm; + } + + public void setHttp3CcAlgorithm(String http3CcAlgorithm) { + this.http3CcAlgorithm = http3CcAlgorithm; + } + public void checkDefault() { if (maxBodySize == null) { maxBodySize = 1 << 23; @@ -233,5 +438,20 @@ public class TripleConfig implements Serializable { if (maxHeaderListSize == null) { maxHeaderListSize = 1 << 15; } + if (http3InitialMaxData == null) { + http3InitialMaxData = 1 << 23; + } + if (http3InitialMaxStreamDataBidiLocal == null) { + http3InitialMaxStreamDataBidiLocal = 1 << 20; + } + if (http3InitialMaxStreamDataBidiRemote == null) { + http3InitialMaxStreamDataBidiRemote = 1 << 20; + } + if (http3InitialMaxStreamsBidi == null) { + http3InitialMaxStreamsBidi = (long) 1 << 30; + } + if (http3InitialMaxStreamsUni == null) { + http3InitialMaxStreamsUni = (long) 1 << 30; + } } } diff --git a/dubbo-demo/dubbo-demo-triple/pom.xml b/dubbo-demo/dubbo-demo-triple/pom.xml index 9889eb8a2e..8d81d57de2 100644 --- a/dubbo-demo/dubbo-demo-triple/pom.xml +++ b/dubbo-demo/dubbo-demo-triple/pom.xml @@ -127,6 +127,15 @@ com.google.protobuf protobuf-java + + org.bouncycastle + bcpkix-jdk15on + + + org.apache.dubbo + dubbo-remoting-http3 + ${project.version} + org.apache.logging.log4j log4j-slf4j-impl diff --git a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/GreeterService.java b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/GreeterService.java index 2506a5da74..e9210d2592 100644 --- a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/GreeterService.java +++ b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/GreeterService.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.demo; +import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.demo.hello.HelloReply; import org.apache.dubbo.demo.hello.HelloRequest; @@ -28,5 +29,18 @@ public interface GreeterService { */ HelloReply sayHello(HelloRequest request); + /** + * Sends a greeting asynchronously + */ CompletableFuture sayHelloAsync(String request); + + /** + * Sends a greeting with server streaming + */ + void sayHelloServerStream(HelloRequest request, StreamObserver responseObserver); + + /** + * Sends greetings with bi streaming + */ + StreamObserver sayHelloBiStream(StreamObserver responseObserver); } diff --git a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/GreeterServiceImpl.java b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/GreeterServiceImpl.java index 36cc07f3ee..9673ac9fdf 100644 --- a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/GreeterServiceImpl.java +++ b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/GreeterServiceImpl.java @@ -16,20 +16,65 @@ */ package org.apache.dubbo.demo; +import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.demo.hello.HelloReply; import org.apache.dubbo.demo.hello.HelloRequest; import java.util.concurrent.CompletableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class GreeterServiceImpl implements GreeterService { + private static final Logger LOG = LoggerFactory.getLogger(GreeterServiceImpl.class); + @Override public HelloReply sayHello(HelloRequest request) { - return HelloReply.newBuilder().setMessage("Hello " + request.getName()).build(); + LOG.info("Received sayHello request: {}", request.getName()); + return toReply("Hello " + request.getName()); } @Override public CompletableFuture sayHelloAsync(String name) { - return CompletableFuture.supplyAsync(() -> name); + LOG.info("Received sayHelloAsync request: {}", name); + return CompletableFuture.supplyAsync(() -> "Hello " + name); + } + + @Override + public void sayHelloServerStream(HelloRequest request, StreamObserver responseObserver) { + LOG.info("Received sayHelloServerStream request"); + for (int i = 1; i < 6; i++) { + LOG.info("sayHelloServerStream onNext: {} {} times", request.getName(), i); + responseObserver.onNext(toReply("Hello " + request.getName() + ' ' + i + " times")); + } + LOG.info("sayHelloServerStream onCompleted"); + responseObserver.onCompleted(); + } + + @Override + public StreamObserver sayHelloBiStream(StreamObserver responseObserver) { + LOG.info("Received sayHelloBiStream request"); + return new StreamObserver() { + @Override + public void onNext(HelloRequest request) { + LOG.info("sayHelloBiStream onNext: {}", request.getName()); + responseObserver.onNext(toReply("Hello " + request.getName())); + } + + @Override + public void onError(Throwable throwable) { + LOG.error("sayHelloBiStream onError", throwable); + } + + @Override + public void onCompleted() { + LOG.info("sayHelloBiStream onCompleted"); + } + }; + } + + private static HelloReply toReply(String message) { + return HelloReply.newBuilder().setMessage(message).build(); } } diff --git a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/consumer/ApiConsumer.java b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/consumer/ApiConsumer.java index 90cbfc49e3..6a78b12f37 100644 --- a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/consumer/ApiConsumer.java +++ b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/consumer/ApiConsumer.java @@ -17,6 +17,7 @@ package org.apache.dubbo.demo.consumer; import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.config.ApplicationConfig; import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.ReferenceConfig; @@ -25,19 +26,23 @@ import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.demo.GreeterService; import org.apache.dubbo.demo.hello.HelloReply; import org.apache.dubbo.demo.hello.HelloRequest; +import org.apache.dubbo.rpc.Constants; -import java.io.IOException; +import java.util.Collections; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; public class ApiConsumer { - public static void main(String[] args) throws InterruptedException, IOException { + + public static void main(String[] args) throws InterruptedException { ReferenceConfig referenceConfig = new ReferenceConfig<>(); referenceConfig.setInterface(GreeterService.class); referenceConfig.setCheck(false); referenceConfig.setProtocol(CommonConstants.TRIPLE); referenceConfig.setLazy(true); referenceConfig.setTimeout(100000); + if (args.length > 0 && Constants.HTTP3_KEY.equals(args[0])) { + referenceConfig.setParameters(Collections.singletonMap(Constants.HTTP3_KEY, "true")); + } DubboBootstrap bootstrap = DubboBootstrap.getInstance(); bootstrap @@ -50,16 +55,63 @@ public class ApiConsumer { GreeterService greeterService = referenceConfig.get(); System.out.println("dubbo referenceConfig started"); try { - final HelloReply reply = greeterService.sayHello( - HelloRequest.newBuilder().setName("triple").build()); - TimeUnit.SECONDS.sleep(1); - System.out.println("Reply: " + reply.getMessage()); + System.out.println("Call sayHello"); + HelloReply reply = greeterService.sayHello(buildRequest("triple")); + System.out.println("sayHello reply: " + reply.getMessage()); + System.out.println("Call sayHelloAsync"); CompletableFuture sayHelloAsync = greeterService.sayHelloAsync("triple"); - System.out.println("Async Reply: " + sayHelloAsync.get()); + sayHelloAsync.thenAccept(value -> System.out.println("sayHelloAsync reply: " + value)); + + StreamObserver responseObserver = new StreamObserver() { + @Override + public void onNext(HelloReply reply) { + System.out.println("sayHelloServerStream onNext: " + reply.getMessage()); + } + + @Override + public void onError(Throwable t) { + System.out.println("sayHelloServerStream onError: " + t.getMessage()); + } + + @Override + public void onCompleted() { + System.out.println("sayHelloServerStream onCompleted"); + } + }; + System.out.println("Call sayHelloServerStream"); + greeterService.sayHelloServerStream(buildRequest("triple"), responseObserver); + + StreamObserver biResponseObserver = new StreamObserver() { + @Override + public void onNext(HelloReply reply) { + System.out.println("biRequestObserver onNext: " + reply.getMessage()); + } + + @Override + public void onError(Throwable t) { + System.out.println("biResponseObserver onError: " + t.getMessage()); + } + + @Override + public void onCompleted() { + System.out.println("biResponseObserver onCompleted"); + } + }; + System.out.println("Call biRequestObserver"); + StreamObserver biRequestObserver = greeterService.sayHelloBiStream(biResponseObserver); + for (int i = 0; i < 5; i++) { + biRequestObserver.onNext(buildRequest("triple" + i)); + } + biRequestObserver.onCompleted(); } catch (Throwable t) { + //noinspection CallToPrintStackTrace t.printStackTrace(); } - System.in.read(); + Thread.sleep(2000); + } + + private static HelloRequest buildRequest(String name) { + return HelloRequest.newBuilder().setName(name).build(); } } diff --git a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/consumer/ApiWrapperConsumer.java b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/consumer/ApiWrapperConsumer.java index 52bb640690..074dc9d296 100644 --- a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/consumer/ApiWrapperConsumer.java +++ b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/consumer/ApiWrapperConsumer.java @@ -23,8 +23,10 @@ import org.apache.dubbo.config.ReferenceConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.demo.GreeterWrapperService; +import org.apache.dubbo.rpc.Constants; import java.io.IOException; +import java.util.Collections; public class ApiWrapperConsumer { public static void main(String[] args) throws IOException { @@ -33,6 +35,9 @@ public class ApiWrapperConsumer { referenceConfig.setCheck(false); referenceConfig.setProtocol("tri"); referenceConfig.setLazy(true); + if (args.length > 0 && Constants.HTTP3_KEY.equals(args[0])) { + referenceConfig.setParameters(Collections.singletonMap(Constants.HTTP3_KEY, "true")); + } DubboBootstrap bootstrap = DubboBootstrap.getInstance(); bootstrap diff --git a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/provider/ApiProvider.java b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/provider/ApiProvider.java index d2c680a87e..9156267702 100644 --- a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/provider/ApiProvider.java +++ b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/provider/ApiProvider.java @@ -24,12 +24,19 @@ import org.apache.dubbo.config.ServiceConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.demo.GreeterService; import org.apache.dubbo.demo.GreeterServiceImpl; +import org.apache.dubbo.rpc.Constants; + +import java.util.Collections; public class ApiProvider { - public static void main(String[] args) throws InterruptedException { + + public static void main(String[] args) { ServiceConfig serviceConfig = new ServiceConfig<>(); serviceConfig.setInterface(GreeterService.class); serviceConfig.setRef(new GreeterServiceImpl()); + if (args.length > 0 && Constants.HTTP3_KEY.equals(args[0])) { + serviceConfig.setParameters(Collections.singletonMap(Constants.HTTP3_KEY, "true")); + } DubboBootstrap bootstrap = DubboBootstrap.getInstance(); bootstrap diff --git a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/provider/ApiWrapperProvider.java b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/provider/ApiWrapperProvider.java index c874960d35..27cdf25aff 100644 --- a/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/provider/ApiWrapperProvider.java +++ b/dubbo-demo/dubbo-demo-triple/src/main/java/org/apache/dubbo/demo/provider/ApiWrapperProvider.java @@ -24,14 +24,19 @@ import org.apache.dubbo.config.ServiceConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.demo.GreeterWrapperService; import org.apache.dubbo.demo.GreeterWrapperServiceImpl; +import org.apache.dubbo.rpc.Constants; import java.io.IOException; +import java.util.Collections; public class ApiWrapperProvider { public static void main(String[] args) throws IOException { ServiceConfig serviceConfig = new ServiceConfig<>(); serviceConfig.setInterface(GreeterWrapperService.class); serviceConfig.setRef(new GreeterWrapperServiceImpl()); + if (args.length > 0 && Constants.HTTP3_KEY.equals(args[0])) { + serviceConfig.setParameters(Collections.singletonMap(Constants.HTTP3_KEY, "true")); + } DubboBootstrap bootstrap = DubboBootstrap.getInstance(); bootstrap diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index f9a9e8ba99..b8d0eb5499 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -96,6 +96,7 @@ 1.14.16 3.2.10.Final 4.1.110.Final + 0.0.28.Final 4.5.14 4.4.16 1.2.83 @@ -247,6 +248,11 @@ netty-all ${netty4_version} + + io.netty.incubator + netty-incubator-codec-http3 + ${netty_http3_version} + org.javassist javassist diff --git a/dubbo-distribution/dubbo-all-shaded/pom.xml b/dubbo-distribution/dubbo-all-shaded/pom.xml index bc7ca34f7f..bbd07fdbfb 100644 --- a/dubbo-distribution/dubbo-all-shaded/pom.xml +++ b/dubbo-distribution/dubbo-all-shaded/pom.xml @@ -687,6 +687,9 @@ META-INF/dubbo/internal/org.apache.dubbo.remoting.Transporter + + META-INF/dubbo/internal/org.apache.dubbo.remoting.transport.netty4.ChannelAddressAccessor + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.dubbo.ByteAccessor @@ -714,6 +717,9 @@ META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListenerFactory + + META-INF/dubbo/internal/org.apache.dubbo.remoting.http3.Http3ServerTransportListenerFactory + META-INF/dubbo/internal/org.apache.dubbo.remoting.telnet.TelnetHandler @@ -834,6 +840,9 @@ META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.stream.ClientStreamFactory + META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml index db8f34bf76..366b4ac7de 100644 --- a/dubbo-distribution/dubbo-all/pom.xml +++ b/dubbo-distribution/dubbo-all/pom.xml @@ -343,6 +343,13 @@ compile true + + org.apache.dubbo + dubbo-remoting-http3 + ${project.version} + compile + true + org.apache.dubbo dubbo-remoting-netty @@ -518,6 +525,7 @@ org.apache.dubbo:dubbo-registry-zookeeper org.apache.dubbo:dubbo-remoting-api org.apache.dubbo:dubbo-remoting-http12 + org.apache.dubbo:dubbo-remoting-http3 org.apache.dubbo:dubbo-remoting-netty4 org.apache.dubbo:dubbo-remoting-netty org.apache.dubbo:dubbo-remoting-zookeeper-curator5 @@ -726,6 +734,9 @@ META-INF/dubbo/internal/org.apache.dubbo.remoting.Transporter + + META-INF/dubbo/internal/org.apache.dubbo.remoting.transport.netty4.ChannelAddressAccessor + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.dubbo.ByteAccessor @@ -753,6 +764,9 @@ META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListenerFactory + + META-INF/dubbo/internal/org.apache.dubbo.remoting.http3.Http3ServerTransportListenerFactory + META-INF/dubbo/internal/org.apache.dubbo.remoting.telnet.TelnetHandler @@ -873,6 +887,9 @@ META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.stream.ClientStreamFactory + META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory diff --git a/dubbo-distribution/dubbo-bom/pom.xml b/dubbo-distribution/dubbo-bom/pom.xml index 4717587345..a9df0e8c35 100644 --- a/dubbo-distribution/dubbo-bom/pom.xml +++ b/dubbo-distribution/dubbo-bom/pom.xml @@ -395,6 +395,11 @@ dubbo-remoting-http12 ${project.version} + + org.apache.dubbo + dubbo-remoting-http3 + ${project.version} + org.apache.dubbo dubbo-remoting-netty diff --git a/dubbo-distribution/dubbo-core-spi/pom.xml b/dubbo-distribution/dubbo-core-spi/pom.xml index 43511a8c92..c7c1872d01 100644 --- a/dubbo-distribution/dubbo-core-spi/pom.xml +++ b/dubbo-distribution/dubbo-core-spi/pom.xml @@ -327,6 +327,9 @@ META-INF/dubbo/internal/org.apache.dubbo.remoting.Transporter + + META-INF/dubbo/internal/org.apache.dubbo.remoting.transport.netty4.ChannelAddressAccessor + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.dubbo.ByteAccessor @@ -354,6 +357,9 @@ META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListenerFactory + + META-INF/dubbo/internal/org.apache.dubbo.remoting.http3.Http3ServerTransportListenerFactory + META-INF/dubbo/internal/org.apache.dubbo.remoting.telnet.TelnetHandler @@ -474,6 +480,9 @@ META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.tri.stream.ClientStreamFactory + META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractClient.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractClient.java index 7be50fff2e..14a5653cca 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractClient.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractClient.java @@ -61,13 +61,13 @@ public abstract class AbstractClient extends AbstractEndpoint implements Client private final boolean needReconnect; + private final FrameworkModel frameworkModel; + protected volatile ExecutorService executor; protected volatile ScheduledExecutorService connectivityExecutor; - private FrameworkModel frameworkModel; - - protected long reconnectDuaration; + protected long reconnectDuration; public AbstractClient(URL url, ChannelHandler handler) throws RemotingException { super(url, handler); @@ -78,7 +78,7 @@ public abstract class AbstractClient extends AbstractEndpoint implements Client initExecutor(url); - reconnectDuaration = getReconnectDuration(url); + reconnectDuration = getReconnectDuration(url); try { doOpen(); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java index 21c820055c..ffb491911f 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java @@ -33,6 +33,8 @@ public abstract class AbstractServerHttpChannelObserver implements CustomizableH private HttpMessageEncoder responseEncoder; + private String altSvc; + private boolean headerSent; private boolean completed; @@ -63,6 +65,10 @@ public abstract class AbstractServerHttpChannelObserver implements CustomizableH this.errorResponseCustomizer = errorResponseCustomizer; } + public void setAltSvc(String altSvc) { + this.altSvc = altSvc; + } + public HttpMessageEncoder getResponseEncoder() { return responseEncoder; } @@ -208,6 +214,9 @@ public abstract class AbstractServerHttpChannelObserver implements CustomizableH HttpHeaders headers = httpMetadata.headers(); headers.set(HttpHeaderNames.STATUS.getName(), statusCode); headers.set(HttpHeaderNames.CONTENT_TYPE.getName(), responseEncoder.contentType()); + if (altSvc != null) { + headers.set(HttpHeaderNames.ALT_SVC.getName(), altSvc); + } if (data instanceof HttpResult) { HttpResult result = (HttpResult) data; if (result.getHeaders() != null) { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaderNames.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaderNames.java index 65623ccacb..d5e0647475 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaderNames.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaderNames.java @@ -27,6 +27,8 @@ public enum HttpHeaderNames { TE("te"), + ALT_SVC("alt-svc"), + ACCEPT("accept"); private final String name; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2InputMessageFrame.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2InputMessageFrame.java index e73955c96e..551ba929a7 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2InputMessageFrame.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2InputMessageFrame.java @@ -20,7 +20,7 @@ import java.io.InputStream; public class Http2InputMessageFrame implements Http2InputMessage { - private int id; + private long id; private final InputStream body; @@ -39,7 +39,7 @@ public class Http2InputMessageFrame implements Http2InputMessage { this.endStream = endStream; } - public void setId(int id) { + public void setId(long id) { this.id = id; } @@ -54,7 +54,7 @@ public class Http2InputMessageFrame implements Http2InputMessage { } @Override - public int id() { + public long id() { return id; } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2MetadataFrame.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2MetadataFrame.java index 2081b9f169..7b197bebb5 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2MetadataFrame.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2MetadataFrame.java @@ -20,7 +20,7 @@ import org.apache.dubbo.remoting.http12.HttpHeaders; public class Http2MetadataFrame implements Http2Header { - private final int streamId; + private final long streamId; private final HttpHeaders headers; @@ -31,10 +31,10 @@ public class Http2MetadataFrame implements Http2Header { } public Http2MetadataFrame(HttpHeaders headers, boolean endStream) { - this(-1, headers, endStream); + this(-1L, headers, endStream); } - public Http2MetadataFrame(int streamId, HttpHeaders headers, boolean endStream) { + public Http2MetadataFrame(long streamId, HttpHeaders headers, boolean endStream) { this.streamId = streamId; this.headers = headers; this.endStream = endStream; @@ -46,7 +46,7 @@ public class Http2MetadataFrame implements Http2Header { } @Override - public int id() { + public long id() { return streamId; } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2OutputMessage.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2OutputMessage.java index 75f34bcb84..ca2a07c8b9 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2OutputMessage.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2OutputMessage.java @@ -26,7 +26,7 @@ public interface Http2OutputMessage extends HttpOutputMessage, Http2StreamFrame } @Override - default int id() { + default long id() { return -1; } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2StreamFrame.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2StreamFrame.java index d57f754c4b..33975f4a6d 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2StreamFrame.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/h2/Http2StreamFrame.java @@ -18,7 +18,7 @@ package org.apache.dubbo.remoting.http12.h2; public interface Http2StreamFrame { - int id(); + long id(); String name(); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java index 980ccf2ccf..5272a701be 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/LengthFieldStreamingDecoder.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.remoting.http12.message; +import org.apache.dubbo.common.io.StreamUtils; import org.apache.dubbo.remoting.http12.CompositeInputStream; import org.apache.dubbo.remoting.http12.exception.DecodeException; @@ -46,7 +47,7 @@ public class LengthFieldStreamingDecoder implements StreamingDecoder { private int requiredLength; - private InputStream dataHeader = new ByteArrayInputStream(new byte[0]); + private InputStream dataHeader = StreamUtils.EMPTY; public LengthFieldStreamingDecoder() { this(4); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java index 7c28b62f71..281ee15eb9 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java @@ -40,8 +40,8 @@ public final class CodecUtils { public CodecUtils(FrameworkModel frameworkModel) { decoderFactories = frameworkModel.getActivateExtensions(HttpMessageDecoderFactory.class); encoderFactories = frameworkModel.getActivateExtensions(HttpMessageEncoderFactory.class); - decoderFactories.forEach(factory -> decoderCache.put(factory.mediaType().getName(), Optional.of(factory))); - encoderFactories.forEach(factory -> encoderCache.put(factory.mediaType().getName(), Optional.of(factory))); + decoderFactories.forEach(f -> decoderCache.putIfAbsent(f.mediaType().getName(), Optional.of(f))); + encoderFactories.forEach(f -> encoderCache.putIfAbsent(f.mediaType().getName(), Optional.of(f))); } public HttpMessageDecoder determineHttpMessageDecoder(URL url, FrameworkModel frameworkModel, String mediaType) { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java index c16dbe2b24..edeee5e01b 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java @@ -68,6 +68,9 @@ public class JsonCodec implements HttpMessageCodec { public Object[] decode(InputStream is, Class[] targetTypes, Charset charset) throws DecodeException { try { int len = targetTypes.length; + if (len == 0) { + return new Object[0]; + } Object obj = JsonUtils.toJavaObject(StreamUtils.toString(is, charset), Object.class); if (obj instanceof List) { List list = (List) obj; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java index 3c1484ce7e..7178746935 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java @@ -41,14 +41,14 @@ public class CodeUtilsTest { HttpMessageDecoder decoder = codecUtils.determineHttpMessageDecoder(null, FrameworkModel.defaultModel(), headers.getContentType()); Assertions.assertNotNull(decoder); - Assertions.assertEquals(JsonCodec.class, decoder.getClass()); + Assertions.assertEquals(JsonPbCodec.class, decoder.getClass()); HttpMessageEncoder encoder; // If no Accept header provided, use Content-Type to find encoder encoder = codecUtils.determineHttpMessageEncoder( null, FrameworkModel.defaultModel(), MediaType.APPLICATION_JSON.getName()); Assertions.assertNotNull(encoder); - Assertions.assertEquals(JsonCodec.class, encoder.getClass()); + Assertions.assertEquals(JsonPbCodec.class, encoder.getClass()); HttpHeaders headers1 = new HttpHeaders(); headers1.put( @@ -67,6 +67,6 @@ public class CodeUtilsTest { encoder = codecUtils.determineHttpMessageEncoder( null, FrameworkModel.defaultModel(), MediaType.APPLICATION_JSON.getName()); Assertions.assertNotNull(encoder); - Assertions.assertEquals(JsonCodec.class, encoder.getClass()); + Assertions.assertEquals(JsonPbCodec.class, encoder.getClass()); } } diff --git a/dubbo-remoting/dubbo-remoting-http3/pom.xml b/dubbo-remoting/dubbo-remoting-http3/pom.xml new file mode 100644 index 0000000000..4955a9ed08 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/pom.xml @@ -0,0 +1,56 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-remoting + ${revision} + ../pom.xml + + dubbo-remoting-http3 + jar + ${project.artifactId} + The http3 remoting module of dubbo project + + false + + + + org.apache.dubbo + dubbo-remoting-http12 + ${project.parent.version} + + + org.apache.dubbo + dubbo-remoting-netty4 + ${project.parent.version} + + + + io.netty.incubator + netty-incubator-codec-http3 + + + + org.apache.logging.log4j + log4j-slf4j-impl + test + + + diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/exchange/Http3Exchanger.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/exchange/Http3Exchanger.java new file mode 100644 index 0000000000..08ebf79118 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/exchange/Http3Exchanger.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.exchange; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.LoggerCodeConstants; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.remoting.ChannelHandler; +import org.apache.dubbo.remoting.RemotingException; +import org.apache.dubbo.remoting.RemotingServer; +import org.apache.dubbo.remoting.api.connection.AbstractConnectionClient; +import org.apache.dubbo.remoting.transport.ChannelHandlerAdapter; +import org.apache.dubbo.remoting.transport.netty4.NettyHttp3ConnectionClient; +import org.apache.dubbo.remoting.transport.netty4.NettyHttp3Server; + +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public final class Http3Exchanger { + + private static final ErrorTypeAwareLogger LOG = LoggerFactory.getErrorTypeAwareLogger(Http3Exchanger.class); + private static final Map SERVERS = new ConcurrentHashMap<>(); + private static final Map CLIENTS = new ConcurrentHashMap<>(16); + private static final ChannelHandler HANDLER = new ChannelHandlerAdapter(); + + private Http3Exchanger() {} + + public static RemotingServer bind(URL url) { + return SERVERS.computeIfAbsent(url.getAddress(), addr -> { + try { + return new NettyHttp3Server(url, HANDLER); + } catch (RemotingException e) { + throw new RuntimeException(e); + } + }); + } + + public static AbstractConnectionClient connect(URL url) { + return CLIENTS.compute(url.getAddress(), (address, client) -> { + try { + if (client == null) { + AbstractConnectionClient connectionClient = new NettyHttp3ConnectionClient(url, HANDLER); + connectionClient.addCloseListener(() -> CLIENTS.remove(address, connectionClient)); + client = connectionClient; + } else { + client.retain(); + } + return client; + } catch (RemotingException e) { + throw new RuntimeException(e); + } + }); + } + + public static void close() { + if (SERVERS.isEmpty()) { + return; + } + ArrayList toClose = new ArrayList<>(SERVERS.values()); + SERVERS.clear(); + for (RemotingServer server : toClose) { + try { + server.close(); + } catch (Throwable t) { + LOG.error(LoggerCodeConstants.PROTOCOL_ERROR_CLOSE_SERVER, "", "", "Close Http3 server failed", t); + } + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3ServerTransportListenerFactory.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3ServerTransportListenerFactory.java new file mode 100644 index 0000000000..1cd6d17381 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3ServerTransportListenerFactory.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http3; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.rpc.model.FrameworkModel; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface Http3ServerTransportListenerFactory { + + Http3TransportListener newInstance(H2StreamChannel streamChannel, URL url, FrameworkModel frameworkModel); + + boolean supportContentType(String contentType); +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3TransportListener.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3TransportListener.java new file mode 100644 index 0000000000..87eb7ad983 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/Http3TransportListener.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http3; + +import org.apache.dubbo.remoting.http12.h2.Http2TransportListener; + +public interface Http3TransportListener extends Http2TransportListener {} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/Http2HeadersAdapter.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/Http2HeadersAdapter.java new file mode 100644 index 0000000000..2a31b0e851 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/Http2HeadersAdapter.java @@ -0,0 +1,627 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http3.netty4; + +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; + +import io.netty.handler.codec.Headers; +import io.netty.handler.codec.http2.Http2Headers; +import io.netty.incubator.codec.http3.Http3Headers; + +public final class Http2HeadersAdapter implements Http2Headers { + + private final Http3Headers headers; + + public Http2HeadersAdapter(Http3Headers headers) { + this.headers = headers; + } + + @Override + public Iterator> iterator() { + return headers.iterator(); + } + + @Override + public Iterator valueIterator(CharSequence name) { + return headers.valueIterator(name); + } + + @Override + public Http2Headers method(CharSequence value) { + headers.method(value); + return this; + } + + @Override + public Http2Headers scheme(CharSequence value) { + headers.scheme(value); + return this; + } + + @Override + public Http2Headers authority(CharSequence value) { + headers.authority(value); + return this; + } + + @Override + public Http2Headers path(CharSequence value) { + headers.path(value); + return this; + } + + @Override + public Http2Headers status(CharSequence value) { + headers.status(value); + return this; + } + + @Override + public CharSequence method() { + return headers.method(); + } + + @Override + public CharSequence scheme() { + return headers.scheme(); + } + + @Override + public CharSequence authority() { + return headers.authority(); + } + + @Override + public CharSequence path() { + return headers.path(); + } + + @Override + public CharSequence status() { + return headers.status(); + } + + @Override + public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) { + return headers.contains(name, value, caseInsensitive); + } + + @Override + public CharSequence get(CharSequence charSequence) { + return headers.get(charSequence); + } + + @Override + public CharSequence get(CharSequence charSequence, CharSequence charSequence2) { + return headers.get(charSequence, charSequence2); + } + + @Override + public CharSequence getAndRemove(CharSequence charSequence) { + return headers.getAndRemove(charSequence); + } + + @Override + public CharSequence getAndRemove(CharSequence charSequence, CharSequence charSequence2) { + return headers.getAndRemove(charSequence, charSequence2); + } + + @Override + public List getAll(CharSequence charSequence) { + return headers.getAll(charSequence); + } + + @Override + public List getAllAndRemove(CharSequence charSequence) { + return headers.getAllAndRemove(charSequence); + } + + @Override + public Boolean getBoolean(CharSequence charSequence) { + return headers.getBoolean(charSequence); + } + + @Override + public boolean getBoolean(CharSequence charSequence, boolean b) { + return headers.getBoolean(charSequence, b); + } + + @Override + public Byte getByte(CharSequence charSequence) { + return headers.getByte(charSequence); + } + + @Override + public byte getByte(CharSequence charSequence, byte b) { + return headers.getByte(charSequence, b); + } + + @Override + public Character getChar(CharSequence charSequence) { + return headers.getChar(charSequence); + } + + @Override + public char getChar(CharSequence charSequence, char c) { + return headers.getChar(charSequence, c); + } + + @Override + public Short getShort(CharSequence charSequence) { + return headers.getShort(charSequence); + } + + @Override + public short getShort(CharSequence charSequence, short i) { + return headers.getShort(charSequence, i); + } + + @Override + public Integer getInt(CharSequence charSequence) { + return headers.getInt(charSequence); + } + + @Override + public int getInt(CharSequence charSequence, int i) { + return headers.getInt(charSequence, i); + } + + @Override + public Long getLong(CharSequence charSequence) { + return headers.getLong(charSequence); + } + + @Override + public long getLong(CharSequence charSequence, long l) { + return headers.getLong(charSequence, l); + } + + @Override + public Float getFloat(CharSequence charSequence) { + return headers.getFloat(charSequence); + } + + @Override + public float getFloat(CharSequence charSequence, float v) { + return headers.getFloat(charSequence, v); + } + + @Override + public Double getDouble(CharSequence charSequence) { + return headers.getDouble(charSequence); + } + + @Override + public double getDouble(CharSequence charSequence, double v) { + return headers.getDouble(charSequence, v); + } + + @Override + public Long getTimeMillis(CharSequence charSequence) { + return headers.getTimeMillis(charSequence); + } + + @Override + public long getTimeMillis(CharSequence charSequence, long l) { + return headers.getTimeMillis(charSequence, l); + } + + @Override + public Boolean getBooleanAndRemove(CharSequence charSequence) { + return headers.getBooleanAndRemove(charSequence); + } + + @Override + public boolean getBooleanAndRemove(CharSequence charSequence, boolean b) { + return headers.getBooleanAndRemove(charSequence, b); + } + + @Override + public Byte getByteAndRemove(CharSequence charSequence) { + return headers.getByteAndRemove(charSequence); + } + + @Override + public byte getByteAndRemove(CharSequence charSequence, byte b) { + return headers.getByteAndRemove(charSequence, b); + } + + @Override + public Character getCharAndRemove(CharSequence charSequence) { + return headers.getCharAndRemove(charSequence); + } + + @Override + public char getCharAndRemove(CharSequence charSequence, char c) { + return headers.getCharAndRemove(charSequence, c); + } + + @Override + public Short getShortAndRemove(CharSequence charSequence) { + return headers.getShortAndRemove(charSequence); + } + + @Override + public short getShortAndRemove(CharSequence charSequence, short i) { + return headers.getShortAndRemove(charSequence, i); + } + + @Override + public Integer getIntAndRemove(CharSequence charSequence) { + return headers.getIntAndRemove(charSequence); + } + + @Override + public int getIntAndRemove(CharSequence charSequence, int i) { + return headers.getIntAndRemove(charSequence, i); + } + + @Override + public Long getLongAndRemove(CharSequence charSequence) { + return headers.getLongAndRemove(charSequence); + } + + @Override + public long getLongAndRemove(CharSequence charSequence, long l) { + return headers.getLongAndRemove(charSequence, l); + } + + @Override + public Float getFloatAndRemove(CharSequence charSequence) { + return headers.getFloatAndRemove(charSequence); + } + + @Override + public float getFloatAndRemove(CharSequence charSequence, float v) { + return headers.getFloatAndRemove(charSequence, v); + } + + @Override + public Double getDoubleAndRemove(CharSequence charSequence) { + return headers.getDoubleAndRemove(charSequence); + } + + @Override + public double getDoubleAndRemove(CharSequence charSequence, double v) { + return headers.getDoubleAndRemove(charSequence, v); + } + + @Override + public Long getTimeMillisAndRemove(CharSequence charSequence) { + return headers.getTimeMillisAndRemove(charSequence); + } + + @Override + public long getTimeMillisAndRemove(CharSequence charSequence, long l) { + return headers.getTimeMillisAndRemove(charSequence, l); + } + + @Override + public boolean contains(CharSequence charSequence) { + return headers.contains(charSequence); + } + + @Override + public boolean contains(CharSequence charSequence, CharSequence charSequence2) { + return headers.contains(charSequence, charSequence2); + } + + @Override + public boolean containsObject(CharSequence charSequence, Object o) { + return headers.containsObject(charSequence, o); + } + + @Override + public boolean containsBoolean(CharSequence charSequence, boolean b) { + return headers.containsBoolean(charSequence, b); + } + + @Override + public boolean containsByte(CharSequence charSequence, byte b) { + return headers.containsByte(charSequence, b); + } + + @Override + public boolean containsChar(CharSequence charSequence, char c) { + return headers.containsChar(charSequence, c); + } + + @Override + public boolean containsShort(CharSequence charSequence, short i) { + return headers.containsShort(charSequence, i); + } + + @Override + public boolean containsInt(CharSequence charSequence, int i) { + return headers.containsInt(charSequence, i); + } + + @Override + public boolean containsLong(CharSequence charSequence, long l) { + return headers.containsLong(charSequence, l); + } + + @Override + public boolean containsFloat(CharSequence charSequence, float v) { + return headers.containsFloat(charSequence, v); + } + + @Override + public boolean containsDouble(CharSequence charSequence, double v) { + return headers.containsDouble(charSequence, v); + } + + @Override + public boolean containsTimeMillis(CharSequence charSequence, long l) { + return headers.containsTimeMillis(charSequence, l); + } + + @Override + public int size() { + return headers.size(); + } + + @Override + public boolean isEmpty() { + return headers.isEmpty(); + } + + @Override + public Set names() { + return headers.names(); + } + + @Override + public Http2Headers add(CharSequence charSequence, CharSequence charSequence2) { + headers.add(charSequence, charSequence2); + return this; + } + + @Override + public Http2Headers add(CharSequence charSequence, Iterable iterable) { + headers.add(charSequence, iterable); + return this; + } + + @Override + public Http2Headers add(CharSequence charSequence, CharSequence... charSequences) { + headers.add(charSequence, charSequences); + return this; + } + + @Override + public Http2Headers addObject(CharSequence charSequence, Object o) { + headers.addObject(charSequence, o); + return this; + } + + @Override + public Http2Headers addObject(CharSequence charSequence, Iterable iterable) { + headers.addObject(charSequence, iterable); + return this; + } + + @Override + public Http2Headers addObject(CharSequence charSequence, Object... objects) { + headers.addObject(charSequence, objects); + return this; + } + + @Override + public Http2Headers addBoolean(CharSequence charSequence, boolean b) { + headers.addBoolean(charSequence, b); + return this; + } + + @Override + public Http2Headers addByte(CharSequence charSequence, byte b) { + headers.addByte(charSequence, b); + return this; + } + + @Override + public Http2Headers addChar(CharSequence charSequence, char c) { + headers.addChar(charSequence, c); + return this; + } + + @Override + public Http2Headers addShort(CharSequence charSequence, short i) { + headers.addShort(charSequence, i); + return this; + } + + @Override + public Http2Headers addInt(CharSequence charSequence, int i) { + headers.addInt(charSequence, i); + return this; + } + + @Override + public Http2Headers addLong(CharSequence charSequence, long l) { + headers.addLong(charSequence, l); + return this; + } + + @Override + public Http2Headers addFloat(CharSequence charSequence, float v) { + headers.addFloat(charSequence, v); + return this; + } + + @Override + public Http2Headers addDouble(CharSequence charSequence, double v) { + headers.addDouble(charSequence, v); + return this; + } + + @Override + public Http2Headers addTimeMillis(CharSequence charSequence, long l) { + headers.addTimeMillis(charSequence, l); + return this; + } + + @Override + public Http2Headers add(Headers headers) { + this.headers.add(headers); + return this; + } + + @Override + public Http2Headers set(CharSequence charSequence, CharSequence charSequence2) { + headers.set(charSequence, charSequence2); + return this; + } + + @Override + public Http2Headers set(CharSequence charSequence, Iterable iterable) { + headers.set(charSequence, iterable); + return this; + } + + @Override + public Http2Headers set(CharSequence charSequence, CharSequence... charSequences) { + headers.set(charSequence, charSequences); + return this; + } + + @Override + public Http2Headers setObject(CharSequence charSequence, Object o) { + headers.setObject(charSequence, o); + return this; + } + + @Override + public Http2Headers setObject(CharSequence charSequence, Iterable iterable) { + headers.setObject(charSequence, iterable); + return this; + } + + @Override + public Http2Headers setObject(CharSequence charSequence, Object... objects) { + headers.setObject(charSequence, objects); + return this; + } + + @Override + public Http2Headers setBoolean(CharSequence charSequence, boolean b) { + headers.setBoolean(charSequence, b); + return this; + } + + @Override + public Http2Headers setByte(CharSequence charSequence, byte b) { + headers.setByte(charSequence, b); + return this; + } + + @Override + public Http2Headers setChar(CharSequence charSequence, char c) { + headers.setChar(charSequence, c); + return this; + } + + @Override + public Http2Headers setShort(CharSequence charSequence, short i) { + headers.setShort(charSequence, i); + return this; + } + + @Override + public Http2Headers setInt(CharSequence charSequence, int i) { + headers.setInt(charSequence, i); + return this; + } + + @Override + public Http2Headers setLong(CharSequence charSequence, long l) { + headers.setLong(charSequence, l); + return this; + } + + @Override + public Http2Headers setFloat(CharSequence charSequence, float v) { + headers.setFloat(charSequence, v); + return this; + } + + @Override + public Http2Headers setDouble(CharSequence charSequence, double v) { + headers.setDouble(charSequence, v); + return this; + } + + @Override + public Http2Headers setTimeMillis(CharSequence charSequence, long l) { + headers.setTimeMillis(charSequence, l); + return this; + } + + @Override + public Http2Headers set(Headers headers) { + this.headers.set(headers); + return this; + } + + @Override + public Http2Headers setAll(Headers headers) { + this.headers.setAll(headers); + return this; + } + + @Override + public boolean remove(CharSequence charSequence) { + return headers.remove(charSequence); + } + + @Override + public Http2Headers clear() { + headers.clear(); + return this; + } + + @Override + public void forEach(Consumer> action) { + headers.forEach(action); + } + + @Override + public Spliterator> spliterator() { + return headers.spliterator(); + } + + @Override + public int hashCode() { + return Objects.hashCode(headers); + } + + @Override + public boolean equals(Object obj) { + return this == obj || obj instanceof Http2Headers && headers.equals(obj); + } + + @Override + public String toString() { + return headers.toString(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/Http3ChannelAddressAccessor.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/Http3ChannelAddressAccessor.java new file mode 100644 index 0000000000..623952816d --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/Http3ChannelAddressAccessor.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http3.netty4; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.transport.netty4.ChannelAddressAccessor; + +import java.net.InetSocketAddress; + +import io.netty.channel.Channel; +import io.netty.incubator.codec.quic.QuicChannel; +import io.netty.incubator.codec.quic.QuicStreamChannel; + +@Activate(order = -100, onClass = "io.netty.incubator.codec.quic.QuicChannel") +public class Http3ChannelAddressAccessor implements ChannelAddressAccessor { + + @Override + public String getProtocol() { + return "UDP"; + } + + @Override + public InetSocketAddress getRemoteAddress(Channel channel) { + if (channel instanceof QuicStreamChannel) { + return (InetSocketAddress) ((QuicStreamChannel) channel).parent().remoteSocketAddress(); + } + if (channel instanceof QuicChannel) { + return (InetSocketAddress) ((QuicChannel) channel).remoteSocketAddress(); + } + return null; + } + + @Override + public InetSocketAddress getLocalAddress(Channel channel) { + if (channel instanceof QuicStreamChannel) { + return (InetSocketAddress) ((QuicStreamChannel) channel).parent().localSocketAddress(); + } + if (channel instanceof QuicChannel) { + return (InetSocketAddress) ((QuicChannel) channel).localSocketAddress(); + } + return null; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3FrameCodec.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3FrameCodec.java new file mode 100644 index 0000000000..a0cf64be7f --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3FrameCodec.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http3.netty4; + +import org.apache.dubbo.common.io.StreamUtils; +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.h2.Http2Header; +import org.apache.dubbo.remoting.http12.h2.Http2InputMessageFrame; +import org.apache.dubbo.remoting.http12.h2.Http2MetadataFrame; +import org.apache.dubbo.remoting.http12.h2.Http2OutputMessage; + +import java.io.OutputStream; +import java.net.SocketAddress; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandler; +import io.netty.channel.ChannelPromise; +import io.netty.incubator.codec.http3.DefaultHttp3DataFrame; +import io.netty.incubator.codec.http3.DefaultHttp3Headers; +import io.netty.incubator.codec.http3.DefaultHttp3HeadersFrame; +import io.netty.incubator.codec.http3.Http3DataFrame; +import io.netty.incubator.codec.http3.Http3Headers; +import io.netty.incubator.codec.http3.Http3HeadersFrame; +import io.netty.incubator.codec.http3.Http3RequestStreamInboundHandler; +import io.netty.incubator.codec.quic.QuicStreamChannel; + +@Sharable +public class NettyHttp3FrameCodec extends Http3RequestStreamInboundHandler implements ChannelOutboundHandler { + + public static final NettyHttp3FrameCodec INSTANCE = new NettyHttp3FrameCodec(); + + @Override + protected void channelRead(ChannelHandlerContext ctx, Http3HeadersFrame frame) { + HttpHeaders headers = new HttpHeaders(); + for (Map.Entry header : frame.headers()) { + headers.set(header.getKey().toString(), header.getValue().toString()); + } + ctx.fireChannelRead(new Http2MetadataFrame(getStreamId(ctx), headers, false)); + } + + @Override + protected void channelRead(ChannelHandlerContext ctx, Http3DataFrame frame) { + Http2InputMessageFrame msg = new Http2InputMessageFrame(new ByteBufInputStream(frame.content(), true)); + msg.setId(getStreamId(ctx)); + ctx.fireChannelRead(msg); + } + + private static long getStreamId(ChannelHandlerContext ctx) { + return ((QuicStreamChannel) ctx.channel()).streamId(); + } + + @Override + protected void channelInputClosed(ChannelHandlerContext ctx) { + Http2InputMessageFrame msg = new Http2InputMessageFrame(StreamUtils.EMPTY, true); + msg.setId(getStreamId(ctx)); + ctx.fireChannelRead(msg); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (msg instanceof Http2Header) { + Http3Headers headers = new DefaultHttp3Headers(); + Http2Header http2Header = (Http2Header) msg; + for (Entry> entry : http2Header.headers().entrySet()) { + headers.add(entry.getKey(), entry.getValue()); + } + ctx.write(new DefaultHttp3HeadersFrame(headers), promise); + if (http2Header.isEndStream()) { + ctx.close(); + } + } else if (msg instanceof Http2OutputMessage) { + Http2OutputMessage outputMessage = (Http2OutputMessage) msg; + try { + OutputStream body = outputMessage.getBody(); + if (body == null) { + Http3DataFrame frame = new DefaultHttp3DataFrame(Unpooled.EMPTY_BUFFER); + ctx.write(frame, promise); + return; + } + if (body instanceof ByteBufOutputStream) { + Http3DataFrame frame = new DefaultHttp3DataFrame(((ByteBufOutputStream) body).buffer()); + ctx.write(frame, promise); + return; + } + } finally { + if (outputMessage.isEndStream()) { + ctx.close(); + } + } + throw new IllegalArgumentException("Http2OutputMessage body must be ByteBufOutputStream"); + } else { + ctx.write(msg, promise); + } + } + + @Override + public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { + ctx.bind(localAddress, promise); + } + + @Override + public void connect( + ChannelHandlerContext ctx, + SocketAddress remoteAddress, + SocketAddress localAddress, + ChannelPromise promise) { + ctx.connect(remoteAddress, localAddress, promise); + } + + @Override + public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) { + ctx.disconnect(promise); + } + + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + ctx.close(promise); + } + + @Override + public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) { + ctx.deregister(promise); + } + + @Override + public void read(ChannelHandlerContext ctx) throws Exception { + ctx.read(); + } + + @Override + public void flush(ChannelHandlerContext ctx) { + ctx.flush(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3ProtocolSelectorHandler.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3ProtocolSelectorHandler.java new file mode 100644 index 0000000000..204d1480a3 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3ProtocolSelectorHandler.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http3.netty4; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.HttpMetadata; +import org.apache.dubbo.remoting.http12.command.HttpWriteQueue; +import org.apache.dubbo.remoting.http12.exception.UnsupportedMediaTypeException; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.remoting.http12.h2.command.Http2WriteQueueChannel; +import org.apache.dubbo.remoting.http12.netty4.HttpWriteQueueHandler; +import org.apache.dubbo.remoting.http12.netty4.h2.NettyHttp2FrameHandler; +import org.apache.dubbo.remoting.http3.Http3ServerTransportListenerFactory; +import org.apache.dubbo.rpc.model.FrameworkModel; + +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.incubator.codec.quic.QuicStreamChannel; + +@Sharable +public class NettyHttp3ProtocolSelectorHandler extends SimpleChannelInboundHandler { + + private final URL url; + private final FrameworkModel frameworkModel; + + public NettyHttp3ProtocolSelectorHandler(URL url, FrameworkModel frameworkModel) { + this.url = url; + this.frameworkModel = frameworkModel; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, HttpMetadata metadata) { + HttpHeaders headers = metadata.headers(); + String contentType = headers.getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); + Http3ServerTransportListenerFactory factory = determineHttp3ServerTransportListenerFactory(contentType); + if (factory == null) { + throw new UnsupportedMediaTypeException(contentType); + } + + H2StreamChannel streamChannel = new NettyHttp3StreamChannel((QuicStreamChannel) ctx.channel()); + HttpWriteQueueHandler writeQueueHandler = ctx.channel().pipeline().get(HttpWriteQueueHandler.class); + if (writeQueueHandler != null) { + HttpWriteQueue writeQueue = writeQueueHandler.getWriteQueue(); + streamChannel = new Http2WriteQueueChannel(streamChannel, writeQueue); + } + + ChannelPipeline pipeline = ctx.pipeline(); + pipeline.addLast( + new NettyHttp2FrameHandler(streamChannel, factory.newInstance(streamChannel, url, frameworkModel))); + pipeline.remove(this); + ctx.fireChannelRead(metadata); + } + + private Http3ServerTransportListenerFactory determineHttp3ServerTransportListenerFactory(String contentType) { + for (Http3ServerTransportListenerFactory factory : + frameworkModel.getActivateExtensions(Http3ServerTransportListenerFactory.class)) { + if (factory.supportContentType(contentType)) { + return factory; + } + } + return null; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3StreamChannel.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3StreamChannel.java new file mode 100644 index 0000000000..b149a0acea --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/http3/netty4/NettyHttp3StreamChannel.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http3.netty4; + +import org.apache.dubbo.remoting.http12.HttpMetadata; +import org.apache.dubbo.remoting.http12.HttpOutputMessage; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.remoting.http12.h2.Http2OutputMessage; +import org.apache.dubbo.remoting.http12.h2.Http2OutputMessageFrame; +import org.apache.dubbo.remoting.http12.netty4.NettyHttpChannelFutureListener; + +import java.net.SocketAddress; +import java.util.concurrent.CompletableFuture; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.incubator.codec.quic.QuicStreamChannel; + +public class NettyHttp3StreamChannel implements H2StreamChannel { + + private final QuicStreamChannel http3StreamChannel; + + public NettyHttp3StreamChannel(QuicStreamChannel http3StreamChannel) { + this.http3StreamChannel = http3StreamChannel; + } + + @Override + public CompletableFuture writeResetFrame(long errorCode) { + NettyHttpChannelFutureListener futureListener = new NettyHttpChannelFutureListener(); + http3StreamChannel.close().addListener(futureListener); + return futureListener; + } + + @Override + public Http2OutputMessage newOutputMessage(boolean endStream) { + ByteBuf buffer = http3StreamChannel.alloc().buffer(); + return new Http2OutputMessageFrame(new ByteBufOutputStream(buffer), endStream); + } + + @Override + public CompletableFuture writeHeader(HttpMetadata httpMetadata) { + NettyHttpChannelFutureListener futureListener = new NettyHttpChannelFutureListener(); + http3StreamChannel.write(httpMetadata).addListener(futureListener); + return futureListener; + } + + @Override + public CompletableFuture writeMessage(HttpOutputMessage httpOutputMessage) { + NettyHttpChannelFutureListener futureListener = new NettyHttpChannelFutureListener(); + http3StreamChannel.write(httpOutputMessage).addListener(futureListener); + return futureListener; + } + + @Override + public SocketAddress remoteAddress() { + return http3StreamChannel.parent().remoteSocketAddress(); + } + + @Override + public SocketAddress localAddress() { + return http3StreamChannel.parent().localSocketAddress(); + } + + @Override + public void flush() { + http3StreamChannel.flush(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java new file mode 100644 index 0000000000..b1d88ff901 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/Helper.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.transport.netty4; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.config.context.ConfigManager; +import org.apache.dubbo.config.nested.TripleConfig; + +import io.netty.incubator.codec.quic.QuicCodecBuilder; +import io.netty.incubator.codec.quic.QuicCongestionControlAlgorithm; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +final class Helper { + + @SuppressWarnings("unchecked") + static > T configCodec(QuicCodecBuilder builder, URL url) { + TripleConfig tripleConfig = ConfigManager.getProtocol(url).getTriple(); + if (tripleConfig.getHttp3InitialMaxData() != null) { + builder.initialMaxData(tripleConfig.getHttp3InitialMaxData()); + } + if (tripleConfig.getHttp3RecvQueueLen() != null && tripleConfig.getHttp3SendQueueLen() != null) { + builder.datagram(tripleConfig.getHttp3RecvQueueLen(), tripleConfig.getHttp3SendQueueLen()); + } + if (tripleConfig.getHttp3InitialMaxStreamDataBidiLocal() != null) { + builder.initialMaxStreamDataBidirectionalLocal(tripleConfig.getHttp3InitialMaxStreamDataBidiLocal()); + } + if (tripleConfig.getHttp3InitialMaxStreamDataBidiRemote() != null) { + builder.initialMaxStreamDataBidirectionalRemote(tripleConfig.getHttp3InitialMaxStreamDataBidiRemote()); + } + if (tripleConfig.getHttp3InitialMaxStreamDataUni() != null) { + builder.initialMaxStreamDataUnidirectional(tripleConfig.getHttp3InitialMaxStreamDataUni()); + } + if (tripleConfig.getHttp3InitialMaxStreamsBidi() != null) { + builder.initialMaxStreamsBidirectional(tripleConfig.getHttp3InitialMaxStreamsBidi()); + } + if (tripleConfig.getHttp3InitialMaxStreamsUni() != null) { + builder.initialMaxStreamsUnidirectional(tripleConfig.getHttp3InitialMaxStreamsUni()); + } + if (tripleConfig.getHttp3MaxAckDelayExponent() != null) { + builder.ackDelayExponent(tripleConfig.getHttp3MaxAckDelayExponent()); + } + if (tripleConfig.getHttp3MaxAckDelay() != null) { + builder.maxAckDelay(tripleConfig.getHttp3MaxAckDelay(), MILLISECONDS); + } + if (tripleConfig.getHttp3DisableActiveMigration() != null) { + builder.activeMigration(tripleConfig.getHttp3DisableActiveMigration()); + } + if (tripleConfig.getHttp3EnableHystart() != null) { + builder.hystart(tripleConfig.getHttp3EnableHystart()); + } + if (tripleConfig.getHttp3CcAlgorithm() != null) { + if ("RENO".equalsIgnoreCase(tripleConfig.getHttp3CcAlgorithm())) { + builder.congestionControlAlgorithm(QuicCongestionControlAlgorithm.RENO); + } else if ("BBR".equalsIgnoreCase(tripleConfig.getHttp3CcAlgorithm())) { + builder.congestionControlAlgorithm(QuicCongestionControlAlgorithm.BBR); + } + } + return (T) builder; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyHttp3ConnectionClient.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyHttp3ConnectionClient.java new file mode 100644 index 0000000000..1395d2e43b --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyHttp3ConnectionClient.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.transport.netty4; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.ChannelHandler; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.RemotingException; +import org.apache.dubbo.remoting.utils.UrlUtils; + +import java.util.concurrent.atomic.AtomicReference; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPromise; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.incubator.codec.http3.Http3; +import io.netty.incubator.codec.http3.Http3ClientConnectionHandler; +import io.netty.incubator.codec.quic.QuicChannel; +import io.netty.incubator.codec.quic.QuicChannelBootstrap; +import io.netty.incubator.codec.quic.QuicSslContext; +import io.netty.incubator.codec.quic.QuicSslContextBuilder; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +public final class NettyHttp3ConnectionClient extends AbstractNettyConnectionClient { + + private AtomicReference datagramChannel; + + private QuicChannelBootstrap bootstrap; + + public NettyHttp3ConnectionClient(URL url, ChannelHandler handler) throws RemotingException { + super(url, handler); + } + + @Override + protected void initConnectionClient() { + super.initConnectionClient(); + datagramChannel = new AtomicReference<>(); + } + + @Override + protected void initBootstrap() throws Exception { + QuicSslContext context = QuicSslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .applicationProtocols(Http3.supportedApplicationProtocols()) + .build(); + int idleTimeout = UrlUtils.getIdleTimeout(getUrl()); + io.netty.channel.ChannelHandler codec = Helper.configCodec(Http3.newQuicClientCodecBuilder(), getUrl()) + .maxIdleTimeout(idleTimeout, MILLISECONDS) + .sslContext(context) + .build(); + io.netty.channel.Channel nettyDatagramChannel = new Bootstrap() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout()) + .group(NettyEventLoopFactory.NIO_EVENT_LOOP_GROUP.get()) + .channel(NioDatagramChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(NioDatagramChannel ch) { + ch.pipeline().addLast(codec); + } + }) + .bind(0) + .sync() + .channel(); + datagramChannel.set(nettyDatagramChannel); + nettyDatagramChannel.closeFuture().addListener(channelFuture -> datagramChannel.set(null)); + + int heartbeat = UrlUtils.getHeartbeat(getUrl()); + NettyConnectionHandler connectionHandler = new NettyConnectionHandler(this); + bootstrap = QuicChannel.newBootstrap(nettyDatagramChannel) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(QuicChannel ch) { + ch.pipeline() + .addLast(new IdleStateHandler(heartbeat, 0, 0, MILLISECONDS)) + .addLast(Constants.CONNECTION_HANDLER_NAME, connectionHandler) + .addLast(new Http3ClientConnectionHandler()); + + ch.closeFuture().addListener(channelFuture -> clearNettyChannel()); + } + }) + .remoteAddress(getConnectAddress()); + } + + @Override + protected ChannelFuture performConnect() { + Channel channel = getNettyDatagramChannel(); + if (channel == null) { + return null; + } + ChannelPromise promise = channel.newPromise(); + GenericFutureListener> listener = f -> { + if (f.isSuccess()) { + promise.setSuccess(null); + } else { + promise.setFailure(f.cause()); + } + }; + bootstrap.connect().addListener(listener); + return promise; + } + + @Override + protected void performClose() { + super.performClose(); + io.netty.channel.Channel current = getNettyDatagramChannel(); + if (current != null) { + current.close(); + } + datagramChannel.set(null); + } + + private io.netty.channel.Channel getNettyDatagramChannel() { + return datagramChannel.get(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyHttp3Server.java b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyHttp3Server.java new file mode 100644 index 0000000000..64b44fca97 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyHttp3Server.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.transport.netty4; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.ConfigurationUtils; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.remoting.Channel; +import org.apache.dubbo.remoting.ChannelHandler; +import org.apache.dubbo.remoting.RemotingException; +import org.apache.dubbo.remoting.http12.netty4.HttpWriteQueueHandler; +import org.apache.dubbo.remoting.http3.netty4.NettyHttp3FrameCodec; +import org.apache.dubbo.remoting.http3.netty4.NettyHttp3ProtocolSelectorHandler; +import org.apache.dubbo.remoting.transport.AbstractServer; +import org.apache.dubbo.remoting.transport.dispatcher.ChannelHandlers; +import org.apache.dubbo.remoting.utils.UrlUtils; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.model.ScopeModelUtil; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.incubator.codec.http3.Http3; +import io.netty.incubator.codec.http3.Http3ServerConnectionHandler; +import io.netty.incubator.codec.quic.InsecureQuicTokenHandler; +import io.netty.incubator.codec.quic.QuicChannel; +import io.netty.incubator.codec.quic.QuicSslContext; +import io.netty.incubator.codec.quic.QuicSslContextBuilder; +import io.netty.incubator.codec.quic.QuicStreamChannel; +import io.netty.util.concurrent.Future; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_CLOSE; +import static org.apache.dubbo.remoting.Constants.EVENT_LOOP_BOSS_POOL_NAME; + +public class NettyHttp3Server extends AbstractServer { + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(NettyHttp3Server.class); + + private Map channels; + private Bootstrap bootstrap; + private EventLoopGroup bossGroup; + private io.netty.channel.Channel channel; + + private final int serverShutdownTimeoutMills; + + public NettyHttp3Server(URL url, ChannelHandler handler) throws RemotingException { + super(url, ChannelHandlers.wrap(handler, url)); + serverShutdownTimeoutMills = ConfigurationUtils.getServerShutdownTimeout(getUrl().getOrDefaultModuleModel()); + } + + @Override + protected void doOpen() throws Throwable { + bootstrap = new Bootstrap(); + + bossGroup = NettyEventLoopFactory.eventLoopGroup(1, EVENT_LOOP_BOSS_POOL_NAME); + + NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this); + channels = nettyServerHandler.getChannels(); + + FrameworkModel frameworkModel = ScopeModelUtil.getFrameworkModel(getUrl().getScopeModel()); + NettyHttp3ProtocolSelectorHandler selectorHandler = + new NettyHttp3ProtocolSelectorHandler(getUrl(), frameworkModel); + + SelfSignedCertificate certificate = new SelfSignedCertificate(); + QuicSslContext context = QuicSslContextBuilder.forServer( + certificate.privateKey(), null, certificate.certificate()) + .applicationProtocols(Http3.supportedApplicationProtocols()) + .build(); + + int idleTimeout = UrlUtils.getIdleTimeout(getUrl()); + io.netty.channel.ChannelHandler codec = Helper.configCodec(Http3.newQuicServerCodecBuilder(), getUrl()) + .sslContext(context) + .maxIdleTimeout(idleTimeout, MILLISECONDS) + .tokenHandler(InsecureQuicTokenHandler.INSTANCE) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(QuicChannel ch) { + ch.pipeline() + .addLast(nettyServerHandler) + .addLast(new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS)) + .addLast(new Http3ServerConnectionHandler(new ChannelInitializer() { + @Override + protected void initChannel(QuicStreamChannel ch) { + ch.pipeline() + .addLast(NettyHttp3FrameCodec.INSTANCE) + .addLast(new HttpWriteQueueHandler()) + .addLast(selectorHandler); + } + })); + } + }) + .build(); + + // bind + try { + ChannelFuture channelFuture = bootstrap + .group(bossGroup) + .channel(NioDatagramChannel.class) + .handler(codec) + .bind(getBindAddress()); + channelFuture.syncUninterruptibly(); + channel = channelFuture.channel(); + } catch (Throwable t) { + closeBootstrap(); + throw t; + } + } + + @Override + protected void doClose() { + try { + if (channel != null) { + // unbind. + channel.close(); + } + } catch (Throwable e) { + logger.warn(TRANSPORT_FAILED_CLOSE, "", "", e.getMessage(), e); + } + try { + Collection channels = getChannels(); + if (CollectionUtils.isNotEmpty(channels)) { + for (Channel channel : channels) { + try { + channel.close(); + } catch (Throwable e) { + logger.warn(TRANSPORT_FAILED_CLOSE, "", "", e.getMessage(), e); + } + } + } + } catch (Throwable e) { + logger.warn(TRANSPORT_FAILED_CLOSE, "", "", e.getMessage(), e); + } + closeBootstrap(); + try { + if (channels != null) { + channels.clear(); + } + } catch (Throwable e) { + logger.warn(TRANSPORT_FAILED_CLOSE, "", "", e.getMessage(), e); + } + } + + private void closeBootstrap() { + try { + if (bootstrap != null) { + long timeout = ConfigurationUtils.reCalShutdownTime(serverShutdownTimeoutMills); + long quietPeriod = Math.min(2000L, timeout); + Future bossGroupShutdownFuture = bossGroup.shutdownGracefully(quietPeriod, timeout, MILLISECONDS); + bossGroupShutdownFuture.syncUninterruptibly(); + } + } catch (Throwable e) { + logger.warn(TRANSPORT_FAILED_CLOSE, "", "", e.getMessage(), e); + } + } + + @Override + protected int getChannelsSize() { + return channels.size(); + } + + @Override + public Collection getChannels() { + return new ArrayList<>(channels.values()); + } + + @Override + public Channel getChannel(InetSocketAddress remoteAddress) { + return channels.get(NetUtils.toAddressString(remoteAddress)); + } + + @Override + public boolean canHandleIdle() { + return true; + } + + @Override + public boolean isBound() { + return channel.isActive(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http3/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.transport.netty4.ChannelAddressAccessor b/dubbo-remoting/dubbo-remoting-http3/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.transport.netty4.ChannelAddressAccessor new file mode 100644 index 0000000000..e4500e87c2 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http3/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.transport.netty4.ChannelAddressAccessor @@ -0,0 +1 @@ +http3=org.apache.dubbo.remoting.http3.netty4.Http3ChannelAddressAccessor diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/AbstractNettyConnectionClient.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/AbstractNettyConnectionClient.java new file mode 100644 index 0000000000..4446df5606 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/AbstractNettyConnectionClient.java @@ -0,0 +1,354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.transport.netty4; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.Version; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.remoting.Channel; +import org.apache.dubbo.remoting.ChannelHandler; +import org.apache.dubbo.remoting.RemotingException; +import org.apache.dubbo.remoting.api.connection.AbstractConnectionClient; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.util.AttributeKey; +import io.netty.util.concurrent.DefaultPromise; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.Promise; + +import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_CLIENT_CONNECT_TIMEOUT; +import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_CONNECT_PROVIDER; +import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_RECONNECT; + +public abstract class AbstractNettyConnectionClient extends AbstractConnectionClient { + + private static final ErrorTypeAwareLogger LOGGER = + LoggerFactory.getErrorTypeAwareLogger(AbstractNettyConnectionClient.class); + + private AtomicReference> connectingPromise; + + private Promise closePromise; + + private AtomicReference channel; + + private AtomicBoolean isReconnecting; + + private ConnectionListener connectionListener; + + public static final AttributeKey CONNECTION = AttributeKey.valueOf("connection"); + + public AbstractNettyConnectionClient(URL url, ChannelHandler handler) throws RemotingException { + super(url, handler); + } + + @Override + protected void doOpen() throws Throwable { + initConnectionClient(); + initBootstrap(); + } + + @Override + protected void initConnectionClient() { + this.remote = getConnectAddress(); + this.connectingPromise = new AtomicReference<>(); + this.connectionListener = new ConnectionListener(); + this.channel = new AtomicReference<>(); + this.isReconnecting = new AtomicBoolean(false); + this.closePromise = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); + this.init = new AtomicBoolean(false); + this.increase(); + } + + protected abstract void initBootstrap() throws Exception; + + @Override + protected void doClose() { + // AbstractPeer close can set closed true. + if (isClosed()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("Connection:%s freed ", this)); + } + performClose(); + closePromise.setSuccess(null); + } + } + + protected void performClose() { + io.netty.channel.Channel current = getNettyChannel(); + if (current != null) { + current.close(); + } + clearNettyChannel(); + } + + @Override + protected void doConnect() throws RemotingException { + if (!isReconnecting.compareAndSet(false, true)) { + return; + } + + if (isClosed()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("%s aborted to reconnect cause connection closed. ", this)); + } + } + init.compareAndSet(false, true); + long start = System.currentTimeMillis(); + + createConnectingPromise(); + Future promise = performConnect(); + + promise.addListener(connectionListener); + + boolean ret = connectingPromise.get().awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS); + // destroy connectingPromise after used + synchronized (this) { + connectingPromise.set(null); + } + if (promise.cause() != null) { + Throwable cause = promise.cause(); + + // 6-1 Failed to connect to provider server by other reason. + RemotingException remotingException = new RemotingException( + this, + "client(url: " + getUrl() + ") failed to connect to server " + getConnectAddress() + + ", error message is:" + cause.getMessage(), + cause); + + LOGGER.error( + TRANSPORT_FAILED_CONNECT_PROVIDER, + "network disconnected", + "", + "Failed to connect to provider server by other reason.", + cause); + + throw remotingException; + } else if (!ret || !promise.isSuccess()) { + // 6-2 Client-side timeout + RemotingException remotingException = new RemotingException( + this, + "client(url: " + getUrl() + ") failed to connect to server " + getConnectAddress() + + " client-side timeout " + getConnectTimeout() + "ms (elapsed: " + + (System.currentTimeMillis() - start) + "ms) from netty client " + NetUtils.getLocalHost() + + " using dubbo version " + + Version.getVersion()); + + LOGGER.error( + TRANSPORT_CLIENT_CONNECT_TIMEOUT, "provider crash", "", "Client-side timeout.", remotingException); + + throw remotingException; + } + } + + protected abstract ChannelFuture performConnect(); + + @Override + protected void doDisConnect() { + NettyChannel.removeChannelIfDisconnected(getNettyChannel()); + } + + @Override + public void onConnected(Object channel) { + if (!(channel instanceof io.netty.channel.Channel)) { + return; + } + io.netty.channel.Channel nettyChannel = ((io.netty.channel.Channel) channel); + if (isClosed()) { + nettyChannel.close(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("%s is closed, ignoring connected event", this)); + } + return; + } + + // Close the existing channel before setting a new channel + io.netty.channel.Channel current = getNettyChannel(); + if (current != null) { + current.close(); + } + + this.channel.set(nettyChannel); + // This indicates that the connection is available. + if (connectingPromise.get() != null) { + connectingPromise.get().trySuccess(CONNECTED_OBJECT); + } + nettyChannel.attr(CONNECTION).set(this); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("%s connected ", this)); + } + } + + @Override + public void onGoaway(Object channel) { + if (!(channel instanceof io.netty.channel.Channel)) { + return; + } + io.netty.channel.Channel nettyChannel = (io.netty.channel.Channel) channel; + if (this.channel.compareAndSet(nettyChannel, null)) { + // Ensure the channel is closed + if (nettyChannel.isOpen()) { + nettyChannel.close(); + } + NettyChannel.removeChannelIfDisconnected(nettyChannel); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("%s goaway", this)); + } + } + } + + @Override + protected Channel getChannel() { + io.netty.channel.Channel c = getNettyChannel(); + if (c == null) { + return null; + } + return NettyChannel.getOrAddChannel(c, getUrl(), this); + } + + private io.netty.channel.Channel getNettyChannel() { + return channel.get(); + } + + protected void clearNettyChannel() { + channel.set(null); + } + + @Override + public Object getChannel(Boolean generalizable) { + return Boolean.TRUE.equals(generalizable) ? getNettyChannel() : getChannel(); + } + + @Override + public boolean isAvailable() { + if (isClosed()) { + return false; + } + io.netty.channel.Channel nettyChannel = getNettyChannel(); + if (nettyChannel != null && nettyChannel.isActive()) { + return true; + } + + if (init.compareAndSet(false, true)) { + try { + doConnect(); + } catch (RemotingException e) { + LOGGER.error(TRANSPORT_FAILED_RECONNECT, "", "", "Failed to connect to server: " + getConnectAddress()); + } + } + + createConnectingPromise(); + connectingPromise.get().awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS); + // destroy connectingPromise after used + synchronized (this) { + connectingPromise.set(null); + } + + nettyChannel = getNettyChannel(); + return nettyChannel != null && nettyChannel.isActive(); + } + + @Override + public void createConnectingPromise() { + connectingPromise.compareAndSet(null, new DefaultPromise<>(GlobalEventExecutor.INSTANCE)); + } + + public Promise getClosePromise() { + return closePromise; + } + + public static AbstractConnectionClient getConnectionClientFromChannel(io.netty.channel.Channel channel) { + return channel.attr(CONNECTION).get(); + } + + public ChannelFuture write(Object request) throws RemotingException { + if (!isAvailable()) { + throw new RemotingException( + null, + null, + "Failed to send request " + request + ", cause: The channel to " + remote + " is closed!"); + } + return ((io.netty.channel.Channel) getChannel()).writeAndFlush(request); + } + + @Override + public void addCloseListener(Runnable func) { + getClosePromise().addListener(future -> func.run()); + } + + @Override + public void destroy() { + close(); + } + + @Override + public String toString() { + return super.toString() + " (Ref=" + getCounter() + ",local=" + + Optional.ofNullable(getChannel()) + .map(Channel::getLocalAddress) + .orElse(null) + ",remote=" + getRemoteAddress(); + } + + class ConnectionListener implements ChannelFutureListener { + + @Override + public void operationComplete(ChannelFuture future) { + if (!isReconnecting.compareAndSet(true, false)) { + return; + } + if (future.isSuccess()) { + return; + } + AbstractNettyConnectionClient connectionClient = AbstractNettyConnectionClient.this; + if (connectionClient.isClosed() || connectionClient.getCounter() == 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format( + "%s aborted to reconnect. %s", + connectionClient, future.cause().getMessage())); + } + return; + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format( + "%s is reconnecting, attempt=%d cause=%s", + connectionClient, 0, future.cause().getMessage())); + } + connectivityExecutor.schedule( + () -> { + try { + connectionClient.doConnect(); + } catch (RemotingException e) { + LOGGER.error( + TRANSPORT_FAILED_RECONNECT, + "", + "", + "Failed to connect to server: " + getConnectAddress()); + } + }, + reconnectDuration, + TimeUnit.MILLISECONDS); + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/AddressUtils.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/AddressUtils.java new file mode 100644 index 0000000000..438cf2c63c --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/AddressUtils.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.transport.netty4; + +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.rpc.model.FrameworkModel; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; + +import io.netty.channel.Channel; + +public final class AddressUtils { + + private static final List ACCESSORS = + FrameworkModel.defaultModel().getActivateExtensions(ChannelAddressAccessor.class); + + private AddressUtils() {} + + public static InetSocketAddress getRemoteAddress(Channel channel) { + InetSocketAddress address; + for (int i = 0, len = ACCESSORS.size(); i < len; i++) { + address = ACCESSORS.get(i).getRemoteAddress(channel); + if (address != null) { + return address; + } + } + return (InetSocketAddress) channel.remoteAddress(); + } + + public static InetSocketAddress getLocalAddress(Channel channel) { + InetSocketAddress address; + for (int i = 0, len = ACCESSORS.size(); i < len; i++) { + address = ACCESSORS.get(i).getLocalAddress(channel); + if (address != null) { + return address; + } + } + return (InetSocketAddress) channel.localAddress(); + } + + public static String getRemoteAddressKey(Channel channel) { + InetSocketAddress address; + for (int i = 0, len = ACCESSORS.size(); i < len; i++) { + ChannelAddressAccessor accessor = ACCESSORS.get(i); + address = accessor.getRemoteAddress(channel); + if (address != null) { + return accessor.getProtocol() + ' ' + NetUtils.toAddressString(address); + } + } + InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress(); + if (remoteAddress == null) { + return "UNKNOWN"; + } + return NetUtils.toAddressString(remoteAddress); + } + + public static String getLocalAddressKey(Channel channel) { + InetSocketAddress address; + for (int i = 0, len = ACCESSORS.size(); i < len; i++) { + ChannelAddressAccessor accessor = ACCESSORS.get(i); + address = accessor.getLocalAddress(channel); + if (address != null) { + return accessor.getProtocol() + ' ' + NetUtils.toAddressString(address); + } + } + SocketAddress localAddress = channel.localAddress(); + if (localAddress == null) { + return "UNKNOWN"; + } + return NetUtils.toAddressString((InetSocketAddress) localAddress); + } +} diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ChannelAddressAccessor.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ChannelAddressAccessor.java new file mode 100644 index 0000000000..0dc1e0ab2e --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/ChannelAddressAccessor.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.transport.netty4; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; + +import java.net.InetSocketAddress; + +import io.netty.channel.Channel; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface ChannelAddressAccessor { + + String getProtocol(); + + InetSocketAddress getRemoteAddress(Channel channel); + + InetSocketAddress getLocalAddress(Channel channel); +} diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannel.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannel.java index 8041c72bd8..8191cdfd48 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannel.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannel.java @@ -150,12 +150,12 @@ final class NettyChannel extends AbstractChannel { @Override public InetSocketAddress getLocalAddress() { - return (InetSocketAddress) channel.localAddress(); + return AddressUtils.getLocalAddress(channel); } @Override public InetSocketAddress getRemoteAddress() { - return (InetSocketAddress) channel.remoteAddress(); + return AddressUtils.getRemoteAddress(channel); } @Override diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannelHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannelHandler.java index 6082db24a0..1c31861c5e 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannelHandler.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyChannelHandler.java @@ -46,15 +46,18 @@ public class NettyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); - NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); + io.netty.channel.Channel ch = ctx.channel(); + NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler); if (channel != null) { - dubboChannels.put( - NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress()), channel); + dubboChannels.put(NetUtils.toAddressString((InetSocketAddress) ch.remoteAddress()), channel); handler.connected(channel); if (logger.isInfoEnabled()) { - logger.info("The connection of " + channel.getRemoteAddress() + " -> " + channel.getLocalAddress() - + " is established."); + logger.info( + "The connection {} of {} -> {} is established.", + ch, + AddressUtils.getRemoteAddressKey(ch), + AddressUtils.getLocalAddressKey(ch)); } } } @@ -62,19 +65,22 @@ public class NettyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); - NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); + io.netty.channel.Channel ch = ctx.channel(); + NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler); try { - dubboChannels.remove( - NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress())); + dubboChannels.remove(NetUtils.toAddressString((InetSocketAddress) ch.remoteAddress())); if (channel != null) { handler.disconnected(channel); if (logger.isInfoEnabled()) { - logger.info("The connection of " + channel.getRemoteAddress() + " -> " + channel.getLocalAddress() - + " is disconnected."); + logger.info( + "The connection {} of {} -> {} is disconnected.", + ch, + AddressUtils.getRemoteAddressKey(ch), + AddressUtils.getLocalAddressKey(ch)); } } } finally { - NettyChannel.removeChannel(ctx.channel()); + NettyChannel.removeChannel(ch); } } } diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClientHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClientHandler.java index 71c9f5ec0b..ed77458cf0 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClientHandler.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyClientHandler.java @@ -23,6 +23,7 @@ import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.remoting.ChannelHandler; import org.apache.dubbo.remoting.exchange.Request; +import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; @@ -54,26 +55,34 @@ public class NettyClientHandler extends ChannelDuplexHandler { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); + Channel ch = ctx.channel(); + NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler); handler.connected(channel); if (logger.isInfoEnabled()) { - logger.info("The connection of " + channel.getLocalAddress() + " -> " + channel.getRemoteAddress() - + " is established."); + logger.info( + "The connection {} of {} -> {} is established.", + ch, + AddressUtils.getLocalAddressKey(ch), + AddressUtils.getRemoteAddressKey(ch)); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { - NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); + Channel ch = ctx.channel(); + NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler); try { handler.disconnected(channel); } finally { - NettyChannel.removeChannel(ctx.channel()); + NettyChannel.removeChannel(ch); } if (logger.isInfoEnabled()) { - logger.info("The connection of " + channel.getLocalAddress() + " -> " + channel.getRemoteAddress() - + " is disconnected."); + logger.info( + "The connection {} of {} -> {} is disconnected.", + ch, + AddressUtils.getLocalAddressKey(ch), + AddressUtils.getRemoteAddressKey(ch)); } } diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConnectionClient.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConnectionClient.java index 5f1c9bd062..fead015b13 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConnectionClient.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConnectionClient.java @@ -17,93 +17,46 @@ package org.apache.dubbo.remoting.transport.netty4; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.Version; -import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; -import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.common.utils.NetUtils; -import org.apache.dubbo.remoting.Channel; import org.apache.dubbo.remoting.ChannelHandler; import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.remoting.RemotingException; import org.apache.dubbo.remoting.api.WireProtocol; -import org.apache.dubbo.remoting.api.connection.AbstractConnectionClient; import org.apache.dubbo.remoting.transport.netty4.ssl.SslClientTlsHandler; import org.apache.dubbo.remoting.transport.netty4.ssl.SslContexts; import org.apache.dubbo.remoting.utils.UrlUtils; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - import io.netty.bootstrap.Bootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.ssl.SslContext; import io.netty.handler.timeout.IdleStateHandler; -import io.netty.util.AttributeKey; -import io.netty.util.concurrent.DefaultPromise; -import io.netty.util.concurrent.GlobalEventExecutor; -import io.netty.util.concurrent.Promise; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_CLIENT_CONNECT_TIMEOUT; -import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_CONNECT_PROVIDER; -import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_RECONNECT; import static org.apache.dubbo.remoting.transport.netty4.NettyEventLoopFactory.socketChannelClass; -public class NettyConnectionClient extends AbstractConnectionClient { - - private static final ErrorTypeAwareLogger LOGGER = - LoggerFactory.getErrorTypeAwareLogger(NettyConnectionClient.class); - - private AtomicReference> connectingPromise; - - private Promise closePromise; - - private AtomicReference channel; - - private ConnectionListener connectionListener; +public final class NettyConnectionClient extends AbstractNettyConnectionClient { private Bootstrap bootstrap; - public static final AttributeKey CONNECTION = AttributeKey.valueOf("connection"); - - private AtomicBoolean isReconnecting; - public NettyConnectionClient(URL url, ChannelHandler handler) throws RemotingException { super(url, handler); } @Override protected void initConnectionClient() { - this.protocol = getUrl().getOrDefaultFrameworkModel() + protocol = getUrl().getOrDefaultFrameworkModel() .getExtensionLoader(WireProtocol.class) .getExtension(getUrl().getProtocol()); - this.remote = getConnectAddress(); - this.connectingPromise = new AtomicReference<>(); - this.connectionListener = new ConnectionListener(); - this.channel = new AtomicReference<>(); - this.closePromise = new DefaultPromise<>(GlobalEventExecutor.INSTANCE); - this.init = new AtomicBoolean(false); - this.increase(); - this.isReconnecting = new AtomicBoolean(false); + super.initConnectionClient(); } - @Override - protected void doOpen() throws Throwable { - initConnectionClient(); - initBootstrap(); - } - - private void initBootstrap() { - final Bootstrap nettyBootstrap = new Bootstrap(); - nettyBootstrap + protected void initBootstrap() { + Bootstrap bootstrap = new Bootstrap(); + bootstrap .group(NettyEventLoopFactory.NIO_EVENT_LOOP_GROUP.get()) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true) @@ -111,10 +64,10 @@ public class NettyConnectionClient extends AbstractConnectionClient { .remoteAddress(getConnectAddress()) .channel(socketChannelClass()); - final NettyConnectionHandler connectionHandler = new NettyConnectionHandler(this); - nettyBootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout()); + NettyConnectionHandler connectionHandler = new NettyConnectionHandler(this); + bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout()); SslContext sslContext = SslContexts.buildClientSslContext(getUrl()); - nettyBootstrap.handler(new ChannelInitializer() { + bootstrap.handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { NettyChannel nettyChannel = NettyChannel.getOrAddChannel(ch, getUrl(), getChannelHandler()); @@ -134,273 +87,16 @@ public class NettyConnectionClient extends AbstractConnectionClient { NettyConfigOperator operator = new NettyConfigOperator(nettyChannel, getChannelHandler()); protocol.configClientPipeline(getUrl(), operator, nettySslContextOperator); - // set null but do not close this client, it will be reconnect in the future - ch.closeFuture().addListener(channelFuture -> channel.set(null)); + // set null but do not close this client, it will be reconnecting in the future + ch.closeFuture().addListener(channelFuture -> clearNettyChannel()); // TODO support Socks5 } }); - this.bootstrap = nettyBootstrap; + this.bootstrap = bootstrap; } @Override - protected void doClose() { - // AbstractPeer close can set closed true. - if (isClosed()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("Connection:%s freed ", this)); - } - final io.netty.channel.Channel current = getNettyChannel(); - if (current != null) { - current.close(); - } - this.channel.set(null); - closePromise.setSuccess(null); - } - } - - @Override - protected void doConnect() throws RemotingException { - if (!isReconnecting.compareAndSet(false, true)) { - return; - } - - if (isClosed()) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - String.format("%s aborted to reconnect cause connection closed. ", NettyConnectionClient.this)); - } - } - init.compareAndSet(false, true); - long start = System.currentTimeMillis(); - - createConnectingPromise(); - final ChannelFuture promise = bootstrap.connect(); - - promise.addListener(this.connectionListener); - - boolean ret = connectingPromise.get().awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS); - // destroy connectingPromise after used - synchronized (this) { - connectingPromise.set(null); - } - if (promise.cause() != null) { - Throwable cause = promise.cause(); - - // 6-1 Failed to connect to provider server by other reason. - RemotingException remotingException = new RemotingException( - this, - "client(url: " + getUrl() + ") failed to connect to server " + getConnectAddress() - + ", error message is:" + cause.getMessage(), - cause); - - LOGGER.error( - TRANSPORT_FAILED_CONNECT_PROVIDER, - "network disconnected", - "", - "Failed to connect to provider server by other reason.", - cause); - - throw remotingException; - } else if (!ret || !promise.isSuccess()) { - // 6-2 Client-side timeout - RemotingException remotingException = new RemotingException( - this, - "client(url: " + getUrl() + ") failed to connect to server " - + getConnectAddress() + " client-side timeout " - + getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) - + "ms) from netty client " - + NetUtils.getLocalHost() + " using dubbo version " + Version.getVersion()); - - LOGGER.error( - TRANSPORT_CLIENT_CONNECT_TIMEOUT, "provider crash", "", "Client-side timeout.", remotingException); - - throw remotingException; - } - } - - @Override - protected void doDisConnect() { - NettyChannel.removeChannelIfDisconnected(getNettyChannel()); - } - - @Override - public void onConnected(Object channel) { - if (!(channel instanceof io.netty.channel.Channel)) { - return; - } - io.netty.channel.Channel nettyChannel = ((io.netty.channel.Channel) channel); - if (isClosed()) { - nettyChannel.close(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("%s is closed, ignoring connected event", this)); - } - return; - } - - // Close the existing channel before setting a new channel - final io.netty.channel.Channel current = getNettyChannel(); - if (current != null) { - current.close(); - } - - this.channel.set(nettyChannel); - // This indicates that the connection is available. - if (this.connectingPromise.get() != null) { - this.connectingPromise.get().trySuccess(CONNECTED_OBJECT); - } - nettyChannel.attr(CONNECTION).set(this); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("%s connected ", this)); - } - } - - @Override - public void onGoaway(Object channel) { - if (!(channel instanceof io.netty.channel.Channel)) { - return; - } - io.netty.channel.Channel nettyChannel = (io.netty.channel.Channel) channel; - if (this.channel.compareAndSet(nettyChannel, null)) { - // Ensure the channel is closed - if (nettyChannel.isOpen()) { - nettyChannel.close(); - } - NettyChannel.removeChannelIfDisconnected(nettyChannel); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("%s goaway", this)); - } - } - } - - @Override - protected Channel getChannel() { - io.netty.channel.Channel c = getNettyChannel(); - if (c == null) { - return null; - } - return NettyChannel.getOrAddChannel(c, getUrl(), this); - } - - io.netty.channel.Channel getNettyChannel() { - return this.channel.get(); - } - - @Override - public Object getChannel(Boolean generalizable) { - return Boolean.TRUE.equals(generalizable) ? getNettyChannel() : getChannel(); - } - - @Override - public boolean isAvailable() { - if (isClosed()) { - return false; - } - io.netty.channel.Channel nettyChannel = getNettyChannel(); - if (nettyChannel != null && nettyChannel.isActive()) { - return true; - } - - if (init.compareAndSet(false, true)) { - try { - doConnect(); - } catch (RemotingException e) { - LOGGER.error(TRANSPORT_FAILED_RECONNECT, "", "", "Failed to connect to server: " + getConnectAddress()); - } - } - - createConnectingPromise(); - connectingPromise.get().awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS); - // destroy connectingPromise after used - synchronized (this) { - connectingPromise.set(null); - } - - nettyChannel = getNettyChannel(); - return nettyChannel != null && nettyChannel.isActive(); - } - - @Override - public void createConnectingPromise() { - connectingPromise.compareAndSet(null, new DefaultPromise<>(GlobalEventExecutor.INSTANCE)); - } - - public Promise getClosePromise() { - return closePromise; - } - - public static AbstractConnectionClient getConnectionClientFromChannel(io.netty.channel.Channel channel) { - return channel.attr(CONNECTION).get(); - } - - public ChannelFuture write(Object request) throws RemotingException { - if (!isAvailable()) { - throw new RemotingException( - null, - null, - "Failed to send request " + request + ", cause: The channel to " + remote + " is closed!"); - } - return ((io.netty.channel.Channel) getChannel()).writeAndFlush(request); - } - - @Override - public void addCloseListener(Runnable func) { - getClosePromise().addListener(future -> func.run()); - } - - @Override - public void destroy() { - close(); - } - - @Override - public String toString() { - return super.toString() + " (Ref=" + this.getCounter() + ",local=" - + Optional.ofNullable(getChannel()) - .map(Channel::getLocalAddress) - .orElse(null) + ",remote=" + getRemoteAddress(); - } - - class ConnectionListener implements ChannelFutureListener { - - @Override - public void operationComplete(ChannelFuture future) { - - if (!isReconnecting.compareAndSet(true, false)) { - return; - } - - if (future.isSuccess()) { - return; - } - final NettyConnectionClient connectionClient = NettyConnectionClient.this; - if (connectionClient.isClosed() || connectionClient.getCounter() == 0) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format( - "%s aborted to reconnect. %s", - connectionClient, future.cause().getMessage())); - } - return; - } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format( - "%s is reconnecting, attempt=%d cause=%s", - connectionClient, 0, future.cause().getMessage())); - } - - connectivityExecutor.schedule( - () -> { - try { - connectionClient.doConnect(); - } catch (RemotingException e) { - LOGGER.error( - TRANSPORT_FAILED_RECONNECT, - "", - "", - "Failed to connect to server: " + getConnectAddress()); - } - }, - reconnectDuaration, - TimeUnit.MILLISECONDS); - } + protected ChannelFuture performConnect() { + return bootstrap.connect(); } } diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConnectionHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConnectionHandler.java index c583cd3c89..68bffdc952 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConnectionHandler.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyConnectionHandler.java @@ -23,7 +23,7 @@ import org.apache.dubbo.remoting.api.connection.ConnectionHandler; import java.util.concurrent.TimeUnit; import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.EventLoop; @@ -33,16 +33,16 @@ import io.netty.util.AttributeKey; import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_RECONNECT; import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_UNEXPECTED_EXCEPTION; -@ChannelHandler.Sharable +@Sharable public class NettyConnectionHandler extends ChannelInboundHandlerAdapter implements ConnectionHandler { private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(NettyConnectionHandler.class); private static final AttributeKey GO_AWAY_KEY = AttributeKey.valueOf("dubbo_channel_goaway"); - private final NettyConnectionClient connectionClient; + private final AbstractNettyConnectionClient connectionClient; - public NettyConnectionHandler(NettyConnectionClient connectionClient) { + public NettyConnectionHandler(AbstractNettyConnectionClient connectionClient) { this.connectionClient = connectionClient; } @@ -52,7 +52,7 @@ public class NettyConnectionHandler extends ChannelInboundHandlerAdapter impleme return; } Channel nettyChannel = ((Channel) channel); - final Attribute attr = nettyChannel.attr(GO_AWAY_KEY); + Attribute attr = nettyChannel.attr(GO_AWAY_KEY); if (Boolean.TRUE.equals(attr.get())) { return; } @@ -76,7 +76,7 @@ public class NettyConnectionHandler extends ChannelInboundHandlerAdapter impleme if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("Connection %s is reconnecting, attempt=%d", connectionClient, 1)); } - final EventLoop eventLoop = nettyChannel.eventLoop(); + EventLoop eventLoop = nettyChannel.eventLoop(); if (connectionClient.isClosed()) { LOGGER.info("The client has been closed and will not reconnect. "); return; @@ -101,12 +101,16 @@ public class NettyConnectionHandler extends ChannelInboundHandlerAdapter impleme @Override public void channelActive(ChannelHandlerContext ctx) { ctx.fireChannelActive(); - NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), connectionClient.getUrl(), connectionClient); + Channel ch = ctx.channel(); + NettyChannel.getOrAddChannel(ch, connectionClient.getUrl(), connectionClient); if (!connectionClient.isClosed()) { - connectionClient.onConnected(ctx.channel()); + connectionClient.onConnected(ch); if (LOGGER.isInfoEnabled()) { - LOGGER.info("The connection of " + channel.getLocalAddress() + " -> " + channel.getRemoteAddress() - + " is established."); + LOGGER.info( + "The connection {} of {} -> {} is established.", + ch, + AddressUtils.getLocalAddressKey(ch), + AddressUtils.getRemoteAddressKey(ch)); } } else { ctx.close(); @@ -114,17 +118,27 @@ public class NettyConnectionHandler extends ChannelInboundHandlerAdapter impleme } @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - LOGGER.warn(TRANSPORT_UNEXPECTED_EXCEPTION, "", "", String.format("Channel error:%s", ctx.channel()), cause); - ctx.close(); + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + Channel ch = ctx.channel(); + try { + Attribute goawayAttr = ch.attr(GO_AWAY_KEY); + if (!Boolean.TRUE.equals(goawayAttr.get())) { + reconnect(ch); + } + LOGGER.info( + "The connection {} of {} -> {} is disconnected.", + ch, + AddressUtils.getLocalAddressKey(ch), + AddressUtils.getRemoteAddressKey(ch)); + } finally { + NettyChannel.removeChannel(ch); + } } @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - super.channelInactive(ctx); - final Attribute goawayAttr = ctx.channel().attr(GO_AWAY_KEY); - if (!Boolean.TRUE.equals(goawayAttr.get())) { - reconnect(ctx.channel()); - } + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + LOGGER.warn(TRANSPORT_UNEXPECTED_EXCEPTION, "", "", String.format("Channel error:%s", ctx.channel()), cause); + ctx.close(); } } diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java index 9f5bd0a075..6de1969cf1 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServer.java @@ -191,7 +191,7 @@ public class NettyServer extends AbstractServer { } @Override - protected void doClose() throws Throwable { + protected void doClose() { try { if (channel != null) { // unbind. @@ -247,10 +247,7 @@ public class NettyServer extends AbstractServer { @Override public Collection getChannels() { - Collection chs = new ArrayList<>(this.channels.size()); - // pick channels from NettyServerHandler ( needless to check connectivity ) - chs.addAll(this.channels.values()); - return chs; + return new ArrayList<>(channels.values()); } @Override diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java index 1eff7fc84b..c9987dcdb1 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/main/java/org/apache/dubbo/remoting/transport/netty4/NettyServerHandler.java @@ -23,7 +23,6 @@ import org.apache.dubbo.common.utils.NetUtils; import org.apache.dubbo.remoting.Channel; import org.apache.dubbo.remoting.ChannelHandler; -import java.net.InetSocketAddress; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -65,33 +64,39 @@ public class NettyServerHandler extends ChannelDuplexHandler { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); + io.netty.channel.Channel ch = ctx.channel(); + NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler); if (channel != null) { - channels.put( - NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress()), channel); + channels.put(NetUtils.toAddressString(channel.getRemoteAddress()), channel); } handler.connected(channel); - if (logger.isInfoEnabled()) { - logger.info("The connection of " + channel.getRemoteAddress() + " -> " + channel.getLocalAddress() - + " is established."); + if (logger.isInfoEnabled() && channel != null) { + logger.info( + "The connection {} of {} -> {} is established.", + ch, + AddressUtils.getRemoteAddressKey(ch), + AddressUtils.getLocalAddressKey(ch)); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { - NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler); + io.netty.channel.Channel ch = ctx.channel(); + NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler); try { - channels.remove( - NetUtils.toAddressString((InetSocketAddress) ctx.channel().remoteAddress())); + channels.remove(NetUtils.toAddressString(channel.getRemoteAddress())); handler.disconnected(channel); } finally { - NettyChannel.removeChannel(ctx.channel()); + NettyChannel.removeChannel(ch); } if (logger.isInfoEnabled()) { - logger.info("The connection of " + channel.getRemoteAddress() + " -> " + channel.getLocalAddress() - + " is disconnected."); + logger.info( + "The connection {} of {} -> {} is disconnected.", + ch, + AddressUtils.getRemoteAddressKey(ch), + AddressUtils.getLocalAddressKey(ch)); } } diff --git a/dubbo-remoting/pom.xml b/dubbo-remoting/pom.xml index a2604965d0..2384d0c81a 100644 --- a/dubbo-remoting/pom.xml +++ b/dubbo-remoting/pom.xml @@ -33,6 +33,7 @@ dubbo-remoting-zookeeper-curator5 dubbo-remoting-netty4 dubbo-remoting-http12 + dubbo-remoting-http3 false diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java index 222dfcaf5d..0e81a1433b 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/Constants.java @@ -97,12 +97,16 @@ public interface Constants { String INVOCATION_KEY = "invocation"; String SERIALIZATION_ID_KEY = "serialization_id"; + String HTTP3_KEY = "http3"; + String H2_SETTINGS_SUPPORT_NO_LOWER_HEADER_KEY = "dubbo.rpc.tri.support-no-lower-header"; String H2_SETTINGS_IGNORE_1_0_0_KEY = "dubbo.rpc.tri.ignore-1.0.0-version"; String H2_SETTINGS_RESOLVE_FALLBACK_TO_DEFAULT_KEY = "dubbo.rpc.tri.resolve-fallback-to-default"; String H2_SETTINGS_BUILTIN_SERVICE_INIT = "dubbo.tri.builtin.service.init"; String H2_SETTINGS_PASS_THROUGH_STANDARD_HTTP_HEADERS = "dubbo.rpc.tri.pass-through-standard-http-headers"; + String H3_SETTINGS_HTTP3_ENABLE = "dubbo.protocol.triple.enable-http3"; + String ADAPTIVE_LOADBALANCE_ATTACHMENT_KEY = "lb_adaptive"; String ADAPTIVE_LOADBALANCE_START_TIME = "adaptive_startTime"; } diff --git a/dubbo-rpc/dubbo-rpc-triple/pom.xml b/dubbo-rpc/dubbo-rpc-triple/pom.xml index bbaa7edd68..aa29ea0bfe 100644 --- a/dubbo-rpc/dubbo-rpc-triple/pom.xml +++ b/dubbo-rpc/dubbo-rpc-triple/pom.xml @@ -41,11 +41,16 @@ dubbo-remoting-http12 ${project.parent.version} + + org.apache.dubbo + dubbo-remoting-http3 + ${project.parent.version} + org.apache.dubbo dubbo-remoting-netty4 ${project.parent.version} - test + true io.netty diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java index cee75e3ec3..7566851c8d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java @@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.protocol.tri; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.config.context.ConfigManager; import org.apache.dubbo.config.nested.TripleConfig; import org.apache.dubbo.remoting.ChannelHandler; import org.apache.dubbo.remoting.api.AbstractWireProtocol; @@ -86,7 +87,7 @@ public class TripleHttp2Protocol extends AbstractWireProtocol implements ScopeMo @Override public void configClientPipeline(URL url, ChannelOperator operator, ContextOperator contextOperator) { - TripleConfig tripleConfig = getTripleConfig(url); + TripleConfig tripleConfig = ConfigManager.getProtocol(url).getTriple(); final Http2FrameCodec codec = Http2FrameCodecBuilder.forClient() .gracefulShutdownTimeoutMillis(10000) .initialSettings(new Http2Settings() @@ -130,7 +131,7 @@ public class TripleHttp2Protocol extends AbstractWireProtocol implements ScopeMo } private void configurerHttp1Handlers(URL url, List handlers) { - TripleConfig tripleConfig = getTripleConfig(url); + TripleConfig tripleConfig = ConfigManager.getProtocol(url).getTriple(); final HttpServerCodec sourceCodec = new HttpServerCodec(new HttpDecoderConfig() .setMaxChunkSize(tripleConfig.getMaxChunkSize()) .setMaxHeaderSize(tripleConfig.getMaxHeaderSize()) @@ -176,7 +177,7 @@ public class TripleHttp2Protocol extends AbstractWireProtocol implements ScopeMo } private void configurerHttp2Handlers(URL url, List handlers) { - TripleConfig tripleConfig = getTripleConfig(url); + TripleConfig tripleConfig = ConfigManager.getProtocol(url).getTriple(); final Http2FrameCodec codec = buildHttp2FrameCodec(tripleConfig); final Http2MultiplexHandler handler = buildHttp2MultiplexHandler(url, tripleConfig); handlers.add(new ChannelHandlerPretender(new HttpWriteQueueHandler())); @@ -202,11 +203,4 @@ public class TripleHttp2Protocol extends AbstractWireProtocol implements ScopeMo .validateHeaders(false) .build(); } - - private TripleConfig getTripleConfig(URL url) { - return url.getOrDefaultApplicationModel() - .getApplicationConfigManager() - .getOrAddProtocol(url.getProtocol()) - .getTriple(); - } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java index fcc85f391e..6286dd7f46 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java @@ -25,7 +25,9 @@ import org.apache.dubbo.common.threadpool.manager.ExecutorRepository; import org.apache.dubbo.common.utils.ExecutorUtil; import org.apache.dubbo.remoting.api.connection.AbstractConnectionClient; import org.apache.dubbo.remoting.api.pu.DefaultPuHandler; +import org.apache.dubbo.remoting.exchange.Http3Exchanger; import org.apache.dubbo.remoting.exchange.PortUnificationExchanger; +import org.apache.dubbo.rpc.Constants; import org.apache.dubbo.rpc.Exporter; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.PathResolver; @@ -55,6 +57,7 @@ import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_IGNORE_1_0_0_KEY; import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_PASS_THROUGH_STANDARD_HTTP_HEADERS; import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_RESOLVE_FALLBACK_TO_DEFAULT_KEY; import static org.apache.dubbo.rpc.Constants.H2_SETTINGS_SUPPORT_NO_LOWER_HEADER_KEY; +import static org.apache.dubbo.rpc.Constants.H3_SETTINGS_HTTP3_ENABLE; public class TripleProtocol extends AbstractProtocol { @@ -69,6 +72,7 @@ public class TripleProtocol extends AbstractProtocol { public static boolean IGNORE_1_0_0_VERSION = false; public static boolean RESOLVE_FALLBACK_TO_DEFAULT = true; public static boolean PASS_THROUGH_STANDARD_HTTP_HEADERS = false; + public static boolean HTTP3_ENABLED = false; public TripleProtocol(FrameworkModel frameworkModel) { this.frameworkModel = frameworkModel; @@ -83,6 +87,9 @@ public class TripleProtocol extends AbstractProtocol { IGNORE_1_0_0_VERSION = conf.getBoolean(H2_SETTINGS_IGNORE_1_0_0_KEY, false); RESOLVE_FALLBACK_TO_DEFAULT = conf.getBoolean(H2_SETTINGS_RESOLVE_FALLBACK_TO_DEFAULT_KEY, true); PASS_THROUGH_STANDARD_HTTP_HEADERS = conf.getBoolean(H2_SETTINGS_PASS_THROUGH_STANDARD_HTTP_HEADERS, false); + + Configuration globalConf = ConfigurationUtils.getGlobalConfiguration(frameworkModel.defaultApplication()); + HTTP3_ENABLED = globalConf.getBoolean(H3_SETTINGS_HTTP3_ENABLE, false); } @Override @@ -160,6 +167,11 @@ public class TripleProtocol extends AbstractProtocol { .createExecutorIfAbsent(ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME)); PortUnificationExchanger.bind(url, new DefaultPuHandler()); + + if (isHttp3Enabled(url)) { + Http3Exchanger.bind(url); + } + optimizeSerialization(url); return exporter; } @@ -168,7 +180,9 @@ public class TripleProtocol extends AbstractProtocol { public Invoker refer(Class type, URL url) throws RpcException { optimizeSerialization(url); ExecutorService streamExecutor = getOrCreateStreamExecutor(url.getOrDefaultApplicationModel(), url); - AbstractConnectionClient connectionClient = PortUnificationExchanger.connect(url, new DefaultPuHandler()); + AbstractConnectionClient connectionClient = isHttp3Enabled(url) + ? Http3Exchanger.connect(url) + : PortUnificationExchanger.connect(url, new DefaultPuHandler()); TripleInvoker invoker = new TripleInvoker<>(type, url, acceptEncodings, connectionClient, invokers, streamExecutor); invokers.add(invoker); @@ -195,8 +209,13 @@ public class TripleProtocol extends AbstractProtocol { logger.info("Destroying protocol [" + getClass().getSimpleName() + "] ..."); } PortUnificationExchanger.close(); + Http3Exchanger.close(); pathResolver.destroy(); mappingRegistry.destroy(); super.destroy(); } + + public static boolean isHttp3Enabled(URL url) { + return HTTP3_ENABLED || url.getParameter(Constants.HTTP3_KEY, false); + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/TripleClientCall.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/TripleClientCall.java index 330deaa0bd..877fcd09d7 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/TripleClientCall.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/call/TripleClientCall.java @@ -27,15 +27,14 @@ import org.apache.dubbo.rpc.protocol.tri.compressor.Compressor; import org.apache.dubbo.rpc.protocol.tri.compressor.Identity; import org.apache.dubbo.rpc.protocol.tri.observer.ClientCallToObserverAdapter; import org.apache.dubbo.rpc.protocol.tri.stream.ClientStream; +import org.apache.dubbo.rpc.protocol.tri.stream.ClientStreamFactory; import org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils; -import org.apache.dubbo.rpc.protocol.tri.stream.TripleClientStream; import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue; import java.util.Map; import java.util.concurrent.Executor; -import io.netty.channel.Channel; -import io.netty.handler.codec.http2.Http2Exception; +import io.netty.handler.codec.http2.Http2Exception.StreamException; import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_RESPONSE; @@ -55,7 +54,7 @@ public class TripleClientCall implements ClientCall, ClientStream.Listener { private boolean headerSent; private boolean autoRequest = true; private boolean done; - private Http2Exception.StreamException streamException; + private StreamException streamException; public TripleClientCall( AbstractConnectionClient connectionClient, @@ -138,7 +137,7 @@ public class TripleClientCall implements ClientCall, ClientStream.Listener { @Override public void onStart() { - listener.onStart(TripleClientCall.this); + listener.onStart(this); } @Override @@ -154,13 +153,12 @@ public class TripleClientCall implements ClientCall, ClientStream.Listener { if (stream == null) { return; } - if (t instanceof Http2Exception.StreamException - && ((Http2Exception.StreamException) t).error().equals(FLOW_CONTROL_ERROR)) { + if (t instanceof StreamException && ((StreamException) t).error().equals(FLOW_CONTROL_ERROR)) { TriRpcStatus status = TriRpcStatus.CANCELLED .withCause(t) .withDescription("Due flowcontrol over pendingbytes, Cancelled by client"); stream.cancelByLocal(status); - streamException = (Http2Exception.StreamException) t; + streamException = (StreamException) t; } else { TriRpcStatus status = TriRpcStatus.CANCELLED.withCause(t).withDescription("Cancelled by client"); stream.cancelByLocal(status); @@ -235,16 +233,22 @@ public class TripleClientCall implements ClientCall, ClientStream.Listener { @Override public void setCompression(String compression) { - this.requestMetadata.compressor = Compressor.getCompressor(frameworkModel, compression); + requestMetadata.compressor = Compressor.getCompressor(frameworkModel, compression); } @Override public StreamObserver start(RequestMetadata metadata, ClientCall.Listener responseListener) { - this.requestMetadata = metadata; - this.listener = responseListener; - this.stream = new TripleClientStream( - frameworkModel, executor, (Channel) connectionClient.getChannel(true), this, writeQueue); - return new ClientCallToObserverAdapter<>(this); + ClientStream stream; + for (ClientStreamFactory factory : frameworkModel.getActivateExtensions(ClientStreamFactory.class)) { + stream = factory.createClientStream(connectionClient, frameworkModel, executor, this, writeQueue); + if (stream != null) { + this.requestMetadata = metadata; + this.listener = responseListener; + this.stream = stream; + return new ClientCallToObserverAdapter<>(this); + } + } + throw new IllegalStateException("No available ClientStreamFactory"); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/command/Http3CreateStreamQueueCommand.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/command/Http3CreateStreamQueueCommand.java new file mode 100644 index 0000000000..6d8e2562cf --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/command/Http3CreateStreamQueueCommand.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.command; + +import org.apache.dubbo.rpc.protocol.tri.stream.TripleStreamChannelFuture; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPromise; +import io.netty.incubator.codec.http3.Http3; +import io.netty.incubator.codec.quic.QuicChannel; +import io.netty.incubator.codec.quic.QuicStreamChannel; + +public class Http3CreateStreamQueueCommand extends QueuedCommand { + + private final ChannelInitializer initializer; + + private final TripleStreamChannelFuture streamChannelFuture; + + private Http3CreateStreamQueueCommand( + ChannelInitializer initializer, TripleStreamChannelFuture future) { + this.initializer = initializer; + this.streamChannelFuture = future; + this.promise(future.getParentChannel().newPromise()); + this.channel(future.getParentChannel()); + } + + public static Http3CreateStreamQueueCommand create( + ChannelInitializer initializer, TripleStreamChannelFuture future) { + return new Http3CreateStreamQueueCommand(initializer, future); + } + + @Override + public void doSend(ChannelHandlerContext ctx, ChannelPromise promise) {} + + @Override + public void run(Channel channel) { + Http3.newRequestStream((QuicChannel) channel, initializer).addListener(future -> { + if (future.isSuccess()) { + streamChannelFuture.complete((Channel) future.getNow()); + } else { + streamChannelFuture.completeExceptionally(future.cause()); + } + }); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java index fc0961fdc7..d856dfad94 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java @@ -232,9 +232,16 @@ public abstract class AbstractServerTransportListener
f.invoke(invoker, inv)); + initializeAltSvc(url); + return onBuildRpcInvocationCompletion(inv); } + /** + * Alt-Svc + */ + protected void initializeAltSvc(URL url) {} + protected RpcInvocation onBuildRpcInvocationCompletion(RpcInvocation invocation) { String timeoutString = httpMetadata.headers().getFirst(TripleHeaderEnum.SERVICE_TIMEOUT.getHeader()); try { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java index d7be95ee66..9a7a4828c5 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java @@ -59,8 +59,9 @@ public class GrpcHttp2ServerTransportListener extends GenericHttp2ServerTranspor } private void grpcTrailersCustomize(HttpHeaders httpHeaders, Throwable throwable) { - httpHeaders.set(GrpcHeaderNames.GRPC_STATUS.getName(), "0"); - if (throwable != null) { + if (throwable == null) { + httpHeaders.set(GrpcHeaderNames.GRPC_STATUS.getName(), "0"); + } else { httpHeaders.set(GrpcHeaderNames.GRPC_STATUS.getName(), httpStatusToGrpcStatus(throwable)); httpHeaders.set(GrpcHeaderNames.GRPC_MESSAGE.getName(), throwable.getMessage()); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java index 3f9406a3cc..5d445dcb3c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java @@ -20,6 +20,7 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.common.threadpool.manager.ExecutorRepository; import org.apache.dubbo.common.threadpool.serial.SerializingExecutor; +import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.remoting.http12.HttpChannel; import org.apache.dubbo.remoting.http12.HttpHeaderNames; import org.apache.dubbo.remoting.http12.HttpInputMessage; @@ -37,6 +38,7 @@ import org.apache.dubbo.rpc.executor.ExecutorSupport; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.MethodDescriptor; import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; +import org.apache.dubbo.rpc.protocol.tri.TripleProtocol; import org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener; import org.apache.dubbo.rpc.protocol.tri.h12.DefaultHttpMessageListener; import org.apache.dubbo.rpc.protocol.tri.h12.HttpMessageListener; @@ -108,6 +110,13 @@ public class DefaultHttp11ServerTransportListener serverChannelObserver.onError(throwable); } + @Override + protected void initializeAltSvc(URL url) { + String protocolId = TripleProtocol.isHttp3Enabled(url) ? "h3" : "h2"; + int bindPort = url.getParameter(Constants.BIND_PORT_KEY, url.getPort()); + serverChannelObserver.setAltSvc(protocolId + "=\":" + bindPort + "\""); + } + @Override public void close() throws Exception { serverChannelObserver.close(); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java index e6c58d6fb0..382df659de 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java @@ -19,6 +19,7 @@ package org.apache.dubbo.rpc.protocol.tri.h12.http2; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.threadpool.manager.ExecutorRepository; import org.apache.dubbo.common.threadpool.serial.SerializingExecutor; +import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.remoting.http12.h2.CancelStreamException; import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; import org.apache.dubbo.remoting.http12.h2.Http2Header; @@ -40,6 +41,7 @@ import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.MethodDescriptor; import org.apache.dubbo.rpc.protocol.tri.ReflectionPackableMethod; import org.apache.dubbo.rpc.protocol.tri.RpcInvocationBuildContext; +import org.apache.dubbo.rpc.protocol.tri.TripleProtocol; import org.apache.dubbo.rpc.protocol.tri.h12.AbstractServerTransportListener; import org.apache.dubbo.rpc.protocol.tri.h12.BiStreamServerCallListener; import org.apache.dubbo.rpc.protocol.tri.h12.HttpMessageListener; @@ -158,6 +160,14 @@ public class GenericHttp2ServerTransportListener extends AbstractServerTransport return new BiStreamServerCallListener(invocation, invoker, responseObserver); } + @Override + protected void initializeAltSvc(URL url) { + if (TripleProtocol.isHttp3Enabled(url)) { + int bindPort = url.getParameter(Constants.BIND_PORT_KEY, url.getPort()); + serverChannelObserver.setAltSvc("h3=\":" + bindPort + "\""); + } + } + @Override protected void onMetadataCompletion(Http2Header metadata) { serverChannelObserver.setResponseEncoder(getContext().getHttpMessageEncoder()); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ClientStreamFactory.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ClientStreamFactory.java new file mode 100644 index 0000000000..56a6828f99 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ClientStreamFactory.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h12.http2; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.api.connection.AbstractConnectionClient; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.call.TripleClientCall; +import org.apache.dubbo.rpc.protocol.tri.stream.ClientStream; +import org.apache.dubbo.rpc.protocol.tri.stream.ClientStreamFactory; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue; + +import java.util.concurrent.Executor; + +import io.netty.channel.Channel; + +@Activate +public class Http2ClientStreamFactory implements ClientStreamFactory { + + @Override + public ClientStream createClientStream( + AbstractConnectionClient client, + FrameworkModel frameworkModel, + Executor executor, + TripleClientCall clientCall, + TripleWriteQueue writeQueue) { + return new Http2TripleClientStream( + frameworkModel, executor, (Channel) client.getChannel(true), clientCall, writeQueue); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2TripleClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2TripleClientStream.java new file mode 100644 index 0000000000..0f4bd44851 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2TripleClientStream.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h12.http2; + +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.command.CreateStreamQueueCommand; +import org.apache.dubbo.rpc.protocol.tri.stream.AbstractTripleClientStream; +import org.apache.dubbo.rpc.protocol.tri.stream.ClientStream; +import org.apache.dubbo.rpc.protocol.tri.stream.TripleStreamChannelFuture; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleCommandOutBoundHandler; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleHttp2ClientResponseHandler; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue; + +import java.util.concurrent.Executor; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.http2.Http2StreamChannel; +import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; + +public final class Http2TripleClientStream extends AbstractTripleClientStream { + + public Http2TripleClientStream( + FrameworkModel frameworkModel, + Executor executor, + Channel parent, + ClientStream.Listener listener, + TripleWriteQueue writeQueue) { + super(frameworkModel, executor, writeQueue, listener, parent); + } + + /** + * For test only + */ + public Http2TripleClientStream( + FrameworkModel frameworkModel, + Executor executor, + TripleWriteQueue writeQueue, + ClientStream.Listener listener, + Http2StreamChannel http2StreamChannel) { + super(frameworkModel, executor, writeQueue, listener, http2StreamChannel); + } + + @Override + protected TripleStreamChannelFuture initStreamChannel(Channel parent) { + Http2StreamChannelBootstrap bootstrap = new Http2StreamChannelBootstrap(parent); + bootstrap.handler(new ChannelInboundHandlerAdapter() { + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + ctx.channel() + .pipeline() + .addLast(new TripleCommandOutBoundHandler()) + .addLast(new TripleHttp2ClientResponseHandler(createTransportListener())); + } + }); + TripleStreamChannelFuture streamChannelFuture = new TripleStreamChannelFuture(parent); + writeQueue.enqueue(CreateStreamQueueCommand.create(bootstrap, streamChannelFuture)); + return streamChannelFuture; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/GenericHttp3ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/GenericHttp3ServerTransportListener.java new file mode 100644 index 0000000000..2f771808ef --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/GenericHttp3ServerTransportListener.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h3; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.remoting.http12.h2.Http2InputMessage; +import org.apache.dubbo.remoting.http3.Http3TransportListener; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.h12.http2.GenericHttp2ServerTransportListener; + +public final class GenericHttp3ServerTransportListener extends GenericHttp2ServerTransportListener + implements Http3TransportListener { + + public GenericHttp3ServerTransportListener( + H2StreamChannel h2StreamChannel, URL url, FrameworkModel frameworkModel) { + super(h2StreamChannel, url, frameworkModel); + } + + @Override + protected void doOnData(Http2InputMessage message) { + if (message.isEndStream()) { + onDataCompletion(message); + return; + } + super.doOnData(message); + } + + @Override + protected void initializeAltSvc(URL url) {} +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/GenericHttp3ServerTransportListenerFactory.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/GenericHttp3ServerTransportListenerFactory.java new file mode 100644 index 0000000000..5fefeb7b7b --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/GenericHttp3ServerTransportListenerFactory.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h3; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.remoting.http3.Http3ServerTransportListenerFactory; +import org.apache.dubbo.remoting.http3.Http3TransportListener; +import org.apache.dubbo.rpc.model.FrameworkModel; + +@Activate +public class GenericHttp3ServerTransportListenerFactory implements Http3ServerTransportListenerFactory { + + @Override + public Http3TransportListener newInstance(H2StreamChannel streamChannel, URL url, FrameworkModel frameworkModel) { + return new GenericHttp3ServerTransportListener(streamChannel, url, frameworkModel); + } + + @Override + public boolean supportContentType(String contentType) { + return true; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3ClientFrameCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3ClientFrameCodec.java new file mode 100644 index 0000000000..4dcbd35afc --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3ClientFrameCodec.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h3; + +import org.apache.dubbo.remoting.http3.netty4.Http2HeadersAdapter; +import org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum; + +import java.util.Map; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2GoAwayFrame; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.DefaultHttp2ResetFrame; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.incubator.codec.http3.DefaultHttp3DataFrame; +import io.netty.incubator.codec.http3.DefaultHttp3Headers; +import io.netty.incubator.codec.http3.DefaultHttp3HeadersFrame; +import io.netty.incubator.codec.http3.Http3DataFrame; +import io.netty.incubator.codec.http3.Http3ErrorCode; +import io.netty.incubator.codec.http3.Http3Exception; +import io.netty.incubator.codec.http3.Http3GoAwayFrame; +import io.netty.incubator.codec.http3.Http3Headers; +import io.netty.incubator.codec.http3.Http3HeadersFrame; +import io.netty.incubator.codec.quic.QuicStreamChannel; + +@Sharable +public class Http3ClientFrameCodec extends ChannelDuplexHandler { + + public static final Http3ClientFrameCodec INSTANCE = new Http3ClientFrameCodec(); + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + if (msg instanceof Http3HeadersFrame) { + Http2HeadersAdapter headers = new Http2HeadersAdapter(((Http3HeadersFrame) msg).headers()); + boolean endStream = headers.contains(TripleHeaderEnum.STATUS_KEY.getHeader()); + ctx.fireChannelRead(new DefaultHttp2HeadersFrame(headers, endStream)); + } else if (msg instanceof Http3DataFrame) { + ctx.fireChannelRead(new DefaultHttp2DataFrame(((Http3DataFrame) msg).content())); + } else if (msg instanceof Http3GoAwayFrame) { + ctx.fireUserEventTriggered(new DefaultHttp2GoAwayFrame(((Http3GoAwayFrame) msg).id())); + } else { + ctx.fireChannelRead(msg); + } + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.fireChannelRead(new DefaultHttp2DataFrame(Unpooled.EMPTY_BUFFER, true)); + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (msg instanceof Http2HeadersFrame) { + Http2HeadersFrame frame = (Http2HeadersFrame) msg; + Http3Headers headers = new DefaultHttp3Headers(); + for (Map.Entry header : frame.headers()) { + headers.set(header.getKey(), header.getValue()); + } + ctx.write(new DefaultHttp3HeadersFrame(headers), promise); + if (frame.isEndStream()) { + ((QuicStreamChannel) ctx.channel()).shutdownOutput(promise); + } + } else if (msg instanceof Http2DataFrame) { + Http2DataFrame frame = (Http2DataFrame) msg; + if (frame.isEndStream()) { + ((QuicStreamChannel) ctx.channel()).shutdownOutput(promise); + return; + } + ctx.write(new DefaultHttp3DataFrame(frame.content()), promise); + } else { + ctx.write(msg, promise); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (cause instanceof Http3Exception) { + Http3Exception e = (Http3Exception) cause; + Http3ErrorCode errorCode = e.errorCode(); + if (errorCode == Http3ErrorCode.H3_CLOSED_CRITICAL_STREAM) { + ctx.fireUserEventTriggered(new DefaultHttp2ResetFrame(256 + errorCode.ordinal())); + return; + } + } + super.exceptionCaught(ctx, cause); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3ClientStreamFactory.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3ClientStreamFactory.java new file mode 100644 index 0000000000..b6e2a5dc59 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3ClientStreamFactory.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h3; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.api.connection.AbstractConnectionClient; +import org.apache.dubbo.remoting.transport.netty4.NettyHttp3ConnectionClient; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.call.TripleClientCall; +import org.apache.dubbo.rpc.protocol.tri.stream.ClientStream; +import org.apache.dubbo.rpc.protocol.tri.stream.ClientStreamFactory; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue; + +import java.util.concurrent.Executor; + +import io.netty.channel.Channel; + +@Activate(order = -100, onClass = "io.netty.incubator.codec.quic.QuicChannel") +public class Http3ClientStreamFactory implements ClientStreamFactory { + + @Override + public ClientStream createClientStream( + AbstractConnectionClient client, + FrameworkModel frameworkModel, + Executor executor, + TripleClientCall clientCall, + TripleWriteQueue writeQueue) { + if (client instanceof NettyHttp3ConnectionClient) { + return new Http3TripleClientStream( + frameworkModel, executor, (Channel) client.getChannel(true), clientCall, writeQueue); + } + return null; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3TripleClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3TripleClientStream.java new file mode 100644 index 0000000000..4e8c0827e8 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/Http3TripleClientStream.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h3; + +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.command.Http3CreateStreamQueueCommand; +import org.apache.dubbo.rpc.protocol.tri.stream.AbstractTripleClientStream; +import org.apache.dubbo.rpc.protocol.tri.stream.ClientStream; +import org.apache.dubbo.rpc.protocol.tri.stream.TripleStreamChannelFuture; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleCommandOutBoundHandler; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleGoAwayHandler; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleHttp2ClientResponseHandler; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleTailHandler; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue; + +import java.util.concurrent.Executor; + +import io.netty.channel.Channel; +import io.netty.handler.codec.http2.Http2StreamChannel; +import io.netty.incubator.codec.http3.Http3RequestStreamInitializer; +import io.netty.incubator.codec.quic.QuicStreamChannel; + +public final class Http3TripleClientStream extends AbstractTripleClientStream { + + public Http3TripleClientStream( + FrameworkModel frameworkModel, + Executor executor, + Channel parent, + ClientStream.Listener listener, + TripleWriteQueue writeQueue) { + super(frameworkModel, executor, writeQueue, listener, parent); + } + + /** + * For test only + */ + public Http3TripleClientStream( + FrameworkModel frameworkModel, + Executor executor, + TripleWriteQueue writeQueue, + ClientStream.Listener listener, + Http2StreamChannel http2StreamChannel) { + super(frameworkModel, executor, writeQueue, listener, http2StreamChannel); + } + + @Override + protected TripleStreamChannelFuture initStreamChannel(Channel parent) { + Http3RequestStreamInitializer initializer = new Http3RequestStreamInitializer() { + @Override + protected void initRequestStream(QuicStreamChannel ch) { + ch.pipeline() + .addLast(Http3ClientFrameCodec.INSTANCE) + .addLast(new TripleCommandOutBoundHandler()) + .addLast(new TripleHttp2ClientResponseHandler(createTransportListener())) + .addLast(new TripleGoAwayHandler()) + .addLast(new TripleTailHandler()); + } + }; + TripleStreamChannelFuture future = new TripleStreamChannelFuture(parent); + writeQueue.enqueue(Http3CreateStreamQueueCommand.create(initializer, future)); + return future; + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/grpc/GrpcHttp3ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/grpc/GrpcHttp3ServerTransportListener.java new file mode 100644 index 0000000000..06f87b4df1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/grpc/GrpcHttp3ServerTransportListener.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h3.grpc; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.remoting.http12.h2.Http2InputMessage; +import org.apache.dubbo.remoting.http3.Http3TransportListener; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcHttp2ServerTransportListener; + +public final class GrpcHttp3ServerTransportListener extends GrpcHttp2ServerTransportListener + implements Http3TransportListener { + + public GrpcHttp3ServerTransportListener(H2StreamChannel h2StreamChannel, URL url, FrameworkModel frameworkModel) { + super(h2StreamChannel, url, frameworkModel); + } + + @Override + protected void doOnData(Http2InputMessage message) { + if (message.isEndStream()) { + onDataCompletion(message); + return; + } + super.doOnData(message); + } + + @Override + protected void initializeAltSvc(URL url) {} +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/grpc/GrpcHttp3ServerTransportListenerFactory.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/grpc/GrpcHttp3ServerTransportListenerFactory.java new file mode 100644 index 0000000000..344399acf5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h3/grpc/GrpcHttp3ServerTransportListenerFactory.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.h3.grpc; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; +import org.apache.dubbo.remoting.http3.Http3ServerTransportListenerFactory; +import org.apache.dubbo.remoting.http3.Http3TransportListener; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum; + +@Activate(order = -100, onClass = "com.google.protobuf.Message") +public class GrpcHttp3ServerTransportListenerFactory implements Http3ServerTransportListenerFactory { + + @Override + public Http3TransportListener newInstance(H2StreamChannel streamChannel, URL url, FrameworkModel frameworkModel) { + return new GrpcHttp3ServerTransportListener(streamChannel, url, frameworkModel); + } + + @Override + public boolean supportContentType(String contentType) { + return contentType != null && contentType.startsWith(TripleHeaderEnum.APPLICATION_GRPC.getHeader()); + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/TripleClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/AbstractTripleClientStream.java similarity index 88% rename from dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/TripleClientStream.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/AbstractTripleClientStream.java index 8b060ae82d..e1fdd0f14a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/TripleClientStream.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/AbstractTripleClientStream.java @@ -25,7 +25,6 @@ import org.apache.dubbo.rpc.protocol.tri.ClassLoadUtil; import org.apache.dubbo.rpc.protocol.tri.ExceptionUtils; import org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum; import org.apache.dubbo.rpc.protocol.tri.command.CancelQueueCommand; -import org.apache.dubbo.rpc.protocol.tri.command.CreateStreamQueueCommand; import org.apache.dubbo.rpc.protocol.tri.command.DataQueueCommand; import org.apache.dubbo.rpc.protocol.tri.command.EndStreamQueueCommand; import org.apache.dubbo.rpc.protocol.tri.command.HeaderQueueCommand; @@ -35,10 +34,7 @@ import org.apache.dubbo.rpc.protocol.tri.frame.Deframer; import org.apache.dubbo.rpc.protocol.tri.frame.TriDecoder; import org.apache.dubbo.rpc.protocol.tri.transport.AbstractH2TransportListener; import org.apache.dubbo.rpc.protocol.tri.transport.H2TransportListener; -import org.apache.dubbo.rpc.protocol.tri.transport.TripleCommandOutBoundHandler; -import org.apache.dubbo.rpc.protocol.tri.transport.TripleHttp2ClientResponseHandler; import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue; -import org.apache.dubbo.rpc.protocol.tri.transport.WriteQueue; import java.io.IOException; import java.net.SocketAddress; @@ -55,27 +51,25 @@ import com.google.rpc.Status; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http2.Http2Error; import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2StreamChannel; -import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; import io.netty.util.ReferenceCountUtil; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_RESPONSE; /** - * ClientStream is an abstraction for bi-directional messaging. It maintains a {@link WriteQueue} to + * ClientStream is an abstraction for bidirectional messaging. It maintains a {@link TripleWriteQueue} to * write Http2Frame to remote. A {@link H2TransportListener} receives Http2Frame from remote. * Instead of maintaining state, this class depends on upper layer or transport layer's states. */ -public class TripleClientStream extends AbstractStream implements ClientStream { +public abstract class AbstractTripleClientStream extends AbstractStream implements ClientStream { - private static final ErrorTypeAwareLogger LOGGER = LoggerFactory.getErrorTypeAwareLogger(TripleClientStream.class); + private static final ErrorTypeAwareLogger LOGGER = + LoggerFactory.getErrorTypeAwareLogger(AbstractTripleClientStream.class); - public final ClientStream.Listener listener; - private final TripleWriteQueue writeQueue; + private final ClientStream.Listener listener; + protected final TripleWriteQueue writeQueue; private Deframer deframer; private final Channel parent; private final TripleStreamChannelFuture streamChannelFuture; @@ -84,8 +78,7 @@ public class TripleClientStream extends AbstractStream implements ClientStream { private boolean isReturnTriException = false; - // for test - TripleClientStream( + protected AbstractTripleClientStream( FrameworkModel frameworkModel, Executor executor, TripleWriteQueue writeQueue, @@ -95,37 +88,23 @@ public class TripleClientStream extends AbstractStream implements ClientStream { this.parent = http2StreamChannel.parent(); this.listener = listener; this.writeQueue = writeQueue; - this.streamChannelFuture = initHttp2StreamChannel(http2StreamChannel); + this.streamChannelFuture = initStreamChannel(http2StreamChannel); } - public TripleClientStream( + protected AbstractTripleClientStream( FrameworkModel frameworkModel, Executor executor, - Channel parent, + TripleWriteQueue writeQueue, ClientStream.Listener listener, - TripleWriteQueue writeQueue) { + Channel parent) { super(executor, frameworkModel); this.parent = parent; this.listener = listener; this.writeQueue = writeQueue; - this.streamChannelFuture = initHttp2StreamChannel(parent); + this.streamChannelFuture = initStreamChannel(parent); } - private TripleStreamChannelFuture initHttp2StreamChannel(Channel parent) { - TripleStreamChannelFuture streamChannelFuture = new TripleStreamChannelFuture(parent); - Http2StreamChannelBootstrap bootstrap = new Http2StreamChannelBootstrap(parent); - bootstrap.handler(new ChannelInboundHandlerAdapter() { - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - Channel channel = ctx.channel(); - channel.pipeline().addLast(new TripleCommandOutBoundHandler()); - channel.pipeline().addLast(new TripleHttp2ClientResponseHandler(createTransportListener())); - } - }); - CreateStreamQueueCommand cmd = CreateStreamQueueCommand.create(bootstrap, streamChannelFuture); - this.writeQueue.enqueue(cmd); - return streamChannelFuture; - } + protected abstract TripleStreamChannelFuture initStreamChannel(Channel parent); public ChannelFuture sendHeader(Http2Headers headers) { if (this.writeQueue == null) { @@ -157,7 +136,7 @@ public class TripleClientStream extends AbstractStream implements ClientStream { } final CancelQueueCommand cmd = CancelQueueCommand.createCommand(streamChannelFuture, Http2Error.CANCEL); - TripleClientStream.this.rst = true; + rst = true; return this.writeQueue.enqueue(cmd); } @@ -212,7 +191,7 @@ public class TripleClientStream extends AbstractStream implements ClientStream { /** * @return transport listener */ - H2TransportListener createTransportListener() { + protected H2TransportListener createTransportListener() { return new ClientTransportListener(); } @@ -225,7 +204,7 @@ public class TripleClientStream extends AbstractStream implements ClientStream { void handleH2TransportError(TriRpcStatus status) { writeQueue.enqueue(CancelQueueCommand.createCommand(streamChannelFuture, Http2Error.NO_ERROR)); - TripleClientStream.this.rst = true; + rst = true; finishProcess(status, null, false); } @@ -304,7 +283,7 @@ public class TripleClientStream extends AbstractStream implements ClientStream { TriDecoder.Listener listener = new TriDecoder.Listener() { @Override public void onRawMessage(byte[] data) { - TripleClientStream.this.listener.onMessage(data, isReturnTriException); + AbstractTripleClientStream.this.listener.onMessage(data, isReturnTriException); } public void close() { @@ -312,7 +291,7 @@ public class TripleClientStream extends AbstractStream implements ClientStream { } }; deframer = new TriDecoder(decompressor, listener); - TripleClientStream.this.listener.onStart(); + AbstractTripleClientStream.this.listener.onStart(); } void onTrailersReceived(Http2Headers trailers) { @@ -421,7 +400,7 @@ public class TripleClientStream extends AbstractStream implements ClientStream { executor.execute(() -> { if (endStream) { if (!halfClosed) { - Http2StreamChannel channel = streamChannelFuture.getNow(); + Channel channel = streamChannelFuture.getNow(); if (channel.isActive() && !rst) { writeQueue.enqueue( CancelQueueCommand.createCommand(streamChannelFuture, Http2Error.CANCEL)); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/ClientStreamFactory.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/ClientStreamFactory.java new file mode 100644 index 0000000000..dc51f78404 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/ClientStreamFactory.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.rpc.protocol.tri.stream; + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.remoting.api.connection.AbstractConnectionClient; +import org.apache.dubbo.rpc.model.FrameworkModel; +import org.apache.dubbo.rpc.protocol.tri.call.TripleClientCall; +import org.apache.dubbo.rpc.protocol.tri.transport.TripleWriteQueue; + +import java.util.concurrent.Executor; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface ClientStreamFactory { + + ClientStream createClientStream( + AbstractConnectionClient client, + FrameworkModel frameworkModel, + Executor executor, + TripleClientCall clientCall, + TripleWriteQueue writeQueue); +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/TripleStreamChannelFuture.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/TripleStreamChannelFuture.java index 6c47761cbe..36cc86ded2 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/TripleStreamChannelFuture.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/stream/TripleStreamChannelFuture.java @@ -23,7 +23,7 @@ import java.util.concurrent.CompletableFuture; import io.netty.channel.Channel; import io.netty.handler.codec.http2.Http2StreamChannel; -public class TripleStreamChannelFuture extends CompletableFuture { +public class TripleStreamChannelFuture extends CompletableFuture { private final Channel parentChannel; @@ -60,7 +60,7 @@ public class TripleStreamChannelFuture extends CompletableFuturedubbo-remoting-http12 ${project.version} + + org.apache.dubbo + dubbo-remoting-http3 + ${project.version} + org.apache.dubbo dubbo-remoting-netty