Skip to content

Commit 821a0f7

Browse files
committedFeb 7, 2017
Don't defer WebSocket opening, close #1348
Motivation: We currently buffer WebSocket opening until first LastHttpContent reception with the UpgradeCallback. This doesn't make sense, and forces us to buffer any frame that might be sent along with the upgrade response. Modifications: * Drop UpgradeHandler that's never used as an abstraction * Perform upgrade/abort as soon as response is received * Ignore LastHttpContent * No need to buffer any frame Result: More simple code

File tree

4 files changed

+62
-173
lines changed

4 files changed

+62
-173
lines changed
 

‎client/src/main/java/org/asynchttpclient/netty/handler/WebSocketHandler.java

+43-83
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.netty.handler.codec.http.HttpHeaderValues;
2222
import io.netty.handler.codec.http.HttpRequest;
2323
import io.netty.handler.codec.http.HttpResponse;
24+
import io.netty.handler.codec.http.LastHttpContent;
2425
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
2526
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
2627
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
@@ -36,7 +37,6 @@
3637
import org.asynchttpclient.HttpResponseStatus;
3738
import org.asynchttpclient.netty.NettyResponseFuture;
3839
import org.asynchttpclient.netty.NettyResponseStatus;
39-
import org.asynchttpclient.netty.OnLastHttpContentCallback;
4040
import org.asynchttpclient.netty.channel.ChannelManager;
4141
import org.asynchttpclient.netty.channel.Channels;
4242
import org.asynchttpclient.netty.request.NettyRequestSender;
@@ -52,71 +52,45 @@ public WebSocketHandler(AsyncHttpClientConfig config,//
5252
super(config, channelManager, requestSender);
5353
}
5454

