├── src ├── test │ ├── resources │ │ └── http3.nyist.xyz.pfx │ └── java │ │ ├── Http3ClientTest.java │ │ └── Http3ServerTest.java └── main │ ├── java │ └── xyz │ │ └── nyist │ │ ├── core │ │ ├── Http3LastFrame.java │ │ ├── package-info.java │ │ ├── Http3FrameTypeValidator.java │ │ ├── Http3Frame.java │ │ ├── Http3PushStreamFrame.java │ │ ├── Http3ControlStreamFrame.java │ │ ├── Http3RequestStreamFrame.java │ │ ├── Http3GoAwayFrame.java │ │ ├── Http3MaxPushIdFrame.java │ │ ├── Http3CancelPushFrame.java │ │ ├── Http3PushPromiseFrame.java │ │ ├── QpackException.java │ │ ├── Http3PushStreamServerValidationHandler.java │ │ ├── Http3HeadersValidationException.java │ │ ├── Http3DataFrame.java │ │ ├── Http3RequestStreamFrameTypeValidator.java │ │ ├── Http3PushStreamFrameTypeValidator.java │ │ ├── DefaultHttp3GoAwayFrame.java │ │ ├── DefaultHttp3MaxPushIdFrame.java │ │ ├── DefaultHttp3CancelPushFrame.java │ │ ├── QpackHeaderField.java │ │ ├── Http3ControlStreamFrameTypeValidator.java │ │ ├── CharSequenceMap.java │ │ ├── Http3FrameTypeInboundValidationHandler.java │ │ ├── Http3UnknownFrame.java │ │ ├── Http3Exception.java │ │ ├── Http3FrameTypeOutboundValidationHandler.java │ │ ├── Http3RequestStreamDecodeStateValidator.java │ │ ├── Http3UnidirectionalStreamInboundServerHandler.java │ │ ├── DefaultHttp3PushPromiseFrame.java │ │ ├── DefaultHttp3DataFrame.java │ │ ├── DefaultHttp3LastDataFrame.java │ │ ├── Http3RequestStreamCodecState.java │ │ ├── Http3SettingsFrame.java │ │ ├── Http3PushStreamClientInitializer.java │ │ ├── Http3RequestStreamInitializer.java │ │ ├── DefaultHttp3SettingsFrame.java │ │ ├── QpackDecoderStateSyncStrategy.java │ │ ├── Http3UnidirectionalStreamInboundClientHandler.java │ │ ├── Http3FrameTypeDuplexValidationHandler.java │ │ ├── DefaultHttp3UnknownFrame.java │ │ ├── Http3PushStreamClientValidationHandler.java │ │ ├── Http3FrameValidationUtils.java │ │ ├── Http3PushStreamServerInitializer.java │ │ ├── Http3ErrorCode.java │ │ ├── QpackAttributes.java │ │ ├── Http3ClientConnectionHandler.java │ │ ├── Http3RequestStreamEncodeStateValidator.java │ │ ├── Http3ServerConnectionHandler.java │ │ ├── QpackHuffmanEncoder.java │ │ ├── QpackDecoderDynamicTable.java │ │ └── QpackDecoderHandler.java │ │ ├── http │ │ ├── Http3Resources.java │ │ ├── Http3Version.java │ │ ├── server │ │ │ ├── Http3ServerInfos.java │ │ │ ├── Http3ServerRequest.java │ │ │ └── Http3ServerBind.java │ │ ├── client │ │ │ ├── Http3ClientResponse.java │ │ │ ├── Http3ClientInfos.java │ │ │ ├── Http3ClientRequest.java │ │ │ └── Http3ConnectionProvider.java │ │ ├── ConnectionChangeHandler.java │ │ ├── Http3OutboundStreamTrafficHandler.java │ │ ├── Http3StreamInfo.java │ │ ├── CombinationChannelFuture.java │ │ ├── ServerCookies.java │ │ ├── Cookies.java │ │ └── Http3InboundStreamTrafficHandler.java │ │ ├── adapter │ │ ├── Http3HeadersAdapter.java │ │ ├── ReactorHttp3HandlerAdapter.java │ │ ├── DefaultSslInfo.java │ │ └── ReactorServerHttp3Response.java │ │ ├── quic │ │ ├── package-info.java │ │ ├── QuicInbound.java │ │ ├── QuicOutbound.java │ │ ├── QuicStreamInfo.java │ │ ├── QuicOutboundStreamOperations.java │ │ ├── QuicInboundStreamOperations.java │ │ ├── QuicOutboundStreamTrafficHandler.java │ │ ├── QuicInboundStreamTrafficHandler.java │ │ ├── QuicConnection.java │ │ └── QuicServerBind.java │ │ └── test │ │ └── ChannelUtil.java │ └── resources │ └── logback.xml ├── README.md ├── .gitignore └── pom.xml /src/test/resources/http3.nyist.xyz.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heixiaoma/http3/master/src/test/resources/http3.nyist.xyz.pfx -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3LastFrame.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.core; 2 | 3 | /** 4 | * @author: fucong 5 | * @Date: 2022/8/30 18:31 6 | * @Description: 7 | */ 8 | public interface Http3LastFrame { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/Http3Resources.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http; 2 | 3 | import reactor.netty.resources.LoopResources; 4 | import xyz.nyist.quic.QuicResources; 5 | 6 | /** 7 | * @author: fucong 8 | * @Date: 2022/7/29 11:14 9 | * @Description: 10 | */ 11 | public class Http3Resources extends QuicResources { 12 | 13 | protected Http3Resources(LoopResources defaultLoops) { 14 | super(defaultLoops); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/adapter/Http3HeadersAdapter.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.adapter; 2 | 3 | import org.springframework.http.HttpHeaders; 4 | import xyz.nyist.core.Http3HeadersFrame; 5 | 6 | /** 7 | * @author: fucong 8 | * @Date: 2022/8/15 14:25 9 | * @Description: 10 | */ 11 | public class Http3HeadersAdapter extends HttpHeaders { 12 | 13 | 14 | public Http3HeadersAdapter(Http3HeadersFrame headersFrame) { 15 | super(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/Http3Version.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http; 2 | 3 | import io.netty.handler.codec.http.HttpVersion; 4 | 5 | /** 6 | * @author: fucong 7 | * @Date: 2022/8/12 17:08 8 | * @Description: 9 | */ 10 | public class Http3Version extends HttpVersion { 11 | 12 | 13 | public static final Http3Version INSTANCE = new Http3Version(); 14 | 15 | 16 | private Http3Version() { 17 | super("http", 3, 0, false); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/server/Http3ServerInfos.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http.server; 2 | 3 | import io.netty.handler.codec.http.cookie.Cookie; 4 | import xyz.nyist.http.Http3StreamInfo; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author: fucong 11 | * @Date: 2022/8/18 14:54 12 | * @Description: 13 | */ 14 | public interface Http3ServerInfos extends Http3StreamInfo { 15 | 16 | /** 17 | * Returns resolved HTTP cookies. As opposed to {@link #cookies()}, this 18 | * returns all cookies, even if they have the same name. 19 | * 20 | * @return Resolved HTTP cookies 21 | */ 22 | Map> allCookies(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netty http3 codec 2 | 3 | 这是一个基于 [netty-incubator-codec-quic](https://github.com/netty/netty-incubator-codec-quic) 4 | 和[netty-incubator-codec-http3](https://github.com/netty/netty-incubator-codec-http3) 5 | 的响应式、高性能 WEB 容器. 6 | 7 | ## 如何使用它? 8 | 9 | 在test下有一些测试类以供参考
10 | 可以添加域名 **http3.nyist.xyz** 到**127.0.0.1** 11 | 12 | ## 如何校验是否成功使用了http3 13 | 14 | ### 1. chrome浏览器 15 | 16 | 在浏览器地址栏输入 chrome://flags 回车,搜索 “quic” 可以看到 “Experimental QUIC Protocol” 点击下拉框选择 “Enabled”。
17 | 18 | mac os使用命令行增加如下启动参数强制访问使用http3: 19 | 20 | ``` 21 | /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --quic-version=h3-29 --origin-to-force-quic-on=http3.nyist.xyz:443 22 | ``` 23 | 24 | 关闭代理,打开chrome network,访问服务,观察Protocol 25 | 26 | ### 2. curl 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse project files 2 | .project 3 | .classpath 4 | .settings 5 | 6 | # IntelliJ IDEA project files and directories 7 | *.iml 8 | *.ipr 9 | *.iws 10 | .idea/ 11 | .shelf/ 12 | 13 | # Geany project file 14 | .geany 15 | 16 | # KDevelop project file and directory 17 | .kdev4/ 18 | *.kdev4 19 | 20 | # Build targets 21 | /target 22 | */target 23 | 24 | # Report directories 25 | /reports 26 | */reports 27 | 28 | # Mac-specific directory that no other operating system needs. 29 | .DS_Store 30 | 31 | # JVM crash logs 32 | hs_err_pid*.log 33 | 34 | dependency-reduced-pom.xml 35 | 36 | */.unison.* 37 | 38 | # exclude mainframer files 39 | mainframer 40 | .mainframer 41 | 42 | # exclude docker-sync stuff 43 | .docker-sync 44 | */.docker-sync 45 | 46 | # exclude vscode files 47 | .vscode/ 48 | *.factorypath 49 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | /** 18 | * HTTP/3 implementation. 19 | */ 20 | package xyz.nyist.core; 21 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/client/Http3ClientResponse.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http.client; 2 | 3 | import reactor.core.publisher.Mono; 4 | import xyz.nyist.core.Http3HeadersFrame; 5 | 6 | /** 7 | * @author: fucong 8 | * @Date: 2022/8/18 14:57 9 | * @Description: 10 | */ 11 | public interface Http3ClientResponse extends Http3ClientInfos { 12 | 13 | /** 14 | * Return response HTTP headers. 15 | * 16 | * @return response HTTP headers. 17 | */ 18 | Http3HeadersFrame responseHeaders(); 19 | 20 | /** 21 | * Return the resolved HTTP Response Status. 22 | * 23 | * @return the resolved HTTP Response Status 24 | */ 25 | CharSequence status(); 26 | 27 | /** 28 | * Return response trailer headers. 29 | * 30 | * @return response trailer headers. 31 | * @since 1.0.12 32 | */ 33 | Mono trailerHeaders(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-Present VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Components for writing QUIC-based clients and servers using Reactor abstractions. 19 | */ 20 | @NonNullApi 21 | package xyz.nyist.quic; 22 | 23 | import reactor.util.annotation.NonNullApi; 24 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3FrameTypeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | @FunctionalInterface 19 | interface Http3FrameTypeValidator { 20 | 21 | Http3FrameTypeValidator NO_VALIDATION = (type, first) -> {}; 22 | 23 | void validate(long type, boolean first) throws Http3Exception; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3Frame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Marker interface that is implemented by all HTTP3 frames. 20 | */ 21 | public interface Http3Frame { 22 | 23 | /** 24 | * The type of the frame. 25 | * 26 | * @return the type. 27 | */ 28 | long type(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3PushStreamFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Marker interface for frames that can be sent and received on a 20 | * HTTP3 push stream. 21 | */ 22 | public interface Http3PushStreamFrame extends Http3Frame { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3ControlStreamFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Marker interface for frames that can be sent and received on a 20 | * HTTP3 control stream. 21 | */ 22 | public interface Http3ControlStreamFrame extends Http3Frame { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3RequestStreamFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Marker interface for frames that can be sent and received on a 20 | * HTTP3 request stream. 21 | */ 22 | public interface Http3RequestStreamFrame extends Http3Frame { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicInbound.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import io.netty.channel.Channel; 19 | import reactor.netty.NettyInbound; 20 | 21 | /** 22 | * An inbound-traffic API delegating to an underlying {@link Channel}. 23 | * 24 | * @author Violeta Georgieva 25 | */ 26 | public interface QuicInbound extends NettyInbound, QuicStreamInfo { 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicOutbound.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import io.netty.channel.Channel; 19 | import reactor.netty.NettyOutbound; 20 | 21 | /** 22 | * An outbound-traffic API delegating to an underlying {@link Channel}. 23 | * 24 | * @author Violeta Georgieva 25 | */ 26 | public interface QuicOutbound extends NettyOutbound, QuicStreamInfo { 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | ${log.pattern} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3GoAwayFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * See GOAWAY. 20 | */ 21 | public interface Http3GoAwayFrame extends Http3ControlStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the id. 30 | * 31 | * @return the id. 32 | */ 33 | long id(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3MaxPushIdFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * See MAX_PUSH_ID. 20 | */ 21 | public interface Http3MaxPushIdFrame extends Http3ControlStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the maximum value for a Push ID that the server can use. 30 | * 31 | * @return the id. 32 | */ 33 | long id(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3CancelPushFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * See CANCEL_PUSH. 20 | */ 21 | public interface Http3CancelPushFrame extends Http3ControlStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the push id that identifies the server push that is being cancelled. 30 | * 31 | * @return the id. 32 | */ 33 | long id(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/ConnectionChangeHandler.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http; 2 | 3 | import io.netty.channel.ChannelDuplexHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.incubator.codec.quic.QuicConnectionEvent; 6 | 7 | import java.net.SocketAddress; 8 | 9 | /** 10 | * @author: fucong 11 | * @Date: 2022/8/16 21:12 12 | * @Description: 13 | */ 14 | public class ConnectionChangeHandler extends ChannelDuplexHandler { 15 | 16 | private SocketAddress remoteAddress; 17 | 18 | 19 | @Override 20 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 21 | if (evt instanceof QuicConnectionEvent) { 22 | QuicConnectionEvent connection = (QuicConnectionEvent) evt; 23 | remoteAddress = connection.newAddress(); 24 | } 25 | super.userEventTriggered(ctx, evt); 26 | } 27 | 28 | // @Override 29 | // public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { 30 | // this.remoteAddress = remoteAddress; 31 | // super.connect(ctx, remoteAddress, localAddress, promise); 32 | // } 33 | 34 | public SocketAddress getRemoteAddress() { 35 | return remoteAddress; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3PushPromiseFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * See PUSH_PROMISE. 20 | */ 21 | public interface Http3PushPromiseFrame extends Http3RequestStreamFrame { 22 | 23 | @Override 24 | default long type() { 25 | return Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE; 26 | } 27 | 28 | /** 29 | * Returns the push id. 30 | * 31 | * @return the id. 32 | */ 33 | long id(); 34 | 35 | /** 36 | * Returns the carried headers. 37 | * 38 | * @return the headers. 39 | */ 40 | Http3Headers headers(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/QpackException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.util.internal.ThrowableUtil; 19 | 20 | /** 21 | * Exception thrown if an error happens during QPACK processing. 22 | */ 23 | public final class QpackException extends Exception { 24 | 25 | private QpackException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 26 | super(message, cause, enableSuppression, writableStackTrace); 27 | } 28 | 29 | static QpackException newStatic(Class clazz, String method, String message) { 30 | return ThrowableUtil.unknownStackTrace(new QpackException(message, null, false, false), clazz, method); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3PushStreamServerValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * See HTTP Message Exchanges. 20 | */ 21 | final class Http3PushStreamServerValidationHandler 22 | extends Http3FrameTypeOutboundValidationHandler { 23 | 24 | static final Http3PushStreamServerValidationHandler INSTANCE = new Http3PushStreamServerValidationHandler(); 25 | 26 | private Http3PushStreamServerValidationHandler() { 27 | super(Http3PushStreamFrame.class); 28 | } 29 | 30 | @Override 31 | public boolean isSharable() { 32 | return true; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3HeadersValidationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Thrown if {@link Http3Headers} validation fails for some reason. 20 | */ 21 | public final class Http3HeadersValidationException extends RuntimeException { 22 | 23 | /** 24 | * Create a new instance. 25 | * 26 | * @param message the message. 27 | */ 28 | public Http3HeadersValidationException(String message) { 29 | super(message); 30 | } 31 | 32 | /** 33 | * Create a new instance. 34 | * 35 | * @param message the message. 36 | * @param cause the wrapped {@link Throwable}. 37 | */ 38 | public Http3HeadersValidationException(String message, Throwable cause) { 39 | super(message, cause); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/Http3OutboundStreamTrafficHandler.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import io.netty.channel.socket.ChannelInputShutdownReadComplete; 6 | import lombok.extern.slf4j.Slf4j; 7 | import reactor.netty.channel.ChannelOperations; 8 | import xyz.nyist.core.Http3Exception; 9 | import xyz.nyist.http.client.Http3ClientOperations; 10 | 11 | import static reactor.netty.ReactorNetty.format; 12 | 13 | /** 14 | * @author: fucong 15 | * @Date: 2022/8/18 17:38 16 | * @Description: 17 | */ 18 | @Slf4j 19 | public class Http3OutboundStreamTrafficHandler extends ChannelInboundHandlerAdapter { 20 | 21 | @Override 22 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Http3Exception { 23 | log.debug("{}收到消息:{}", ctx.channel(), msg.getClass().getSimpleName()); 24 | ctx.fireChannelRead(msg); 25 | } 26 | 27 | @Override 28 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 29 | if (evt == ChannelInputShutdownReadComplete.INSTANCE) { 30 | Http3ClientOperations ops = (Http3ClientOperations) ChannelOperations.get(ctx.channel()); 31 | if (ops != null) { 32 | if (log.isDebugEnabled()) { 33 | log.debug(format(ctx.channel(), "Remote peer sent WRITE_FIN.")); 34 | } 35 | ctx.channel().config().setAutoRead(true); 36 | ops.onInboundComplete(); 37 | } 38 | } 39 | ctx.fireUserEventTriggered(evt); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicStreamInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import io.netty.incubator.codec.quic.QuicStreamChannel; 19 | import io.netty.incubator.codec.quic.QuicStreamType; 20 | 21 | /** 22 | * Exposes information for the {@link QuicStreamChannel} as stream id etc. 23 | * 24 | * @author Violeta Georgieva 25 | */ 26 | public interface QuicStreamInfo { 27 | 28 | /** 29 | * Returns {@code true} if the stream was created by this peer. 30 | * 31 | * @return {@code true} if created by this peer, {@code false} otherwise. 32 | */ 33 | boolean isLocalStream(); 34 | 35 | /** 36 | * The id of the stream. 37 | * 38 | * @return the stream id of this {@link QuicStreamChannel}. 39 | */ 40 | long streamId(); 41 | 42 | /** 43 | * Returns the {@link QuicStreamType} of the stream. 44 | * 45 | * @return {@link QuicStreamType} of this stream. 46 | */ 47 | QuicStreamType streamType(); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicOutboundStreamOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import reactor.netty.Connection; 19 | import reactor.netty.ConnectionObserver; 20 | import reactor.netty.channel.ChannelOperations; 21 | 22 | import static reactor.netty.ReactorNetty.format; 23 | 24 | /** 25 | * An outbound QUIC stream ready {@link ChannelOperations} with state management for FIN. 26 | * After sending FIN it's not possible anymore to write data. 27 | * 28 | * @author Violeta Georgieva 29 | */ 30 | public final class QuicOutboundStreamOperations extends QuicStreamOperations { 31 | 32 | public QuicOutboundStreamOperations(Connection connection, ConnectionObserver listener) { 33 | super(connection, listener); 34 | } 35 | 36 | @Override 37 | protected void onOutboundComplete() { 38 | if (log.isDebugEnabled()) { 39 | log.debug(format(channel(), "Outbound completed. Sending WRITE_FIN.")); 40 | } 41 | 42 | sendFinNow(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3DataFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.ByteBufHolder; 20 | 21 | /** 22 | * See DATA. 23 | */ 24 | public interface Http3DataFrame extends ByteBufHolder, Http3RequestStreamFrame, Http3PushStreamFrame { 25 | 26 | @Override 27 | default long type() { 28 | return Http3CodecUtils.HTTP3_DATA_FRAME_TYPE; 29 | } 30 | 31 | @Override 32 | Http3DataFrame copy(); 33 | 34 | @Override 35 | Http3DataFrame duplicate(); 36 | 37 | @Override 38 | Http3DataFrame retainedDuplicate(); 39 | 40 | @Override 41 | Http3DataFrame replace(ByteBuf content); 42 | 43 | @Override 44 | Http3DataFrame retain(); 45 | 46 | @Override 47 | Http3DataFrame retain(int increment); 48 | 49 | @Override 50 | Http3DataFrame touch(); 51 | 52 | @Override 53 | Http3DataFrame touch(Object hint); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/Http3StreamInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.http; 17 | 18 | import io.netty.incubator.codec.quic.QuicStreamChannel; 19 | import io.netty.incubator.codec.quic.QuicStreamType; 20 | import reactor.netty.http.HttpInfos; 21 | 22 | /** 23 | * Exposes information for the {@link QuicStreamChannel} as stream id etc. 24 | * 25 | * @author silence 26 | */ 27 | public interface Http3StreamInfo extends HttpInfos { 28 | 29 | /** 30 | * Returns {@code true} if the stream was created by this peer. 31 | * 32 | * @return {@code true} if created by this peer, {@code false} otherwise. 33 | */ 34 | boolean isLocalStream(); 35 | 36 | /** 37 | * The id of the stream. 38 | * 39 | * @return the stream id of this {@link QuicStreamChannel}. 40 | */ 41 | long streamId(); 42 | 43 | /** 44 | * Returns the {@link QuicStreamType} of the stream. 45 | * 46 | * @return {@link QuicStreamType} of this stream. 47 | */ 48 | QuicStreamType streamType(); 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicInboundStreamOperations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import reactor.netty.Connection; 19 | import reactor.netty.ConnectionObserver; 20 | import reactor.netty.channel.ChannelOperations; 21 | 22 | import static reactor.netty.ReactorNetty.format; 23 | 24 | /** 25 | * An inbound QUIC stream ready {@link ChannelOperations} with state management for FIN. 26 | * After sending FIN it's not possible anymore to write data, the stream will be terminated. 27 | * 28 | * @author Violeta Georgieva 29 | */ 30 | final class QuicInboundStreamOperations extends QuicStreamOperations { 31 | 32 | QuicInboundStreamOperations(Connection connection, ConnectionObserver listener) { 33 | super(connection, listener); 34 | } 35 | 36 | @Override 37 | protected void onOutboundComplete() { 38 | if (log.isDebugEnabled()) { 39 | log.debug(format(channel(), "Outbound completed. Sending WRITE_FIN.")); 40 | } 41 | 42 | sendFinNow(f -> terminate()); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3RequestStreamFrameTypeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Validate that the frame type is valid for a request stream. 20 | */ 21 | final class Http3RequestStreamFrameTypeValidator implements Http3FrameTypeValidator { 22 | 23 | static final Http3RequestStreamFrameTypeValidator INSTANCE = new Http3RequestStreamFrameTypeValidator(); 24 | 25 | private Http3RequestStreamFrameTypeValidator() {} 26 | 27 | @Override 28 | public void validate(long type, boolean first) throws Http3Exception { 29 | switch ((int) type) { 30 | case Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE: 31 | case Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE: 32 | case Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE: 33 | case Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE: 34 | throw new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED, 35 | "Unexpected frame type '" + type + "' received"); 36 | default: 37 | break; 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3PushStreamFrameTypeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Validate that the frame type is valid for a push stream. 20 | */ 21 | final class Http3PushStreamFrameTypeValidator implements Http3FrameTypeValidator { 22 | 23 | static final Http3PushStreamFrameTypeValidator INSTANCE = new Http3PushStreamFrameTypeValidator(); 24 | 25 | private Http3PushStreamFrameTypeValidator() {} 26 | 27 | @Override 28 | public void validate(long type, boolean first) throws Http3Exception { 29 | switch ((int) type) { 30 | case Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE: 31 | case Http3CodecUtils.HTTP3_CANCEL_PUSH_FRAME_TYPE: 32 | case Http3CodecUtils.HTTP3_GO_AWAY_FRAME_TYPE: 33 | case Http3CodecUtils.HTTP3_MAX_PUSH_ID_FRAME_TYPE: 34 | case Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE: 35 | throw new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED, 36 | "Unexpected frame type '" + type + "' received"); 37 | default: 38 | break; 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/DefaultHttp3GoAwayFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3GoAwayFrame implements Http3GoAwayFrame { 24 | 25 | private final long id; 26 | 27 | public DefaultHttp3GoAwayFrame(long id) { 28 | this.id = ObjectUtil.checkPositiveOrZero(id, "id"); 29 | } 30 | 31 | @Override 32 | public long id() { 33 | return id; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) { 39 | return true; 40 | } 41 | if (o == null || getClass() != o.getClass()) { 42 | return false; 43 | } 44 | DefaultHttp3GoAwayFrame that = (DefaultHttp3GoAwayFrame) o; 45 | return id == that.id; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(id); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return StringUtil.simpleClassName(this) + "(id=" + id() + ')'; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicOutboundStreamTrafficHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelInboundHandlerAdapter; 20 | import io.netty.channel.socket.ChannelInputShutdownReadComplete; 21 | import reactor.util.Logger; 22 | import reactor.util.Loggers; 23 | 24 | import static reactor.netty.ReactorNetty.format; 25 | 26 | /** 27 | * @author Violeta Georgieva 28 | */ 29 | public final class QuicOutboundStreamTrafficHandler extends ChannelInboundHandlerAdapter { 30 | 31 | static final Logger log = Loggers.getLogger(QuicOutboundStreamTrafficHandler.class); 32 | 33 | @Override 34 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 35 | if (evt == ChannelInputShutdownReadComplete.INSTANCE) { 36 | if (log.isDebugEnabled()) { 37 | log.debug(format(ctx.channel(), "Remote peer sent WRITE_FIN.")); 38 | } 39 | ctx.channel().config().setAutoRead(true); 40 | QuicStreamOperations.callTerminate(ctx.channel()); 41 | } 42 | ctx.fireUserEventTriggered(evt); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/DefaultHttp3MaxPushIdFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3MaxPushIdFrame implements Http3MaxPushIdFrame { 24 | 25 | private final long id; 26 | 27 | public DefaultHttp3MaxPushIdFrame(long id) { 28 | this.id = ObjectUtil.checkPositiveOrZero(id, "id"); 29 | } 30 | 31 | @Override 32 | public long id() { 33 | return id; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) { 39 | return true; 40 | } 41 | if (o == null || getClass() != o.getClass()) { 42 | return false; 43 | } 44 | DefaultHttp3MaxPushIdFrame that = (DefaultHttp3MaxPushIdFrame) o; 45 | return id == that.id; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(id); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return StringUtil.simpleClassName(this) + "(id=" + id() + ')'; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/DefaultHttp3CancelPushFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3CancelPushFrame implements Http3CancelPushFrame { 24 | 25 | private final long id; 26 | 27 | public DefaultHttp3CancelPushFrame(long id) { 28 | this.id = ObjectUtil.checkPositiveOrZero(id, "id"); 29 | } 30 | 31 | @Override 32 | public long id() { 33 | return id; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) { 39 | return true; 40 | } 41 | if (o == null || getClass() != o.getClass()) { 42 | return false; 43 | } 44 | DefaultHttp3CancelPushFrame that = (DefaultHttp3CancelPushFrame) o; 45 | return id == that.id; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | return Objects.hash(id); 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return StringUtil.simpleClassName(this) + "(id=" + id() + ')'; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/QpackHeaderField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import static io.netty.util.internal.ObjectUtil.checkNotNull; 19 | 20 | class QpackHeaderField { 21 | 22 | /** 23 | * Section 3.2.1 Dynamic Table Size 24 | * https://tools.ietf.org/html/draft-ietf-quic-qpack-19#section-3.2.1 25 | *

26 | * The size of an entry is the sum of its name's length in bytes, its 27 | * value's length in bytes, and 32. 28 | */ 29 | static final int ENTRY_OVERHEAD = 32; 30 | 31 | final CharSequence name; 32 | 33 | final CharSequence value; 34 | 35 | // This constructor can only be used if name and value are ISO-8859-1 encoded. 36 | QpackHeaderField(CharSequence name, CharSequence value) { 37 | this.name = checkNotNull(name, "name"); 38 | this.value = checkNotNull(value, "value"); 39 | } 40 | 41 | static long sizeOf(CharSequence name, CharSequence value) { 42 | return name.length() + value.length() + ENTRY_OVERHEAD; 43 | } 44 | 45 | long size() { 46 | return sizeOf(name, value); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return name + ": " + value; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3ControlStreamFrameTypeValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Validate that the frame type is valid for a control stream. 20 | */ 21 | final class Http3ControlStreamFrameTypeValidator implements Http3FrameTypeValidator { 22 | 23 | static final Http3ControlStreamFrameTypeValidator INSTANCE = new Http3ControlStreamFrameTypeValidator(); 24 | 25 | private Http3ControlStreamFrameTypeValidator() {} 26 | 27 | @Override 28 | public void validate(long type, boolean first) throws Http3Exception { 29 | switch ((int) type) { 30 | case Http3CodecUtils.HTTP3_PUSH_PROMISE_FRAME_TYPE: 31 | case Http3CodecUtils.HTTP3_HEADERS_FRAME_TYPE: 32 | case Http3CodecUtils.HTTP3_DATA_FRAME_TYPE: 33 | if (first) { 34 | throw new Http3Exception(Http3ErrorCode.H3_MISSING_SETTINGS, 35 | "Missing settings frame."); 36 | } 37 | throw new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED, 38 | "Unexpected frame type '" + type + "' received"); 39 | default: 40 | break; 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/CharSequenceMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.handler.codec.DefaultHeaders; 19 | import io.netty.handler.codec.UnsupportedValueConverter; 20 | import io.netty.handler.codec.ValueConverter; 21 | 22 | import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER; 23 | import static io.netty.util.AsciiString.CASE_SENSITIVE_HASHER; 24 | 25 | /** 26 | * Internal use only! 27 | */ 28 | public final class CharSequenceMap extends DefaultHeaders> { 29 | 30 | public CharSequenceMap() { 31 | this(true); 32 | } 33 | 34 | CharSequenceMap(boolean caseSensitive) { 35 | this(caseSensitive, UnsupportedValueConverter.instance()); 36 | } 37 | 38 | CharSequenceMap(boolean caseSensitive, ValueConverter valueConverter) { 39 | super(caseSensitive ? CASE_SENSITIVE_HASHER : CASE_INSENSITIVE_HASHER, valueConverter); 40 | } 41 | 42 | @SuppressWarnings("unchecked") 43 | CharSequenceMap(boolean caseSensitive, ValueConverter valueConverter, int arraySizeHint) { 44 | super(caseSensitive ? CASE_SENSITIVE_HASHER : CASE_INSENSITIVE_HASHER, valueConverter, 45 | NameValidator.NOT_NULL, arraySizeHint); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicInboundStreamTrafficHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelInboundHandlerAdapter; 20 | import io.netty.channel.socket.ChannelInputShutdownReadComplete; 21 | import reactor.netty.channel.ChannelOperations; 22 | import reactor.util.Logger; 23 | import reactor.util.Loggers; 24 | 25 | import static reactor.netty.ReactorNetty.format; 26 | 27 | /** 28 | * @author Violeta Georgieva 29 | */ 30 | final class QuicInboundStreamTrafficHandler extends ChannelInboundHandlerAdapter { 31 | 32 | static final Logger log = Loggers.getLogger(QuicInboundStreamTrafficHandler.class); 33 | 34 | @Override 35 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 36 | if (evt == ChannelInputShutdownReadComplete.INSTANCE) { 37 | QuicStreamOperations ops = (QuicStreamOperations) ChannelOperations.get(ctx.channel()); 38 | if (ops != null) { 39 | if (log.isDebugEnabled()) { 40 | log.debug(format(ops.channel(), "Remote peer sent WRITE_FIN.")); 41 | } 42 | ctx.channel().config().setAutoRead(true); 43 | ops.onInboundComplete(); 44 | } 45 | } 46 | ctx.fireUserEventTriggered(evt); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/client/Http3ClientInfos.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http.client; 2 | 3 | import reactor.netty.http.client.HttpClient; 4 | import reactor.util.annotation.Nullable; 5 | import reactor.util.context.Context; 6 | import reactor.util.context.ContextView; 7 | import xyz.nyist.core.Http3HeadersFrame; 8 | import xyz.nyist.http.Http3StreamInfo; 9 | 10 | /** 11 | * @author: fucong 12 | * @Date: 2022/8/18 14:55 13 | * @Description: 14 | */ 15 | public interface Http3ClientInfos extends Http3StreamInfo { 16 | 17 | /** 18 | * Return the current {@link Context} associated with the Mono/Flux exposed 19 | * via {@link HttpClient.ResponseReceiver#response()} or related terminating API. 20 | * 21 | * @return the current user {@link Context} 22 | * @deprecated Use {@link #currentContextView()}. This method 23 | * will be removed in version 1.1.0. 24 | */ 25 | @Deprecated 26 | Context currentContext(); 27 | 28 | /** 29 | * Return the current {@link ContextView} associated with the Mono/Flux exposed 30 | * via {@link HttpClient.ResponseReceiver#response()} or related terminating API. 31 | * 32 | * @return the current user {@link ContextView} 33 | * @since 1.0.0 34 | */ 35 | ContextView currentContextView(); 36 | 37 | /** 38 | * Return the previous redirections or empty array 39 | * 40 | * @return the previous redirections or empty array 41 | */ 42 | String[] redirectedFrom(); 43 | 44 | /** 45 | * Return outbound headers to be sent 46 | * 47 | * @return outbound headers to be sent 48 | */ 49 | Http3HeadersFrame requestHeaders(); 50 | 51 | /** 52 | * Return the fully qualified URL of the requested resource. In case of redirects, return the URL the last 53 | * redirect led to. 54 | * 55 | * @return The URL of the retrieved resource. This method can return null in case there was an error before the 56 | * client could create the URL 57 | */ 58 | @Nullable 59 | String resourceUrl(); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3FrameTypeInboundValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelInboundHandlerAdapter; 20 | import io.netty.util.internal.ObjectUtil; 21 | 22 | import static xyz.nyist.core.Http3FrameValidationUtils.frameTypeUnexpected; 23 | import static xyz.nyist.core.Http3FrameValidationUtils.validateFrameRead; 24 | 25 | class Http3FrameTypeInboundValidationHandler extends ChannelInboundHandlerAdapter { 26 | 27 | protected final Class frameType; 28 | 29 | Http3FrameTypeInboundValidationHandler(Class frameType) { 30 | this.frameType = ObjectUtil.checkNotNull(frameType, "frameType"); 31 | } 32 | 33 | @Override 34 | public final void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 35 | final T frame = validateFrameRead(frameType, msg); 36 | if (frame != null) { 37 | channelRead(ctx, frame); 38 | } else { 39 | readFrameDiscarded(ctx, msg); 40 | } 41 | } 42 | 43 | void channelRead(ChannelHandlerContext ctx, T frame) throws Exception { 44 | ctx.fireChannelRead(frame); 45 | } 46 | 47 | void readFrameDiscarded(ChannelHandlerContext ctx, Object discardedFrame) { 48 | frameTypeUnexpected(ctx, discardedFrame); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3UnknownFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.ByteBufHolder; 20 | 21 | /** 22 | * Unknown HTTP3 frame. 23 | * These frames are valid on all stream types. 24 | *

25 |  *    HTTP/3 Frame Format {
26 |  *      Type (i),
27 |  *      Length (i),
28 |  *      Frame Payload (..),
29 |  *    }
30 |  * 
31 | */ 32 | public interface Http3UnknownFrame extends 33 | Http3RequestStreamFrame, Http3PushStreamFrame, Http3ControlStreamFrame, ByteBufHolder { 34 | 35 | /** 36 | * Return the payload length of the frame. 37 | * 38 | * @return the length. 39 | */ 40 | default long length() { 41 | return content().readableBytes(); 42 | } 43 | 44 | @Override 45 | Http3UnknownFrame copy(); 46 | 47 | @Override 48 | Http3UnknownFrame duplicate(); 49 | 50 | @Override 51 | Http3UnknownFrame retainedDuplicate(); 52 | 53 | @Override 54 | Http3UnknownFrame replace(ByteBuf content); 55 | 56 | @Override 57 | Http3UnknownFrame retain(); 58 | 59 | @Override 60 | Http3UnknownFrame retain(int increment); 61 | 62 | @Override 63 | Http3UnknownFrame touch(); 64 | 65 | @Override 66 | Http3UnknownFrame touch(Object hint); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3Exception.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | 20 | /** 21 | * An exception related to violate the HTTP3 spec. 22 | */ 23 | public final class Http3Exception extends Exception { 24 | 25 | private final Http3ErrorCode errorCode; 26 | 27 | /** 28 | * Create a new instance. 29 | * 30 | * @param errorCode the {@link Http3ErrorCode} that caused this exception. 31 | * @param message the message to include. 32 | */ 33 | public Http3Exception(Http3ErrorCode errorCode, String message) { 34 | super(message); 35 | this.errorCode = ObjectUtil.checkNotNull(errorCode, "errorCode"); 36 | } 37 | 38 | /** 39 | * Create a new instance. 40 | * 41 | * @param errorCode the {@link Http3ErrorCode} that caused this exception. 42 | * @param message the message to include. 43 | * @param cause the {@link Throwable} to wrap. 44 | */ 45 | public Http3Exception(Http3ErrorCode errorCode, String message, Throwable cause) { 46 | super(message, cause); 47 | this.errorCode = ObjectUtil.checkNotNull(errorCode, "errorCode"); 48 | } 49 | 50 | /** 51 | * Returns the related {@link Http3ErrorCode}. 52 | * 53 | * @return the {@link Http3ErrorCode}. 54 | */ 55 | public Http3ErrorCode errorCode() { 56 | return errorCode; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/test/ChannelUtil.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.test; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.DefaultChannelPipeline; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | /** 10 | * @author: fucong 11 | * @Date: 2022/7/22 14:24 12 | * @Description: 13 | */ 14 | public class ChannelUtil { 15 | 16 | private static final Class CHANNEL_PIPELINE_CLASS = DefaultChannelPipeline.class; 17 | 18 | private static final Class HANDLER_CONTEXT_CLASS; 19 | 20 | static { 21 | try { 22 | HANDLER_CONTEXT_CLASS = Class.forName("io.netty.channel.AbstractChannelHandlerContext"); 23 | } catch (ClassNotFoundException e) { 24 | throw new RuntimeException(e); 25 | } 26 | } 27 | 28 | 29 | public static String printChannel(Channel channel) { 30 | try { 31 | System.out.println("=========" + channel + "=========="); 32 | DefaultChannelPipeline pipeline = (DefaultChannelPipeline) channel.pipeline(); 33 | Field head = CHANNEL_PIPELINE_CLASS.getDeclaredField("head"); 34 | head.setAccessible(true); 35 | ChannelHandlerContext context = (ChannelHandlerContext) head.get(pipeline); 36 | print(context, 1); 37 | System.out.println("==================="); 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | return "ok"; 42 | } 43 | 44 | 45 | private static void print(ChannelHandlerContext context, int i) throws Exception { 46 | if (context == null) { 47 | return; 48 | } 49 | System.out.println(i + ". " + context.name() + " " + context.handler().getClass().getSimpleName() + " " + context.handler().hashCode()); 50 | if (HANDLER_CONTEXT_CLASS.isAssignableFrom(context.getClass())) { 51 | Field next = HANDLER_CONTEXT_CLASS.getDeclaredField("next"); 52 | next.setAccessible(true); 53 | print((ChannelHandlerContext) next.get(context), i + 1); 54 | } 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3FrameTypeOutboundValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelOutboundHandlerAdapter; 20 | import io.netty.channel.ChannelPromise; 21 | import io.netty.util.internal.ObjectUtil; 22 | 23 | import static xyz.nyist.core.Http3FrameValidationUtils.frameTypeUnexpected; 24 | import static xyz.nyist.core.Http3FrameValidationUtils.validateFrameWritten; 25 | 26 | class Http3FrameTypeOutboundValidationHandler extends ChannelOutboundHandlerAdapter { 27 | 28 | private final Class frameType; 29 | 30 | Http3FrameTypeOutboundValidationHandler(Class frameType) { 31 | this.frameType = ObjectUtil.checkNotNull(frameType, "frameType"); 32 | } 33 | 34 | @Override 35 | public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { 36 | T frame = validateFrameWritten(frameType, msg); 37 | if (frame != null) { 38 | write(ctx, frame, promise); 39 | } else { 40 | writeFrameDiscarded(msg, promise); 41 | } 42 | } 43 | 44 | void write(ChannelHandlerContext ctx, T msg, ChannelPromise promise) { 45 | ctx.write(msg, promise); 46 | } 47 | 48 | void writeFrameDiscarded(Object discardedFrame, ChannelPromise promise) { 49 | frameTypeUnexpected(promise, discardedFrame); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3RequestStreamDecodeStateValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelInboundHandlerAdapter; 20 | 21 | import static xyz.nyist.core.Http3FrameValidationUtils.frameTypeUnexpected; 22 | import static xyz.nyist.core.Http3RequestStreamEncodeStateValidator.*; 23 | 24 | final class Http3RequestStreamDecodeStateValidator extends ChannelInboundHandlerAdapter 25 | implements Http3RequestStreamCodecState { 26 | 27 | private State state = State.None; 28 | 29 | @Override 30 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 31 | if (!(msg instanceof Http3RequestStreamFrame)) { 32 | super.channelRead(ctx, msg); 33 | return; 34 | } 35 | final Http3RequestStreamFrame frame = (Http3RequestStreamFrame) msg; 36 | final State nextState = evaluateFrame(state, frame); 37 | if (nextState == null) { 38 | frameTypeUnexpected(ctx, msg); 39 | return; 40 | } 41 | state = nextState; 42 | super.channelRead(ctx, msg); 43 | } 44 | 45 | @Override 46 | public boolean started() { 47 | return isStreamStarted(state); 48 | } 49 | 50 | @Override 51 | public boolean receivedFinalHeaders() { 52 | return isFinalHeadersReceived(state); 53 | } 54 | 55 | @Override 56 | public boolean terminated() { 57 | return isTrailersReceived(state); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/server/Http3ServerRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.http.server; 17 | 18 | import io.netty.channel.Channel; 19 | import io.netty.handler.codec.http.HttpHeaders; 20 | import reactor.netty.Connection; 21 | import reactor.netty.NettyInbound; 22 | import reactor.util.annotation.Nullable; 23 | import xyz.nyist.core.Http3HeadersFrame; 24 | 25 | import java.net.InetSocketAddress; 26 | import java.util.function.Consumer; 27 | 28 | /** 29 | * An inbound-traffic API delegating to an underlying {@link Channel}. 30 | * 31 | * @author silence 32 | */ 33 | public interface Http3ServerRequest extends NettyInbound, Http3ServerInfos { 34 | 35 | @Override 36 | Http3ServerRequest withConnection(Consumer withConnection); 37 | 38 | 39 | /** 40 | * Returns the address of the host peer or {@code null} in case of Unix Domain Sockets. 41 | * 42 | * @return the host's address 43 | */ 44 | @Nullable 45 | InetSocketAddress hostAddress(); 46 | 47 | /** 48 | * Returns the address of the remote peer or {@code null} in case of Unix Domain Sockets. 49 | * 50 | * @return the peer's address 51 | */ 52 | @Nullable 53 | InetSocketAddress remoteAddress(); 54 | 55 | /** 56 | * Returns inbound {@link HttpHeaders} 57 | * 58 | * @return inbound {@link HttpHeaders} 59 | */ 60 | Http3HeadersFrame requestHeaders(); 61 | 62 | /** 63 | * Returns the current protocol scheme 64 | * 65 | * @return the protocol scheme 66 | */ 67 | String scheme(); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3UnidirectionalStreamInboundServerHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package xyz.nyist.core; 18 | 19 | import io.netty.channel.ChannelHandler; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import xyz.nyist.core.Http3FrameCodec.Http3FrameCodecFactory; 22 | 23 | import java.util.function.LongFunction; 24 | import java.util.function.Supplier; 25 | 26 | final class Http3UnidirectionalStreamInboundServerHandler extends Http3UnidirectionalStreamInboundHandler { 27 | 28 | Http3UnidirectionalStreamInboundServerHandler(Http3FrameCodecFactory codecFactory, 29 | Http3ControlStreamInboundHandler localControlStreamHandler, 30 | Http3ControlStreamOutboundHandler remoteControlStreamHandler, 31 | LongFunction unknownStreamHandlerFactory, 32 | Supplier qpackEncoderHandlerFactory, 33 | Supplier qpackDecoderHandlerFactory) { 34 | super(codecFactory, localControlStreamHandler, remoteControlStreamHandler, unknownStreamHandlerFactory, 35 | qpackEncoderHandlerFactory, qpackDecoderHandlerFactory); 36 | } 37 | 38 | @Override 39 | void initPushStream(ChannelHandlerContext ctx, long id) { 40 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_STREAM_CREATION_ERROR, 41 | "Server received push stream.", false); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/DefaultHttp3PushPromiseFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.util.internal.ObjectUtil; 19 | import io.netty.util.internal.StringUtil; 20 | 21 | import java.util.Objects; 22 | 23 | public final class DefaultHttp3PushPromiseFrame implements Http3PushPromiseFrame { 24 | 25 | private final long id; 26 | 27 | private final Http3Headers headers; 28 | 29 | public DefaultHttp3PushPromiseFrame(long id) { 30 | this(id, new DefaultHttp3Headers()); 31 | } 32 | 33 | public DefaultHttp3PushPromiseFrame(long id, Http3Headers headers) { 34 | this.id = ObjectUtil.checkPositiveOrZero(id, "id"); 35 | this.headers = ObjectUtil.checkNotNull(headers, "headers"); 36 | } 37 | 38 | @Override 39 | public long id() { 40 | return id; 41 | } 42 | 43 | @Override 44 | public Http3Headers headers() { 45 | return headers; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (this == o) { 51 | return true; 52 | } 53 | if (o == null || getClass() != o.getClass()) { 54 | return false; 55 | } 56 | DefaultHttp3PushPromiseFrame that = (DefaultHttp3PushPromiseFrame) o; 57 | return id == that.id && 58 | Objects.equals(headers, that.headers); 59 | } 60 | 61 | @Override 62 | public int hashCode() { 63 | return Objects.hash(id, headers); 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return StringUtil.simpleClassName(this) + "(id=" + id() + ", headers=" + headers() + ')'; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/DefaultHttp3DataFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.DefaultByteBufHolder; 20 | import io.netty.util.internal.StringUtil; 21 | 22 | public final class DefaultHttp3DataFrame extends DefaultByteBufHolder implements Http3DataFrame { 23 | 24 | public DefaultHttp3DataFrame(ByteBuf data) { 25 | super(data); 26 | } 27 | 28 | @Override 29 | public Http3DataFrame copy() { 30 | return new DefaultHttp3DataFrame(content().copy()); 31 | } 32 | 33 | @Override 34 | public Http3DataFrame duplicate() { 35 | return new DefaultHttp3DataFrame(content().duplicate()); 36 | } 37 | 38 | @Override 39 | public Http3DataFrame retainedDuplicate() { 40 | return new DefaultHttp3DataFrame(content().retainedDuplicate()); 41 | } 42 | 43 | @Override 44 | public Http3DataFrame replace(ByteBuf content) { 45 | return new DefaultHttp3DataFrame(content); 46 | } 47 | 48 | @Override 49 | public Http3DataFrame retain() { 50 | super.retain(); 51 | return this; 52 | } 53 | 54 | @Override 55 | public Http3DataFrame retain(int increment) { 56 | super.retain(increment); 57 | return this; 58 | } 59 | 60 | @Override 61 | public Http3DataFrame touch() { 62 | super.touch(); 63 | return this; 64 | } 65 | 66 | @Override 67 | public Http3DataFrame touch(Object hint) { 68 | super.touch(hint); 69 | return this; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return StringUtil.simpleClassName(this) + "(content=" + content() + ')'; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/DefaultHttp3LastDataFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.DefaultByteBufHolder; 20 | import io.netty.util.internal.StringUtil; 21 | 22 | public final class DefaultHttp3LastDataFrame extends DefaultByteBufHolder implements Http3DataFrame, Http3LastFrame { 23 | 24 | public DefaultHttp3LastDataFrame(ByteBuf data) { 25 | super(data); 26 | } 27 | 28 | @Override 29 | public Http3DataFrame copy() { 30 | return new DefaultHttp3LastDataFrame(content().copy()); 31 | } 32 | 33 | @Override 34 | public Http3DataFrame duplicate() { 35 | return new DefaultHttp3LastDataFrame(content().duplicate()); 36 | } 37 | 38 | @Override 39 | public Http3DataFrame retainedDuplicate() { 40 | return new DefaultHttp3LastDataFrame(content().retainedDuplicate()); 41 | } 42 | 43 | @Override 44 | public Http3DataFrame replace(ByteBuf content) { 45 | return new DefaultHttp3LastDataFrame(content); 46 | } 47 | 48 | @Override 49 | public Http3DataFrame retain() { 50 | super.retain(); 51 | return this; 52 | } 53 | 54 | @Override 55 | public Http3DataFrame retain(int increment) { 56 | super.retain(increment); 57 | return this; 58 | } 59 | 60 | @Override 61 | public Http3DataFrame touch() { 62 | super.touch(); 63 | return this; 64 | } 65 | 66 | @Override 67 | public Http3DataFrame touch(Object hint) { 68 | super.touch(hint); 69 | return this; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return StringUtil.simpleClassName(this) + "(content=" + content() + ')'; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3RequestStreamCodecState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * State of encoding or decoding for a stream following the 21 | * HTTP message exchange semantics 22 | */ 23 | interface Http3RequestStreamCodecState { 24 | 25 | /** 26 | * An implementation of {@link Http3RequestStreamCodecState} that managed no state. 27 | */ 28 | Http3RequestStreamCodecState NO_STATE = new Http3RequestStreamCodecState() { 29 | @Override 30 | public boolean started() { 31 | return false; 32 | } 33 | 34 | @Override 35 | public boolean receivedFinalHeaders() { 36 | return false; 37 | } 38 | 39 | @Override 40 | public boolean terminated() { 41 | return false; 42 | } 43 | }; 44 | 45 | /** 46 | * If any {@link Http3HeadersFrame} or {@link Http3DataFrame} has been received/sent on this stream. 47 | * 48 | * @return {@code true} if any {@link Http3HeadersFrame} or {@link Http3DataFrame} has been received/sent on this 49 | * stream. 50 | */ 51 | boolean started(); 52 | 53 | /** 54 | * If a final {@link Http3HeadersFrame} has been received/sent before {@link Http3DataFrame} starts. 55 | * 56 | * @return {@code true} if a final {@link Http3HeadersFrame} has been received/sent before {@link Http3DataFrame} 57 | * starts 58 | */ 59 | boolean receivedFinalHeaders(); 60 | 61 | /** 62 | * If no more frames are expected on this stream. 63 | * 64 | * @return {@code true} if no more frames are expected on this stream. 65 | */ 66 | boolean terminated(); 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/Http3ClientTest.java: -------------------------------------------------------------------------------- 1 | import io.netty.buffer.Unpooled; 2 | import io.netty.handler.codec.http.HttpMethod; 3 | import io.netty.handler.ssl.util.InsecureTrustManagerFactory; 4 | import io.netty.incubator.codec.quic.QuicSslContext; 5 | import io.netty.incubator.codec.quic.QuicSslContextBuilder; 6 | import io.netty.util.CharsetUtil; 7 | import reactor.core.publisher.Mono; 8 | import xyz.nyist.core.Http3; 9 | import xyz.nyist.http.client.Http3Client; 10 | import xyz.nyist.http.client.Http3ClientOperations; 11 | 12 | import java.net.InetSocketAddress; 13 | 14 | /** 15 | * @author: fucong 16 | * @Date: 2022/8/18 13:45 17 | * @Description: 18 | */ 19 | public class Http3ClientTest { 20 | 21 | public static void main(String[] args) { 22 | 23 | QuicSslContext context = QuicSslContextBuilder.forClient() 24 | .keylog(true) 25 | .trustManager(InsecureTrustManagerFactory.INSTANCE) 26 | .applicationProtocols(Http3.supportedApplicationProtocols()).build(); 27 | //curl 'https://quic.cloud/wp-includes/js/wp-emoji-release.min.js?ver=6.0.1' -I --http3 28 | //https://www.cloudflare.com/favicon.ico 29 | //https://quic.tech:8443/quic.ico 30 | Http3Client.create() 31 | .remoteAddress(() -> new InetSocketAddress("quic.nginx.org", 443)) 32 | .secure(context) 33 | .sendHandler(http3ClientRequest -> { 34 | //Host: www.cloudflare.com 35 | //> user-agent: curl/7.85.0-DEV 36 | //> accept: */* 37 | return http3ClientRequest.addHeader("host", "quic.nginx.org") 38 | .addHeader("user-agent", "curl/7.85.0-DEV") 39 | .addHeader("accept", "*/*") 40 | .method(HttpMethod.GET) 41 | .uri("/test").send(Mono.just(Unpooled.wrappedBuffer("api.toString()".getBytes(CharsetUtil.UTF_8)))); 42 | //.uri("/test").send(Mono.empty()); 43 | }) 44 | .response(http3ClientResponse -> { 45 | Http3ClientOperations operations = (Http3ClientOperations) http3ClientResponse; 46 | return operations.receive() 47 | .asString() 48 | .defaultIfEmpty("Hello Empty!") 49 | .doOnNext(System.out::println) 50 | .then(); 51 | }) 52 | .executeNow(); 53 | 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/server/Http3ServerBind.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2022 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.http.server; 17 | 18 | import io.netty.channel.ChannelOption; 19 | import io.netty.util.NetUtil; 20 | import reactor.core.publisher.Mono; 21 | import reactor.netty.DisposableServer; 22 | import xyz.nyist.quic.QuicServer; 23 | 24 | import java.net.InetSocketAddress; 25 | import java.util.Collections; 26 | import java.util.Objects; 27 | 28 | /** 29 | * Provides the actual {@link QuicServer} instance. 30 | * 31 | * @author Violeta Georgieva 32 | */ 33 | final class Http3ServerBind extends Http3Server { 34 | 35 | static final Http3ServerBind INSTANCE = new Http3ServerBind(); 36 | 37 | final Http3ServerConfig config; 38 | 39 | Http3ServerBind() { 40 | this.config = new Http3ServerConfig( 41 | Collections.emptyMap(), 42 | //Collections.emptyMap(), 43 | Collections.singletonMap(ChannelOption.AUTO_READ, false), 44 | () -> new InetSocketAddress(NetUtil.LOCALHOST, 0)); 45 | } 46 | 47 | Http3ServerBind(Http3ServerConfig config) { 48 | this.config = config; 49 | } 50 | 51 | void validate(Http3ServerConfig config) { 52 | Objects.requireNonNull(config.bindAddress(), "bindAddress"); 53 | Objects.requireNonNull(config.sslEngineProvider(), "sslEngineProvider"); 54 | Objects.requireNonNull(config.tokenHandler, "tokenHandler"); 55 | } 56 | 57 | @Override 58 | public Mono bind() { 59 | Http3ServerConfig config = configuration(); 60 | validate(config); 61 | return super.bind(); 62 | } 63 | 64 | @Override 65 | public Http3ServerConfig configuration() { 66 | return config; 67 | } 68 | 69 | @Override 70 | protected Http3Server duplicate() { 71 | return new Http3ServerBind(new Http3ServerConfig(config)); 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import io.netty.incubator.codec.quic.QuicStreamType; 19 | import org.reactivestreams.Publisher; 20 | import reactor.core.publisher.Mono; 21 | import reactor.netty.Connection; 22 | import xyz.nyist.http.client.Http3ClientRequest; 23 | import xyz.nyist.http.client.Http3ClientResponse; 24 | 25 | import java.util.function.BiFunction; 26 | 27 | /** 28 | * API for creating and handling streams. 29 | * 30 | * @author Violeta Georgieva 31 | */ 32 | public interface QuicConnection extends Connection { 33 | 34 | /** 35 | * Creates a bidirectional stream. A {@link Mono} completing when the stream is created, 36 | * then the provided callback is invoked. If the stream creation is not 37 | * successful the returned {@link Mono} fails. 38 | * 39 | * @param streamHandler the I/O handler for the stream 40 | * @return a {@link Mono} completing when the stream is created, otherwise fails 41 | */ 42 | default Mono createStream( 43 | BiFunction> streamHandler) { 44 | return createStream(QuicStreamType.BIDIRECTIONAL, streamHandler); 45 | } 46 | 47 | /** 48 | * Creates a stream. A {@link Mono} completing when the stream is created, 49 | * then the provided callback is invoked. If the stream creation is not 50 | * successful the returned {@link Mono} fails. 51 | * 52 | * @param streamType the {@link QuicStreamType} 53 | * @param streamHandler the I/O handler for the stream 54 | * @return a {@link Mono} completing when the stream is created, otherwise fails 55 | */ 56 | Mono createStream( 57 | QuicStreamType streamType, 58 | BiFunction> streamHandler); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/adapter/ReactorHttp3HandlerAdapter.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.adapter; 2 | 3 | import io.netty.handler.codec.http.HttpResponseStatus; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.core.io.buffer.NettyDataBufferFactory; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.http.server.reactive.HttpHandler; 8 | import org.springframework.http.server.reactive.HttpHeadResponseDecorator; 9 | import org.springframework.http.server.reactive.ServerHttpResponse; 10 | import org.springframework.util.Assert; 11 | import reactor.core.publisher.Mono; 12 | import xyz.nyist.core.Http3Exception; 13 | import xyz.nyist.http.server.Http3ServerRequest; 14 | import xyz.nyist.http.server.Http3ServerResponse; 15 | 16 | import java.net.URISyntaxException; 17 | import java.util.function.BiFunction; 18 | 19 | /** 20 | * @author: fucong 21 | * @Date: 2022/8/2 18:38 22 | * @Description: 23 | */ 24 | @Slf4j 25 | public class ReactorHttp3HandlerAdapter implements BiFunction> { 26 | 27 | 28 | private final HttpHandler httpHandler; 29 | 30 | 31 | public ReactorHttp3HandlerAdapter(HttpHandler httpHandler) { 32 | Assert.notNull(httpHandler, "HttpHandler must not be null"); 33 | this.httpHandler = httpHandler; 34 | } 35 | 36 | 37 | @Override 38 | public Mono apply(Http3ServerRequest reactorRequest, Http3ServerResponse reactorResponse) { 39 | NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(reactorResponse.alloc()); 40 | try { 41 | ReactorServerHttp3Request request = new ReactorServerHttp3Request(reactorRequest, bufferFactory); 42 | ServerHttpResponse response = new ReactorServerHttp3Response(reactorResponse, bufferFactory); 43 | 44 | if (request.getMethod() == HttpMethod.HEAD) { 45 | response = new HttpHeadResponseDecorator(response); 46 | } 47 | 48 | return this.httpHandler.handle(request, response) 49 | .doOnError(ex -> log.error(request.getLogPrefix() + "Failed to complete: " + ex.getMessage())) 50 | .doOnSuccess(aVoid -> log.debug(request.getLogPrefix() + "Handling completed")); 51 | } catch (URISyntaxException ex) { 52 | ex.printStackTrace(); 53 | if (log.isDebugEnabled()) { 54 | log.warn("Failed to get request URI: " + ex.getMessage()); 55 | } 56 | reactorResponse.status(HttpResponseStatus.BAD_REQUEST); 57 | return Mono.empty(); 58 | } catch (Http3Exception e) { 59 | throw new RuntimeException(e); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3SettingsFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import java.util.Map; 19 | 20 | /** 21 | * See SETTINGS. 22 | */ 23 | public interface Http3SettingsFrame extends Http3ControlStreamFrame, Iterable> { 24 | 25 | /** 26 | * See 27 | * SETTINGS_QPACK_MAX_TABLE_CAPACITY. 28 | */ 29 | long HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY = 0x1; 30 | /** 31 | * See 32 | * SETTINGS_QPACK_BLOCKED_STREAMS. 33 | */ 34 | long HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS = 0x7; 35 | /** 36 | * See 37 | * SETTINGS_MAX_FIELD_SECTION_SIZE. 38 | */ 39 | long HTTP3_SETTINGS_MAX_FIELD_SECTION_SIZE = 0x6; 40 | 41 | @Override 42 | default long type() { 43 | return Http3CodecUtils.HTTP3_SETTINGS_FRAME_TYPE; 44 | } 45 | 46 | /** 47 | * Get a setting from the frame. 48 | * 49 | * @param key the key of the setting. 50 | * @return the value of the setting or {@code null} if none was found with the given key. 51 | */ 52 | Long get(long key); 53 | 54 | /** 55 | * Get a setting from the frame. 56 | * 57 | * @param key the key of the setting. 58 | * @param defaultValue If the setting does not exist. 59 | * @return the value of the setting or {@code defaultValue} if none was found with the given key. 60 | */ 61 | default Long getOrDefault(long key, long defaultValue) { 62 | final Long val = get(key); 63 | return val == null ? defaultValue : val; 64 | } 65 | 66 | /** 67 | * Put a setting in the frame. 68 | * 69 | * @param key the key of the setting 70 | * @param value the value of the setting. 71 | * @return the previous stored valued for the given key or {@code null} if none was stored before. 72 | */ 73 | Long put(long key, Long value); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/adapter/DefaultSslInfo.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.adapter; 2 | 3 | import org.springframework.http.server.reactive.SslInfo; 4 | import org.springframework.lang.Nullable; 5 | import org.springframework.util.Assert; 6 | 7 | import javax.net.ssl.SSLSession; 8 | import java.security.cert.Certificate; 9 | import java.security.cert.X509Certificate; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * @author: fucong 15 | * @Date: 2022/8/17 11:05 16 | * @Description: 17 | */ 18 | public class DefaultSslInfo implements SslInfo { 19 | 20 | @Nullable 21 | private final String sessionId; 22 | 23 | @Nullable 24 | private final X509Certificate[] peerCertificates; 25 | 26 | 27 | DefaultSslInfo(@Nullable String sessionId, X509Certificate[] peerCertificates) { 28 | Assert.notNull(peerCertificates, "No SSL certificates"); 29 | this.sessionId = sessionId; 30 | this.peerCertificates = peerCertificates; 31 | } 32 | 33 | DefaultSslInfo(SSLSession session) { 34 | Assert.notNull(session, "SSLSession is required"); 35 | this.sessionId = initSessionId(session); 36 | this.peerCertificates = initCertificates(session); 37 | } 38 | 39 | @Nullable 40 | private static String initSessionId(SSLSession session) { 41 | byte[] bytes = session.getId(); 42 | if (bytes == null) { 43 | return null; 44 | } 45 | 46 | StringBuilder sb = new StringBuilder(); 47 | for (byte b : bytes) { 48 | String digit = Integer.toHexString(b); 49 | if (digit.length() < 2) { 50 | sb.append('0'); 51 | } 52 | if (digit.length() > 2) { 53 | digit = digit.substring(digit.length() - 2); 54 | } 55 | sb.append(digit); 56 | } 57 | return sb.toString(); 58 | } 59 | 60 | @Nullable 61 | private static X509Certificate[] initCertificates(SSLSession session) { 62 | Certificate[] certificates; 63 | try { 64 | certificates = session.getPeerCertificates(); 65 | } catch (Throwable ex) { 66 | return null; 67 | } 68 | 69 | List result = new ArrayList<>(certificates.length); 70 | for (Certificate certificate : certificates) { 71 | if (certificate instanceof X509Certificate) { 72 | result.add((X509Certificate) certificate); 73 | } 74 | } 75 | return (!result.isEmpty() ? result.toArray(new X509Certificate[0]) : null); 76 | } 77 | 78 | @Override 79 | @Nullable 80 | public String getSessionId() { 81 | return this.sessionId; 82 | } 83 | 84 | @Override 85 | @Nullable 86 | public X509Certificate[] getPeerCertificates() { 87 | return this.peerCertificates; 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/CombinationChannelFuture.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelFutureListener; 6 | import io.netty.channel.DefaultChannelPromise; 7 | 8 | /** 9 | * @author: fucong 10 | * @Date: 2022/8/12 14:42 11 | * @Description: 12 | */ 13 | public class CombinationChannelFuture extends DefaultChannelPromise { 14 | 15 | 16 | private CombinationChannelFuture(Channel channel) { 17 | super(channel); 18 | } 19 | 20 | public static CombinationChannelFuture create(ChannelFuture future1, ChannelFuture future2) { 21 | if (future1.channel() == future2.channel()) { 22 | return new SingleThreadCombinationChannelFuture(future1, future2); 23 | } 24 | //todo 两个future不是一个channel 需要考虑线程安全问题 25 | return null; 26 | } 27 | 28 | 29 | private static class SingleThreadCombinationChannelFuture extends CombinationChannelFuture { 30 | 31 | private final ChannelFuture future1; 32 | 33 | private final ChannelFuture future2; 34 | 35 | private SingleThreadCombinationChannelFuture(ChannelFuture future1, ChannelFuture future2) { 36 | super(future1.channel()); 37 | this.future1 = future1; 38 | this.future2 = future2; 39 | 40 | 41 | future1.addListener((ChannelFutureListener) this::operationComplete); 42 | future2.addListener((ChannelFutureListener) this::operationComplete); 43 | } 44 | 45 | 46 | public void operationComplete(ChannelFuture future) { 47 | if (this.isDone()) { 48 | return; 49 | } 50 | if (future1.isSuccess() && future2.isSuccess()) { 51 | this.setSuccess(); 52 | } 53 | 54 | if (future1.isDone()) { 55 | if (future1.isCancelled()) { 56 | if (!future2.isDone() && future2.isCancellable()) { 57 | future2.cancel(true); 58 | } 59 | this.cancel(true); 60 | } else if (!future1.isSuccess()) { 61 | if (!future2.isDone() && future2.isCancellable()) { 62 | future2.cancel(true); 63 | } 64 | this.setFailure(future1.cause()); 65 | } 66 | } else if (future2.isDone()) { 67 | if (future2.isCancelled()) { 68 | if (!future1.isDone() && future1.isCancellable()) { 69 | future1.cancel(true); 70 | } 71 | this.cancel(true); 72 | } else if (!future2.isSuccess()) { 73 | if (!future1.isDone() && future1.isCancellable()) { 74 | future1.cancel(true); 75 | } 76 | this.setFailure(future2.cause()); 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3PushStreamClientInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelInitializer; 19 | import io.netty.channel.ChannelPipeline; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | 22 | import static xyz.nyist.core.Http3CodecUtils.isServerInitiatedQuicStream; 23 | import static xyz.nyist.core.Http3RequestStreamCodecState.NO_STATE; 24 | 25 | /** 26 | * Abstract base class that users can extend to init HTTP/3 push-streams for clients. This initializer 27 | * will automatically add HTTP/3 codecs etc to the {@link ChannelPipeline} as well. 28 | */ 29 | public abstract class Http3PushStreamClientInitializer extends ChannelInitializer { 30 | 31 | @Override 32 | protected final void initChannel(QuicStreamChannel ch) { 33 | if (isServerInitiatedQuicStream(ch)) { 34 | throw new IllegalArgumentException("Using client push stream initializer for server stream: " + 35 | ch.streamId()); 36 | } 37 | Http3CodecUtils.verifyIsUnidirectional(ch); 38 | 39 | Http3ConnectionHandler connectionHandler = Http3CodecUtils.getConnectionHandlerOrClose(ch.parent()); 40 | if (connectionHandler == null) { 41 | // connection should have been closed 42 | return; 43 | } 44 | ChannelPipeline pipeline = ch.pipeline(); 45 | Http3RequestStreamDecodeStateValidator decodeStateValidator = new Http3RequestStreamDecodeStateValidator(); 46 | // Add the encoder and decoder in the pipeline, so we can handle Http3Frames 47 | pipeline.addLast(connectionHandler.newCodec(NO_STATE, decodeStateValidator)); 48 | pipeline.addLast(decodeStateValidator); 49 | // Add the handler that will validate what we write and receive on this stream. 50 | pipeline.addLast(connectionHandler.newPushStreamValidationHandler(ch, decodeStateValidator)); 51 | initPushStream(ch); 52 | } 53 | 54 | /** 55 | * Initialize the {@link QuicStreamChannel} to handle {@link Http3PushStreamFrame}s. At the point of calling this 56 | * method it is already valid to write {@link Http3PushStreamFrame}s as the codec is already in the pipeline. 57 | * 58 | * @param ch the {QuicStreamChannel} for the push stream. 59 | */ 60 | protected abstract void initPushStream(QuicStreamChannel ch); 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3RequestStreamInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelInitializer; 19 | import io.netty.channel.ChannelPipeline; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | import io.netty.util.internal.StringUtil; 22 | 23 | /** 24 | * Abstract base class that users can extend to init HTTP/3 request-streams. This initializer 25 | * will automatically add HTTP/3 codecs etc to the {@link ChannelPipeline} as well. 26 | */ 27 | public abstract class Http3RequestStreamInitializer extends ChannelInitializer { 28 | 29 | @Override 30 | protected final void initChannel(QuicStreamChannel ch) { 31 | ChannelPipeline pipeline = ch.pipeline(); 32 | Http3ConnectionHandler connectionHandler = ch.parent().pipeline().get(Http3ConnectionHandler.class); 33 | if (connectionHandler == null) { 34 | throw new IllegalStateException("Couldn't obtain the " + 35 | StringUtil.simpleClassName(Http3ConnectionHandler.class) + " of the parent Channel"); 36 | } 37 | 38 | Http3RequestStreamEncodeStateValidator encodeStateValidator = new Http3RequestStreamEncodeStateValidator(); 39 | Http3RequestStreamDecodeStateValidator decodeStateValidator = new Http3RequestStreamDecodeStateValidator(); 40 | 41 | // Add the encoder and decoder in the pipeline so we can handle Http3Frames 42 | pipeline.addLast(connectionHandler.newCodec(encodeStateValidator, decodeStateValidator)); 43 | // Add the handler that will validate what we write and receive on this stream. 44 | pipeline.addLast(encodeStateValidator); 45 | pipeline.addLast(decodeStateValidator); 46 | pipeline.addLast(connectionHandler.newRequestStreamValidationHandler(ch, encodeStateValidator, 47 | decodeStateValidator)); 48 | initRequestStream(ch); 49 | } 50 | 51 | /** 52 | * Init the {@link QuicStreamChannel} to handle {@link Http3RequestStreamFrame}s. At the point of calling this 53 | * method it is already valid to write {@link Http3RequestStreamFrame}s as the codec is already in the pipeline. 54 | * 55 | * @param ch the {QuicStreamChannel} for the request stream. 56 | */ 57 | protected abstract void initRequestStream(QuicStreamChannel ch); 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/DefaultHttp3SettingsFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.util.collection.LongObjectHashMap; 19 | import io.netty.util.collection.LongObjectMap; 20 | import io.netty.util.internal.StringUtil; 21 | 22 | import java.util.Iterator; 23 | import java.util.Map; 24 | 25 | public final class DefaultHttp3SettingsFrame implements Http3SettingsFrame { 26 | 27 | private final LongObjectMap settings = new LongObjectHashMap<>(4); 28 | 29 | /** 30 | * Creates a new {@link DefaultHttp3SettingsFrame} which is a copy of the given settings. 31 | * 32 | * @param settingsFrame the frame to copy. 33 | * @return the newly created copy. 34 | */ 35 | public static DefaultHttp3SettingsFrame copyOf(Http3SettingsFrame settingsFrame) { 36 | DefaultHttp3SettingsFrame copy = new DefaultHttp3SettingsFrame(); 37 | if (settingsFrame instanceof DefaultHttp3SettingsFrame) { 38 | copy.settings.putAll(((DefaultHttp3SettingsFrame) settingsFrame).settings); 39 | } else { 40 | for (Map.Entry entry : settingsFrame) { 41 | copy.put(entry.getKey(), entry.getValue()); 42 | } 43 | } 44 | return copy; 45 | } 46 | 47 | @Override 48 | public Long get(long key) { 49 | return settings.get(key); 50 | } 51 | 52 | @Override 53 | public Long put(long key, Long value) { 54 | if (Http3CodecUtils.isReservedHttp2Setting(key)) { 55 | throw new IllegalArgumentException("Setting is reserved for HTTP/2: " + key); 56 | } 57 | return settings.put(key, value); 58 | } 59 | 60 | @Override 61 | public Iterator> iterator() { 62 | return settings.entrySet().iterator(); 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return settings.hashCode(); 68 | } 69 | 70 | @Override 71 | public boolean equals(Object o) { 72 | if (this == o) { 73 | return true; 74 | } 75 | if (o == null || getClass() != o.getClass()) { 76 | return false; 77 | } 78 | DefaultHttp3SettingsFrame that = (DefaultHttp3SettingsFrame) o; 79 | return that.settings.equals(settings); 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return StringUtil.simpleClassName(this) + "(settings=" + settings + ')'; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/ServerCookies.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http; 2 | 3 | import io.netty.handler.codec.http.HttpHeaderNames; 4 | import io.netty.handler.codec.http.cookie.Cookie; 5 | import io.netty.handler.codec.http.cookie.ServerCookieDecoder; 6 | import xyz.nyist.core.Http3HeadersFrame; 7 | 8 | import java.util.*; 9 | 10 | /** 11 | * @author: fucong 12 | * @Date: 2022/8/12 16:29 13 | * @Description: 14 | */ 15 | public class ServerCookies extends Cookies { 16 | 17 | final ServerCookieDecoder serverCookieDecoder; 18 | 19 | Map> allCachedCookies; 20 | 21 | ServerCookies(Http3HeadersFrame headersFrame, CharSequence cookiesHeaderName, boolean isClientChannel, ServerCookieDecoder decoder) { 22 | super(headersFrame, cookiesHeaderName, isClientChannel, decoder); 23 | this.serverCookieDecoder = decoder; 24 | allCachedCookies = Collections.emptyMap(); 25 | } 26 | 27 | /** 28 | * Return a new cookies holder from server request headers. 29 | * 30 | * @param headersFrame server request 31 | * @return a new cookies holder from server request headers 32 | */ 33 | public static ServerCookies newServerRequestHolder(Http3HeadersFrame headersFrame, ServerCookieDecoder decoder) { 34 | return new ServerCookies(headersFrame, HttpHeaderNames.COOKIE, false, decoder); 35 | } 36 | 37 | @Override 38 | public Map> getCachedCookies() { 39 | getAllCachedCookies(); 40 | return cachedCookies; 41 | } 42 | 43 | /** 44 | * Wait for the cookies to become available, cache them and subsequently return the cached map of cookies. 45 | * As opposed to {@link #getCachedCookies()}, this returns all cookies, even if they have the same name. 46 | * 47 | * @return the cached map of cookies 48 | */ 49 | public Map> getAllCachedCookies() { 50 | if (!markReadingCookies()) { 51 | for (; ; ) { 52 | if (hasReadCookies()) { 53 | return allCachedCookies; 54 | } 55 | } 56 | } 57 | 58 | List allCookieHeaders = allCookieHeaders(); 59 | Map> cookies = new HashMap<>(); 60 | Map> allCookies = new HashMap<>(); 61 | for (CharSequence aCookieHeader : allCookieHeaders) { 62 | List decode = serverCookieDecoder.decodeAll(aCookieHeader.toString()); 63 | for (Cookie cookie : decode) { 64 | Set existingCookiesOfNameSet = cookies.computeIfAbsent(cookie.name(), k -> new HashSet<>()); 65 | existingCookiesOfNameSet.add(cookie); 66 | List existingCookiesOfNameList = allCookies.computeIfAbsent(cookie.name(), k -> new ArrayList<>()); 67 | existingCookiesOfNameList.add(cookie); 68 | } 69 | } 70 | cachedCookies = Collections.unmodifiableMap(cookies); 71 | allCachedCookies = Collections.unmodifiableMap(allCookies); 72 | markReadCookies(); 73 | return allCachedCookies; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/QpackDecoderStateSyncStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package xyz.nyist.core; 18 | 19 | /** 20 | * A strategy that determines when to send acknowledgment of new table 22 | * entries on the QPACK decoder stream. 23 | */ 24 | public interface QpackDecoderStateSyncStrategy { 25 | 26 | /** 27 | * Returns a {@link QpackDecoderStateSyncStrategy} that will acknowledge each entry added via 28 | * {@link #entryAdded(int)} unless a prior {@link #sectionAcknowledged(int)} call has implicitly acknowledged the 29 | * addition. 30 | * 31 | * @return A {@link QpackDecoderStateSyncStrategy} that will acknowledge each entry added via 32 | * {@link #entryAdded(int)} unless a prior {@link #sectionAcknowledged(int)} call has implicitly acknowledged the 33 | * addition. 34 | */ 35 | static QpackDecoderStateSyncStrategy ackEachInsert() { 36 | return new QpackDecoderStateSyncStrategy() { 37 | private int lastCountAcknowledged; 38 | 39 | @Override 40 | public void sectionAcknowledged(int requiredInsertCount) { 41 | if (lastCountAcknowledged < requiredInsertCount) { 42 | lastCountAcknowledged = requiredInsertCount; 43 | } 44 | } 45 | 46 | @Override 47 | public boolean entryAdded(int insertCount) { 48 | if (lastCountAcknowledged < insertCount) { 49 | lastCountAcknowledged = insertCount; 50 | return true; 51 | } 52 | return false; 53 | } 54 | }; 55 | } 56 | 57 | /** 58 | * Callback when an 60 | * encoded header field section is decoded successfully by the decoder. 61 | * 62 | * @param requiredInsertCount for the encoded field section. 63 | */ 64 | void sectionAcknowledged(int requiredInsertCount); 65 | 66 | /** 67 | * When a header field entry is added to the decoder dynamic table. 68 | * 69 | * @param insertCount for the entry. 70 | * @return {@code true} if an insert count 72 | * increment decoder instruction should be sent. 73 | */ 74 | boolean entryAdded(int insertCount); 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3UnidirectionalStreamInboundClientHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package xyz.nyist.core; 18 | 19 | import io.netty.channel.ChannelHandler; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import xyz.nyist.core.Http3FrameCodec.Http3FrameCodecFactory; 22 | 23 | import java.util.function.LongFunction; 24 | import java.util.function.Supplier; 25 | 26 | final class Http3UnidirectionalStreamInboundClientHandler extends Http3UnidirectionalStreamInboundHandler { 27 | 28 | private final LongFunction pushStreamHandlerFactory; 29 | 30 | Http3UnidirectionalStreamInboundClientHandler( 31 | Http3FrameCodecFactory codecFactory, 32 | Http3ControlStreamInboundHandler localControlStreamHandler, 33 | Http3ControlStreamOutboundHandler remoteControlStreamHandler, 34 | LongFunction unknownStreamHandlerFactory, 35 | LongFunction pushStreamHandlerFactory, 36 | Supplier qpackEncoderHandlerFactory, Supplier qpackDecoderHandlerFactory) { 37 | super(codecFactory, localControlStreamHandler, remoteControlStreamHandler, unknownStreamHandlerFactory, 38 | qpackEncoderHandlerFactory, qpackDecoderHandlerFactory); 39 | this.pushStreamHandlerFactory = pushStreamHandlerFactory == null ? __ -> ReleaseHandler.INSTANCE : 40 | pushStreamHandlerFactory; 41 | } 42 | 43 | @Override 44 | void initPushStream(ChannelHandlerContext ctx, long pushId) { 45 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-4.4 46 | Long maxPushId = remoteControlStreamHandler.sentMaxPushId(); 47 | if (maxPushId == null) { 48 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_ID_ERROR, 49 | "Received push stream before sending MAX_PUSH_ID frame.", false); 50 | } else if (maxPushId < pushId) { 51 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_ID_ERROR, 52 | "Received push stream with ID " + pushId + " greater than the max push ID " + maxPushId 53 | + '.', false); 54 | } else { 55 | // Replace this handler with the actual push stream handlers. 56 | final ChannelHandler pushStreamHandler = pushStreamHandlerFactory.apply(pushId); 57 | ctx.pipeline().replace(this, null, pushStreamHandler); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3FrameTypeDuplexValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelOutboundHandler; 20 | import io.netty.channel.ChannelPromise; 21 | 22 | import java.net.SocketAddress; 23 | 24 | import static xyz.nyist.core.Http3FrameValidationUtils.frameTypeUnexpected; 25 | import static xyz.nyist.core.Http3FrameValidationUtils.validateFrameWritten; 26 | 27 | class Http3FrameTypeDuplexValidationHandler extends Http3FrameTypeInboundValidationHandler 28 | implements ChannelOutboundHandler { 29 | 30 | Http3FrameTypeDuplexValidationHandler(Class frameType) { 31 | super(frameType); 32 | } 33 | 34 | @Override 35 | public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { 36 | T frame = validateFrameWritten(frameType, msg); 37 | if (frame != null) { 38 | write(ctx, frame, promise); 39 | } else { 40 | writeFrameDiscarded(msg, promise); 41 | } 42 | } 43 | 44 | void write(ChannelHandlerContext ctx, T msg, ChannelPromise promise) { 45 | ctx.write(msg, promise); 46 | } 47 | 48 | void writeFrameDiscarded(Object discardedFrame, ChannelPromise promise) { 49 | frameTypeUnexpected(promise, discardedFrame); 50 | } 51 | 52 | @Override 53 | public void flush(ChannelHandlerContext ctx) { 54 | ctx.flush(); 55 | } 56 | 57 | @Override 58 | public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) { 59 | ctx.bind(localAddress, promise); 60 | } 61 | 62 | @Override 63 | public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, 64 | ChannelPromise promise) throws Exception { 65 | ctx.connect(remoteAddress, localAddress, promise); 66 | } 67 | 68 | @Override 69 | public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) { 70 | ctx.disconnect(promise); 71 | } 72 | 73 | @Override 74 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 75 | ctx.close(promise); 76 | } 77 | 78 | @Override 79 | public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 80 | ctx.deregister(promise); 81 | } 82 | 83 | @Override 84 | public void read(ChannelHandlerContext ctx) throws Exception { 85 | ctx.read(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/client/Http3ClientRequest.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http.client; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.handler.codec.http.HttpMethod; 5 | import io.netty.handler.codec.http.cookie.Cookie; 6 | import org.reactivestreams.Publisher; 7 | import reactor.netty.http.client.HttpClient; 8 | import xyz.nyist.core.Http3Headers; 9 | 10 | import java.time.Duration; 11 | 12 | /** 13 | * @author: fucong 14 | * @Date: 2022/8/18 14:56 15 | * @Description: 16 | */ 17 | public interface Http3ClientRequest extends Http3ClientInfos { 18 | 19 | /** 20 | * Add an outbound cookie 21 | * 22 | * @return this outbound 23 | */ 24 | Http3ClientRequest addCookie(Cookie cookie); 25 | 26 | /** 27 | * Add an outbound http header, appending the value if the header is already set. 28 | * 29 | * @param name header name 30 | * @param value header value 31 | * @return this outbound 32 | */ 33 | Http3ClientRequest addHeader(CharSequence name, CharSequence value); 34 | 35 | /** 36 | * Set an outbound header, replacing any pre-existing value. 37 | * 38 | * @param name headers key 39 | * @param value header value 40 | * @return this outbound 41 | */ 42 | Http3ClientRequest setHeader(CharSequence name, CharSequence value); 43 | 44 | /** 45 | * Set outbound headers from the passed headers. It will however ignore {@code 46 | * HOST} header key. Any pre-existing value for the passed headers will be replaced. 47 | * 48 | * @param headers a netty headers map 49 | * @return this outbound 50 | */ 51 | Http3ClientRequest headers(Http3Headers headers); 52 | 53 | /** 54 | * Return true if redirected will be followed 55 | * 56 | * @return true if redirected will be followed 57 | */ 58 | boolean isFollowRedirect(); 59 | 60 | /** 61 | * Specifies the maximum duration allowed between each network-level read operation while reading a given response 62 | * (resolution: ms). In other words, {@link io.netty.handler.timeout.ReadTimeoutHandler} is added to the channel 63 | * pipeline after sending the request and is removed when the response is fully received. 64 | * If the {@code maxReadOperationInterval} is {@code null}, any previous setting will be removed and no 65 | * {@code maxReadOperationInterval} will be applied. 66 | * If the {@code maxReadOperationInterval} is less than {@code 1ms}, then {@code 1ms} will be the 67 | * {@code maxReadOperationInterval}. 68 | * The {@code maxReadOperationInterval} setting on {@link Http3ClientRequest} level overrides any 69 | * {@code maxReadOperationInterval} setting on {@link HttpClient} level. 70 | * 71 | * @param maxReadOperationInterval the maximum duration allowed between each network-level read operations 72 | * (resolution: ms). 73 | * @return this outbound 74 | * @see io.netty.handler.timeout.ReadTimeoutHandler 75 | * @since 0.9.11 76 | */ 77 | Http3ClientRequest responseTimeout(Duration maxReadOperationInterval); 78 | 79 | 80 | Http3ClientRequest method(HttpMethod method); 81 | 82 | 83 | Http3ClientRequest uri(String uri); 84 | 85 | 86 | Publisher send(Publisher dataStream); 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/DefaultHttp3UnknownFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.buffer.DefaultByteBufHolder; 20 | import io.netty.util.internal.StringUtil; 21 | 22 | import java.util.Objects; 23 | 24 | public final class DefaultHttp3UnknownFrame extends DefaultByteBufHolder implements Http3UnknownFrame { 25 | 26 | private final long type; 27 | 28 | public DefaultHttp3UnknownFrame(long type, ByteBuf payload) { 29 | super(payload); 30 | this.type = Http3CodecUtils.checkIsReservedFrameType(type); 31 | } 32 | 33 | @Override 34 | public long type() { 35 | return type; 36 | } 37 | 38 | @Override 39 | public Http3UnknownFrame copy() { 40 | return new DefaultHttp3UnknownFrame(type, content().copy()); 41 | } 42 | 43 | @Override 44 | public Http3UnknownFrame duplicate() { 45 | return new DefaultHttp3UnknownFrame(type, content().duplicate()); 46 | } 47 | 48 | @Override 49 | public Http3UnknownFrame retainedDuplicate() { 50 | return new DefaultHttp3UnknownFrame(type, content().retainedDuplicate()); 51 | } 52 | 53 | @Override 54 | public Http3UnknownFrame replace(ByteBuf content) { 55 | return new DefaultHttp3UnknownFrame(type, content); 56 | } 57 | 58 | @Override 59 | public Http3UnknownFrame retain() { 60 | super.retain(); 61 | return this; 62 | } 63 | 64 | @Override 65 | public Http3UnknownFrame retain(int increment) { 66 | super.retain(increment); 67 | return this; 68 | } 69 | 70 | @Override 71 | public Http3UnknownFrame touch() { 72 | super.touch(); 73 | return this; 74 | } 75 | 76 | @Override 77 | public Http3UnknownFrame touch(Object hint) { 78 | super.touch(hint); 79 | return this; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return StringUtil.simpleClassName(this) + "(type=" + type() + ", content=" + content() + ')'; 85 | } 86 | 87 | @Override 88 | public boolean equals(Object o) { 89 | if (this == o) { 90 | return true; 91 | } 92 | if (o == null || getClass() != o.getClass()) { 93 | return false; 94 | } 95 | DefaultHttp3UnknownFrame that = (DefaultHttp3UnknownFrame) o; 96 | if (type != that.type) { 97 | return false; 98 | } 99 | return super.equals(o); 100 | } 101 | 102 | @Override 103 | public int hashCode() { 104 | return Objects.hash(super.hashCode(), type); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/Http3ServerTest.java: -------------------------------------------------------------------------------- 1 | import io.netty.buffer.Unpooled; 2 | import io.netty.incubator.codec.quic.InsecureQuicTokenHandler; 3 | import io.netty.incubator.codec.quic.QuicSslContext; 4 | import io.netty.incubator.codec.quic.QuicSslContextBuilder; 5 | import io.netty.util.CharsetUtil; 6 | import lombok.extern.slf4j.Slf4j; 7 | import reactor.core.publisher.Mono; 8 | import xyz.nyist.core.Http3; 9 | import xyz.nyist.http.server.Http3Server; 10 | import xyz.nyist.http.server.Http3ServerOperations; 11 | 12 | import javax.net.ssl.KeyManagerFactory; 13 | import java.io.InputStream; 14 | import java.net.InetSocketAddress; 15 | import java.security.KeyStore; 16 | import java.time.Duration; 17 | 18 | /** 19 | * @author: fucong 20 | * @Date: 2022/7/29 13:56 21 | * @Description: 22 | */ 23 | @Slf4j 24 | public class Http3ServerTest { 25 | 26 | public static void main(String[] args) throws Exception { 27 | 28 | InputStream inputStream = Http3ServerTest.class.getClassLoader().getResourceAsStream("http3.nyist.xyz.pfx"); 29 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 30 | keyStore.load(inputStream, "123456".toCharArray()); 31 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 32 | keyManagerFactory.init(keyStore, "123456".toCharArray()); 33 | 34 | QuicSslContext serverCtx = QuicSslContextBuilder.forServer(keyManagerFactory, "123456") 35 | .keylog(true) 36 | .applicationProtocols(Http3.supportedApplicationProtocols()).build(); 37 | 38 | 39 | Http3Server.create() 40 | .disableQpackDynamicTable(true) 41 | .tokenHandler(InsecureQuicTokenHandler.INSTANCE) 42 | .handleStream((http3ServerRequest, http3ServerResponse) -> { 43 | Http3ServerOperations operations = ((Http3ServerOperations) http3ServerResponse); 44 | operations.outboundHttpMessage().add("content-type", "text/plain"); 45 | operations.outboundHttpMessage().add("content-length", 14); 46 | String path = http3ServerRequest.fullPath(); 47 | if ("/api".equals(path)) { 48 | return http3ServerResponse.send(Mono.just(Unpooled.wrappedBuffer("api.toString()".getBytes(CharsetUtil.UTF_8)))); 49 | } 50 | return http3ServerResponse.send(Mono.empty()); 51 | } 52 | ) 53 | .bindAddress(() -> new InetSocketAddress("0.0.0.0", 443)) 54 | .wiretap(true) 55 | .secure(serverCtx) 56 | .idleTimeout(Duration.ofSeconds(5)) 57 | .initialSettings(spec -> 58 | spec.maxData(10000000) 59 | .maxStreamDataBidirectionalLocal(1000000) 60 | .maxStreamDataBidirectionalRemote(1000000) 61 | .maxStreamsBidirectional(100) 62 | .maxStreamDataUnidirectional(1024) 63 | .maxStreamsUnidirectional(3) 64 | ).bindNow().onDispose().block(); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3PushStreamClientValidationHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.socket.ChannelInputShutdownReadComplete; 20 | 21 | import static xyz.nyist.core.Http3RequestStreamValidationUtils.*; 22 | 23 | final class Http3PushStreamClientValidationHandler 24 | extends Http3FrameTypeInboundValidationHandler { 25 | 26 | private final QpackAttributes qpackAttributes; 27 | 28 | private final QpackDecoder qpackDecoder; 29 | 30 | private final Http3RequestStreamCodecState decodeState; 31 | 32 | private long expectedLength = -1; 33 | 34 | private long seenLength; 35 | 36 | Http3PushStreamClientValidationHandler(QpackAttributes qpackAttributes, QpackDecoder qpackDecoder, 37 | Http3RequestStreamCodecState decodeState) { 38 | super(Http3RequestStreamFrame.class); 39 | this.qpackAttributes = qpackAttributes; 40 | this.qpackDecoder = qpackDecoder; 41 | this.decodeState = decodeState; 42 | } 43 | 44 | @Override 45 | void channelRead(ChannelHandlerContext ctx, Http3RequestStreamFrame frame) { 46 | if (frame instanceof Http3PushPromiseFrame) { 47 | ctx.fireChannelRead(frame); 48 | return; 49 | } 50 | 51 | if (frame instanceof Http3HeadersFrame) { 52 | Http3HeadersFrame headersFrame = (Http3HeadersFrame) frame; 53 | long maybeContentLength = validateHeaderFrameRead(headersFrame, ctx, decodeState); 54 | if (maybeContentLength >= 0) { 55 | expectedLength = maybeContentLength; 56 | } else if (maybeContentLength == INVALID_FRAME_READ) { 57 | return; 58 | } 59 | } 60 | 61 | if (frame instanceof Http3DataFrame) { 62 | final Http3DataFrame dataFrame = (Http3DataFrame) frame; 63 | long maybeContentLength = validateDataFrameRead(dataFrame, ctx, expectedLength, seenLength, false); 64 | if (maybeContentLength >= 0) { 65 | seenLength = maybeContentLength; 66 | } else if (maybeContentLength == INVALID_FRAME_READ) { 67 | return; 68 | } 69 | } 70 | ctx.fireChannelRead(frame); 71 | } 72 | 73 | @Override 74 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 75 | if (evt == ChannelInputShutdownReadComplete.INSTANCE) { 76 | sendStreamAbandonedIfRequired(ctx, qpackAttributes, qpackDecoder, decodeState); 77 | if (!validateOnStreamClosure(ctx, expectedLength, seenLength, false)) { 78 | return; 79 | } 80 | } 81 | ctx.fireUserEventTriggered(evt); 82 | } 83 | 84 | @Override 85 | public boolean isSharable() { 86 | // This handle keeps state so we can't share it. 87 | return false; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3FrameValidationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelPromise; 20 | import io.netty.util.ReferenceCountUtil; 21 | import io.netty.util.internal.StringUtil; 22 | 23 | final class Http3FrameValidationUtils { 24 | 25 | private Http3FrameValidationUtils() { 26 | // no instances 27 | } 28 | 29 | @SuppressWarnings("unchecked") 30 | private static T cast(Object msg) { 31 | return (T) msg; 32 | } 33 | 34 | private static boolean isValid(Class frameType, Object msg) { 35 | return frameType.isInstance(msg); 36 | } 37 | 38 | /** 39 | * Check if the passed {@code msg} is of the {@code expectedFrameType} and return the expected type, else return 40 | * {@code null}. 41 | * 42 | * @param expectedFrameType {@link Class} of the expected frame type. 43 | * @param msg to validate. 44 | * @param Expected type. 45 | * @return {@code msg} as expected frame type or {@code null} if it can not be converted to the expected type. 46 | */ 47 | static T validateFrameWritten(Class expectedFrameType, Object msg) { 48 | if (isValid(expectedFrameType, msg)) { 49 | return cast(msg); 50 | } 51 | return null; 52 | } 53 | 54 | /** 55 | * Check if the passed {@code msg} is of the {@code expectedFrameType} and return the expected type, else return 56 | * {@code null}. 57 | * 58 | * @param expectedFrameType {@link Class} of the expected frame type. 59 | * @param msg to validate. 60 | * @param Expected type. 61 | * @return {@code msg} as expected frame type or {@code null} if it can not be converted to the expected type. 62 | */ 63 | static T validateFrameRead(Class expectedFrameType, Object msg) { 64 | if (isValid(expectedFrameType, msg)) { 65 | return cast(msg); 66 | } 67 | return null; 68 | } 69 | 70 | /** 71 | * Handle unexpected frame type by failing the passed {@link ChannelPromise}. 72 | * 73 | * @param promise to fail. 74 | * @param frame which is unexpected. 75 | */ 76 | static void frameTypeUnexpected(ChannelPromise promise, Object frame) { 77 | String type = StringUtil.simpleClassName(frame); 78 | ReferenceCountUtil.release(frame); 79 | promise.setFailure(new Http3Exception(Http3ErrorCode.H3_FRAME_UNEXPECTED, 80 | "Frame of type " + type + " unexpected")); 81 | } 82 | 83 | /** 84 | * Handle unexpected frame type by propagating a connection error with code: 85 | * {@link Http3ErrorCode#H3_FRAME_UNEXPECTED}. 86 | * 87 | * @param ctx to use for propagation of failure. 88 | * @param frame which is unexpected. 89 | */ 90 | static void frameTypeUnexpected(ChannelHandlerContext ctx, Object frame) { 91 | ReferenceCountUtil.release(frame); 92 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_FRAME_UNEXPECTED, "Frame type unexpected", true); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3PushStreamServerInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.channel.ChannelInitializer; 20 | import io.netty.channel.ChannelPipeline; 21 | import io.netty.incubator.codec.quic.QuicStreamChannel; 22 | 23 | import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero; 24 | import static xyz.nyist.core.Http3CodecUtils.isServerInitiatedQuicStream; 25 | import static xyz.nyist.core.Http3CodecUtils.writeVariableLengthInteger; 26 | import static xyz.nyist.core.Http3RequestStreamCodecState.NO_STATE; 27 | 28 | /** 29 | * Abstract base class that users can extend to init HTTP/3 push-streams for servers. This initializer 30 | * will automatically add HTTP/3 codecs etc to the {@link ChannelPipeline} as well. 31 | */ 32 | public abstract class Http3PushStreamServerInitializer extends ChannelInitializer { 33 | 34 | private final long pushId; 35 | 36 | protected Http3PushStreamServerInitializer(long pushId) { 37 | this.pushId = checkPositiveOrZero(pushId, "pushId"); 38 | } 39 | 40 | @Override 41 | protected final void initChannel(QuicStreamChannel ch) { 42 | if (!isServerInitiatedQuicStream(ch)) { 43 | throw new IllegalArgumentException("Using server push stream initializer for client stream: " + 44 | ch.streamId()); 45 | } 46 | Http3CodecUtils.verifyIsUnidirectional(ch); 47 | 48 | // We need to write stream type into the stream before doing anything else. 49 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.2.1 50 | // Just allocate 16 bytes which would be the max needed to write 2 variable length ints. 51 | ByteBuf buffer = ch.alloc().buffer(16); 52 | writeVariableLengthInteger(buffer, Http3CodecUtils.HTTP3_PUSH_STREAM_TYPE); 53 | writeVariableLengthInteger(buffer, pushId); 54 | ch.write(buffer); 55 | 56 | Http3ConnectionHandler connectionHandler = Http3CodecUtils.getConnectionHandlerOrClose(ch.parent()); 57 | if (connectionHandler == null) { 58 | // connection should have been closed 59 | return; 60 | } 61 | 62 | ChannelPipeline pipeline = ch.pipeline(); 63 | Http3RequestStreamEncodeStateValidator encodeStateValidator = new Http3RequestStreamEncodeStateValidator(); 64 | // Add the encoder and decoder in the pipeline so we can handle Http3Frames 65 | pipeline.addLast(connectionHandler.newCodec(encodeStateValidator, NO_STATE)); 66 | pipeline.addLast(encodeStateValidator); 67 | // Add the handler that will validate what we write and receive on this stream. 68 | pipeline.addLast(connectionHandler.newPushStreamValidationHandler(ch, NO_STATE)); 69 | initPushStream(ch); 70 | } 71 | 72 | /** 73 | * Initialize the {@link QuicStreamChannel} to handle {@link Http3PushStreamFrame}s. At the point of calling this 74 | * method it is already valid to write {@link Http3PushStreamFrame}s as the codec is already in the pipeline. 75 | * 76 | * @param ch the {QuicStreamChannel} for the push stream. 77 | */ 78 | protected abstract void initPushStream(QuicStreamChannel ch); 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/client/Http3ConnectionProvider.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http.client; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.resolver.AddressResolverGroup; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.reactivestreams.Subscription; 7 | import reactor.core.CoreSubscriber; 8 | import reactor.core.Disposable; 9 | import reactor.core.publisher.Mono; 10 | import reactor.core.publisher.MonoSink; 11 | import reactor.core.publisher.Operators; 12 | import reactor.netty.ChannelBindException; 13 | import reactor.netty.Connection; 14 | import reactor.netty.ConnectionObserver; 15 | import reactor.netty.resources.ConnectionProvider; 16 | import reactor.netty.transport.TransportConfig; 17 | import reactor.util.annotation.Nullable; 18 | import reactor.util.context.Context; 19 | 20 | import java.io.IOException; 21 | import java.net.BindException; 22 | import java.net.SocketAddress; 23 | import java.util.function.Supplier; 24 | 25 | import static reactor.netty.ReactorNetty.format; 26 | 27 | /** 28 | * @author: fucong 29 | * @Date: 2022/8/17 17:49 30 | * @Description: 31 | */ 32 | @Slf4j 33 | class Http3ConnectionProvider implements ConnectionProvider { 34 | 35 | //todo 36 | @Override 37 | public Mono acquire(TransportConfig config, ConnectionObserver connectionObserver, Supplier remoteAddress, AddressResolverGroup resolverGroup) { 38 | 39 | Mono mono = Mono.create(sink -> { 40 | 41 | // DisposableConnect disposableConnect = new DisposableConnect(sink, config.bindAddress()); 42 | // TransportConnector.bind(config, config.parentChannelInitializer(), local, false) 43 | // .subscribe(disposableConnect); 44 | }); 45 | 46 | // if (config.doOnConnect() != null) { 47 | // mono = mono.doOnSubscribe(s -> config.doOnConnect.accept(config)); 48 | // } 49 | 50 | return mono; 51 | } 52 | 53 | static final class DisposableConnect implements CoreSubscriber, Disposable { 54 | 55 | final MonoSink sink; 56 | 57 | final Context currentContext; 58 | 59 | final Supplier bindAddress; 60 | 61 | Subscription subscription; 62 | 63 | DisposableConnect(MonoSink sink, @Nullable Supplier bindAddress) { 64 | this.sink = sink; 65 | this.currentContext = Context.of(sink.contextView()); 66 | this.bindAddress = bindAddress; 67 | } 68 | 69 | @Override 70 | public Context currentContext() { 71 | return currentContext; 72 | } 73 | 74 | @Override 75 | public void dispose() { 76 | subscription.cancel(); 77 | } 78 | 79 | @Override 80 | public void onComplete() { 81 | } 82 | 83 | @Override 84 | public void onError(Throwable t) { 85 | if (bindAddress != null && (t instanceof BindException || 86 | // With epoll/kqueue transport it is 87 | // io.netty.channel.unix.Errors$NativeIoException: bind(..) failed: Address already in use 88 | (t instanceof IOException && t.getMessage() != null && 89 | t.getMessage().contains("bind(..)")))) { 90 | sink.error(ChannelBindException.fail(bindAddress.get(), null)); 91 | } else { 92 | sink.error(t); 93 | } 94 | } 95 | 96 | @Override 97 | public void onNext(Channel channel) { 98 | if (log.isDebugEnabled()) { 99 | log.debug(format(channel, "Connected new channel")); 100 | } 101 | } 102 | 103 | @Override 104 | public void onSubscribe(Subscription s) { 105 | if (Operators.validate(subscription, s)) { 106 | this.subscription = s; 107 | sink.onCancel(this); 108 | s.request(Long.MAX_VALUE); 109 | } 110 | } 111 | 112 | } 113 | 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3ErrorCode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | /** 19 | * Different HTTP3 error codes. 20 | */ 21 | public enum Http3ErrorCode { 22 | 23 | /** 24 | * No error. This is used when the connection or stream needs to be closed, but there is no error to signal. 25 | */ 26 | H3_NO_ERROR(0x100), 27 | 28 | /** 29 | * Peer violated protocol requirements in a way that does not match a more specific error code, 30 | * or endpoint declines to use the more specific error code. 31 | */ 32 | H3_GENERAL_PROTOCOL_ERROR(0x101), 33 | 34 | /** 35 | * An internal error has occurred in the HTTP stack. 36 | */ 37 | H3_INTERNAL_ERROR(0x102), 38 | 39 | /** 40 | * The endpoint detected that its peer created a stream that it will not accept. 41 | */ 42 | H3_STREAM_CREATION_ERROR(0x103), 43 | 44 | /** 45 | * A stream required by the HTTP/3 connection was closed or reset. 46 | */ 47 | H3_CLOSED_CRITICAL_STREAM(0x104), 48 | 49 | /** 50 | * A frame was received that was not permitted in the current state or on the current stream. 51 | */ 52 | H3_FRAME_UNEXPECTED(0x105), 53 | 54 | /** 55 | * A frame that fails to satisfy layout requirements or with an invalid size was received. 56 | */ 57 | H3_FRAME_ERROR(0x106), 58 | 59 | /** 60 | * The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load. 61 | */ 62 | H3_EXCESSIVE_LOAD(0x107), 63 | 64 | /** 65 | * A Stream ID or Push ID was used incorrectly, such as exceeding a limit, reducing a limit, or being reused. 66 | */ 67 | H3_ID_ERROR(0x108), 68 | 69 | /** 70 | * An endpoint detected an error in the payload of a SETTINGS frame. 71 | */ 72 | H3_SETTINGS_ERROR(0x109), 73 | 74 | /** 75 | * No SETTINGS frame was received at the beginning of the control stream. 76 | */ 77 | H3_MISSING_SETTINGS(0x10a), 78 | 79 | /** 80 | * A server rejected a request without performing any application processing. 81 | */ 82 | H3_REQUEST_REJECTED(0x10b), 83 | 84 | /** 85 | * The request or its response (including pushed response) is cancelled. 86 | */ 87 | H3_REQUEST_CANCELLED(0x10c), 88 | 89 | /** 90 | * The client's stream terminated without containing a fully-formed request. 91 | */ 92 | H3_REQUEST_INCOMPLETE(0x10d), 93 | 94 | /** 95 | * An HTTP message was malformed and cannot be processed. 96 | */ 97 | H3_MESSAGE_ERROR(0x10e), 98 | 99 | /** 100 | * The TCP connection established in response to a CONNECT request was reset or abnormally closed. 101 | */ 102 | H3_CONNECT_ERROR(0x10f), 103 | 104 | /** 105 | * The requested operation cannot be served over HTTP/3. The peer should retry over HTTP/1.1. 106 | */ 107 | H3_VERSION_FALLBACK(0x110), 108 | 109 | /** 110 | * The decoder failed to interpret an encoded field section and is not able to continue decoding that field section. 111 | */ 112 | QPACK_DECOMPRESSION_FAILED(0x200), 113 | 114 | /** 115 | * The decoder failed to interpret an encoder instruction received on the encoder stream. 116 | */ 117 | QPACK_ENCODER_STREAM_ERROR(0x201), 118 | 119 | /** 120 | * The encoder failed to interpret a decoder instruction received on the decoder stream. 121 | */ 122 | QPACK_DECODER_STREAM_ERROR(0x202); 123 | 124 | final int code; 125 | 126 | Http3ErrorCode(int code) { 127 | this.code = code; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/QpackAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package xyz.nyist.core; 18 | 19 | import io.netty.incubator.codec.quic.QuicChannel; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | import io.netty.util.concurrent.Future; 22 | import io.netty.util.concurrent.GenericFutureListener; 23 | import io.netty.util.concurrent.Promise; 24 | 25 | import static java.util.Objects.requireNonNull; 26 | 27 | final class QpackAttributes { 28 | 29 | private final QuicChannel channel; 30 | 31 | private final boolean dynamicTableDisabled; 32 | 33 | private final Promise encoderStreamPromise; 34 | 35 | private final Promise decoderStreamPromise; 36 | 37 | private QuicStreamChannel encoderStream; 38 | 39 | private QuicStreamChannel decoderStream; 40 | 41 | QpackAttributes(QuicChannel channel, boolean disableDynamicTable) { 42 | this.channel = channel; 43 | dynamicTableDisabled = disableDynamicTable; 44 | encoderStreamPromise = dynamicTableDisabled ? null : channel.eventLoop().newPromise(); 45 | decoderStreamPromise = dynamicTableDisabled ? null : channel.eventLoop().newPromise(); 46 | } 47 | 48 | boolean dynamicTableDisabled() { 49 | return dynamicTableDisabled; 50 | } 51 | 52 | boolean decoderStreamAvailable() { 53 | return !dynamicTableDisabled && decoderStream != null; 54 | } 55 | 56 | boolean encoderStreamAvailable() { 57 | return !dynamicTableDisabled && encoderStream != null; 58 | } 59 | 60 | void whenEncoderStreamAvailable(GenericFutureListener> listener) { 61 | assert !dynamicTableDisabled; 62 | assert encoderStreamPromise != null; 63 | encoderStreamPromise.addListener(listener); 64 | } 65 | 66 | void whenDecoderStreamAvailable(GenericFutureListener> listener) { 67 | assert !dynamicTableDisabled; 68 | assert decoderStreamPromise != null; 69 | decoderStreamPromise.addListener(listener); 70 | } 71 | 72 | QuicStreamChannel decoderStream() { 73 | assert decoderStreamAvailable(); 74 | return decoderStream; 75 | } 76 | 77 | QuicStreamChannel encoderStream() { 78 | assert encoderStreamAvailable(); 79 | return encoderStream; 80 | } 81 | 82 | void decoderStream(QuicStreamChannel decoderStream) { 83 | assert channel.eventLoop().inEventLoop(); 84 | assert !dynamicTableDisabled; 85 | assert decoderStreamPromise != null; 86 | assert this.decoderStream == null; 87 | this.decoderStream = requireNonNull(decoderStream); 88 | decoderStreamPromise.setSuccess(decoderStream); 89 | } 90 | 91 | void encoderStream(QuicStreamChannel encoderStream) { 92 | assert channel.eventLoop().inEventLoop(); 93 | assert !dynamicTableDisabled; 94 | assert encoderStreamPromise != null; 95 | assert this.encoderStream == null; 96 | this.encoderStream = requireNonNull(encoderStream); 97 | encoderStreamPromise.setSuccess(encoderStream); 98 | } 99 | 100 | void encoderStreamInactive(Throwable cause) { 101 | assert channel.eventLoop().inEventLoop(); 102 | assert !dynamicTableDisabled; 103 | assert encoderStreamPromise != null; 104 | encoderStreamPromise.tryFailure(cause); 105 | } 106 | 107 | void decoderStreamInactive(Throwable cause) { 108 | assert channel.eventLoop().inEventLoop(); 109 | assert !dynamicTableDisabled; 110 | assert decoderStreamPromise != null; 111 | decoderStreamPromise.tryFailure(cause); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3ClientConnectionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandler; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.incubator.codec.quic.QuicStreamChannel; 21 | 22 | import java.util.function.LongFunction; 23 | 24 | import static xyz.nyist.core.Http3SettingsFrame.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY; 25 | 26 | public final class Http3ClientConnectionHandler extends Http3ConnectionHandler { 27 | 28 | private final LongFunction pushStreamHandlerFactory; 29 | 30 | /** 31 | * Create a new instance. 32 | */ 33 | public Http3ClientConnectionHandler() { 34 | this(null, null, null, null, false); 35 | } 36 | 37 | /** 38 | * Create a new instance. 39 | * 40 | * @param inboundControlStreamHandler the {@link ChannelHandler} which will be notified about 41 | * {@link Http3RequestStreamFrame}s or {@code null} if the user is not 42 | * interested in these. 43 | * @param pushStreamHandlerFactory the {@link LongFunction} that will provide a custom 44 | * {@link ChannelHandler} for push streams {@code null} if no special 45 | * handling should be done. When present, push ID will be passed as an 46 | * argument to the {@link LongFunction}. 47 | * @param unknownInboundStreamHandlerFactory the {@link LongFunction} that will provide a custom 48 | * {@link ChannelHandler} for unknown inbound stream types or 49 | * {@code null} if no special handling should be done. 50 | * @param localSettings the local {@link Http3SettingsFrame} that should be sent to the 51 | * remote peer or {@code null} if the default settings should be used. 52 | * @param disableQpackDynamicTable If QPACK dynamic table should be disabled. 53 | */ 54 | public Http3ClientConnectionHandler(ChannelHandler inboundControlStreamHandler, 55 | LongFunction pushStreamHandlerFactory, 56 | LongFunction unknownInboundStreamHandlerFactory, 57 | Http3SettingsFrame localSettings, boolean disableQpackDynamicTable) { 58 | super(false, inboundControlStreamHandler, unknownInboundStreamHandlerFactory, localSettings, 59 | disableQpackDynamicTable); 60 | this.pushStreamHandlerFactory = pushStreamHandlerFactory; 61 | } 62 | 63 | @Override 64 | void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel channel) { 65 | // See https://tools.ietf.org/html/draft-ietf-quic-http-32#section-6.1 66 | Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_STREAM_CREATION_ERROR, 67 | "Server initiated bidirectional streams are not allowed", true); 68 | } 69 | 70 | @Override 71 | void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel) { 72 | final Long maxTableCapacity = remoteControlStreamHandler.localSettings() 73 | .get(HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY); 74 | streamChannel.pipeline().addLast( 75 | new Http3UnidirectionalStreamInboundClientHandler(codecFactory, 76 | localControlStreamHandler, remoteControlStreamHandler, 77 | unknownInboundStreamHandlerFactory, pushStreamHandlerFactory, 78 | () -> new QpackEncoderHandler(maxTableCapacity, qpackDecoder), 79 | () -> new QpackDecoderHandler(qpackEncoder))); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3RequestStreamEncodeStateValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelOutboundHandlerAdapter; 20 | import io.netty.channel.ChannelPromise; 21 | import io.netty.handler.codec.http.HttpStatusClass; 22 | 23 | import static xyz.nyist.core.Http3FrameValidationUtils.frameTypeUnexpected; 24 | 25 | final class Http3RequestStreamEncodeStateValidator extends ChannelOutboundHandlerAdapter 26 | implements Http3RequestStreamCodecState { 27 | 28 | private State state = State.None; 29 | 30 | /** 31 | * Evaluates the passed frame and returns the following: 32 | *
    33 | *
  • Modified {@link State} if the state should be changed.
  • 34 | *
  • Same {@link State} as the passed {@code state} if no state change is necessary
  • 35 | *
  • {@code null} if the frame is unexpected
  • 36 | *
37 | * 38 | * @param state Current state. 39 | * @param frame to evaluate. 40 | * @return Next {@link State} or {@code null} if the frame is invalid. 41 | */ 42 | static State evaluateFrame(State state, Http3RequestStreamFrame frame) { 43 | if (frame instanceof Http3PushPromiseFrame || frame instanceof Http3UnknownFrame) { 44 | // always allow push promise frames. 45 | return state; 46 | } 47 | switch (state) { 48 | case None: 49 | case Headers: 50 | if (!(frame instanceof Http3HeadersFrame)) { 51 | return null; 52 | } 53 | return isInformationalResponse((Http3HeadersFrame) frame) ? State.Headers : State.FinalHeaders; 54 | case FinalHeaders: 55 | if (frame instanceof Http3HeadersFrame) { 56 | if (isInformationalResponse((Http3HeadersFrame) frame)) { 57 | // Information response after final response headers 58 | return null; 59 | } 60 | // trailers 61 | return State.Trailers; 62 | } 63 | return state; 64 | case Trailers: 65 | return null; 66 | default: 67 | throw new Error(); 68 | } 69 | } 70 | 71 | static boolean isStreamStarted(State state) { 72 | return state != State.None; 73 | } 74 | 75 | static boolean isFinalHeadersReceived(State state) { 76 | return isStreamStarted(state) && state != State.Headers; 77 | } 78 | 79 | static boolean isTrailersReceived(State state) { 80 | return state == State.Trailers; 81 | } 82 | 83 | private static boolean isInformationalResponse(Http3HeadersFrame headersFrame) { 84 | return HttpStatusClass.valueOf(headersFrame.headers().status()) == HttpStatusClass.INFORMATIONAL; 85 | } 86 | 87 | @Override 88 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 89 | if (!(msg instanceof Http3RequestStreamFrame)) { 90 | super.write(ctx, msg, promise); 91 | return; 92 | } 93 | final Http3RequestStreamFrame frame = (Http3RequestStreamFrame) msg; 94 | final State nextState = evaluateFrame(state, frame); 95 | if (nextState == null) { 96 | frameTypeUnexpected(ctx, msg); 97 | return; 98 | } 99 | state = nextState; 100 | super.write(ctx, msg, promise); 101 | } 102 | 103 | @Override 104 | public boolean started() { 105 | return isStreamStarted(state); 106 | } 107 | 108 | @Override 109 | public boolean receivedFinalHeaders() { 110 | return isFinalHeadersReceived(state); 111 | } 112 | 113 | @Override 114 | public boolean terminated() { 115 | return isTrailersReceived(state); 116 | } 117 | 118 | enum State { 119 | None, 120 | Headers, 121 | FinalHeaders, 122 | Trailers 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/Cookies.java: -------------------------------------------------------------------------------- 1 | package xyz.nyist.http; 2 | 3 | import io.netty.handler.codec.http.HttpHeaderNames; 4 | import io.netty.handler.codec.http.cookie.ClientCookieDecoder; 5 | import io.netty.handler.codec.http.cookie.Cookie; 6 | import io.netty.handler.codec.http.cookie.CookieDecoder; 7 | import io.netty.handler.codec.http.cookie.ServerCookieDecoder; 8 | import xyz.nyist.core.Http3HeadersFrame; 9 | 10 | import java.util.*; 11 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 12 | 13 | /** 14 | * @author: fucong 15 | * @Date: 2022/8/12 16:30 16 | * @Description: 17 | */ 18 | public class Cookies { 19 | 20 | final static int NOT_READ = 0; 21 | 22 | final static int READING = 1; 23 | 24 | final static int READ = 2; 25 | 26 | static final AtomicIntegerFieldUpdater STATE = 27 | AtomicIntegerFieldUpdater.newUpdater(Cookies.class, "state"); 28 | 29 | final Http3HeadersFrame nettyHeaders; 30 | 31 | final CharSequence cookiesHeaderName; 32 | 33 | final boolean isClientChannel; 34 | 35 | final CookieDecoder decoder; 36 | 37 | protected Map> cachedCookies; 38 | 39 | volatile int state; 40 | 41 | protected Cookies(Http3HeadersFrame nettyHeaders, CharSequence cookiesHeaderName, boolean isClientChannel, 42 | CookieDecoder decoder) { 43 | this.nettyHeaders = Objects.requireNonNull(nettyHeaders, "nettyHeaders"); 44 | this.cookiesHeaderName = cookiesHeaderName; 45 | this.isClientChannel = isClientChannel; 46 | this.decoder = Objects.requireNonNull(decoder, "decoder"); 47 | cachedCookies = Collections.emptyMap(); 48 | } 49 | 50 | /** 51 | * Return a new cookies holder from client response headers. 52 | * 53 | * @param headers client response headers 54 | * @return a new cookies holder from client response headers 55 | */ 56 | public static Cookies newClientResponseHolder(Http3HeadersFrame headers, ClientCookieDecoder decoder) { 57 | return new Cookies(headers, HttpHeaderNames.SET_COOKIE, true, decoder); 58 | } 59 | 60 | /** 61 | * Return a new cookies holder from server request headers. 62 | * 63 | * @param headers server request headers 64 | * @return a new cookies holder from server request headers 65 | * @deprecated as of 1.0.8. 66 | * Prefer {@link ServerCookies#newServerRequestHolder(Http3HeadersFrame, ServerCookieDecoder)}. 67 | * This method will be removed in version 1.2.0. 68 | */ 69 | @Deprecated 70 | public static Cookies newServerRequestHolder(Http3HeadersFrame headers, ServerCookieDecoder decoder) { 71 | return new Cookies(headers, HttpHeaderNames.COOKIE, false, decoder); 72 | } 73 | 74 | /** 75 | * Wait for the cookies to become available, cache them and subsequently return the cached map of cookies. 76 | * 77 | * @return the cached map of cookies 78 | */ 79 | public Map> getCachedCookies() { 80 | if (!markReadingCookies()) { 81 | for (; ; ) { 82 | if (hasReadCookies()) { 83 | return cachedCookies; 84 | } 85 | } 86 | } 87 | 88 | List allCookieHeaders = allCookieHeaders(); 89 | Map> cookies = new HashMap<>(); 90 | for (CharSequence aCookieHeader : allCookieHeaders) { 91 | Set decode; 92 | if (isClientChannel) { 93 | final Cookie c = ((ClientCookieDecoder) decoder).decode(aCookieHeader.toString()); 94 | if (c == null) { 95 | continue; 96 | } 97 | Set existingCookiesOfName = cookies.computeIfAbsent(c.name(), k -> new HashSet<>()); 98 | existingCookiesOfName.add(c); 99 | } else { 100 | decode = ((ServerCookieDecoder) decoder).decode(aCookieHeader.toString()); 101 | for (Cookie cookie : decode) { 102 | Set existingCookiesOfName = cookies.computeIfAbsent(cookie.name(), k -> new HashSet<>()); 103 | existingCookiesOfName.add(cookie); 104 | } 105 | } 106 | } 107 | cachedCookies = Collections.unmodifiableMap(cookies); 108 | markReadCookies(); 109 | return cachedCookies; 110 | } 111 | 112 | protected List allCookieHeaders() { 113 | return nettyHeaders.headers().getAll(cookiesHeaderName); 114 | } 115 | 116 | protected final boolean hasReadCookies() { 117 | return state == READ; 118 | } 119 | 120 | protected final boolean markReadCookies() { 121 | return STATE.compareAndSet(this, READING, READ); 122 | } 123 | 124 | protected final boolean markReadingCookies() { 125 | return STATE.compareAndSet(this, NOT_READ, READING); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/http/Http3InboundStreamTrafficHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.http; 17 | 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.channel.ChannelInboundHandlerAdapter; 20 | import io.netty.channel.socket.ChannelInputShutdownReadComplete; 21 | import lombok.extern.slf4j.Slf4j; 22 | import reactor.netty.Connection; 23 | import reactor.netty.ConnectionObserver; 24 | import reactor.netty.channel.ChannelOperations; 25 | import xyz.nyist.core.Http3Exception; 26 | import xyz.nyist.core.Http3HeadersFrame; 27 | import xyz.nyist.http.server.Http3ServerOperations; 28 | 29 | import java.net.SocketAddress; 30 | import java.util.Queue; 31 | import java.util.function.BiFunction; 32 | 33 | import static reactor.netty.ReactorNetty.format; 34 | 35 | /** 36 | * @author Violeta Georgieva 37 | */ 38 | @Slf4j 39 | public final class Http3InboundStreamTrafficHandler extends ChannelInboundHandlerAdapter { 40 | 41 | 42 | final BiFunction forwardedHeaderHandler; 43 | 44 | ConnectionObserver listener; 45 | 46 | ChannelHandlerContext ctx; 47 | 48 | 49 | Queue pipelined; 50 | 51 | SocketAddress remoteAddress; 52 | 53 | public Http3InboundStreamTrafficHandler(ConnectionObserver listener) { 54 | this.listener = listener; 55 | this.forwardedHeaderHandler = null; 56 | } 57 | 58 | @Override 59 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 60 | super.handlerAdded(ctx); 61 | this.ctx = ctx; 62 | if (log.isDebugEnabled()) { 63 | log.debug(format(ctx.channel(), "New stream connection, requesting read")); 64 | } 65 | ctx.read(); 66 | } 67 | 68 | @Override 69 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Http3Exception { 70 | log.debug("{}收到消息:{}", ctx.channel(), msg.getClass().getSimpleName()); 71 | if (remoteAddress == null) { 72 | remoteAddress = ctx.channel().remoteAddress(); 73 | // Optional.ofNullable(HAProxyMessageReader.resolveRemoteAddressFromProxyProtocol(ctx.channel())) 74 | // .orElse(ctx.channel().remoteAddress()); 75 | } 76 | // read message and track if it was keepAlive 77 | if (msg instanceof Http3HeadersFrame) { 78 | 79 | final Http3HeadersFrame request = (Http3HeadersFrame) msg; 80 | 81 | Connection conn = Connection.from(ctx.channel()); 82 | 83 | 84 | Http3ServerOperations ops = new Http3ServerOperations(conn, listener, request, 85 | null, 86 | ConnectionInfo.from(ctx.channel(), 87 | request, 88 | forwardedHeaderHandler), 89 | Http3ServerFormDecoderProvider.DEFAULT_FORM_DECODER_SPEC, 90 | null, 91 | true 92 | ); 93 | ops.bind(); 94 | listener.onStateChange(ops, ConnectionObserver.State.CONFIGURED); 95 | 96 | ctx.fireChannelRead(msg); 97 | return; 98 | } 99 | 100 | ctx.fireChannelRead(msg); 101 | } 102 | 103 | @Override 104 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 105 | if (evt == ChannelInputShutdownReadComplete.INSTANCE) { 106 | Http3ServerOperations ops = (Http3ServerOperations) ChannelOperations.get(ctx.channel()); 107 | if (ops != null) { 108 | if (log.isDebugEnabled()) { 109 | log.debug(format(ops.channel(), "Remote peer sent WRITE_FIN.")); 110 | } 111 | ctx.channel().config().setAutoRead(true); 112 | ops.onInboundComplete(); 113 | } 114 | } 115 | ctx.fireUserEventTriggered(evt); 116 | } 117 | 118 | 119 | @Override 120 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 121 | log.error(cause.toString()); 122 | super.exceptionCaught(ctx, cause); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/Http3ServerConnectionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.channel.ChannelHandler; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.ChannelPipeline; 21 | import io.netty.incubator.codec.quic.QuicStreamChannel; 22 | import io.netty.util.internal.ObjectUtil; 23 | 24 | import java.util.function.LongFunction; 25 | 26 | import static xyz.nyist.core.Http3SettingsFrame.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY; 27 | 28 | 29 | /** 30 | * Handler that handles HTTP3 for the server-side. 31 | */ 32 | public final class Http3ServerConnectionHandler extends Http3ConnectionHandler { 33 | 34 | private final ChannelHandler requestStreamHandler; 35 | 36 | /** 37 | * Create a new instance. 38 | * 39 | * @param requestStreamHandler the {@link ChannelHandler} that is used for each new request stream. 40 | * This handler will receive {@link Http3HeadersFrame} and {@link Http3DataFrame}s. 41 | */ 42 | public Http3ServerConnectionHandler(ChannelHandler requestStreamHandler, boolean disableQpackDynamicTable) { 43 | this(requestStreamHandler, null, null, null, disableQpackDynamicTable); 44 | } 45 | 46 | /** 47 | * Create a new instance. 48 | * 49 | * @param requestStreamHandler the {@link ChannelHandler} that is used for each new request stream. 50 | * This handler will receive {@link Http3HeadersFrame} and 51 | * {@link Http3DataFrame}s. 52 | * @param inboundControlStreamHandler the {@link ChannelHandler} which will be notified about 53 | * {@link Http3RequestStreamFrame}s or {@code null} if the user is not 54 | * interested in these. 55 | * @param unknownInboundStreamHandlerFactory the {@link LongFunction} that will provide a custom 56 | * {@link ChannelHandler} for unknown inbound stream types or 57 | * {@code null} if no special handling should be done. 58 | * @param localSettings the local {@link Http3SettingsFrame} that should be sent to the 59 | * remote peer or {@code null} if the default settings should be used. 60 | * @param disableQpackDynamicTable If QPACK dynamic table should be disabled. 61 | */ 62 | public Http3ServerConnectionHandler(ChannelHandler requestStreamHandler, 63 | ChannelHandler inboundControlStreamHandler, 64 | LongFunction unknownInboundStreamHandlerFactory, 65 | Http3SettingsFrame localSettings, boolean disableQpackDynamicTable) { 66 | super(true, inboundControlStreamHandler, unknownInboundStreamHandlerFactory, localSettings, 67 | disableQpackDynamicTable); 68 | this.requestStreamHandler = ObjectUtil.checkNotNull(requestStreamHandler, "requestStreamHandler"); 69 | } 70 | 71 | @Override 72 | void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel) { 73 | ChannelPipeline pipeline = streamChannel.pipeline(); 74 | Http3RequestStreamEncodeStateValidator encodeStateValidator = new Http3RequestStreamEncodeStateValidator(); 75 | Http3RequestStreamDecodeStateValidator decodeStateValidator = new Http3RequestStreamDecodeStateValidator(); 76 | // Add the encoder and decoder in the pipeline so we can handle Http3Frames 77 | pipeline.addLast(newCodec(encodeStateValidator, decodeStateValidator)); 78 | pipeline.addLast(encodeStateValidator); 79 | pipeline.addLast(decodeStateValidator); 80 | pipeline.addLast(newRequestStreamValidationHandler(streamChannel, encodeStateValidator, decodeStateValidator)); 81 | pipeline.addLast(requestStreamHandler); 82 | } 83 | 84 | @Override 85 | void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel) { 86 | final Long maxTableCapacity = remoteControlStreamHandler.localSettings() 87 | .get(HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY); 88 | streamChannel.pipeline().addLast( 89 | new Http3UnidirectionalStreamInboundServerHandler(codecFactory, 90 | localControlStreamHandler, remoteControlStreamHandler, 91 | unknownInboundStreamHandlerFactory, 92 | () -> new QpackEncoderHandler(maxTableCapacity, qpackDecoder), 93 | () -> new QpackDecoderHandler(qpackEncoder))); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | xyz.nyist 8 | http3 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 8 13 | 8 14 | 4.1.77.Final 15 | 0.0.28.Final 16 | 1.0.19 17 | ${os.detected.name}-${os.detected.arch} 18 | 19 | 20 | 21 | 22 | 23 | commons-io 24 | commons-io 25 | 2.11.0 26 | 27 | 28 | 29 | org.projectlombok 30 | lombok 31 | 1.18.24 32 | true 33 | 34 | 35 | 36 | 37 | org.slf4j 38 | slf4j-api 39 | 1.7.36 40 | 41 | 42 | 43 | ch.qos.logback 44 | logback-classic 45 | 1.2.11 46 | 47 | 48 | 49 | ch.qos.logback 50 | logback-core 51 | 1.2.11 52 | 53 | 54 | 55 | io.netty 56 | netty-all 57 | ${netty.version} 58 | 59 | 60 | 61 | 62 | io.projectreactor.netty 63 | reactor-netty-core 64 | ${reactor.netty.version} 65 | 66 | 67 | 68 | io.projectreactor.netty 69 | reactor-netty-http 70 | ${reactor.netty.version} 71 | 72 | 73 | 74 | io.netty.incubator 75 | netty-incubator-codec-classes-quic 76 | ${netty.quic.version} 77 | 78 | 79 | 80 | io.netty.incubator 81 | netty-incubator-codec-native-quic 82 | ${netty.quic.version} 83 | ${netty.quic.classifier} 84 | runtime 85 | 86 | 87 | 88 | org.springframework 89 | spring-web 90 | 5.3.21 91 | provided 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | kr.motd.maven 102 | os-maven-plugin 103 | 1.7.0 104 | 105 | 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-dependency-plugin 110 | 3.1.2 111 | 112 | 113 | maven-compiler-plugin 114 | 3.8.0 115 | 116 | 1.8 117 | true 118 | 1.8 119 | 1.8 120 | true 121 | true 122 | true 123 | true 124 | -Xlint:-options 125 | 256m 126 | 1024m 127 | 128 | **/package-info.java 129 | 130 | 131 | 132 | 133 | 134 | org.apache.maven.plugins 135 | maven-source-plugin 136 | 3.2.1 137 | 138 | true 139 | 140 | 141 | 142 | compile 143 | 144 | jar 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/quic/QuicServerBind.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-2022 VMware, Inc. or its affiliates, All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package xyz.nyist.quic; 17 | 18 | import io.netty.channel.Channel; 19 | import io.netty.channel.ChannelOption; 20 | import io.netty.util.NetUtil; 21 | import org.reactivestreams.Subscription; 22 | import reactor.core.CoreSubscriber; 23 | import reactor.core.Disposable; 24 | import reactor.core.publisher.Mono; 25 | import reactor.core.publisher.MonoSink; 26 | import reactor.core.publisher.Operators; 27 | import reactor.netty.ChannelBindException; 28 | import reactor.netty.Connection; 29 | import reactor.netty.transport.AddressUtils; 30 | import reactor.netty.transport.TransportConnector; 31 | import reactor.util.context.Context; 32 | 33 | import java.io.IOException; 34 | import java.net.BindException; 35 | import java.net.InetSocketAddress; 36 | import java.net.SocketAddress; 37 | import java.util.Collections; 38 | import java.util.Objects; 39 | 40 | import static reactor.netty.ReactorNetty.format; 41 | 42 | /** 43 | * Provides the actual {@link QuicServer} instance. 44 | * 45 | * @author Violeta Georgieva 46 | */ 47 | final class QuicServerBind extends QuicServer { 48 | 49 | static final QuicServerBind INSTANCE = new QuicServerBind(); 50 | 51 | final QuicServerConfig config; 52 | 53 | QuicServerBind() { 54 | this.config = new QuicServerConfig( 55 | Collections.emptyMap(), 56 | Collections.singletonMap(ChannelOption.AUTO_READ, false), 57 | () -> new InetSocketAddress(NetUtil.LOCALHOST, 0)); 58 | } 59 | 60 | QuicServerBind(QuicServerConfig config) { 61 | this.config = config; 62 | } 63 | 64 | static void validate(QuicServerConfig config) { 65 | Objects.requireNonNull(config.bindAddress(), "bindAddress"); 66 | Objects.requireNonNull(config.sslEngineProvider, "sslEngineProvider"); 67 | Objects.requireNonNull(config.tokenHandler, "tokenHandler"); 68 | } 69 | 70 | @Override 71 | public Mono bind() { 72 | QuicServerConfig config = configuration(); 73 | validate(config); 74 | 75 | Mono mono = Mono.create(sink -> { 76 | SocketAddress local = Objects.requireNonNull(config.bindAddress().get(), "Bind Address supplier returned null"); 77 | if (local instanceof InetSocketAddress) { 78 | InetSocketAddress localInet = (InetSocketAddress) local; 79 | 80 | if (localInet.isUnresolved()) { 81 | local = AddressUtils.createResolved(localInet.getHostName(), localInet.getPort()); 82 | } 83 | } 84 | 85 | DisposableBind disposableBind = new DisposableBind(local, sink); 86 | TransportConnector.bind(config, config.parentChannelInitializer(), local, false) 87 | .subscribe(disposableBind); 88 | }); 89 | 90 | if (config.doOnBind() != null) { 91 | mono = mono.doOnSubscribe(s -> config.doOnBind().accept(config)); 92 | } 93 | 94 | return mono; 95 | } 96 | 97 | @Override 98 | public QuicServerConfig configuration() { 99 | return config; 100 | } 101 | 102 | @Override 103 | protected QuicServer duplicate() { 104 | return new QuicServerBind(new QuicServerConfig(config)); 105 | } 106 | 107 | static final class DisposableBind implements CoreSubscriber, Disposable { 108 | 109 | final SocketAddress bindAddress; 110 | 111 | final Context currentContext; 112 | 113 | final MonoSink sink; 114 | 115 | Subscription subscription; 116 | 117 | DisposableBind(SocketAddress bindAddress, MonoSink sink) { 118 | this.bindAddress = bindAddress; 119 | this.currentContext = Context.of(sink.contextView()); 120 | this.sink = sink; 121 | } 122 | 123 | @Override 124 | public Context currentContext() { 125 | return currentContext; 126 | } 127 | 128 | @Override 129 | public void dispose() { 130 | subscription.cancel(); 131 | } 132 | 133 | @Override 134 | public void onComplete() { 135 | } 136 | 137 | @Override 138 | public void onError(Throwable t) { 139 | if (t instanceof BindException || 140 | // With epoll/kqueue transport it is 141 | // io.netty.channel.unix.Errors$NativeIoException: bind(..) failed: Address already in use 142 | (t instanceof IOException && t.getMessage() != null && t.getMessage().contains("bind(..)"))) { 143 | sink.error(ChannelBindException.fail(bindAddress, null)); 144 | } else { 145 | sink.error(t); 146 | } 147 | } 148 | 149 | @Override 150 | public void onNext(Channel channel) { 151 | if (log.isDebugEnabled()) { 152 | log.debug(format(channel, "Bound new channel")); 153 | } 154 | sink.success(Connection.from(channel)); 155 | } 156 | 157 | @Override 158 | public void onSubscribe(Subscription s) { 159 | if (Operators.validate(subscription, s)) { 160 | this.subscription = s; 161 | sink.onCancel(this); 162 | s.request(Long.MAX_VALUE); 163 | } 164 | } 165 | 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/QpackHuffmanEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.util.AsciiString; 20 | import io.netty.util.ByteProcessor; 21 | import io.netty.util.internal.ObjectUtil; 22 | 23 | final class QpackHuffmanEncoder { 24 | 25 | private final int[] codes; 26 | 27 | private final byte[] lengths; 28 | 29 | private final EncodedLengthProcessor encodedLengthProcessor = new EncodedLengthProcessor(); 30 | 31 | private final EncodeProcessor encodeProcessor = new EncodeProcessor(); 32 | 33 | QpackHuffmanEncoder() { 34 | this(QpackUtil.HUFFMAN_CODES, QpackUtil.HUFFMAN_CODE_LENGTHS); 35 | } 36 | 37 | /** 38 | * Creates a new Huffman encoder with the specified Huffman coding. 39 | * 40 | * @param codes the Huffman codes indexed by symbol 41 | * @param lengths the length of each Huffman code 42 | */ 43 | private QpackHuffmanEncoder(int[] codes, byte[] lengths) { 44 | this.codes = codes; 45 | this.lengths = lengths; 46 | } 47 | 48 | /** 49 | * Compresses the input string literal using the Huffman coding. 50 | * 51 | * @param out the output stream for the compressed data 52 | * @param data the string literal to be Huffman encoded 53 | */ 54 | public void encode(ByteBuf out, CharSequence data) { 55 | ObjectUtil.checkNotNull(out, "out"); 56 | if (data instanceof AsciiString) { 57 | AsciiString string = (AsciiString) data; 58 | try { 59 | encodeProcessor.out = out; 60 | string.forEachByte(encodeProcessor); 61 | } catch (Exception e) { 62 | throw new IllegalStateException(e); 63 | } finally { 64 | encodeProcessor.end(); 65 | } 66 | } else { 67 | encodeSlowPath(out, data); 68 | } 69 | } 70 | 71 | private void encodeSlowPath(ByteBuf out, CharSequence data) { 72 | long current = 0; 73 | int n = 0; 74 | 75 | for (int i = 0; i < data.length(); i++) { 76 | int b = data.charAt(i) & 0xFF; 77 | int code = codes[b]; 78 | int nbits = lengths[b]; 79 | 80 | current <<= nbits; 81 | current |= code; 82 | n += nbits; 83 | 84 | while (n >= 8) { 85 | n -= 8; 86 | out.writeByte((int) (current >> n)); 87 | } 88 | } 89 | 90 | if (n > 0) { 91 | current <<= 8 - n; 92 | current |= 0xFF >>> n; // this should be EOS symbol 93 | out.writeByte((int) current); 94 | } 95 | } 96 | 97 | /** 98 | * Returns the number of bytes required to Huffman encode the input string literal. 99 | * 100 | * @param data the string literal to be Huffman encoded 101 | * @return the number of bytes required to Huffman encode {@code data} 102 | */ 103 | int getEncodedLength(CharSequence data) { 104 | if (data instanceof AsciiString) { 105 | AsciiString string = (AsciiString) data; 106 | try { 107 | encodedLengthProcessor.reset(); 108 | string.forEachByte(encodedLengthProcessor); 109 | return encodedLengthProcessor.length(); 110 | } catch (Exception e) { 111 | throw new IllegalStateException(e); 112 | } 113 | } else { 114 | return getEncodedLengthSlowPath(data); 115 | } 116 | } 117 | 118 | private int getEncodedLengthSlowPath(CharSequence data) { 119 | long len = 0; 120 | for (int i = 0; i < data.length(); i++) { 121 | len += lengths[data.charAt(i) & 0xFF]; 122 | } 123 | return (int) ((len + 7) >> 3); 124 | } 125 | 126 | private final class EncodeProcessor implements ByteProcessor { 127 | 128 | ByteBuf out; 129 | 130 | private long current; 131 | 132 | private int n; 133 | 134 | @Override 135 | public boolean process(byte value) { 136 | int b = value & 0xFF; 137 | int nbits = lengths[b]; 138 | 139 | current <<= nbits; 140 | current |= codes[b]; 141 | n += nbits; 142 | 143 | while (n >= 8) { 144 | n -= 8; 145 | out.writeByte((int) (current >> n)); 146 | } 147 | return true; 148 | } 149 | 150 | void end() { 151 | try { 152 | if (n > 0) { 153 | current <<= 8 - n; 154 | current |= 0xFF >>> n; // this should be EOS symbol 155 | out.writeByte((int) current); 156 | } 157 | } finally { 158 | out = null; 159 | current = 0; 160 | n = 0; 161 | } 162 | } 163 | 164 | } 165 | 166 | private final class EncodedLengthProcessor implements ByteProcessor { 167 | 168 | private long len; 169 | 170 | @Override 171 | public boolean process(byte value) { 172 | len += lengths[value & 0xFF]; 173 | return true; 174 | } 175 | 176 | void reset() { 177 | len = 0; 178 | } 179 | 180 | int length() { 181 | return (int) ((len + 7) >> 3); 182 | } 183 | 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/QpackDecoderDynamicTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package xyz.nyist.core; 18 | 19 | import java.util.Arrays; 20 | 21 | import static java.lang.Math.floorDiv; 22 | import static xyz.nyist.core.QpackHeaderField.ENTRY_OVERHEAD; 23 | import static xyz.nyist.core.QpackUtil.*; 24 | 25 | final class QpackDecoderDynamicTable { 26 | 27 | private static final QpackException GET_ENTRY_ILLEGAL_INDEX_VALUE = 28 | QpackException.newStatic(QpackDecoderDynamicTable.class, "getEntry(...)", 29 | "QPACK - illegal decoder dynamic table index value"); 30 | 31 | private static final QpackException HEADER_TOO_LARGE = 32 | QpackException.newStatic(QpackDecoderDynamicTable.class, "add(...)", "QPACK - header entry too large."); 33 | 34 | // a circular queue of header fields 35 | private QpackHeaderField[] fields; 36 | 37 | private int head; 38 | 39 | private int tail; 40 | 41 | private long size; 42 | 43 | private long capacity = -1; // ensure setCapacity creates the array 44 | 45 | private int insertCount; 46 | 47 | int length() { 48 | return head < tail ? fields.length - tail + head : head - tail; 49 | } 50 | 51 | long size() { 52 | return size; 53 | } 54 | 55 | int insertCount() { 56 | return insertCount; 57 | } 58 | 59 | QpackHeaderField getEntry(int index) throws QpackException { 60 | if (index < 0 || fields == null || index >= fields.length) { 61 | throw GET_ENTRY_ILLEGAL_INDEX_VALUE; 62 | } 63 | QpackHeaderField entry = fields[index]; 64 | if (entry == null) { 65 | throw GET_ENTRY_ILLEGAL_INDEX_VALUE; 66 | } 67 | return entry; 68 | } 69 | 70 | QpackHeaderField getEntryRelativeEncodedField(int index) throws QpackException { 71 | // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#name-relative-indexing 72 | return getEntry(moduloIndex(index)); 73 | } 74 | 75 | QpackHeaderField getEntryRelativeEncoderInstructions(int index) throws QpackException { 76 | // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#name-relative-indexing 77 | // Name index is the relative index, relative to the last added entry. 78 | return getEntry(index > tail ? fields.length - index + tail : tail - index); 79 | } 80 | 81 | void add(QpackHeaderField header) throws QpackException { 82 | long headerSize = header.size(); 83 | if (headerSize > capacity) { 84 | throw HEADER_TOO_LARGE; 85 | } 86 | while (capacity - size < headerSize) { 87 | remove(); 88 | } 89 | insertCount++; 90 | fields[getAndIncrementHead()] = header; 91 | size += headerSize; 92 | } 93 | 94 | private void remove() { 95 | QpackHeaderField removed = fields[tail]; 96 | if (removed == null) { 97 | return; 98 | } 99 | size -= removed.size(); 100 | fields[getAndIncrementTail()] = null; 101 | } 102 | 103 | void clear() { 104 | if (fields != null) { 105 | Arrays.fill(fields, null); 106 | } 107 | head = 0; 108 | tail = 0; 109 | size = 0; 110 | } 111 | 112 | void setCapacity(long capacity) throws QpackException { 113 | if (capacity < MIN_HEADER_TABLE_SIZE || capacity > MAX_HEADER_TABLE_SIZE) { 114 | throw new IllegalArgumentException("capacity is invalid: " + capacity); 115 | } 116 | // initially capacity will be -1 so init won't return here 117 | if (this.capacity == capacity) { 118 | return; 119 | } 120 | this.capacity = capacity; 121 | 122 | if (capacity == 0) { 123 | clear(); 124 | } else { 125 | // initially size will be 0 so remove won't be called 126 | while (size > capacity) { 127 | remove(); 128 | } 129 | } 130 | 131 | int maxEntries = toIntOrThrow(2 * floorDiv(capacity, ENTRY_OVERHEAD)); 132 | 133 | // check if capacity change requires us to reallocate the array 134 | if (fields != null && fields.length == maxEntries) { 135 | return; 136 | } 137 | 138 | QpackHeaderField[] tmp = new QpackHeaderField[maxEntries]; 139 | 140 | // initially length will be 0 so there will be no copy 141 | int len = length(); 142 | if (fields != null && tail != head) { 143 | if (head > tail) { 144 | System.arraycopy(fields, tail, tmp, 0, head - tail); 145 | } else { 146 | System.arraycopy(fields, 0, tmp, 0, head); 147 | System.arraycopy(fields, tail, tmp, head, fields.length - tail); 148 | } 149 | } 150 | 151 | tail = 0; 152 | head = tail + len; 153 | fields = tmp; 154 | } 155 | 156 | private int getAndIncrementHead() { 157 | int val = this.head; 158 | this.head = safeIncrementIndex(val); 159 | return val; 160 | } 161 | 162 | private int getAndIncrementTail() { 163 | int val = this.tail; 164 | this.tail = safeIncrementIndex(val); 165 | return val; 166 | } 167 | 168 | private int safeIncrementIndex(int index) { 169 | return ++index % fields.length; 170 | } 171 | 172 | private int moduloIndex(int index) { 173 | return fields == null ? index : index % fields.length; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/adapter/ReactorServerHttp3Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package xyz.nyist.adapter; 18 | 19 | import io.netty.buffer.ByteBuf; 20 | import io.netty.channel.ChannelId; 21 | import org.apache.commons.logging.Log; 22 | import org.apache.commons.logging.LogFactory; 23 | import org.reactivestreams.Publisher; 24 | import org.springframework.core.io.buffer.DataBuffer; 25 | import org.springframework.core.io.buffer.DataBufferFactory; 26 | import org.springframework.core.io.buffer.DataBufferUtils; 27 | import org.springframework.core.io.buffer.NettyDataBufferFactory; 28 | import org.springframework.http.HttpHeaders; 29 | import org.springframework.http.HttpStatus; 30 | import org.springframework.http.ResponseCookie; 31 | import org.springframework.http.ZeroCopyHttpOutputMessage; 32 | import org.springframework.http.server.reactive.AbstractServerHttpResponse; 33 | import org.springframework.http.server.reactive.ServerHttpResponse; 34 | import org.springframework.util.Assert; 35 | import reactor.core.publisher.Flux; 36 | import reactor.core.publisher.Mono; 37 | import reactor.netty.ChannelOperationsId; 38 | import reactor.netty.http.server.HttpServerResponse; 39 | import xyz.nyist.core.Http3Exception; 40 | import xyz.nyist.http.server.Http3ServerResponse; 41 | 42 | import java.nio.file.Path; 43 | import java.util.List; 44 | 45 | /** 46 | * Adapt {@link ServerHttpResponse} to the {@link HttpServerResponse}. 47 | * 48 | * @author Stephane Maldini 49 | * @author Rossen Stoyanchev 50 | * @since 5.0 51 | */ 52 | public class ReactorServerHttp3Response extends AbstractServerHttpResponse implements ZeroCopyHttpOutputMessage { 53 | 54 | private static final Log logger = LogFactory.getLog(ReactorServerHttp3Response.class); 55 | 56 | 57 | private final Http3ServerResponse response; 58 | 59 | 60 | public ReactorServerHttp3Response(Http3ServerResponse response, DataBufferFactory bufferFactory) throws Http3Exception { 61 | super(bufferFactory, new HttpHeaders(new Netty3HeadersAdapter(response.outboundHttpMessage(), false))); 62 | Assert.notNull(response, "HttpServerResponse must not be null"); 63 | this.response = response; 64 | } 65 | 66 | 67 | @SuppressWarnings("unchecked") 68 | @Override 69 | public T getNativeResponse() { 70 | return (T) this.response; 71 | } 72 | 73 | @Override 74 | public HttpStatus getStatusCode() { 75 | HttpStatus status = super.getStatusCode(); 76 | return (status != null ? status : HttpStatus.resolve(Integer.parseInt(this.response.status().toString()))); 77 | } 78 | 79 | @Override 80 | public Integer getRawStatusCode() { 81 | Integer status = super.getRawStatusCode(); 82 | return (status != null ? status : Integer.parseInt(this.response.status().toString())); 83 | } 84 | 85 | @Override 86 | protected void applyStatusCode() { 87 | Integer status = super.getRawStatusCode(); 88 | if (status != null) { 89 | this.response.status(status); 90 | } 91 | } 92 | 93 | @Override 94 | protected Mono writeWithInternal(Publisher publisher) { 95 | return this.response.send(toByteBufs(publisher)).then(); 96 | } 97 | 98 | @Override 99 | protected Mono writeAndFlushWithInternal(Publisher> publisher) { 100 | return this.response.sendGroups(Flux.from(publisher).map(this::toByteBufs)).then(); 101 | } 102 | 103 | @Override 104 | protected void applyHeaders() { 105 | } 106 | 107 | @Override 108 | protected void applyCookies() { 109 | // Netty Cookie doesn't support sameSite. When this is resolved, we can adapt to it again: 110 | // https://github.com/netty/netty/issues/8161 111 | for (List cookies : getCookies().values()) { 112 | for (ResponseCookie cookie : cookies) { 113 | this.response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); 114 | } 115 | } 116 | } 117 | 118 | @Override 119 | public Mono writeWith(Path file, long position, long count) { 120 | return doCommit(() -> this.response.sendFile(file, position, count).then()); 121 | } 122 | 123 | private Publisher toByteBufs(Publisher dataBuffers) { 124 | return dataBuffers instanceof Mono ? 125 | Mono.from(dataBuffers).map(NettyDataBufferFactory::toByteBuf) : 126 | Flux.from(dataBuffers).map(NettyDataBufferFactory::toByteBuf); 127 | } 128 | 129 | @Override 130 | protected void touchDataBuffer(DataBuffer buffer) { 131 | if (logger.isDebugEnabled()) { 132 | if (ReactorServerHttp3Request.REACTOR_NETTY_REQUEST_CHANNEL_OPERATIONS_ID_PRESENT) { 133 | if (ChannelOperationsIdHelper.touch(buffer, this.response)) { 134 | return; 135 | } 136 | } 137 | this.response.withConnection(connection -> { 138 | ChannelId id = connection.channel().id(); 139 | DataBufferUtils.touch(buffer, "Channel id: " + id.asShortText()); 140 | }); 141 | } 142 | } 143 | 144 | 145 | private static class ChannelOperationsIdHelper { 146 | 147 | public static boolean touch(DataBuffer dataBuffer, Http3ServerResponse response) { 148 | if (response instanceof ChannelOperationsId) { 149 | String id = ((ChannelOperationsId) response).asLongText(); 150 | DataBufferUtils.touch(dataBuffer, "Channel id: " + id); 151 | return true; 152 | } 153 | return false; 154 | } 155 | 156 | } 157 | 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/xyz/nyist/core/QpackDecoderHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package xyz.nyist.core; 17 | 18 | import io.netty.buffer.ByteBuf; 19 | import io.netty.channel.ChannelHandlerContext; 20 | import io.netty.channel.socket.ChannelInputShutdownEvent; 21 | import io.netty.handler.codec.ByteToMessageDecoder; 22 | 23 | import java.util.List; 24 | 25 | import static xyz.nyist.core.Http3CodecUtils.connectionError; 26 | import static xyz.nyist.core.Http3ErrorCode.QPACK_DECODER_STREAM_ERROR; 27 | import static xyz.nyist.core.QpackUtil.decodePrefixedIntegerAsInt; 28 | 29 | final class QpackDecoderHandler extends ByteToMessageDecoder { 30 | 31 | private final QpackEncoder qpackEncoder; 32 | 33 | private boolean discard; 34 | 35 | QpackDecoderHandler(QpackEncoder qpackEncoder) { 36 | this.qpackEncoder = qpackEncoder; 37 | } 38 | 39 | @Override 40 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 41 | if (!in.isReadable()) { 42 | return; 43 | } 44 | if (discard) { 45 | in.skipBytes(in.readableBytes()); 46 | return; 47 | } 48 | 49 | byte b = in.getByte(in.readerIndex()); 50 | 51 | // 4.4.1. Section Acknowledgment 52 | // 53 | // 0 1 2 3 4 5 6 7 54 | // +---+---+---+---+---+---+---+---+ 55 | // | 1 | Stream ID (7+) | 56 | // +---+---------------------------+ 57 | if ((b & 0b1000_0000) == 0b1000_0000) { 58 | long streamId = QpackUtil.decodePrefixedInteger(in, 7); 59 | if (streamId < 0) { 60 | // Not enough readable bytes 61 | return; 62 | } 63 | try { 64 | qpackEncoder.sectionAcknowledgment(streamId); 65 | } catch (QpackException e) { 66 | connectionError(ctx, new Http3Exception(QPACK_DECODER_STREAM_ERROR, 67 | "Section acknowledgment decode failed.", e), true); 68 | } 69 | return; 70 | } 71 | 72 | // 4.4.2. Stream Cancellation 73 | // 74 | // 0 1 2 3 4 5 6 7 75 | // +---+---+---+---+---+---+---+---+ 76 | // | 0 | 1 | Stream ID (6+) | 77 | // +---+---+-----------------------+ 78 | if ((b & 0b1100_0000) == 0b0100_0000) { 79 | long streamId = QpackUtil.decodePrefixedInteger(in, 6); 80 | if (streamId < 0) { 81 | // Not enough readable bytes 82 | return; 83 | } 84 | try { 85 | qpackEncoder.streamCancellation(streamId); 86 | } catch (QpackException e) { 87 | connectionError(ctx, new Http3Exception(QPACK_DECODER_STREAM_ERROR, 88 | "Stream cancellation decode failed.", e), true); 89 | } 90 | return; 91 | } 92 | 93 | // 4.4.3. Insert Count Increment 94 | // 95 | // 0 1 2 3 4 5 6 7 96 | // +---+---+---+---+---+---+---+---+ 97 | // | 0 | 0 | Increment (6+) | 98 | // +---+---+-----------------------+ 99 | if ((b & 0b1100_0000) == 0b0000_0000) { 100 | int increment = decodePrefixedIntegerAsInt(in, 6); 101 | if (increment == 0) { 102 | discard = true; 103 | // Zero is not allowed as an increment 104 | // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#name-insert-count-increment 105 | // Increment is an unsigned integer, so only 0 is the invalid value. 106 | // https://www.rfc-editor.org/rfc/rfc7541#section-5 107 | connectionError(ctx, QPACK_DECODER_STREAM_ERROR, 108 | "Invalid increment '" + increment + "'.", false); 109 | return; 110 | } 111 | if (increment < 0) { 112 | // Not enough readable bytes 113 | return; 114 | } 115 | try { 116 | qpackEncoder.insertCountIncrement(increment); 117 | } catch (QpackException e) { 118 | connectionError(ctx, new Http3Exception(QPACK_DECODER_STREAM_ERROR, 119 | "Insert count increment decode failed.", e), true); 120 | } 121 | return; 122 | } 123 | // unknown frame 124 | discard = true; 125 | connectionError(ctx, QPACK_DECODER_STREAM_ERROR, 126 | "Unknown decoder instruction '" + b + "'.", false); 127 | } 128 | 129 | @Override 130 | public void channelReadComplete(ChannelHandlerContext ctx) { 131 | ctx.fireChannelReadComplete(); 132 | 133 | // QPACK streams should always be processed, no matter what the user is doing in terms of configuration 134 | // and AUTO_READ. 135 | Http3CodecUtils.readIfNoAutoRead(ctx); 136 | } 137 | 138 | @Override 139 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { 140 | if (evt instanceof ChannelInputShutdownEvent) { 141 | // See https://www.ietf.org/archive/id/draft-ietf-quic-qpack-19.html#section-4.2 142 | Http3CodecUtils.criticalStreamClosed(ctx); 143 | } 144 | ctx.fireUserEventTriggered(evt); 145 | } 146 | 147 | @Override 148 | public void channelInactive(ChannelHandlerContext ctx) { 149 | // See https://www.ietf.org/archive/id/draft-ietf-quic-qpack-19.html#section-4.2 150 | Http3CodecUtils.criticalStreamClosed(ctx); 151 | ctx.fireChannelInactive(); 152 | } 153 | 154 | } 155 | --------------------------------------------------------------------------------