|
30 | 30 | import io.netty.channel.group.ChannelGroup; |
31 | 31 | import io.netty.channel.group.DefaultChannelGroup; |
32 | 32 | import io.netty.channel.unix.DomainSocketAddress; |
| 33 | +import io.netty.handler.codec.http.HttpHeaderNames; |
| 34 | +import io.netty.handler.codec.http.HttpHeaderValues; |
33 | 35 | import io.netty.handler.codec.http.HttpRequest; |
34 | 36 | import io.netty.handler.codec.http.LastHttpContent; |
35 | 37 | import io.netty.handler.codec.http2.Http2StreamChannel; |
@@ -169,13 +171,14 @@ static void createSelfSignedCertificate() throws CertificateException { |
169 | 171 | * <ul> |
170 | 172 | * <li> /1 is used by testExistingEndpoint test</li> |
171 | 173 | * <li> /2 is used by testExistingEndpoint, and testUriTagValueFunctionNotSharedForClient tests</li> |
172 | | - * <li> /3 does not exists but is used by testNonExistingEndpoint, checkExpectationsNonExisting tests</li> |
| 174 | + * <li> /3 does not exist but is used by testNonExistingEndpoint, checkExpectationsNonExisting tests</li> |
173 | 175 | * <li> /4 is used by testServerConnectionsMicrometer test</li> |
174 | 176 | * <li> /5 is used by testServerConnectionsRecorder test</li> |
175 | 177 | * <li> /6 is used by testServerConnectionsMicrometerConnectionClose test</li> |
176 | 178 | * <li> /7 is used by testServerConnectionsRecorderConnectionClose test</li> |
177 | 179 | * <li> /8 is used by testServerConnectionsWebsocketMicrometer test</li> |
178 | 180 | * <li> /9 is used by testServerConnectionsWebsocketRecorder test</li> |
| 181 | + * <li> /10 is used by testIssue3883 for testing 100-Continue</li> |
179 | 182 | * </ul> |
180 | 183 | */ |
181 | 184 | @BeforeEach |
@@ -210,6 +213,10 @@ void setUp() { |
210 | 213 | out.sendString(Mono.just("Hello World!").doOnNext(b -> checkServerConnectionsMicrometer(req))))) |
211 | 214 | .get("/9", (req, res) -> res.sendWebsocket((in, out) -> |
212 | 215 | out.sendString(Mono.just("Hello World!").doOnNext(b -> checkServerConnectionsRecorder(req))))) |
| 216 | + .post("/10", (req, res) -> req.receive() |
| 217 | + .aggregate() |
| 218 | + .asString() |
| 219 | + .flatMap(s -> res.header("Connection", "close").sendString(Mono.just(s)).then())) |
213 | 220 | ); |
214 | 221 |
|
215 | 222 | provider = ConnectionProvider.create("HttpMetricsHandlerTests", 1); |
@@ -1076,6 +1083,58 @@ void testIssue3060ConnectTimeoutException(@SuppressWarnings("unused") HttpProtoc |
1076 | 1083 | assertTimer(registry, CLIENT_CONNECT_TIME, summaryTags).hasCountEqualTo(1); |
1077 | 1084 | } |
1078 | 1085 |
|
| 1086 | + @ParameterizedTest |
| 1087 | + @MethodSource("httpCompatibleProtocols") |
| 1088 | + void testIssue3883(HttpProtocol[] serverProtocols, HttpProtocol[] clientProtocols, |
| 1089 | + @Nullable ProtocolSslContextSpec serverCtx, @Nullable ProtocolSslContextSpec clientCtx) throws Exception { |
| 1090 | + CountDownLatch responseSent = new CountDownLatch(1); // response fully sent by the server |
| 1091 | + AtomicReference<CountDownLatch> responseSentRef = new AtomicReference<>(responseSent); |
| 1092 | + ResponseSentHandler responseSentHandler = ResponseSentHandler.INSTANCE; |
| 1093 | + disposableServer = customizeServerOptions(httpServer, serverCtx, serverProtocols) |
| 1094 | + .doOnConnection(cnx -> responseSentHandler.register(responseSentRef, cnx.channel().pipeline())) |
| 1095 | + .bindNow(); |
| 1096 | + |
| 1097 | + AtomicReference<SocketAddress> serverAddress = new AtomicReference<>(); |
| 1098 | + CountDownLatch clientCompleted = new CountDownLatch(1); // client received full response |
| 1099 | + AtomicReference<CountDownLatch> clientCompletedRef = new AtomicReference<>(clientCompleted); |
| 1100 | + httpClient = customizeClientOptions(httpClient, clientCtx, clientProtocols) |
| 1101 | + .doAfterRequest((req, conn) -> serverAddress.set(conn.channel().remoteAddress())) |
| 1102 | + .doAfterResponseSuccess((resp, conn) -> clientCompletedRef.get().countDown()); |
| 1103 | + |
| 1104 | + httpClient.headers(h -> h.add(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE)) |
| 1105 | + .post() |
| 1106 | + .uri("/10") |
| 1107 | + .send(body) |
| 1108 | + .responseContent() |
| 1109 | + .aggregate() |
| 1110 | + .asString() |
| 1111 | + .as(StepVerifier::create) |
| 1112 | + .expectNext("Hello World!") |
| 1113 | + .expectComplete() |
| 1114 | + .verify(Duration.ofSeconds(5)); |
| 1115 | + |
| 1116 | + assertThat(responseSentRef.get().await(30, TimeUnit.SECONDS)).as("responseSentRef latch await").isTrue(); |
| 1117 | + assertThat(clientCompletedRef.get().await(30, TimeUnit.SECONDS)).as("clientCompletedRef latch await").isTrue(); |
| 1118 | + |
| 1119 | + InetSocketAddress sa = (InetSocketAddress) serverAddress.get(); |
| 1120 | + |
| 1121 | + int[] numWrites = new int[]{14, 25}; |
| 1122 | + int[] bytesWrite = new int[]{160, 243}; |
| 1123 | + if ((serverProtocols.length == 1 && serverProtocols[0] == HttpProtocol.HTTP11) || |
| 1124 | + (clientProtocols.length == 1 && clientProtocols[0] == HttpProtocol.HTTP11)) { |
| 1125 | + numWrites = new int[]{14, 28}; |
| 1126 | + bytesWrite = new int[]{151, 310}; |
| 1127 | + } |
| 1128 | + else if (clientProtocols.length == 2 && |
| 1129 | + Arrays.equals(clientProtocols, new HttpProtocol[]{HttpProtocol.H2C, HttpProtocol.HTTP11})) { |
| 1130 | + numWrites = new int[]{17, 28}; |
| 1131 | + bytesWrite = new int[]{315, 435}; |
| 1132 | + } |
| 1133 | + |
| 1134 | + checkExpectationsExisting("/10", sa.getHostString() + ":" + sa.getPort(), 1, serverCtx != null, |
| 1135 | + numWrites[0], bytesWrite[0]); |
| 1136 | + } |
| 1137 | + |
1079 | 1138 | static Stream<Arguments> combinationsIssue2956() { |
1080 | 1139 | return Stream.of( |
1081 | 1140 | // isCustomRecorder, isHttp2 |
|
0 commit comments