|
19 | 19 | import io.netty.bootstrap.Bootstrap;
|
20 | 20 | import io.netty.bootstrap.ServerBootstrap;
|
21 | 21 | import io.netty.buffer.ByteBuf;
|
| 22 | +import io.netty.buffer.Unpooled; |
22 | 23 | import io.netty.channel.Channel;
|
| 24 | +import io.netty.channel.ChannelFuture; |
23 | 25 | import io.netty.channel.ChannelHandlerContext;
|
24 | 26 | import io.netty.channel.ChannelInboundHandlerAdapter;
|
25 | 27 | import io.netty.channel.ChannelInitializer;
|
|
28 | 30 | import io.netty.channel.socket.SocketChannel;
|
29 | 31 | import io.netty.channel.socket.nio.NioServerSocketChannel;
|
30 | 32 | import io.netty.channel.socket.nio.NioSocketChannel;
|
| 33 | +import io.netty.handler.codec.http.DefaultFullHttpRequest; |
| 34 | +import io.netty.handler.codec.http.FullHttpRequest; |
| 35 | +import io.netty.handler.codec.http.FullHttpResponse; |
31 | 36 | import io.netty.handler.codec.http.HttpClientCodec;
|
| 37 | +import io.netty.handler.codec.http.HttpMethod; |
32 | 38 | import io.netty.handler.codec.http.HttpObjectAggregator;
|
33 | 39 | import io.netty.handler.codec.http.HttpRequest;
|
| 40 | +import io.netty.handler.codec.http.HttpResponseStatus; |
34 | 41 | import io.netty.handler.codec.http.HttpServerCodec;
|
| 42 | +import io.netty.handler.codec.http.HttpVersion; |
| 43 | +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakeException; |
35 | 44 | import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig;
|
| 45 | +import io.netty.util.AttributeKey; |
36 | 46 | import io.netty.util.ReferenceCountUtil;
|
| 47 | +import io.netty.util.concurrent.DefaultPromise; |
| 48 | +import io.netty.util.concurrent.Future; |
| 49 | +import io.netty.util.concurrent.Promise; |
37 | 50 | import java.net.SocketAddress;
|
| 51 | +import java.nio.channels.ClosedChannelException; |
38 | 52 | import java.util.concurrent.CompletableFuture;
|
39 | 53 | import java.util.function.Consumer;
|
40 | 54 | import org.assertj.core.api.Assertions;
|
@@ -174,6 +188,123 @@ void serverBuilderMissingHandler() {
|
174 | 188 | });
|
175 | 189 | }
|
176 | 190 |
|
| 191 | + @Timeout(15) |
| 192 | + @Test |
| 193 | + void clientTimeout() throws InterruptedException { |
| 194 | + Channel s = |
| 195 | + server = |
| 196 | + new ServerBootstrap() |
| 197 | + .group(new NioEventLoopGroup(1)) |
| 198 | + .channel(NioServerSocketChannel.class) |
| 199 | + .childHandler( |
| 200 | + new ChannelInitializer<SocketChannel>() { |
| 201 | + |
| 202 | + @Override |
| 203 | + protected void initChannel(SocketChannel ch) { |
| 204 | + ChannelPipeline pipeline = ch.pipeline(); |
| 205 | + pipeline.addLast( |
| 206 | + new ChannelInboundHandlerAdapter() { |
| 207 | + @Override |
| 208 | + public void channelRead(ChannelHandlerContext ctx, Object msg) { |
| 209 | + ReferenceCountUtil.safeRelease(msg); |
| 210 | + } |
| 211 | + }); |
| 212 | + } |
| 213 | + }) |
| 214 | + .bind("localhost", 0) |
| 215 | + .sync() |
| 216 | + .channel(); |
| 217 | + |
| 218 | + AttributeKey<ChannelFuture> handshakeKey = AttributeKey.newInstance("handshake"); |
| 219 | + |
| 220 | + Channel client = |
| 221 | + new Bootstrap() |
| 222 | + .group(new NioEventLoopGroup(1)) |
| 223 | + .channel(NioSocketChannel.class) |
| 224 | + .handler( |
| 225 | + new ChannelInitializer<SocketChannel>() { |
| 226 | + @Override |
| 227 | + protected void initChannel(SocketChannel ch) { |
| 228 | + |
| 229 | + HttpClientCodec http1Codec = new HttpClientCodec(); |
| 230 | + HttpObjectAggregator http1Aggregator = new HttpObjectAggregator(65536); |
| 231 | + |
| 232 | + WebSocketClientProtocolHandler webSocketProtocolHandler = |
| 233 | + WebSocketClientProtocolHandler.create() |
| 234 | + .handshakeTimeoutMillis(1) |
| 235 | + .allowMaskMismatch(true) |
| 236 | + .webSocketHandler( |
| 237 | + (ctx, webSocketFrameFactory) -> { |
| 238 | + throw new AssertionError("should not be called"); |
| 239 | + }) |
| 240 | + .build(); |
| 241 | + |
| 242 | + ChannelPipeline pipeline = ch.pipeline(); |
| 243 | + pipeline.addLast(http1Codec, http1Aggregator, webSocketProtocolHandler); |
| 244 | + |
| 245 | + ChannelFuture handshake = webSocketProtocolHandler.handshakeCompleted(); |
| 246 | + ch.attr(handshakeKey).set(handshake); |
| 247 | + } |
| 248 | + }) |
| 249 | + .connect(s.localAddress()) |
| 250 | + .sync() |
| 251 | + .channel(); |
| 252 | + |
| 253 | + ChannelFuture handshakeFuture = client.attr(handshakeKey).get(); |
| 254 | + handshakeFuture.await(); |
| 255 | + Throwable cause = handshakeFuture.cause(); |
| 256 | + Assertions.assertThat(cause).isNotNull(); |
| 257 | + Assertions.assertThat(cause).isInstanceOf(WebSocketClientHandshakeException.class); |
| 258 | + client.closeFuture().await(); |
| 259 | + Assertions.assertThat(client.isOpen()).isFalse(); |
| 260 | + } |
| 261 | + |
| 262 | + @Timeout(15) |
| 263 | + @Test |
| 264 | + void serverNonWebSocketRequest() throws InterruptedException { |
| 265 | + WebSocketDecoderConfig decoderConfig = webSocketDecoderConfig(false, true, 125); |
| 266 | + TestWebSocketHandler serverHandler = new TestWebSocketHandler(); |
| 267 | + Channel s = server = testServer("/", decoderConfig, serverHandler); |
| 268 | + |
| 269 | + AttributeKey<Future<FullHttpResponse>> handshakeKey = AttributeKey.newInstance("response"); |
| 270 | + |
| 271 | + Channel client = |
| 272 | + new Bootstrap() |
| 273 | + .group(new NioEventLoopGroup(1)) |
| 274 | + .channel(NioSocketChannel.class) |
| 275 | + .handler( |
| 276 | + new ChannelInitializer<SocketChannel>() { |
| 277 | + @Override |
| 278 | + protected void initChannel(SocketChannel ch) { |
| 279 | + |
| 280 | + HttpClientCodec http1Codec = new HttpClientCodec(); |
| 281 | + HttpObjectAggregator http1Aggregator = new HttpObjectAggregator(65536); |
| 282 | + NonWebSocketRequestHandler nonWebSocketRequestHandler = |
| 283 | + new NonWebSocketRequestHandler(); |
| 284 | + ChannelPipeline pipeline = ch.pipeline(); |
| 285 | + pipeline.addLast(http1Codec, http1Aggregator, nonWebSocketRequestHandler); |
| 286 | + |
| 287 | + Future<FullHttpResponse> handshake = nonWebSocketRequestHandler.response(); |
| 288 | + ch.attr(handshakeKey).set(handshake); |
| 289 | + } |
| 290 | + }) |
| 291 | + .connect(s.localAddress()) |
| 292 | + .sync() |
| 293 | + .channel(); |
| 294 | + |
| 295 | + Future<FullHttpResponse> responseFuture = client.attr(handshakeKey).get(); |
| 296 | + responseFuture.await(); |
| 297 | + FullHttpResponse response = responseFuture.getNow(); |
| 298 | + try { |
| 299 | + Assertions.assertThat(response).isNotNull(); |
| 300 | + Assertions.assertThat(response.status()).isEqualTo(HttpResponseStatus.BAD_REQUEST); |
| 301 | + } finally { |
| 302 | + response.release(); |
| 303 | + } |
| 304 | + client.closeFuture().await(); |
| 305 | + Assertions.assertThat(client.isOpen()).isFalse(); |
| 306 | + } |
| 307 | + |
177 | 308 | static Channel testClient(
|
178 | 309 | SocketAddress address,
|
179 | 310 | String path,
|
@@ -236,6 +367,51 @@ static Channel testServer(
|
236 | 367 | .channel();
|
237 | 368 | }
|
238 | 369 |
|
| 370 | + static class NonWebSocketRequestHandler extends ChannelInboundHandlerAdapter { |
| 371 | + private Promise<FullHttpResponse> responsePromise; |
| 372 | + |
| 373 | + @Override |
| 374 | + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
| 375 | + if (msg instanceof FullHttpResponse) { |
| 376 | + responsePromise.trySuccess((FullHttpResponse) msg); |
| 377 | + return; |
| 378 | + } |
| 379 | + super.channelRead(ctx, msg); |
| 380 | + } |
| 381 | + |
| 382 | + @Override |
| 383 | + public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
| 384 | + responsePromise.tryFailure(new ClosedChannelException()); |
| 385 | + super.channelInactive(ctx); |
| 386 | + } |
| 387 | + |
| 388 | + @Override |
| 389 | + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { |
| 390 | + responsePromise.tryFailure(cause); |
| 391 | + super.exceptionCaught(ctx, cause); |
| 392 | + } |
| 393 | + |
| 394 | + @Override |
| 395 | + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { |
| 396 | + super.handlerAdded(ctx); |
| 397 | + responsePromise = new DefaultPromise<>(ctx.executor()); |
| 398 | + } |
| 399 | + |
| 400 | + @Override |
| 401 | + public void channelActive(ChannelHandlerContext ctx) throws Exception { |
| 402 | + super.channelActive(ctx); |
| 403 | + FullHttpRequest request = |
| 404 | + new DefaultFullHttpRequest( |
| 405 | + HttpVersion.HTTP_1_1, HttpMethod.POST, "/", Unpooled.EMPTY_BUFFER); |
| 406 | + |
| 407 | + ctx.writeAndFlush(request); |
| 408 | + } |
| 409 | + |
| 410 | + Future<FullHttpResponse> response() { |
| 411 | + return responsePromise; |
| 412 | + } |
| 413 | + } |
| 414 | + |
239 | 415 | static class TestAcceptor extends ChannelInitializer<SocketChannel> {
|
240 | 416 | private final String path;
|
241 | 417 | private final WebSocketDecoderConfig webSocketDecoderConfig;
|
|
0 commit comments