Minor performance improvement in websocket upgrade (#10710)
Motivation: I noticed WebSocketServerExtensionHandler taking up a non-trivial amount of CPU time for a non-websocket based menchmark. This attempts to speed it up. Modifications: - It is faster to check for a 101 response than to look at headers, so an initial response code check is done - Move all the actual upgrade code into its own method to increase chance of this method being inlined - Add an extra contains() check for the upgrade header, to avoid allocating an iterator if there is no upgrade header Result: A small but noticable performance increase. Signed-off-by: Stuart Douglas <stuart.w.douglas@gmail.com>
This commit is contained in:
parent
ffbddcd842
commit
303baf5c0b
@ -37,7 +37,10 @@ public final class WebSocketExtensionUtil {
|
||||
private static final Pattern PARAMETER = Pattern.compile("^([^=]+)(=[\\\"]?([^\\\"]+)[\\\"]?)?$");
|
||||
|
||||
static boolean isWebsocketUpgrade(HttpHeaders headers) {
|
||||
return headers.containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true) &&
|
||||
//this contains check does not allocate an iterator, and most requests are not upgrades
|
||||
//so we do the contains check first before checking for specific values
|
||||
return headers.contains(HttpHeaderNames.UPGRADE) &&
|
||||
headers.containsValue(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true) &&
|
||||
headers.contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -104,50 +105,61 @@ public class WebSocketServerExtensionHandler extends ChannelDuplexHandler {
|
||||
@Override
|
||||
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (msg instanceof HttpResponse) {
|
||||
HttpHeaders headers = ((HttpResponse) msg).headers();
|
||||
|
||||
if (WebSocketExtensionUtil.isWebsocketUpgrade(headers)) {
|
||||
|
||||
if (validExtensions != null) {
|
||||
String headerValue = headers.getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
|
||||
|
||||
for (WebSocketServerExtension extension : validExtensions) {
|
||||
WebSocketExtensionData extensionData = extension.newReponseData();
|
||||
headerValue = WebSocketExtensionUtil.appendExtension(headerValue,
|
||||
extensionData.name(),
|
||||
extensionData.parameters());
|
||||
}
|
||||
promise.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
if (future.isSuccess()) {
|
||||
for (WebSocketServerExtension extension : validExtensions) {
|
||||
WebSocketExtensionDecoder decoder = extension.newExtensionDecoder();
|
||||
WebSocketExtensionEncoder encoder = extension.newExtensionEncoder();
|
||||
ctx.pipeline()
|
||||
.addAfter(ctx.name(), decoder.getClass().getName(), decoder)
|
||||
.addAfter(ctx.name(), encoder.getClass().getName(), encoder);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (headerValue != null) {
|
||||
headers.set(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
promise.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
if (future.isSuccess()) {
|
||||
ctx.pipeline().remove(WebSocketServerExtensionHandler.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
HttpResponse httpResponse = (HttpResponse) msg;
|
||||
//checking the status is faster than looking at headers
|
||||
//so we do this first
|
||||
if (HttpResponseStatus.SWITCHING_PROTOCOLS.equals(httpResponse.status())) {
|
||||
handlePotentialUpgrade(ctx, promise, httpResponse);
|
||||
}
|
||||
}
|
||||
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
|
||||
private void handlePotentialUpgrade(final ChannelHandlerContext ctx,
|
||||
ChannelPromise promise, HttpResponse httpResponse) {
|
||||
HttpHeaders headers = httpResponse.headers();
|
||||
|
||||
if (WebSocketExtensionUtil.isWebsocketUpgrade(headers)) {
|
||||
|
||||
if (validExtensions != null) {
|
||||
String headerValue = headers.getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
|
||||
|
||||
for (WebSocketServerExtension extension : validExtensions) {
|
||||
WebSocketExtensionData extensionData = extension.newReponseData();
|
||||
headerValue = WebSocketExtensionUtil.appendExtension(headerValue,
|
||||
extensionData.name(),
|
||||
extensionData.parameters());
|
||||
}
|
||||
promise.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
if (future.isSuccess()) {
|
||||
for (WebSocketServerExtension extension : validExtensions) {
|
||||
WebSocketExtensionDecoder decoder = extension.newExtensionDecoder();
|
||||
WebSocketExtensionEncoder encoder = extension.newExtensionEncoder();
|
||||
String name = ctx.name();
|
||||
ctx.pipeline()
|
||||
.addAfter(name, decoder.getClass().getName(), decoder)
|
||||
.addAfter(name, encoder.getClass().getName(), encoder);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (headerValue != null) {
|
||||
headers.set(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
promise.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) {
|
||||
if (future.isSuccess()) {
|
||||
ctx.pipeline().remove(WebSocketServerExtensionHandler.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user