Skip to content

Commit e207c5c

Browse files
committed
Merge branch 'release/0.9.2'
2 parents c9c3e70 + e3dca48 commit e207c5c

File tree

8 files changed

+365
-17
lines changed

8 files changed

+365
-17
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,18 @@ Per-core throughput [this codec perf-test](https://github.com/jauntsdn/netty-web
3636
comparison with netty's out-of-the-box websocket handlers:
3737
non-masked frames with 8, 64, 125, 1000 bytes of payload over encrypted/non-encrypted connection.
3838

39+
java 9+
3940
```
4041
./gradlew clean build installDist
4142
./perf_server_run.sh
4243
./perf_client_run.sh
4344
```
45+
java 8
46+
```
47+
./gradlew clean build installDist
48+
./perf_server_java8_run.sh
49+
./perf_client_run.sh
50+
```
4451

4552
* non-encrypted
4653

@@ -166,7 +173,7 @@ repositories {
166173
}
167174
168175
dependencies {
169-
implementation "com.jauntsdn.netty:netty-websocket-http1:0.9.0"
176+
implementation "com.jauntsdn.netty:netty-websocket-http1:0.9.1"
170177
}
171178
```
172179

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group=com.jauntsdn.netty
2-
version=0.9.1
2+
version=0.9.2
33

44
googleJavaFormatPluginVersion=0.9
55
dependencyManagementPluginVersion=1.1.0

netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketHandshakeTest.java

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import io.netty.bootstrap.Bootstrap;
2020
import io.netty.bootstrap.ServerBootstrap;
2121
import io.netty.buffer.ByteBuf;
22+
import io.netty.buffer.Unpooled;
2223
import io.netty.channel.Channel;
24+
import io.netty.channel.ChannelFuture;
2325
import io.netty.channel.ChannelHandlerContext;
2426
import io.netty.channel.ChannelInboundHandlerAdapter;
2527
import io.netty.channel.ChannelInitializer;
@@ -28,13 +30,25 @@
2830
import io.netty.channel.socket.SocketChannel;
2931
import io.netty.channel.socket.nio.NioServerSocketChannel;
3032
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;
3136
import io.netty.handler.codec.http.HttpClientCodec;
37+
import io.netty.handler.codec.http.HttpMethod;
3238
import io.netty.handler.codec.http.HttpObjectAggregator;
3339
import io.netty.handler.codec.http.HttpRequest;
40+
import io.netty.handler.codec.http.HttpResponseStatus;
3441
import io.netty.handler.codec.http.HttpServerCodec;
42+
import io.netty.handler.codec.http.HttpVersion;
43+
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakeException;
3544
import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig;
45+
import io.netty.util.AttributeKey;
3646
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;
3750
import java.net.SocketAddress;
51+
import java.nio.channels.ClosedChannelException;
3852
import java.util.concurrent.CompletableFuture;
3953
import java.util.function.Consumer;
4054
import org.assertj.core.api.Assertions;
@@ -174,6 +188,123 @@ void serverBuilderMissingHandler() {
174188
});
175189
}
176190

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+
177308
static Channel testClient(
178309
SocketAddress address,
179310
String path,
@@ -236,6 +367,51 @@ static Channel testServer(
236367
.channel();
237368
}
238369

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+
239415
static class TestAcceptor extends ChannelInitializer<SocketChannel> {
240416
private final String path;
241417
private final WebSocketDecoderConfig webSocketDecoderConfig;

netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketValidationTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,28 @@ void tearDown() throws Exception {
6060
}
6161
}
6262

63+
@Test
64+
void serverBuilderMinimalConfigIsValid() {
65+
WebSocketServerProtocolHandler serverProtocolHandler =
66+
WebSocketServerProtocolHandler.create()
67+
.webSocketCallbacksHandler(
68+
(ctx, webSocketFrameFactory) -> {
69+
throw new AssertionError("never called");
70+
})
71+
.build();
72+
}
73+
74+
@Test
75+
void clientBuilderMinimalConfigIsValid() {
76+
WebSocketClientProtocolHandler clientProtocolHandler =
77+
WebSocketClientProtocolHandler.create()
78+
.webSocketHandler(
79+
(ctx, webSocketFrameFactory) -> {
80+
throw new AssertionError("never called");
81+
})
82+
.build();
83+
}
84+
6385
@Timeout(15)
6486
@Test
6587
void frameSizeLimit() throws Exception {

netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.netty.channel.ChannelPromise;
2323
import io.netty.handler.codec.http.FullHttpResponse;
2424
import io.netty.handler.codec.http.HttpHeaders;
25+
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakeException;
2526
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
2627
import io.netty.handler.ssl.SslHandler;
2728
import io.netty.util.concurrent.ScheduledFuture;
@@ -110,16 +111,29 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception {
110111
mask,
111112
expectMaskedFrames,
112113
allowMaskMismatch);
113-
h.handshake(ctx.channel());
114114
startHandshakeTimeout(ctx, handshakeTimeoutMillis);
115+
h.handshake(ctx.channel())
116+
.addListener(
117+
future -> {
118+
Throwable cause = future.cause();
119+
if (cause != null) {
120+
handshakeCompleted.tryFailure(cause);
121+
ctx.fireExceptionCaught(cause);
122+
cancelHandshakeTimeout();
123+
} else {
124+
ctx.fireUserEventTriggered(
125+
io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler
126+
.ClientHandshakeStateEvent.HANDSHAKE_ISSUED);
127+
}
128+
});
115129
}
116130

117131
@Override
118132
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
119133
cancelHandshakeTimeout();
120134
ChannelPromise completed = handshakeCompleted;
121135
if (!completed.isDone()) {
122-
completed.setFailure(new ClosedChannelException());
136+
completed.tryFailure(new ClosedChannelException());
123137
}
124138
super.channelInactive(ctx);
125139
}
@@ -153,15 +167,22 @@ private void completeHandshake(ChannelHandlerContext ctx, FullHttpResponse respo
153167
try {
154168
h.finishHandshake(ctx.channel(), response);
155169
handshaker = null;
156-
cancelHandshakeTimeout();
157-
} catch (WebSocketHandshakeException e) {
158-
handshakeCompleted.setFailure(e);
170+
} catch (Exception e) {
171+
handshakeCompleted.tryFailure(e);
172+
if (!(e instanceof WebSocketHandshakeException)) {
173+
ctx.fireExceptionCaught(e);
174+
}
159175
ctx.close();
160176
return;
177+
} finally {
178+
cancelHandshakeTimeout();
161179
}
162180
ctx.pipeline().remove(this);
163181
WebSocketCallbacksHandler.exchange(ctx, webSocketHandler);
164-
handshakeCompleted.setSuccess();
182+
handshakeCompleted.trySuccess();
183+
ctx.fireUserEventTriggered(
184+
io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler
185+
.ClientHandshakeStateEvent.HANDSHAKE_COMPLETE);
165186
}
166187

167188
private void startHandshakeTimeout(ChannelHandlerContext ctx, long timeoutMillis) {
@@ -170,7 +191,19 @@ private void startHandshakeTimeout(ChannelHandlerContext ctx, long timeoutMillis
170191
.schedule(
171192
() -> {
172193
handshakeTimeoutFuture = null;
173-
ctx.close();
194+
ChannelPromise handshake = handshakeCompleted;
195+
if (!handshake.isDone()
196+
&& handshake.tryFailure(
197+
new WebSocketClientHandshakeException(
198+
"websocket handshake timeout after "
199+
+ handshakeTimeoutMillis
200+
+ " millis"))) {
201+
ctx.flush();
202+
ctx.fireUserEventTriggered(
203+
io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler
204+
.ClientHandshakeStateEvent.HANDSHAKE_TIMEOUT);
205+
ctx.close();
206+
}
174207
},
175208
timeoutMillis,
176209
TimeUnit.MILLISECONDS);
@@ -213,7 +246,7 @@ public static final class Builder {
213246
private String subprotocol;
214247
private HttpHeaders headers;
215248
private boolean mask = true;
216-
private boolean allowMaskMismatch;
249+
private boolean allowMaskMismatch = true;
217250
private int maxFramePayloadLength = 65_535;
218251
private long handshakeTimeoutMillis = 15_000;
219252
private WebSocketCallbacksHandler webSocketHandler;

0 commit comments

Comments
 (0)