55-
private class UpgradeCallback extends OnLastHttpContentCallback {
56-
57-
private final Channel channel;
58-
private final HttpResponse response;
59-
private final WebSocketUpgradeHandler handler;
60-
private final HttpResponseStatus status;
61-
private final HttpResponseHeaders responseHeaders;
62-
63-
public UpgradeCallback(NettyResponseFuture<?> future, Channel channel, HttpResponse response, WebSocketUpgradeHandler handler, HttpResponseStatus status,
64-
HttpResponseHeaders responseHeaders) {
65-
super(future);
66-
this.channel = channel;
67-
this.response = response;
68-
this.handler = handler;
69-
this.status = status;
70-
this.responseHeaders = responseHeaders;
55+
private void upgrade(Channel channel, NettyResponseFuture<?> future, WebSocketUpgradeHandler handler, HttpResponse response, HttpResponseHeaders responseHeaders)
56+
throws Exception {
57+
boolean validStatus = response.status().equals(SWITCHING_PROTOCOLS);
58+
boolean validUpgrade = response.headers().get(UPGRADE) != null;
59+
String connection = response.headers().get(CONNECTION);
60+
boolean validConnection = HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(connection);
61+
final boolean headerOK = handler.onHeadersReceived(responseHeaders) == State.CONTINUE;
62+
if (!headerOK || !validStatus || !validUpgrade || !validConnection) {
63+
requestSender.abort(channel, future, new IOException("Invalid handshake response"));
64+
return;
7165
}
7266

73-
// We don't need to synchronize as replacing the "ws-decoder" will
74-
// process using the same thread.
75-
private void invokeOnSucces(Channel channel, WebSocketUpgradeHandler h) {
76-
try {
77-
h.onSuccess(new NettyWebSocket(channel, responseHeaders.getHeaders()));
78-
} catch (Exception ex) {
79-
logger.warn("onSuccess unexpected exception", ex);
80-
}
67+
String accept = response.headers().get(SEC_WEBSOCKET_ACCEPT);
68+
String key = getAcceptKey(future.getNettyRequest().getHttpRequest().headers().get(SEC_WEBSOCKET_KEY));
69+
if (accept == null || !accept.equals(key)) {
70+
requestSender.abort(channel, future, new IOException("Invalid challenge. Actual: " + accept + ". Expected: " + key));
8171
}
8272

83-
@Override
84-
public void call() throws Exception {
85-
boolean validStatus = response.status().equals(SWITCHING_PROTOCOLS);
86-
boolean validUpgrade = response.headers().get(UPGRADE) != null;
87-
String connection = response.headers().get(CONNECTION);
88-
boolean validConnection = HttpHeaderValues.UPGRADE.contentEqualsIgnoreCase(connection);
89-
boolean statusReceived = handler.onStatusReceived(status) == State.CONTINUE;
90-
91-
if (!statusReceived) {
92-
try {
93-
handler.onCompleted();
94-
} finally {
95-
future.done();
96-
}
97-
return;
98-
}
99-
100-
final boolean headerOK = handler.onHeadersReceived(responseHeaders) == State.CONTINUE;
101-
if (!headerOK || !validStatus || !validUpgrade || !validConnection) {
102-
requestSender.abort(channel, future, new IOException("Invalid handshake response"));
103-
return;
104-
}
73+
// set back the future so the protocol gets notified of frames
74+
// removing the HttpClientCodec from the pipeline might trigger a read with a WebSocket message
75+
// if it comes in the same frame as the HTTP Upgrade response
76+
Channels.setAttribute(channel, future);
10577

106-
String accept = response.headers().get(SEC_WEBSOCKET_ACCEPT);
107-
String key = getAcceptKey(future.getNettyRequest().getHttpRequest().headers().get(SEC_WEBSOCKET_KEY));
108-
if (accept == null || !accept.equals(key)) {
109-
requestSender.abort(channel, future, new IOException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, key)));
110-
}
78+
channelManager.upgradePipelineForWebSockets(channel.pipeline());
11179

112-
// set back the future so the protocol gets notified of frames
113-
// removing the HttpClientCodec from the pipeline might trigger a read with a WebSocket message
114-
// if it comes in the same frame as the HTTP Upgrade response
115-
Channels.setAttribute(channel, future);
116-
117-
channelManager.upgradePipelineForWebSockets(channel.pipeline());
80+
// We don't need to synchronize as replacing the "ws-decoder" will
81+
// process using the same thread.
82+
try {
83+
handler.openWebSocket(new NettyWebSocket(channel, responseHeaders.getHeaders()));
84+
} catch (Exception ex) {
85+
logger.warn("onSuccess unexpected exception", ex);
86+
}
87+
future.done();
88+
}
11889

119-
invokeOnSucces(channel, handler);
90+
private void abort(NettyResponseFuture<?> future, WebSocketUpgradeHandler handler, HttpResponseStatus status) throws Exception {
91+
try {
92+
handler.onThrowable(new IOException("Invalid Status code=" + status.getStatusCode() + " text=" + status.getStatusText()));
93+
} finally {
12094
future.done();
12195
}
12296
}
@@ -136,36 +110,23 @@ public void handleRead(Channel channel, NettyResponseFuture<?> future, Object e)
136110
HttpResponseHeaders responseHeaders = new HttpResponseHeaders(response.headers());
137111

138112
if (!interceptors.exitAfterIntercept(channel, future, handler, response, status, responseHeaders)) {
139-
Channels.setAttribute(channel, new UpgradeCallback(future, channel, response, handler, status, responseHeaders));
113+
switch (handler.onStatusReceived(status)) {
114+
case CONTINUE:
115+
upgrade(channel, future, handler, response, responseHeaders);
116+
break;
117+
default:
118+
abort(future, handler, status);
119+
}
140120
}
141121

142122
} else if (e instanceof WebSocketFrame) {
143123
final WebSocketFrame frame = (WebSocketFrame) e;
144124
WebSocketUpgradeHandler handler = (WebSocketUpgradeHandler) future.getAsyncHandler();
145125
NettyWebSocket webSocket = (NettyWebSocket) handler.onCompleted();
126+
handleFrame(channel, frame, handler, webSocket);
146127

147-
if (webSocket != null) {
148-
handleFrame(channel, frame, handler, webSocket);
149-
} else {
150-
logger.debug("Frame received but WebSocket is not available yet, buffering frame");
151-
frame.retain();
152-
Runnable bufferedFrame = new Runnable() {
153-
public void run() {
154-
try {
155-
// WebSocket is now not null
156-
NettyWebSocket webSocket = (NettyWebSocket) handler.onCompleted();
157-
handleFrame(channel, frame, handler, webSocket);
158-
} catch (Exception e) {
159-
logger.debug("Failure while handling buffered frame", e);
160-
handler.onFailure(e);
161-
} finally {
162-
frame.release();
163-
}
164-
}
165-
};
166-
handler.bufferFrame(bufferedFrame);
167-
}
168-
} else {
128+
} else if (!(e instanceof LastHttpContent)) {
129+
// ignore, end of handshake response
169130
logger.error("Invalid message {}", e);
170131
}
171132
}
@@ -197,7 +158,6 @@ public void handleException(NettyResponseFuture<?> future, Throwable e) {
197158

198159
try {
199160
WebSocketUpgradeHandler h = (WebSocketUpgradeHandler) future.getAsyncHandler();
200-
201161
NettyWebSocket webSocket = NettyWebSocket.class.cast(h.onCompleted());
202162
if (webSocket != null) {
203163
webSocket.onError(e.getCause());

‎client/src/main/java/org/asynchttpclient/ws/UpgradeHandler.java

-37
This file was deleted.

‎client/src/main/java/org/asynchttpclient/ws/WebSocketUpgradeHandler.java

+16-51
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/*
2-
* Copyright (c) 2010-2012 Sonatype, Inc. All rights reserved.
2+
* Copyright (c) 2017 AsyncHttpClient Project. All rights reserved.
33
*
44
* This program is licensed to you under the Apache License Version 2.0,
55
* and you may not use this file except in compliance with the Apache License Version 2.0.
6-
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
78
*
89
* Unless required by applicable law or agreed to in writing,
910
* software distributed under the Apache License Version 2.0 is distributed on an
@@ -12,11 +13,8 @@
1213
*/
1314
package org.asynchttpclient.ws;
1415

15-
import static org.asynchttpclient.util.MiscUtils.isNonEmpty;
16-
1716
import java.util.ArrayList;
1817
import java.util.List;
19-
import java.util.concurrent.atomic.AtomicBoolean;
2018

2119
import org.asynchttpclient.AsyncHandler;
2220
import org.asynchttpclient.HttpResponseBodyPart;
@@ -26,85 +24,52 @@
2624
/**
2725
* An {@link AsyncHandler} which is able to execute WebSocket upgrade. Use the Builder for configuring WebSocket options.
2826
*/
29-
public class WebSocketUpgradeHandler implements UpgradeHandler<WebSocket>, AsyncHandler<WebSocket> {
27+
public class WebSocketUpgradeHandler implements AsyncHandler<WebSocket> {
3028

3129
private static final int SWITCHING_PROTOCOLS = io.netty.handler.codec.http.HttpResponseStatus.SWITCHING_PROTOCOLS.code();
3230

3331
private WebSocket webSocket;
3432
private final List<WebSocketListener> listeners;
35-
private final AtomicBoolean ok = new AtomicBoolean(false);
36-
private int status;
37-
private List<Runnable> bufferedFrames;
3833

3934
public WebSocketUpgradeHandler(List<WebSocketListener> listeners) {
4035
this.listeners = listeners;
4136
}
4237

43-
public void bufferFrame(Runnable bufferedFrame) {
44-
if (bufferedFrames == null) {
45-
bufferedFrames = new ArrayList<>(1);
46-
}
47-
bufferedFrames.add(bufferedFrame);
48-
}
49-
5038
@Override
51-
public final void onThrowable(Throwable t) {
52-
onFailure(t);
39+
public final State onStatusReceived(HttpResponseStatus responseStatus) throws Exception {
40+
return responseStatus.getStatusCode() == SWITCHING_PROTOCOLS ? State.CONTINUE : State.ABORT;
5341
}
5442

55-
5643
@Override
57-
public final State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
44+
public final State onHeadersReceived(HttpResponseHeaders headers) throws Exception {
5845
return State.CONTINUE;
5946
}
6047

6148
@Override
62-
public final State onStatusReceived(HttpResponseStatus responseStatus) throws Exception {
63-
status = responseStatus.getStatusCode();
64-
return status == SWITCHING_PROTOCOLS ? State.CONTINUE : State.ABORT;
65-
}
66-
67-
@Override
68-
public final State onHeadersReceived(HttpResponseHeaders headers) throws Exception {
49+
public final State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception {
6950
return State.CONTINUE;
7051
}
7152

7253
@Override
7354
public final WebSocket onCompleted() throws Exception {
74-
if (status != SWITCHING_PROTOCOLS) {
75-
IllegalStateException e = new IllegalStateException("Invalid Status Code " + status);
76-
for (WebSocketListener listener : listeners) {
77-
listener.onError(e);
78-
}
79-
throw e;
80-
}
81-
8255
return webSocket;
8356
}
8457

8558
@Override
86-
public final void onSuccess(WebSocket webSocket) {
87-
this.webSocket = webSocket;
59+
public final void onThrowable(Throwable t) {
8860
for (WebSocketListener listener : listeners) {
89-
webSocket.addWebSocketListener(listener);
90-
listener.onOpen(webSocket);
91-
}
92-
if (isNonEmpty(bufferedFrames)) {
93-
for (Runnable bufferedFrame : bufferedFrames) {
94-
bufferedFrame.run();
61+
if (webSocket != null) {
62+
webSocket.addWebSocketListener(listener);
9563
}
96-
bufferedFrames = null;
64+
listener.onError(t);
9765
}
98-
ok.set(true);
9966
}
10067

101-
@Override
102-
public final void onFailure(Throwable t) {
68+
public final void openWebSocket(WebSocket webSocket) {
69+
this.webSocket = webSocket;
10370
for (WebSocketListener listener : listeners) {
104-
if (!ok.get() && webSocket != null) {
105-
webSocket.addWebSocketListener(listener);
106-
}
107-
listener.onError(t);
71+
webSocket.addWebSocketListener(listener);
72+
listener.onOpen(webSocket);
10873
}
10974
}
11075

‎client/src/test/java/org/asynchttpclient/ws/CloseCodeReasonMessageTest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
*/
1313
package org.asynchttpclient.ws;
1414

15-
import static org.asynchttpclient.Dsl.*;
15+
import static org.asynchttpclient.Dsl.asyncHttpClient;
1616
import static org.testng.Assert.*;
1717

18+
import java.io.IOException;
1819
import java.util.concurrent.CountDownLatch;
1920
import java.util.concurrent.ExecutionException;
2021
import java.util.concurrent.atomic.AtomicReference;
@@ -156,7 +157,7 @@ public void onError(Throwable t) {
156157
}
157158
}
158159

159-
@Test(groups = "online", timeOut = 60000, expectedExceptions = IllegalStateException.class)
160+
@Test(groups = "online", timeOut = 60000, expectedExceptions = IOException.class)
160161
public void wrongProtocolCode() throws Throwable {
161162
try (AsyncHttpClient c = asyncHttpClient()) {
162163
final CountDownLatch latch = new CountDownLatch(1);

0 commit comments

Comments
 (0)
Please sign in to comment.