[#4212] Backport WebSocket Extension handlers for client and server.

Motivation:

We have websocket extension support (with compression) in old master. We should port this to 4.1

Modifications:

Backport relevant code.

Result:

websocket extension support (with compression) is now in 4.1.
This commit is contained in:
Norman Maurer 2015-12-17 14:57:36 +01:00
parent b39380ad83
commit dc615ecaaf
45 changed files with 3913 additions and 42 deletions

View File

@ -20,7 +20,7 @@ import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
/**
* Web Socket text frame with assumed UTF-8 encoding
* Web Socket text frame
*/
public class TextWebSocketFrame extends WebSocketFrame {
@ -45,7 +45,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
* Creates a new text frame with the specified binary data. The final fragment flag is set to true.
*
* @param binaryData
* the content of the frame. Must be UTF-8 encoded
* the content of the frame.
*/
public TextWebSocketFrame(ByteBuf binaryData) {
super(binaryData);
@ -81,7 +81,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame. Must be UTF-8 encoded
* the content of the frame.
*/
public TextWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
super(finalFragment, rsv, binaryData);

View File

@ -0,0 +1,103 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder.State;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.util.List;
import static io.netty.buffer.ByteBufUtil.readBytes;
/**
*
*/
public class Utf8FrameValidator extends ChannelInboundHandlerAdapter {
private int fragmentedFramesCount;
private Utf8Validator utf8Validator;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg;
// Processing for possible fragmented messages for text and binary
// frames
if (((WebSocketFrame) msg).isFinalFragment()) {
// Final frame of the sequence. Apparently ping frames are
// allowed in the middle of a fragmented message
if (!(frame instanceof PingWebSocketFrame)) {
fragmentedFramesCount = 0;
// Check text for UTF8 correctness
if ((frame instanceof TextWebSocketFrame) ||
(utf8Validator != null && utf8Validator.isChecking())) {
// Check UTF-8 correctness for this payload
checkUTF8String(ctx, frame.content());
// This does a second check to make sure UTF-8
// correctness for entire text message
utf8Validator.finish();
}
}
} else {
// Not final frame so we can expect more frames in the
// fragmented sequence
if (fragmentedFramesCount == 0) {
// First text or binary frame for a fragmented set
if (frame instanceof TextWebSocketFrame) {
checkUTF8String(ctx, frame.content());
}
} else {
// Subsequent frames - only check if init frame is text
if (utf8Validator != null && utf8Validator.isChecking()) {
checkUTF8String(ctx, frame.content());
}
}
// Increment counter
fragmentedFramesCount++;
}
}
super.channelRead(ctx, msg);
}
private void checkUTF8String(ChannelHandlerContext ctx, ByteBuf buffer) {
try {
if (utf8Validator == null) {
utf8Validator = new Utf8Validator();
}
utf8Validator.check(buffer);
} catch (CorruptedFrameException ex) {
if (ctx.channel().isActive()) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
}
}
}

View File

@ -107,7 +107,6 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
private byte[] maskingKey;
private int framePayloadLen1;
private boolean receivedClosingHandshake;
private Utf8Validator utf8Validator;
private State state = State.READING_FIRST;
/**
@ -340,33 +339,8 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
// allowed in the middle of a fragmented message
if (frameOpcode != OPCODE_PING) {
fragmentedFramesCount = 0;
// Check text for UTF8 correctness
if (frameOpcode == OPCODE_TEXT ||
utf8Validator != null && utf8Validator.isChecking()) {
// Check UTF-8 correctness for this payload
checkUTF8String(ctx, payloadBuffer);
// This does a second check to make sure UTF-8
// correctness for entire text message
utf8Validator.finish();
}
}
} else {
// Not final frame so we can expect more frames in the
// fragmented sequence
if (fragmentedFramesCount == 0) {
// First text or binary frame for a fragmented set
if (frameOpcode == OPCODE_TEXT) {
checkUTF8String(ctx, payloadBuffer);
}
} else {
// Subsequent frames - only check if init frame is text
if (utf8Validator != null && utf8Validator.isChecking()) {
checkUTF8String(ctx, payloadBuffer);
}
}
// Increment counter
fragmentedFramesCount++;
}
@ -460,17 +434,6 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
}
}
private void checkUTF8String(ChannelHandlerContext ctx, ByteBuf buffer) {
try {
if (utf8Validator == null) {
utf8Validator = new Utf8Validator();
}
utf8Validator.check(buffer);
} catch (CorruptedFrameException ex) {
protocolViolation(ctx, ex);
}
}
/** */
protected void checkCloseFrameBody(
ChannelHandlerContext ctx, ByteBuf buffer) {

View File

@ -183,5 +183,10 @@ public class WebSocketClientProtocolHandler extends WebSocketProtocolHandler {
ctx.pipeline().addBefore(ctx.name(), WebSocketClientProtocolHandshakeHandler.class.getName(),
new WebSocketClientProtocolHandshakeHandler(handshaker));
}
if (cp.get(Utf8FrameValidator.class) == null) {
// Add the UFT8 checking before this one.
ctx.pipeline().addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
new Utf8FrameValidator());
}
}
}

View File

@ -105,6 +105,11 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols,
allowExtensions, maxFramePayloadLength, allowMaskMismatch));
}
if (cp.get(Utf8FrameValidator.class) == null) {
// Add the UFT8 checking before this one.
ctx.pipeline().addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
new Utf8FrameValidator());
}
}
@Override

View File

@ -0,0 +1,23 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
/**
* Created once the handshake phase is done.
*/
public interface WebSocketClientExtension extends WebSocketExtension {
}

View File

@ -0,0 +1,131 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.CodecException;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* This handler negotiates and initializes the WebSocket Extensions.
*
* This implementation negotiates the extension with the server in a defined order,
* ensures that the successfully negotiated extensions are consistent between them,
* and initializes the channel pipeline with the extension decoder and encoder.
*
* Find a basic implementation for compression extensions at
* <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler</tt>.
*/
public class WebSocketClientExtensionHandler extends ChannelDuplexHandler {
private final List<WebSocketClientExtensionHandshaker> extensionHandshakers;
/**
* Constructor
*
* @param extensionHandshakers
* The extension handshaker in priority order. A handshaker could be repeated many times
* with fallback configuration.
*/
public WebSocketClientExtensionHandler(WebSocketClientExtensionHandshaker... extensionHandshakers) {
if (extensionHandshakers == null) {
throw new NullPointerException("extensionHandshakers");
}
if (extensionHandshakers.length == 0) {
throw new IllegalArgumentException("extensionHandshakers must contains at least one handshaker");
}
this.extensionHandshakers = Arrays.asList(extensionHandshakers);
}
@Override
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof HttpRequest && WebSocketExtensionUtil.isWebsocketUpgrade((HttpRequest) msg)) {
HttpRequest request = (HttpRequest) msg;
String headerValue = request.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
for (WebSocketClientExtensionHandshaker extentionHandshaker : extensionHandshakers) {
WebSocketExtensionData extensionData = extentionHandshaker.newRequestData();
headerValue = WebSocketExtensionUtil.appendExtension(headerValue,
extensionData.name(), extensionData.parameters());
}
request.headers().set(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS, headerValue);
}
super.write(ctx, msg, promise);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
if (msg instanceof HttpResponse) {
HttpResponse response = (HttpResponse) msg;
if (WebSocketExtensionUtil.isWebsocketUpgrade(response)) {
String extensionsHeader = response.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
if (extensionsHeader != null) {
List<WebSocketExtensionData> extensions =
WebSocketExtensionUtil.extractExtensions(extensionsHeader);
List<WebSocketClientExtension> validExtensions =
new ArrayList<WebSocketClientExtension>(extensions.size());
int rsv = 0;
for (WebSocketExtensionData extensionData : extensions) {
Iterator<WebSocketClientExtensionHandshaker> extensionHandshakersIterator =
extensionHandshakers.iterator();
WebSocketClientExtension validExtension = null;
while (validExtension == null && extensionHandshakersIterator.hasNext()) {
WebSocketClientExtensionHandshaker extensionHandshaker =
extensionHandshakersIterator.next();
validExtension = extensionHandshaker.handshakeExtension(extensionData);
}
if (validExtension != null && ((validExtension.rsv() & rsv) == 0)) {
rsv = rsv | validExtension.rsv();
validExtensions.add(validExtension);
} else {
throw new CodecException(
"invalid WebSocket Extension handhshake for \"" + extensionsHeader + "\"");
}
}
for (WebSocketClientExtension validExtension : validExtensions) {
WebSocketExtensionDecoder decoder = validExtension.newExtensionDecoder();
WebSocketExtensionEncoder encoder = validExtension.newExtensionEncoder();
ctx.pipeline().addAfter(ctx.name(), decoder.getClass().getName(), decoder);
ctx.pipeline().addAfter(ctx.name(), encoder.getClass().getName(), encoder);
}
}
ctx.pipeline().remove(ctx.name());
}
}
super.channelRead(ctx, msg);
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
/**
* Handshakes a client extension with the server.
*/
public interface WebSocketClientExtensionHandshaker {
/**
* Return extension configuration to submit to the server.
*
* @return the desired extension configuration.
*/
WebSocketExtensionData newRequestData();
/**
* Handshake based on server response. It should always succeed because server response
* should be a request acknowledge.
*
* @param extensionData
* the extension configuration sent by the server.
* @return an initialized extension if handshake phase succeed or null if failed.
*/
WebSocketClientExtension handshakeExtension(WebSocketExtensionData extensionData);
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
/**
* Created once the handshake phase is done.
*/
public interface WebSocketExtension {
int RSV1 = 0x04;
int RSV2 = 0x02;
int RSV3 = 0x01;
/**
* @return the reserved bit value to ensure that no other extension should interfere.
*/
int rsv();
/**
* @return create the extension encoder.
*/
WebSocketExtensionEncoder newExtensionEncoder();
/**
* @return create the extension decoder.
*/
WebSocketExtensionDecoder newExtensionDecoder();
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import java.util.Collections;
import java.util.Map;
/**
* A WebSocket Extension data from the <tt>Sec-WebSocket-Extensions</tt> header.
*
* See <tt>io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_EXTENSIONS</tt>.
*/
public final class WebSocketExtensionData {
private final String name;
private final Map<String, String> parameters;
public WebSocketExtensionData(String name, Map<String, String> parameters) {
if (name == null) {
throw new NullPointerException("name");
}
if (parameters == null) {
throw new NullPointerException("parameters");
}
this.name = name;
this.parameters = Collections.unmodifiableMap(parameters);
}
/**
* @return the extension name.
*/
public String name() {
return name;
}
/**
* @return the extension optional parameters.
*/
public Map<String, String> parameters() {
return parameters;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
/**
* Convenient class for <tt>io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension</tt> decoder.
*/
public abstract class WebSocketExtensionDecoder extends MessageToMessageDecoder<WebSocketFrame> {
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
/**
* Convenient class for <tt>io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension</tt> encoder.
*/
public abstract class WebSocketExtensionEncoder extends MessageToMessageEncoder<WebSocketFrame> {
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class WebSocketExtensionUtil {
private static final char EXTENSION_SEPARATOR = ',';
private static final char PARAMETER_SEPARATOR = ';';
private static final char PARAMETER_EQUAL = '=';
private static final Pattern PARAMETER = Pattern.compile("^([^=]+)(=[\\\"]?([^\\\"]+)[\\\"]?)?$");
static boolean isWebsocketUpgrade(HttpMessage httpMessage) {
if (httpMessage == null) {
throw new NullPointerException("httpMessage");
}
return httpMessage.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true) &&
httpMessage.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true);
}
public static List<WebSocketExtensionData> extractExtensions(String extensionHeader) {
String[] rawExtensions = StringUtil.split(extensionHeader, EXTENSION_SEPARATOR);
if (rawExtensions.length > 0) {
List<WebSocketExtensionData> extensions = new ArrayList<WebSocketExtensionData>(rawExtensions.length);
for (String rawExtension : rawExtensions) {
String[] extensionParameters = StringUtil.split(rawExtension, PARAMETER_SEPARATOR);
String name = extensionParameters[0].trim();
Map<String, String> parameters;
if (extensionParameters.length > 1) {
parameters = new HashMap<String, String>(extensionParameters.length - 1);
for (int i = 1; i < extensionParameters.length; i++) {
String parameter = extensionParameters[i].trim();
Matcher parameterMatcher = PARAMETER.matcher(parameter);
if (parameterMatcher.matches() && parameterMatcher.group(1) != null) {
parameters.put(parameterMatcher.group(1), parameterMatcher.group(3));
}
}
} else {
parameters = Collections.<String, String>emptyMap();
}
extensions.add(new WebSocketExtensionData(name, parameters));
}
return extensions;
} else {
return Collections.emptyList();
}
}
static String appendExtension(String currentHeaderValue, String extensionName,
Map<String, String> extensionParameters) {
StringBuilder newHeaderValue = new StringBuilder(
currentHeaderValue != null ? currentHeaderValue.length() : 0 + extensionName.length() + 1);
if (currentHeaderValue != null && !currentHeaderValue.trim().isEmpty()) {
newHeaderValue.append(currentHeaderValue);
newHeaderValue.append(EXTENSION_SEPARATOR);
}
newHeaderValue.append(extensionName);
boolean isFirst = true;
for (Entry<String, String> extensionParameter : extensionParameters.entrySet()) {
if (isFirst) {
newHeaderValue.append(PARAMETER_SEPARATOR);
} else {
isFirst = false;
}
newHeaderValue.append(extensionParameter.getKey());
if (extensionParameter.getValue() != null) {
newHeaderValue.append(PARAMETER_EQUAL);
newHeaderValue.append(extensionParameter.getValue());
}
}
return newHeaderValue.toString();
}
private WebSocketExtensionUtil() {
// Unused
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
/**
* Created once the handshake phase is done.
*/
public interface WebSocketServerExtension extends WebSocketExtension {
/**
* Return an extension configuration to submit to the client as an acknowledge.
*
* @return the acknowledged extension configuration.
*/
WebSocketExtensionData newReponseData();
}

View File

@ -0,0 +1,141 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* This handler negotiates and initializes the WebSocket Extensions.
*
* It negotiates the extensions based on the client desired order,
* ensures that the successfully negotiated extensions are consistent between them,
* and initializes the channel pipeline with the extension decoder and encoder.
*
* Find a basic implementation for compression extensions at
* <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler</tt>.
*/
public class WebSocketServerExtensionHandler extends ChannelDuplexHandler {
private final List<WebSocketServerExtensionHandshaker> extensionHandshakers;
private List<WebSocketServerExtension> validExtensions;
/**
* Constructor
*
* @param extensionHandshakers
* The extension handshaker in priority order. A handshaker could be repeated many times
* with fallback configuration.
*/
public WebSocketServerExtensionHandler(WebSocketServerExtensionHandshaker... extensionHandshakers) {
if (extensionHandshakers == null) {
throw new NullPointerException("extensionHandshakers");
}
if (extensionHandshakers.length == 0) {
throw new IllegalArgumentException("extensionHandshakers must contains at least one handshaker");
}
this.extensionHandshakers = Arrays.asList(extensionHandshakers);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
if (WebSocketExtensionUtil.isWebsocketUpgrade(request)) {
String extensionsHeader = request.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
if (extensionsHeader != null) {
List<WebSocketExtensionData> extensions =
WebSocketExtensionUtil.extractExtensions(extensionsHeader);
int rsv = 0;
for (WebSocketExtensionData extensionData : extensions) {
Iterator<WebSocketServerExtensionHandshaker> extensionHandshakersIterator =
extensionHandshakers.iterator();
WebSocketServerExtension validExtension = null;
while (validExtension == null && extensionHandshakersIterator.hasNext()) {
WebSocketServerExtensionHandshaker extensionHandshaker =
extensionHandshakersIterator.next();
validExtension = extensionHandshaker.handshakeExtension(extensionData);
}
if (validExtension != null && ((validExtension.rsv() & rsv) == 0)) {
if (validExtensions == null) {
validExtensions = new ArrayList<WebSocketServerExtension>(1);
}
rsv = rsv | validExtension.rsv();
validExtensions.add(validExtension);
}
}
}
}
}
super.channelRead(ctx, msg);
}
@Override
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof HttpResponse &&
WebSocketExtensionUtil.isWebsocketUpgrade((HttpResponse) msg) && validExtensions != null) {
HttpResponse response = (HttpResponse) msg;
String headerValue = response.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) throws Exception {
if (future.isSuccess()) {
for (WebSocketServerExtension extension : validExtensions) {
WebSocketExtensionDecoder decoder = extension.newExtensionDecoder();
WebSocketExtensionEncoder encoder = extension.newExtensionEncoder();
ctx.pipeline().addAfter(ctx.name(), decoder.getClass().getName(), decoder);
ctx.pipeline().addAfter(ctx.name(), encoder.getClass().getName(), encoder);
}
}
ctx.pipeline().remove(ctx.name());
}
});
if (headerValue != null) {
response.headers().set(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS, headerValue);
}
}
super.write(ctx, msg, promise);
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
/**
* Handshakes a client extension based on this server capabilities.
*/
public interface WebSocketServerExtensionHandshaker {
/**
* Handshake based on client request. It must failed with <tt>null</tt> if server cannot handle it.
*
* @param extensionData
* the extension configuration sent by the client.
* @return an initialized extension if handshake phase succeed or null if failed.
*/
WebSocketServerExtension handshakeExtension(WebSocketExtensionData extensionData);
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CodecException;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
import io.netty.util.ReferenceCountUtil;
import java.util.List;
/**
* Deflate implementation of a payload decompressor for
* <tt>io.netty.handler.codec.http.websocketx.WebSocketFrame</tt>.
*/
abstract class DeflateDecoder extends WebSocketExtensionDecoder {
static final byte[] FRAME_TAIL = new byte[] {0x00, 0x00, (byte) 0xff, (byte) 0xff};
private final boolean noContext;
private EmbeddedChannel decoder;
/**
* Constructor
* @param noContext true to disable context takeover.
*/
public DeflateDecoder(boolean noContext) {
this.noContext = noContext;
}
protected abstract boolean appendFrameTail(WebSocketFrame msg);
protected abstract int newRsv(WebSocketFrame msg);
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
if (decoder == null) {
if (!(msg instanceof TextWebSocketFrame) && !(msg instanceof BinaryWebSocketFrame)) {
throw new CodecException("unexpected initial frame type: " + msg.getClass().getName());
}
decoder = new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.NONE));
}
boolean readable = msg.content().isReadable();
decoder.writeInbound(msg.content().retain());
if (appendFrameTail(msg)) {
decoder.writeInbound(Unpooled.wrappedBuffer(FRAME_TAIL));
}
CompositeByteBuf compositeUncompressedContent = ctx.alloc().compositeBuffer();
for (;;) {
ByteBuf partUncompressedContent = decoder.readInbound();
if (partUncompressedContent == null) {
break;
}
if (!partUncompressedContent.isReadable()) {
partUncompressedContent.release();
continue;
}
compositeUncompressedContent.addComponent(partUncompressedContent);
compositeUncompressedContent.writerIndex(compositeUncompressedContent.writerIndex() +
partUncompressedContent.readableBytes());
}
// Correctly handle empty frames
// See https://github.com/netty/netty/issues/4348
if (readable && compositeUncompressedContent.numComponents() <= 0) {
compositeUncompressedContent.release();
throw new CodecException("cannot read uncompressed buffer");
}
if (msg.isFinalFragment() && noContext) {
cleanup();
}
WebSocketFrame outMsg;
if (msg instanceof TextWebSocketFrame) {
outMsg = new TextWebSocketFrame(msg.isFinalFragment(), newRsv(msg), compositeUncompressedContent);
} else if (msg instanceof BinaryWebSocketFrame) {
outMsg = new BinaryWebSocketFrame(msg.isFinalFragment(), newRsv(msg), compositeUncompressedContent);
} else if (msg instanceof ContinuationWebSocketFrame) {
outMsg = new ContinuationWebSocketFrame(msg.isFinalFragment(), newRsv(msg),
compositeUncompressedContent);
} else {
throw new CodecException("unexpected frame type: " + msg.getClass().getName());
}
out.add(outMsg);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
cleanup();
super.handlerRemoved(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
cleanup();
super.channelInactive(ctx);
}
private void cleanup() {
if (decoder != null) {
// Clean-up the previous encoder if not cleaned up correctly.
if (decoder.finish()) {
for (;;) {
ByteBuf buf = decoder.readOutbound();
if (buf == null) {
break;
}
// Release the buffer
buf.release();
}
}
decoder = null;
}
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateDecoder.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CodecException;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
import java.util.List;
/**
* Deflate implementation of a payload compressor for
* <tt>io.netty.handler.codec.http.websocketx.WebSocketFrame</tt>.
*/
abstract class DeflateEncoder extends WebSocketExtensionEncoder {
private final int compressionLevel;
private final int windowSize;
private final boolean noContext;
private EmbeddedChannel encoder;
/**
* Constructor
* @param compressionLevel compression level of the compressor.
* @param windowSize maximum size of the window compressor buffer.
* @param noContext true to disable context takeover.
*/
public DeflateEncoder(int compressionLevel, int windowSize, boolean noContext) {
this.compressionLevel = compressionLevel;
this.windowSize = windowSize;
this.noContext = noContext;
}
/**
* @param msg the current frame.
* @return the rsv bits to set in the compressed frame.
*/
protected abstract int rsv(WebSocketFrame msg);
/**
* @param msg the current frame.
* @return true if compressed payload tail needs to be removed.
*/
protected abstract boolean removeFrameTail(WebSocketFrame msg);
@Override
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
List<Object> out) throws Exception {
if (encoder == null) {
encoder = new EmbeddedChannel(ZlibCodecFactory.newZlibEncoder(
ZlibWrapper.NONE, compressionLevel, windowSize, 8));
}
encoder.writeOutbound(msg.content().retain());
CompositeByteBuf fullCompressedContent = ctx.alloc().compositeBuffer();
for (;;) {
ByteBuf partCompressedContent = encoder.readOutbound();
if (partCompressedContent == null) {
break;
}
if (!partCompressedContent.isReadable()) {
partCompressedContent.release();
continue;
}
fullCompressedContent.addComponent(partCompressedContent);
fullCompressedContent.writerIndex(fullCompressedContent.writerIndex() +
partCompressedContent.readableBytes());
}
if (fullCompressedContent.numComponents() <= 0) {
fullCompressedContent.release();
throw new CodecException("cannot read compressed buffer");
}
if (msg.isFinalFragment() && noContext) {
cleanup();
}
ByteBuf compressedContent;
if (removeFrameTail(msg)) {
int realLength = fullCompressedContent.readableBytes() - FRAME_TAIL.length;
compressedContent = fullCompressedContent.slice(0, realLength);
} else {
compressedContent = fullCompressedContent;
}
WebSocketFrame outMsg;
if (msg instanceof TextWebSocketFrame) {
outMsg = new TextWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
} else if (msg instanceof BinaryWebSocketFrame) {
outMsg = new BinaryWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
} else if (msg instanceof ContinuationWebSocketFrame) {
outMsg = new ContinuationWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
} else {
throw new CodecException("unexpected frame type: " + msg.getClass().getName());
}
out.add(outMsg);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
cleanup();
super.handlerRemoved(ctx);
}
private void cleanup() {
if (encoder != null) {
// Clean-up the previous encoder if not cleaned up correctly.
if (encoder.finish()) {
for (;;) {
ByteBuf buf = encoder.readOutbound();
if (buf == null) {
break;
}
// Release the buffer
buf.release();
}
}
encoder = null;
}
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static io.netty.handler.codec.http.websocketx.extensions.compression.
DeflateFrameServerExtensionHandshaker.*;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
import java.util.Collections;
/**
* <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-06.txt">perframe-deflate</a>
* handshake implementation.
*/
public final class DeflateFrameClientExtensionHandshaker implements WebSocketClientExtensionHandshaker {
private final int compressionLevel;
private final boolean useWebkitExtensionName;
/**
* Constructor with default configuration.
*/
public DeflateFrameClientExtensionHandshaker(boolean useWebkitExtensionName) {
this(6, useWebkitExtensionName);
}
/**
* Constructor with custom configuration.
*
* @param compressionLevel
* Compression level between 0 and 9 (default is 6).
*/
public DeflateFrameClientExtensionHandshaker(int compressionLevel, boolean useWebkitExtensionName) {
if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
}
this.compressionLevel = compressionLevel;
this.useWebkitExtensionName = useWebkitExtensionName;
}
@Override
public WebSocketExtensionData newRequestData() {
return new WebSocketExtensionData(
useWebkitExtensionName ? X_WEBKIT_DEFLATE_FRAME_EXTENSION : DEFLATE_FRAME_EXTENSION,
Collections.<String, String>emptyMap());
}
@Override
public WebSocketClientExtension handshakeExtension(WebSocketExtensionData extensionData) {
if (!X_WEBKIT_DEFLATE_FRAME_EXTENSION.equals(extensionData.name()) &&
!DEFLATE_FRAME_EXTENSION.equals(extensionData.name())) {
return null;
}
if (extensionData.parameters().isEmpty()) {
return new DeflateFrameClientExtension(compressionLevel);
} else {
return null;
}
}
private static class DeflateFrameClientExtension implements WebSocketClientExtension {
private final int compressionLevel;
public DeflateFrameClientExtension(int compressionLevel) {
this.compressionLevel = compressionLevel;
}
@Override
public int rsv() {
return RSV1;
}
@Override
public WebSocketExtensionEncoder newExtensionEncoder() {
return new PerFrameDeflateEncoder(compressionLevel, 15, false);
}
@Override
public WebSocketExtensionDecoder newExtensionDecoder() {
return new PerFrameDeflateDecoder(false);
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandshaker;
import java.util.Collections;
/**
* <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-06.txt">perframe-deflate</a>
* handshake implementation.
*/
public final class DeflateFrameServerExtensionHandshaker implements WebSocketServerExtensionHandshaker {
static final String X_WEBKIT_DEFLATE_FRAME_EXTENSION = "x-webkit-deflate-frame";
static final String DEFLATE_FRAME_EXTENSION = "deflate-frame";
private final int compressionLevel;
/**
* Constructor with default configuration.
*/
public DeflateFrameServerExtensionHandshaker() {
this(6);
}
/**
* Constructor with custom configuration.
*
* @param compressionLevel
* Compression level between 0 and 9 (default is 6).
*/
public DeflateFrameServerExtensionHandshaker(int compressionLevel) {
if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
}
this.compressionLevel = compressionLevel;
}
@Override
public WebSocketServerExtension handshakeExtension(WebSocketExtensionData extensionData) {
if (!X_WEBKIT_DEFLATE_FRAME_EXTENSION.equals(extensionData.name()) &&
!DEFLATE_FRAME_EXTENSION.equals(extensionData.name())) {
return null;
}
if (extensionData.parameters().isEmpty()) {
return new DeflateFrameServerExtension(compressionLevel, extensionData.name());
} else {
return null;
}
}
private static class DeflateFrameServerExtension implements WebSocketServerExtension {
private final String extensionName;
private final int compressionLevel;
public DeflateFrameServerExtension(int compressionLevel, String extensionName) {
this.extensionName = extensionName;
this.compressionLevel = compressionLevel;
}
@Override
public int rsv() {
return RSV1;
}
@Override
public WebSocketExtensionEncoder newExtensionEncoder() {
return new PerFrameDeflateEncoder(compressionLevel, 15, false);
}
@Override
public WebSocketExtensionDecoder newExtensionDecoder() {
return new PerFrameDeflateDecoder(false);
}
@Override
public WebSocketExtensionData newReponseData() {
return new WebSocketExtensionData(extensionName, Collections.<String, String>emptyMap());
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
/**
* Per-frame implementation of deflate decompressor.
*/
class PerFrameDeflateDecoder extends DeflateDecoder {
/**
* Constructor
* @param noContext true to disable context takeover.
*/
public PerFrameDeflateDecoder(boolean noContext) {
super(noContext);
}
@Override
public boolean acceptInboundMessage(Object msg) throws Exception {
return (msg instanceof TextWebSocketFrame ||
msg instanceof BinaryWebSocketFrame ||
msg instanceof ContinuationWebSocketFrame) &&
(((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) > 0;
}
@Override
protected int newRsv(WebSocketFrame msg) {
return msg.rsv() ^ WebSocketExtension.RSV1;
}
@Override
protected boolean appendFrameTail(WebSocketFrame msg) {
return true;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
/**
* Per-frame implementation of deflate compressor.
*/
class PerFrameDeflateEncoder extends DeflateEncoder {
/**
* Constructor
* @param compressionLevel compression level of the compressor.
* @param windowSize maximum size of the window compressor buffer.
* @param noContext true to disable context takeover.
*/
public PerFrameDeflateEncoder(int compressionLevel, int windowSize, boolean noContext) {
super(compressionLevel, windowSize, noContext);
}
@Override
public boolean acceptOutboundMessage(Object msg) throws Exception {
return (msg instanceof TextWebSocketFrame ||
msg instanceof BinaryWebSocketFrame ||
msg instanceof ContinuationWebSocketFrame) &&
((WebSocketFrame) msg).content().readableBytes() > 0 &&
(((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) == 0;
}
@Override
protected int rsv(WebSocketFrame msg) {
return msg.rsv() | WebSocketExtension.RSV1;
}
@Override
protected boolean removeFrameTail(WebSocketFrame msg) {
return true;
}
}

View File

@ -0,0 +1,197 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static io.netty.handler.codec.http.websocketx.extensions.compression.
PerMessageDeflateServerExtensionHandshaker.*;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
/**
* <a href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18">permessage-deflate</a>
* handshake implementation.
*/
public final class PerMessageDeflateClientExtensionHandshaker implements WebSocketClientExtensionHandshaker {
private final int compressionLevel;
private final boolean allowClientWindowSize;
private final int requestedServerWindowSize;
private final boolean allowClientNoContext;
private final boolean requestedServerNoContext;
/**
* Constructor with default configuration.
*/
public PerMessageDeflateClientExtensionHandshaker() {
this(6, false, MAX_WINDOW_SIZE, false, false);
}
/**
* Constructor with custom configuration.
*
* @param compressionLevel
* Compression level between 0 and 9 (default is 6).
* @param allowClientWindowSize
* allows WebSocket server to customize the client inflater window size
* (default is false).
* @param requestedServerWindowSize
* indicates the requested sever window size to use if server inflater is customizable.
* @param allowClientNoContext
* allows WebSocket server to activate client_no_context_takeover
* (default is false).
* @param requestedServerNoContext
* indicates if client needs to activate server_no_context_takeover
* if server is compatible with (default is false).
*/
public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
boolean allowClientWindowSize, int requestedServerWindowSize,
boolean allowClientNoContext, boolean requestedServerNoContext) {
if (requestedServerWindowSize > MAX_WINDOW_SIZE || requestedServerWindowSize < MIN_WINDOW_SIZE) {
throw new IllegalArgumentException(
"requestedServerWindowSize: " + requestedServerWindowSize + " (expected: 8-15)");
}
if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
}
this.compressionLevel = compressionLevel;
this.allowClientWindowSize = allowClientWindowSize;
this.requestedServerWindowSize = requestedServerWindowSize;
this.allowClientNoContext = allowClientNoContext;
this.requestedServerNoContext = requestedServerNoContext;
}
@Override
public WebSocketExtensionData newRequestData() {
HashMap<String, String> parameters = new HashMap<String, String>(4);
if (requestedServerWindowSize != MAX_WINDOW_SIZE) {
parameters.put(SERVER_NO_CONTEXT, null);
}
if (allowClientNoContext) {
parameters.put(CLIENT_NO_CONTEXT, null);
}
if (requestedServerWindowSize != MAX_WINDOW_SIZE) {
parameters.put(SERVER_MAX_WINDOW, Integer.toString(requestedServerWindowSize));
}
if (allowClientWindowSize) {
parameters.put(CLIENT_MAX_WINDOW, null);
}
return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
}
@Override
public WebSocketClientExtension handshakeExtension(WebSocketExtensionData extensionData) {
if (!PERMESSAGE_DEFLATE_EXTENSION.equals(extensionData.name())) {
return null;
}
boolean succeed = true;
int clientWindowSize = MAX_WINDOW_SIZE;
int serverWindowSize = MAX_WINDOW_SIZE;
boolean serverNoContext = false;
boolean clientNoContext = false;
Iterator<Entry<String, String>> parametersIterator =
extensionData.parameters().entrySet().iterator();
while (succeed && parametersIterator.hasNext()) {
Entry<String, String> parameter = parametersIterator.next();
if (CLIENT_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
// allowed client_window_size_bits
if (allowClientWindowSize) {
clientWindowSize = Integer.parseInt(parameter.getValue());
} else {
succeed = false;
}
} else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
// acknowledged server_window_size_bits
serverWindowSize = Integer.parseInt(parameter.getValue());
if (clientWindowSize > MAX_WINDOW_SIZE || clientWindowSize < MIN_WINDOW_SIZE) {
succeed = false;
}
} else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
// allowed client_no_context_takeover
if (allowClientNoContext) {
clientNoContext = true;
} else {
succeed = false;
}
} else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
// acknowledged server_no_context_takeover
if (requestedServerNoContext) {
serverNoContext = true;
} else {
succeed = false;
}
} else {
// unknown parameter
succeed = false;
}
}
if ((requestedServerNoContext && !serverNoContext) ||
requestedServerWindowSize != serverWindowSize) {
succeed = false;
}
if (succeed) {
return new PermessageDeflateExtension(serverNoContext, serverWindowSize,
clientNoContext, clientWindowSize);
} else {
return null;
}
}
private final class PermessageDeflateExtension implements WebSocketClientExtension {
private final boolean serverNoContext;
private final int serverWindowSize;
private final boolean clientNoContext;
private final int clientWindowSize;
@Override
public int rsv() {
return RSV1;
}
public PermessageDeflateExtension(boolean serverNoContext, int serverWindowSize,
boolean clientNoContext, int clientWindowSize) {
this.serverNoContext = serverNoContext;
this.serverWindowSize = serverWindowSize;
this.clientNoContext = clientNoContext;
this.clientWindowSize = clientWindowSize;
}
@Override
public WebSocketExtensionEncoder newExtensionEncoder() {
return new PerMessageDeflateEncoder(compressionLevel, serverWindowSize, serverNoContext);
}
@Override
public WebSocketExtensionDecoder newExtensionDecoder() {
return new PerMessageDeflateDecoder(clientNoContext);
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
import java.util.List;
/**
* Per-message implementation of deflate decompressor.
*/
class PerMessageDeflateDecoder extends DeflateDecoder {
private boolean compressing;
/**
* Constructor
* @param noContext true to disable context takeover.
*/
public PerMessageDeflateDecoder(boolean noContext) {
super(noContext);
}
@Override
public boolean acceptInboundMessage(Object msg) throws Exception {
return ((msg instanceof TextWebSocketFrame ||
msg instanceof BinaryWebSocketFrame) &&
(((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) > 0) ||
(msg instanceof ContinuationWebSocketFrame && compressing);
}
@Override
protected int newRsv(WebSocketFrame msg) {
return (((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) > 0 ?
msg.rsv() ^ WebSocketExtension.RSV1 : msg.rsv();
}
@Override
protected boolean appendFrameTail(WebSocketFrame msg) {
return msg.isFinalFragment();
}
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,
List<Object> out) throws Exception {
super.decode(ctx, msg, out);
if (msg.isFinalFragment()) {
compressing = false;
} else if (msg instanceof TextWebSocketFrame || msg instanceof BinaryWebSocketFrame) {
compressing = true;
}
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
import java.util.List;
/**
* Per-message implementation of deflate compressor.
*/
class PerMessageDeflateEncoder extends DeflateEncoder {
private boolean compressing;
/**
* Constructor
* @param compressionLevel compression level of the compressor.
* @param windowSize maximum size of the window compressor buffer.
* @param noContext true to disable context takeover.
*/
public PerMessageDeflateEncoder(int compressionLevel, int windowSize, boolean noContext) {
super(compressionLevel, windowSize, noContext);
}
@Override
public boolean acceptOutboundMessage(Object msg) throws Exception {
return ((msg instanceof TextWebSocketFrame ||
msg instanceof BinaryWebSocketFrame) &&
(((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) == 0) ||
(msg instanceof ContinuationWebSocketFrame && compressing);
}
@Override
protected int rsv(WebSocketFrame msg) {
return msg instanceof TextWebSocketFrame || msg instanceof BinaryWebSocketFrame ?
msg.rsv() | WebSocketExtension.RSV1 : msg.rsv();
}
@Override
protected boolean removeFrameTail(WebSocketFrame msg) {
return msg.isFinalFragment();
}
@Override
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
List<Object> out) throws Exception {
super.encode(ctx, msg, out);
if (msg.isFinalFragment()) {
compressing = false;
} else if (msg instanceof TextWebSocketFrame || msg instanceof BinaryWebSocketFrame) {
compressing = true;
}
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandshaker;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
/**
* <a href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18">permessage-deflate</a>
* handshake implementation.
*/
public final class PerMessageDeflateServerExtensionHandshaker implements WebSocketServerExtensionHandshaker {
public static final int MIN_WINDOW_SIZE = 8;
public static final int MAX_WINDOW_SIZE = 15;
static final String PERMESSAGE_DEFLATE_EXTENSION = "permessage-deflate";
static final String CLIENT_MAX_WINDOW = "client_max_window_bits";
static final String SERVER_MAX_WINDOW = "server_max_window_bits";
static final String CLIENT_NO_CONTEXT = "client_no_context_takeover";
static final String SERVER_NO_CONTEXT = "server_no_context_takeover";
private final int compressionLevel;
private final boolean allowServerWindowSize;
private final int preferredClientWindowSize;
private final boolean allowServerNoContext;
private final boolean preferredClientNoContext;
/**
* Constructor with default configuration.
*/
public PerMessageDeflateServerExtensionHandshaker() {
this(6, false, MAX_WINDOW_SIZE, false, false);
}
/**
* Constructor with custom configuration.
*
* @param compressionLevel
* Compression level between 0 and 9 (default is 6).
* @param allowServerWindowSize
* allows WebSocket client to customize the server inflater window size
* (default is false).
* @param preferredClientWindowSize
* indicates the preferred client window size to use if client inflater is customizable.
* @param allowServerNoContext
* allows WebSocket client to activate server_no_context_takeover
* (default is false).
* @param preferredClientNoContext
* indicates if server prefers to activate client_no_context_takeover
* if client is compatible with (default is false).
*/
public PerMessageDeflateServerExtensionHandshaker(int compressionLevel,
boolean allowServerWindowSize, int preferredClientWindowSize,
boolean allowServerNoContext, boolean preferredClientNoContext) {
if (preferredClientWindowSize > MAX_WINDOW_SIZE || preferredClientWindowSize < MIN_WINDOW_SIZE) {
throw new IllegalArgumentException(
"preferredServerWindowSize: " + preferredClientWindowSize + " (expected: 8-15)");
}
if (compressionLevel < 0 || compressionLevel > 9) {
throw new IllegalArgumentException(
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
}
this.compressionLevel = compressionLevel;
this.allowServerWindowSize = allowServerWindowSize;
this.preferredClientWindowSize = preferredClientWindowSize;
this.allowServerNoContext = allowServerNoContext;
this.preferredClientNoContext = preferredClientNoContext;
}
@Override
public WebSocketServerExtension handshakeExtension(WebSocketExtensionData extensionData) {
if (!PERMESSAGE_DEFLATE_EXTENSION.equals(extensionData.name())) {
return null;
}
boolean deflateEnabled = true;
int clientWindowSize = MAX_WINDOW_SIZE;
int serverWindowSize = MAX_WINDOW_SIZE;
boolean serverNoContext = false;
boolean clientNoContext = false;
Iterator<Entry<String, String>> parametersIterator =
extensionData.parameters().entrySet().iterator();
while (deflateEnabled && parametersIterator.hasNext()) {
Entry<String, String> parameter = parametersIterator.next();
if (CLIENT_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
// use preferred clientWindowSize because client is compatible with customization
clientWindowSize = preferredClientWindowSize;
} else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
// use provided windowSize if it is allowed
if (allowServerWindowSize) {
serverWindowSize = Integer.parseInt(parameter.getValue());
if (serverWindowSize > MAX_WINDOW_SIZE || serverWindowSize < MIN_WINDOW_SIZE) {
deflateEnabled = false;
}
} else {
deflateEnabled = false;
}
} else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
// use preferred clientNoContext because client is compatible with customization
clientNoContext = preferredClientNoContext;
} else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
// use server no context if allowed
if (allowServerNoContext) {
serverNoContext = true;
} else {
deflateEnabled = false;
}
} else {
// unknown parameter
deflateEnabled = false;
}
}
if (deflateEnabled) {
return new PermessageDeflateExtension(compressionLevel, serverNoContext,
serverWindowSize, clientNoContext, clientWindowSize);
} else {
return null;
}
}
private static class PermessageDeflateExtension implements WebSocketServerExtension {
private final int compressionLevel;
private final boolean serverNoContext;
private final int serverWindowSize;
private final boolean clientNoContext;
private final int clientWindowSize;
public PermessageDeflateExtension(int compressionLevel, boolean serverNoContext,
int serverWindowSize, boolean clientNoContext, int clientWindowSize) {
this.compressionLevel = compressionLevel;
this.serverNoContext = serverNoContext;
this.serverWindowSize = serverWindowSize;
this.clientNoContext = clientNoContext;
this.clientWindowSize = clientWindowSize;
}
@Override
public int rsv() {
return RSV1;
}
@Override
public WebSocketExtensionEncoder newExtensionEncoder() {
return new PerMessageDeflateEncoder(compressionLevel, clientWindowSize, clientNoContext);
}
@Override
public WebSocketExtensionDecoder newExtensionDecoder() {
return new PerMessageDeflateDecoder(serverNoContext);
}
@Override
public WebSocketExtensionData newReponseData() {
HashMap<String, String> parameters = new HashMap<String, String>(4);
if (serverNoContext) {
parameters.put(SERVER_NO_CONTEXT, null);
}
if (clientNoContext) {
parameters.put(CLIENT_NO_CONTEXT, null);
}
if (serverWindowSize != MAX_WINDOW_SIZE) {
parameters.put(SERVER_MAX_WINDOW, Integer.toString(serverWindowSize));
}
if (clientWindowSize != MAX_WINDOW_SIZE) {
parameters.put(CLIENT_MAX_WINDOW, Integer.toString(clientWindowSize));
}
return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker;
import java.util.Arrays;
/**
* Extends <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientExtensionHandler</tt>
* to handle the most common WebSocket Compression Extensions.
*
* See <tt>io.netty.example.http.websocketx.client.WebSocketClient</tt> for usage.
*/
public class WebSocketClientCompressionHandler extends WebSocketClientExtensionHandler {
/**
* Constructor with default configuration.
*/
public WebSocketClientCompressionHandler() {
super(new PerMessageDeflateClientExtensionHandshaker(),
new DeflateFrameClientExtensionHandshaker(false),
new DeflateFrameClientExtensionHandshaker(true));
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler;
/**
* Extends <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerExtensionHandler</tt>
* to handle the most common WebSocket Compression Extensions.
*
* See <tt>io.netty.example.http.websocketx.html5.WebSocketServer</tt> for usage.
*/
public class WebSocketServerCompressionHandler extends WebSocketServerExtensionHandler {
/**
* Constructor with default configuration.
*/
public WebSocketServerCompressionHandler() {
super(new PerMessageDeflateServerExtensionHandshaker(),
new DeflateFrameServerExtensionHandshaker());
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Encoder, decoder, handshakers to handle most common WebSocket Compression Extensions.
* <p>
* This package supports different web socket extensions.
* The specification currently supported are:
* <ul>
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18">permessage-deflate</a></li>
* <li><a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-06.txt">
* perframe-deflate and x-webkit-deflate-frame</a></li>
* </ul>
* </p>
* <p>
* See <tt>io.netty.example.http.websocketx.client.WebSocketClient</tt> and
* <tt>io.netty.example.http.websocketx.html5.WebSocketServer</tt> for usage.
* </p>
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;

View File

@ -0,0 +1,23 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Encoder, decoder, handshakers to handle
* <a href="http://tools.ietf.org/html/rfc6455#section-9.1">WebSocket Extensions</a>.
*
* See <tt>WebSocketServerExtensionHandler</tt> for more details.
*/
package io.netty.handler.codec.http.websocketx.extensions;

View File

@ -0,0 +1,253 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import static io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionTestUtil.*;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CodecException;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
public class WebSocketClientExtensionHandlerTest {
WebSocketClientExtensionHandshaker mainHandshakerMock =
createMock("mainHandshaker", WebSocketClientExtensionHandshaker.class);
WebSocketClientExtensionHandshaker fallbackHandshakerMock =
createMock("fallbackHandshaker", WebSocketClientExtensionHandshaker.class);
WebSocketClientExtension mainExtensionMock =
createMock("mainExtension", WebSocketClientExtension.class);
WebSocketClientExtension fallbackExtensionMock =
createMock("fallbackExtension", WebSocketClientExtension.class);
@Test
public void testMainSuccess() {
// initialize
expect(mainHandshakerMock.newRequestData()).
andReturn(new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
expect(mainHandshakerMock.handshakeExtension(
anyObject(WebSocketExtensionData.class))).andReturn(mainExtensionMock).once();
replay(mainHandshakerMock);
expect(fallbackHandshakerMock.newRequestData()).
andReturn(new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
replay(fallbackHandshakerMock);
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
expect(mainExtensionMock.newExtensionEncoder()).andReturn(new DummyEncoder()).once();
expect(mainExtensionMock.newExtensionDecoder()).andReturn(new DummyDecoder()).once();
replay(mainExtensionMock);
// execute
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
mainHandshakerMock, fallbackHandshakerMock));
HttpRequest req = newUpgradeRequest(null);
ch.writeOutbound(req);
HttpRequest req2 = ch.readOutbound();
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.extractExtensions(
req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
HttpResponse res = newUpgradeResponse("main");
ch.writeInbound(res);
HttpResponse res2 = ch.readInbound();
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
// test
assertEquals(2, reqExts.size());
assertEquals("main", reqExts.get(0).name());
assertEquals("fallback", reqExts.get(1).name());
assertEquals(1, resExts.size());
assertEquals("main", resExts.get(0).name());
assertTrue(resExts.get(0).parameters().isEmpty());
assertTrue(ch.pipeline().get(DummyDecoder.class) != null);
assertTrue(ch.pipeline().get(DummyEncoder.class) != null);
}
@Test
public void testFallbackSuccess() {
// initialize
expect(mainHandshakerMock.newRequestData()).
andReturn(new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
expect(mainHandshakerMock.handshakeExtension(
anyObject(WebSocketExtensionData.class))).andReturn(null).once();
replay(mainHandshakerMock);
expect(fallbackHandshakerMock.newRequestData()).
andReturn(new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
expect(fallbackHandshakerMock.handshakeExtension(
anyObject(WebSocketExtensionData.class))).andReturn(fallbackExtensionMock).once();
replay(fallbackHandshakerMock);
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
expect(fallbackExtensionMock.newExtensionEncoder()).andReturn(new DummyEncoder()).once();
expect(fallbackExtensionMock.newExtensionDecoder()).andReturn(new DummyDecoder()).once();
replay(fallbackExtensionMock);
// execute
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
mainHandshakerMock, fallbackHandshakerMock));
HttpRequest req = newUpgradeRequest(null);
ch.writeOutbound(req);
HttpRequest req2 = ch.readOutbound();
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.extractExtensions(
req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
HttpResponse res = newUpgradeResponse("fallback");
ch.writeInbound(res);
HttpResponse res2 = ch.readInbound();
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
// test
assertEquals(2, reqExts.size());
assertEquals("main", reqExts.get(0).name());
assertEquals("fallback", reqExts.get(1).name());
assertEquals(1, resExts.size());
assertEquals("fallback", resExts.get(0).name());
assertTrue(resExts.get(0).parameters().isEmpty());
assertTrue(ch.pipeline().get(DummyDecoder.class) != null);
assertTrue(ch.pipeline().get(DummyEncoder.class) != null);
}
@Test
public void testAllSuccess() {
// initialize
expect(mainHandshakerMock.newRequestData()).
andReturn(new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
expect(mainHandshakerMock.handshakeExtension(
webSocketExtensionDataEqual("main"))).andReturn(mainExtensionMock).anyTimes();
expect(mainHandshakerMock.handshakeExtension(
webSocketExtensionDataEqual("fallback"))).andReturn(null).anyTimes();
replay(mainHandshakerMock);
expect(fallbackHandshakerMock.newRequestData()).
andReturn(new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
expect(fallbackHandshakerMock.handshakeExtension(
webSocketExtensionDataEqual("main"))).andReturn(null).anyTimes();
expect(fallbackHandshakerMock.handshakeExtension(
webSocketExtensionDataEqual("fallback"))).andReturn(fallbackExtensionMock).anyTimes();
replay(fallbackHandshakerMock);
DummyEncoder mainEncoder = new DummyEncoder();
DummyDecoder mainDecoder = new DummyDecoder();
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
expect(mainExtensionMock.newExtensionEncoder()).andReturn(mainEncoder).once();
expect(mainExtensionMock.newExtensionDecoder()).andReturn(mainDecoder).once();
replay(mainExtensionMock);
Dummy2Encoder fallbackEncoder = new Dummy2Encoder();
Dummy2Decoder fallbackDecoder = new Dummy2Decoder();
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV2).anyTimes();
expect(fallbackExtensionMock.newExtensionEncoder()).andReturn(fallbackEncoder).once();
expect(fallbackExtensionMock.newExtensionDecoder()).andReturn(fallbackDecoder).once();
replay(fallbackExtensionMock);
// execute
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
mainHandshakerMock, fallbackHandshakerMock));
HttpRequest req = newUpgradeRequest(null);
ch.writeOutbound(req);
HttpRequest req2 = ch.readOutbound();
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.extractExtensions(
req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
HttpResponse res = newUpgradeResponse("main, fallback");
ch.writeInbound(res);
HttpResponse res2 = ch.readInbound();
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
// test
assertEquals(2, reqExts.size());
assertEquals("main", reqExts.get(0).name());
assertEquals("fallback", reqExts.get(1).name());
assertEquals(2, resExts.size());
assertEquals("main", resExts.get(0).name());
assertEquals("fallback", resExts.get(1).name());
assertTrue(ch.pipeline().context(mainEncoder) != null);
assertTrue(ch.pipeline().context(mainDecoder) != null);
assertTrue(ch.pipeline().context(fallbackEncoder) != null);
assertTrue(ch.pipeline().context(fallbackDecoder) != null);
}
@Test(expected = CodecException.class)
public void testIfMainAndFallbackUseRSV1WillFail() {
// initialize
expect(mainHandshakerMock.newRequestData()).
andReturn(new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
expect(mainHandshakerMock.handshakeExtension(
webSocketExtensionDataEqual("main"))).andReturn(mainExtensionMock).anyTimes();
expect(mainHandshakerMock.handshakeExtension(
webSocketExtensionDataEqual("fallback"))).andReturn(null).anyTimes();
replay(mainHandshakerMock);
expect(fallbackHandshakerMock.newRequestData()).
andReturn(new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
expect(fallbackHandshakerMock.handshakeExtension(
webSocketExtensionDataEqual("main"))).andReturn(null).anyTimes();
expect(fallbackHandshakerMock.handshakeExtension(
webSocketExtensionDataEqual("fallback"))).andReturn(fallbackExtensionMock).anyTimes();
replay(fallbackHandshakerMock);
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
replay(mainExtensionMock);
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
replay(fallbackExtensionMock);
// execute
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
mainHandshakerMock, fallbackHandshakerMock));
HttpRequest req = newUpgradeRequest(null);
ch.writeOutbound(req);
HttpRequest req2 = ch.readOutbound();
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.extractExtensions(
req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
HttpResponse res = newUpgradeResponse("main, fallback");
ch.writeInbound(res);
// test
assertEquals(2, reqExts.size());
assertEquals("main", reqExts.get(0).name());
assertEquals("fallback", reqExts.get(1).name());
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version
* 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import java.util.List;
import org.easymock.EasyMock;
import org.easymock.IArgumentMatcher;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.ReferenceCountUtil;
public final class WebSocketExtensionTestUtil {
public static HttpRequest newUpgradeRequest(String ext) {
HttpRequest req = ReferenceCountUtil.releaseLater(new DefaultHttpRequest(
HttpVersion.HTTP_1_1, HttpMethod.GET, "/chat"));
req.headers().set(Names.HOST, "server.example.com");
req.headers().set(Names.UPGRADE, Values.WEBSOCKET.toString().toLowerCase());
req.headers().set(Names.CONNECTION, "Upgrade");
req.headers().set(Names.ORIGIN, "http://example.com");
if (ext != null) {
req.headers().set(Names.SEC_WEBSOCKET_EXTENSIONS, ext);
}
return req;
}
public static HttpResponse newUpgradeResponse(String ext) {
HttpResponse res = ReferenceCountUtil.releaseLater(new DefaultHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS));
res.headers().set(Names.HOST, "server.example.com");
res.headers().set(Names.UPGRADE, Values.WEBSOCKET.toString().toLowerCase());
res.headers().set(Names.CONNECTION, "Upgrade");
res.headers().set(Names.ORIGIN, "http://example.com");
if (ext != null) {
res.headers().set(Names.SEC_WEBSOCKET_EXTENSIONS, ext);
}
return res;
}
public static WebSocketExtensionData webSocketExtensionDataEqual(String name) {
EasyMock.reportMatcher(new WebSocketExtensionDataMatcher(name));
return null;
}
public static class WebSocketExtensionDataMatcher implements IArgumentMatcher {
private final String name;
public WebSocketExtensionDataMatcher(String name) {
this.name = name;
}
@Override
public void appendTo(StringBuffer buf) {
buf.append("WebSocketExtensionData with name=" + name);
}
@Override
public boolean matches(Object o) {
return o instanceof WebSocketExtensionData &&
name.equals(((WebSocketExtensionData) o).name());
}
}
private WebSocketExtensionTestUtil() {
// unused
}
static class DummyEncoder extends WebSocketExtensionEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
List<Object> out) throws Exception {
// unused
}
}
static class DummyDecoder extends WebSocketExtensionDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,
List<Object> out) throws Exception {
// unused
}
}
static class Dummy2Encoder extends WebSocketExtensionEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
List<Object> out) throws Exception {
// unused
}
}
static class Dummy2Decoder extends WebSocketExtensionDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,
List<Object> out) throws Exception {
// unused
}
}
}

View File

@ -0,0 +1,176 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions;
import static io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionTestUtil.*;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.CodecException;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
public class WebSocketServerExtensionHandlerTest {
WebSocketServerExtensionHandshaker mainHandshakerMock =
createMock("mainHandshaker", WebSocketServerExtensionHandshaker.class);
WebSocketServerExtensionHandshaker fallbackHandshakerMock =
createMock("fallbackHandshaker", WebSocketServerExtensionHandshaker.class);
WebSocketServerExtension mainExtensionMock =
createMock("mainExtension", WebSocketServerExtension.class);
WebSocketServerExtension fallbackExtensionMock =
createMock("fallbackExtension", WebSocketServerExtension.class);
@Test
public void testMainSuccess() {
// initialize
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("main"))).
andReturn(mainExtensionMock).anyTimes();
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("fallback"))).
andReturn(null).anyTimes();
replay(mainHandshakerMock);
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("fallback"))).
andReturn(fallbackExtensionMock).anyTimes();
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("main"))).
andReturn(null).anyTimes();
replay(fallbackHandshakerMock);
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
expect(mainExtensionMock.newReponseData()).andReturn(
new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
expect(mainExtensionMock.newExtensionEncoder()).andReturn(new DummyEncoder()).once();
expect(mainExtensionMock.newExtensionDecoder()).andReturn(new DummyDecoder()).once();
replay(mainExtensionMock);
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
replay(fallbackExtensionMock);
// execute
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
mainHandshakerMock, fallbackHandshakerMock));
HttpRequest req = newUpgradeRequest("main, fallback");
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
// test
assertEquals(1, resExts.size());
assertEquals("main", resExts.get(0).name());
assertTrue(resExts.get(0).parameters().isEmpty());
assertTrue(ch.pipeline().get(DummyDecoder.class) != null);
assertTrue(ch.pipeline().get(DummyEncoder.class) != null);
}
@Test
public void testCompatibleExtensionTogetherSuccess() {
// initialize
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("main"))).
andReturn(mainExtensionMock).anyTimes();
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("fallback"))).
andReturn(null).anyTimes();
replay(mainHandshakerMock);
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("fallback"))).
andReturn(fallbackExtensionMock).anyTimes();
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("main"))).
andReturn(null).anyTimes();
replay(fallbackHandshakerMock);
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
expect(mainExtensionMock.newReponseData()).andReturn(
new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
expect(mainExtensionMock.newExtensionEncoder()).andReturn(new DummyEncoder()).once();
expect(mainExtensionMock.newExtensionDecoder()).andReturn(new DummyDecoder()).once();
replay(mainExtensionMock);
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV2).anyTimes();
expect(fallbackExtensionMock.newReponseData()).andReturn(
new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
expect(fallbackExtensionMock.newExtensionEncoder()).andReturn(new Dummy2Encoder()).once();
expect(fallbackExtensionMock.newExtensionDecoder()).andReturn(new Dummy2Decoder()).once();
replay(fallbackExtensionMock);
// execute
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
mainHandshakerMock, fallbackHandshakerMock));
HttpRequest req = newUpgradeRequest("main, fallback");
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
// test
assertEquals(2, resExts.size());
assertEquals("main", resExts.get(0).name());
assertEquals("fallback", resExts.get(1).name());
assertTrue(ch.pipeline().get(DummyDecoder.class) != null);
assertTrue(ch.pipeline().get(DummyEncoder.class) != null);
assertTrue(ch.pipeline().get(Dummy2Decoder.class) != null);
assertTrue(ch.pipeline().get(Dummy2Encoder.class) != null);
}
@Test
public void testNoneExtensionMatchingSuccess() {
// initialize
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("unknown"))).
andReturn(null).anyTimes();
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("unknown2"))).
andReturn(null).anyTimes();
replay(mainHandshakerMock);
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("unknown"))).
andReturn(null).anyTimes();
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("unknown2"))).
andReturn(null).anyTimes();
replay(fallbackHandshakerMock);
// execute
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
mainHandshakerMock, fallbackHandshakerMock));
HttpRequest req = newUpgradeRequest("unknown, unknown2");
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
// test
assertFalse(res2.headers().contains(Names.SEC_WEBSOCKET_EXTENSIONS));
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static io.netty.handler.codec.http.websocketx.extensions.compression.
DeflateFrameServerExtensionHandshaker.*;
import static org.junit.Assert.*;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class DeflateFrameClientExtensionHandshakerTest {
@Test
public void testWebkitDeflateFrameData() {
DeflateFrameClientExtensionHandshaker handshaker =
new DeflateFrameClientExtensionHandshaker(true);
WebSocketExtensionData data = handshaker.newRequestData();
assertEquals(X_WEBKIT_DEFLATE_FRAME_EXTENSION, data.name());
assertTrue(data.parameters().isEmpty());
}
@Test
public void testDeflateFrameData() {
DeflateFrameClientExtensionHandshaker handshaker =
new DeflateFrameClientExtensionHandshaker(false);
WebSocketExtensionData data = handshaker.newRequestData();
assertEquals(DEFLATE_FRAME_EXTENSION, data.name());
assertTrue(data.parameters().isEmpty());
}
@Test
public void testNormalHandshake() {
DeflateFrameClientExtensionHandshaker handshaker =
new DeflateFrameClientExtensionHandshaker(false);
WebSocketClientExtension extension = handshaker.handshakeExtension(
new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, Collections.<String, String>emptyMap()));
assertNotNull(extension);
assertEquals(WebSocketClientExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerFrameDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerFrameDeflateEncoder);
}
@Test
public void testFailedHandshake() {
// initialize
DeflateFrameClientExtensionHandshaker handshaker =
new DeflateFrameClientExtensionHandshaker(false);
Map<String, String> parameters = new HashMap<String, String>();
parameters.put("invalid", "12");
// execute
WebSocketClientExtension extension = handshaker.handshakeExtension(
new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, parameters));
// test
assertNull(extension);
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static io.netty.handler.codec.http.websocketx.extensions.compression.
DeflateFrameServerExtensionHandshaker.*;
import static org.junit.Assert.*;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class DeflateFrameServerExtensionHandshakerTest {
@Test
public void testNormalHandshake() {
// initialize
DeflateFrameServerExtensionHandshaker handshaker =
new DeflateFrameServerExtensionHandshaker();
// execute
WebSocketServerExtension extension = handshaker.handshakeExtension(
new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, Collections.<String, String>emptyMap()));
// test
assertNotNull(extension);
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerFrameDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerFrameDeflateEncoder);
}
@Test
public void testWebkitHandshake() {
// initialize
DeflateFrameServerExtensionHandshaker handshaker =
new DeflateFrameServerExtensionHandshaker();
// execute
WebSocketServerExtension extension = handshaker.handshakeExtension(
new WebSocketExtensionData(X_WEBKIT_DEFLATE_FRAME_EXTENSION, Collections.<String, String>emptyMap()));
// test
assertNotNull(extension);
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerFrameDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerFrameDeflateEncoder);
}
@Test
public void testFailedHandshake() {
// initialize
DeflateFrameServerExtensionHandshaker handshaker =
new DeflateFrameServerExtensionHandshaker();
Map<String, String> parameters;
parameters = new HashMap<String, String>();
parameters.put("unknown", "11");
// execute
WebSocketServerExtension extension = handshaker.handshakeExtension(
new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, parameters));
// test
assertNull(extension);
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static org.junit.Assert.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
import java.util.Arrays;
import java.util.Random;
import org.junit.Test;
public class PerFrameDeflateDecoderTest {
private static final Random random = new Random();
@Test
public void testCompressedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.NONE, 9, 15, 8));
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
encoderChannel.writeOutbound(Unpooled.wrappedBuffer(payload));
ByteBuf compressedPayload = encoderChannel.readOutbound();
BinaryWebSocketFrame compressedFrame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV1 | WebSocketExtension.RSV3,
compressedPayload.slice(0, compressedPayload.readableBytes() - 4));
// execute
decoderChannel.writeInbound(compressedFrame);
BinaryWebSocketFrame uncompressedFrame = decoderChannel.readInbound();
// test
assertNotNull(uncompressedFrame);
assertNotNull(uncompressedFrame.content());
assertTrue(uncompressedFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV3, uncompressedFrame.rsv());
assertEquals(300, uncompressedFrame.content().readableBytes());
byte[] finalPayload = new byte[300];
uncompressedFrame.content().readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
uncompressedFrame.release();
}
@Test
public void testNormalFrame() {
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
BinaryWebSocketFrame frame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload));
// execute
decoderChannel.writeInbound(frame);
BinaryWebSocketFrame newFrame = decoderChannel.readInbound();
// test
assertNotNull(newFrame);
assertNotNull(newFrame.content());
assertTrue(newFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV3, newFrame.rsv());
assertEquals(300, newFrame.content().readableBytes());
byte[] finalPayload = new byte[300];
newFrame.content().readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
newFrame.release();
}
// See https://github.com/netty/netty/issues/4348
@Test
public void testCompressedEmptyFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.NONE, 9, 15, 8));
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
encoderChannel.writeOutbound(Unpooled.EMPTY_BUFFER);
ByteBuf compressedPayload = encoderChannel.readOutbound();
BinaryWebSocketFrame compressedFrame =
new BinaryWebSocketFrame(true, WebSocketExtension.RSV1 | WebSocketExtension.RSV3, compressedPayload);
// execute
decoderChannel.writeInbound(compressedFrame);
BinaryWebSocketFrame uncompressedFrame = decoderChannel.readInbound();
// test
assertNotNull(uncompressedFrame);
assertNotNull(uncompressedFrame.content());
assertTrue(uncompressedFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV3, uncompressedFrame.rsv());
assertEquals(0, uncompressedFrame.content().readableBytes());
uncompressedFrame.release();
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static org.junit.Assert.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
import java.util.Arrays;
import java.util.Random;
import org.junit.Test;
public class PerFrameDeflateEncoderTest {
private static final Random random = new Random();
@Test
public void testCompressedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(new PerFrameDeflateEncoder(9, 15, false));
EmbeddedChannel decoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibDecoder(ZlibWrapper.NONE));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
BinaryWebSocketFrame frame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload));
// execute
encoderChannel.writeOutbound(frame);
BinaryWebSocketFrame compressedFrame = encoderChannel.readOutbound();
// test
assertNotNull(compressedFrame);
assertNotNull(compressedFrame.content());
assertTrue(compressedFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV1 | WebSocketExtension.RSV3, compressedFrame.rsv());
decoderChannel.writeInbound(compressedFrame.content());
decoderChannel.writeInbound(DeflateDecoder.FRAME_TAIL);
ByteBuf uncompressedPayload = decoderChannel.readInbound();
assertEquals(300, uncompressedPayload.readableBytes());
byte[] finalPayload = new byte[300];
uncompressedPayload.readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
uncompressedPayload.release();
}
@Test
public void testAlreadyCompressedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(new PerFrameDeflateEncoder(9, 15, false));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
BinaryWebSocketFrame frame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV3 | WebSocketExtension.RSV1, Unpooled.wrappedBuffer(payload));
// execute
encoderChannel.writeOutbound(frame);
BinaryWebSocketFrame newFrame = encoderChannel.readOutbound();
// test
assertNotNull(newFrame);
assertNotNull(newFrame.content());
assertTrue(newFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV3 | WebSocketExtension.RSV1, newFrame.rsv());
assertEquals(300, newFrame.content().readableBytes());
byte[] finalPayload = new byte[300];
newFrame.content().readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
newFrame.release();
}
@Test
public void testFramementedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(new PerFrameDeflateEncoder(9, 15, false));
EmbeddedChannel decoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibDecoder(ZlibWrapper.NONE));
// initialize
byte[] payload1 = new byte[100];
random.nextBytes(payload1);
byte[] payload2 = new byte[100];
random.nextBytes(payload2);
byte[] payload3 = new byte[100];
random.nextBytes(payload3);
BinaryWebSocketFrame frame1 = new BinaryWebSocketFrame(false,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload1));
ContinuationWebSocketFrame frame2 = new ContinuationWebSocketFrame(false,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload2));
ContinuationWebSocketFrame frame3 = new ContinuationWebSocketFrame(true,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload3));
// execute
encoderChannel.writeOutbound(frame1);
encoderChannel.writeOutbound(frame2);
encoderChannel.writeOutbound(frame3);
BinaryWebSocketFrame compressedFrame1 = encoderChannel.readOutbound();
ContinuationWebSocketFrame compressedFrame2 = encoderChannel.readOutbound();
ContinuationWebSocketFrame compressedFrame3 = encoderChannel.readOutbound();
// test
assertNotNull(compressedFrame1);
assertNotNull(compressedFrame2);
assertNotNull(compressedFrame3);
assertEquals(WebSocketExtension.RSV1 | WebSocketExtension.RSV3, compressedFrame1.rsv());
assertEquals(WebSocketExtension.RSV1 | WebSocketExtension.RSV3, compressedFrame2.rsv());
assertEquals(WebSocketExtension.RSV1 | WebSocketExtension.RSV3, compressedFrame3.rsv());
assertFalse(compressedFrame1.isFinalFragment());
assertFalse(compressedFrame2.isFinalFragment());
assertTrue(compressedFrame3.isFinalFragment());
decoderChannel.writeInbound(compressedFrame1.content());
decoderChannel.writeInbound(Unpooled.wrappedBuffer(DeflateDecoder.FRAME_TAIL));
ByteBuf uncompressedPayload1 = decoderChannel.readInbound();
byte[] finalPayload1 = new byte[100];
uncompressedPayload1.readBytes(finalPayload1);
assertTrue(Arrays.equals(finalPayload1, payload1));
uncompressedPayload1.release();
decoderChannel.writeInbound(compressedFrame2.content());
decoderChannel.writeInbound(Unpooled.wrappedBuffer(DeflateDecoder.FRAME_TAIL));
ByteBuf uncompressedPayload2 = decoderChannel.readInbound();
byte[] finalPayload2 = new byte[100];
uncompressedPayload2.readBytes(finalPayload2);
assertTrue(Arrays.equals(finalPayload2, payload2));
uncompressedPayload2.release();
decoderChannel.writeInbound(compressedFrame3.content());
decoderChannel.writeInbound(Unpooled.wrappedBuffer(DeflateDecoder.FRAME_TAIL));
ByteBuf uncompressedPayload3 = decoderChannel.readInbound();
byte[] finalPayload3 = new byte[100];
uncompressedPayload3.readBytes(finalPayload3);
assertTrue(Arrays.equals(finalPayload3, payload3));
uncompressedPayload3.release();
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static io.netty.handler.codec.http.websocketx.extensions.compression.
PerMessageDeflateServerExtensionHandshaker.*;
import static org.junit.Assert.*;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class PerMessageDeflateClientExtensionHandshakerTest {
@Test
public void testNormalData() {
PerMessageDeflateClientExtensionHandshaker handshaker =
new PerMessageDeflateClientExtensionHandshaker();
WebSocketExtensionData data = handshaker.newRequestData();
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
assertTrue(data.parameters().isEmpty());
}
@Test
public void testCustomData() {
PerMessageDeflateClientExtensionHandshaker handshaker =
new PerMessageDeflateClientExtensionHandshaker(6, true, 10, true, true);
WebSocketExtensionData data = handshaker.newRequestData();
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
assertTrue(data.parameters().containsKey(CLIENT_MAX_WINDOW));
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
assertTrue(data.parameters().get(SERVER_MAX_WINDOW).equals("10"));
assertTrue(data.parameters().containsKey(CLIENT_MAX_WINDOW));
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
}
@Test
public void testNormalHandshake() {
PerMessageDeflateClientExtensionHandshaker handshaker =
new PerMessageDeflateClientExtensionHandshaker();
WebSocketClientExtension extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, Collections.<String, String>emptyMap()));
assertNotNull(extension);
assertEquals(WebSocketClientExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
}
@Test
public void testCustomHandshake() {
WebSocketClientExtension extension;
Map<String, String> parameters;
// initialize
PerMessageDeflateClientExtensionHandshaker handshaker =
new PerMessageDeflateClientExtensionHandshaker(6, true, 10, true, true);
parameters = new HashMap<String, String>();
parameters.put(CLIENT_MAX_WINDOW, "12");
parameters.put(SERVER_MAX_WINDOW, "10");
parameters.put(CLIENT_NO_CONTEXT, null);
parameters.put(SERVER_NO_CONTEXT, null);
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
// test
assertNotNull(extension);
assertEquals(WebSocketClientExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
// initialize
parameters = new HashMap<String, String>();
parameters.put(SERVER_MAX_WINDOW, "10");
parameters.put(SERVER_NO_CONTEXT, null);
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
// test
assertNotNull(extension);
assertEquals(WebSocketClientExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
// initialize
parameters = new HashMap<String, String>();
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
// test
assertNull(extension);
}
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static org.junit.Assert.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
import java.util.Arrays;
import java.util.Random;
import org.junit.Test;
public class PerMessageDeflateDecoderTest {
private static final Random random = new Random();
@Test
public void testCompressedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.NONE, 9, 15, 8));
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerMessageDeflateDecoder(false));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
encoderChannel.writeOutbound(Unpooled.wrappedBuffer(payload));
ByteBuf compressedPayload = encoderChannel.readOutbound();
BinaryWebSocketFrame compressedFrame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV1 | WebSocketExtension.RSV3,
compressedPayload.slice(0, compressedPayload.readableBytes() - 4));
// execute
decoderChannel.writeInbound(compressedFrame);
BinaryWebSocketFrame uncompressedFrame = decoderChannel.readInbound();
// test
assertNotNull(uncompressedFrame);
assertNotNull(uncompressedFrame.content());
assertTrue(uncompressedFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV3, uncompressedFrame.rsv());
assertEquals(300, uncompressedFrame.content().readableBytes());
byte[] finalPayload = new byte[300];
uncompressedFrame.content().readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
uncompressedFrame.release();
}
@Test
public void testNormalFrame() {
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerMessageDeflateDecoder(false));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
BinaryWebSocketFrame frame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload));
// execute
decoderChannel.writeInbound(frame);
BinaryWebSocketFrame newFrame = decoderChannel.readInbound();
// test
assertNotNull(newFrame);
assertNotNull(newFrame.content());
assertTrue(newFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV3, newFrame.rsv());
assertEquals(300, newFrame.content().readableBytes());
byte[] finalPayload = new byte[300];
newFrame.content().readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
newFrame.release();
}
@Test
public void testFramementedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.NONE, 9, 15, 8));
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerMessageDeflateDecoder(false));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
encoderChannel.writeOutbound(Unpooled.wrappedBuffer(payload));
ByteBuf compressedPayload = encoderChannel.readOutbound();
compressedPayload = compressedPayload.slice(0, compressedPayload.readableBytes() - 4);
int oneThird = compressedPayload.readableBytes() / 3;
BinaryWebSocketFrame compressedFrame1 = new BinaryWebSocketFrame(false,
WebSocketExtension.RSV1 | WebSocketExtension.RSV3,
compressedPayload.slice(0, oneThird));
ContinuationWebSocketFrame compressedFrame2 = new ContinuationWebSocketFrame(false,
WebSocketExtension.RSV3, compressedPayload.slice(oneThird, oneThird));
ContinuationWebSocketFrame compressedFrame3 = new ContinuationWebSocketFrame(true,
WebSocketExtension.RSV3, compressedPayload.slice(oneThird * 2,
compressedPayload.readableBytes() - oneThird * 2));
// execute
decoderChannel.writeInbound(compressedFrame1.retain());
decoderChannel.writeInbound(compressedFrame2.retain());
decoderChannel.writeInbound(compressedFrame3);
BinaryWebSocketFrame uncompressedFrame1 = decoderChannel.readInbound();
ContinuationWebSocketFrame uncompressedFrame2 = decoderChannel.readInbound();
ContinuationWebSocketFrame uncompressedFrame3 = decoderChannel.readInbound();
// test
assertNotNull(uncompressedFrame1);
assertNotNull(uncompressedFrame2);
assertNotNull(uncompressedFrame3);
assertEquals(WebSocketExtension.RSV3, uncompressedFrame1.rsv());
assertEquals(WebSocketExtension.RSV3, uncompressedFrame2.rsv());
assertEquals(WebSocketExtension.RSV3, uncompressedFrame3.rsv());
ByteBuf finalPayloadWrapped = Unpooled.wrappedBuffer(uncompressedFrame1.content(),
uncompressedFrame2.content(), uncompressedFrame3.content());
assertEquals(300, finalPayloadWrapped.readableBytes());
byte[] finalPayload = new byte[300];
finalPayloadWrapped.readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
uncompressedFrame1.release();
uncompressedFrame2.release();
uncompressedFrame3.release();
}
@Test
public void testMultiCompressedPayloadWithinFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.NONE, 9, 15, 8));
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerMessageDeflateDecoder(false));
// initialize
byte[] payload1 = new byte[100];
random.nextBytes(payload1);
byte[] payload2 = new byte[100];
random.nextBytes(payload2);
encoderChannel.writeOutbound(Unpooled.wrappedBuffer(payload1));
ByteBuf compressedPayload1 = encoderChannel.readOutbound();
encoderChannel.writeOutbound(Unpooled.wrappedBuffer(payload2));
ByteBuf compressedPayload2 = encoderChannel.readOutbound();
BinaryWebSocketFrame compressedFrame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV1 | WebSocketExtension.RSV3,
Unpooled.wrappedBuffer(
compressedPayload1,
compressedPayload2.slice(0, compressedPayload2.readableBytes() - 4)));
// execute
decoderChannel.writeInbound(compressedFrame);
BinaryWebSocketFrame uncompressedFrame = decoderChannel.readInbound();
// test
assertNotNull(uncompressedFrame);
assertNotNull(uncompressedFrame.content());
assertTrue(uncompressedFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV3, uncompressedFrame.rsv());
assertEquals(200, uncompressedFrame.content().readableBytes());
byte[] finalPayload1 = new byte[100];
uncompressedFrame.content().readBytes(finalPayload1);
assertTrue(Arrays.equals(finalPayload1, payload1));
byte[] finalPayload2 = new byte[100];
uncompressedFrame.content().readBytes(finalPayload2);
assertTrue(Arrays.equals(finalPayload2, payload2));
uncompressedFrame.release();
}
}

View File

@ -0,0 +1,160 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static org.junit.Assert.*;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
import java.util.Arrays;
import java.util.Random;
import org.junit.Test;
public class PerMessageDeflateEncoderTest {
private static final Random random = new Random();
@Test
public void testCompressedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(new PerMessageDeflateEncoder(9, 15, false));
EmbeddedChannel decoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibDecoder(ZlibWrapper.NONE));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
BinaryWebSocketFrame frame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload));
// execute
encoderChannel.writeOutbound(frame);
BinaryWebSocketFrame compressedFrame = encoderChannel.readOutbound();
// test
assertNotNull(compressedFrame);
assertNotNull(compressedFrame.content());
assertTrue(compressedFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV1 | WebSocketExtension.RSV3, compressedFrame.rsv());
decoderChannel.writeInbound(compressedFrame.content());
decoderChannel.writeInbound(DeflateDecoder.FRAME_TAIL);
ByteBuf uncompressedPayload = decoderChannel.readInbound();
assertEquals(300, uncompressedPayload.readableBytes());
byte[] finalPayload = new byte[300];
uncompressedPayload.readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
uncompressedPayload.release();
}
@Test
public void testAlreadyCompressedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(new PerMessageDeflateEncoder(9, 15, false));
// initialize
byte[] payload = new byte[300];
random.nextBytes(payload);
BinaryWebSocketFrame frame = new BinaryWebSocketFrame(true,
WebSocketExtension.RSV3 | WebSocketExtension.RSV1, Unpooled.wrappedBuffer(payload));
// execute
encoderChannel.writeOutbound(frame);
BinaryWebSocketFrame newFrame = encoderChannel.readOutbound();
// test
assertNotNull(newFrame);
assertNotNull(newFrame.content());
assertTrue(newFrame instanceof BinaryWebSocketFrame);
assertEquals(WebSocketExtension.RSV3 | WebSocketExtension.RSV1, newFrame.rsv());
assertEquals(300, newFrame.content().readableBytes());
byte[] finalPayload = new byte[300];
newFrame.content().readBytes(finalPayload);
assertTrue(Arrays.equals(finalPayload, payload));
newFrame.release();
}
@Test
public void testFramementedFrame() {
EmbeddedChannel encoderChannel = new EmbeddedChannel(new PerMessageDeflateEncoder(9, 15, false));
EmbeddedChannel decoderChannel = new EmbeddedChannel(
ZlibCodecFactory.newZlibDecoder(ZlibWrapper.NONE));
// initialize
byte[] payload1 = new byte[100];
random.nextBytes(payload1);
byte[] payload2 = new byte[100];
random.nextBytes(payload2);
byte[] payload3 = new byte[100];
random.nextBytes(payload3);
BinaryWebSocketFrame frame1 = new BinaryWebSocketFrame(false,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload1));
ContinuationWebSocketFrame frame2 = new ContinuationWebSocketFrame(false,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload2));
ContinuationWebSocketFrame frame3 = new ContinuationWebSocketFrame(true,
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload3));
// execute
encoderChannel.writeOutbound(frame1);
encoderChannel.writeOutbound(frame2);
encoderChannel.writeOutbound(frame3);
BinaryWebSocketFrame compressedFrame1 = encoderChannel.readOutbound();
ContinuationWebSocketFrame compressedFrame2 = encoderChannel.readOutbound();
ContinuationWebSocketFrame compressedFrame3 = encoderChannel.readOutbound();
// test
assertNotNull(compressedFrame1);
assertNotNull(compressedFrame2);
assertNotNull(compressedFrame3);
assertEquals(WebSocketExtension.RSV1 | WebSocketExtension.RSV3, compressedFrame1.rsv());
assertEquals(WebSocketExtension.RSV3, compressedFrame2.rsv());
assertEquals(WebSocketExtension.RSV3, compressedFrame3.rsv());
assertFalse(compressedFrame1.isFinalFragment());
assertFalse(compressedFrame2.isFinalFragment());
assertTrue(compressedFrame3.isFinalFragment());
decoderChannel.writeInbound(compressedFrame1.content());
ByteBuf uncompressedPayload1 = decoderChannel.readInbound();
byte[] finalPayload1 = new byte[100];
uncompressedPayload1.readBytes(finalPayload1);
assertTrue(Arrays.equals(finalPayload1, payload1));
uncompressedPayload1.release();
decoderChannel.writeInbound(compressedFrame2.content());
ByteBuf uncompressedPayload2 = decoderChannel.readInbound();
byte[] finalPayload2 = new byte[100];
uncompressedPayload2.readBytes(finalPayload2);
assertTrue(Arrays.equals(finalPayload2, payload2));
uncompressedPayload2.release();
decoderChannel.writeInbound(compressedFrame3.content());
decoderChannel.writeInbound(DeflateDecoder.FRAME_TAIL);
ByteBuf uncompressedPayload3 = decoderChannel.readInbound();
byte[] finalPayload3 = new byte[100];
uncompressedPayload3.readBytes(finalPayload3);
assertTrue(Arrays.equals(finalPayload3, payload3));
uncompressedPayload3.release();
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static io.netty.handler.codec.http.websocketx.extensions.compression.
PerMessageDeflateServerExtensionHandshaker.*;
import static org.junit.Assert.*;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class PerMessageDeflateServerExtensionHandshakerTest {
@Test
public void testNormalHandshake() {
WebSocketServerExtension extension;
WebSocketExtensionData data;
Map<String, String> parameters;
// initialize
PerMessageDeflateServerExtensionHandshaker handshaker =
new PerMessageDeflateServerExtensionHandshaker();
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, Collections.<String, String>emptyMap()));
// test
assertNotNull(extension);
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
// execute
data = extension.newReponseData();
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
assertTrue(data.parameters().isEmpty());
// initialize
parameters = new HashMap<String, String>();
parameters.put(CLIENT_MAX_WINDOW, null);
parameters.put(CLIENT_NO_CONTEXT, null);
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, Collections.<String, String>emptyMap()));
// test
assertNotNull(extension);
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
// execute
data = extension.newReponseData();
// test
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
assertTrue(data.parameters().isEmpty());
// initialize
parameters = new HashMap<String, String>();
parameters.put(SERVER_MAX_WINDOW, "12");
parameters.put(SERVER_NO_CONTEXT, null);
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
// test
assertNull(extension);
}
@Test
public void testCustomHandshake() {
WebSocketServerExtension extension;
Map<String, String> parameters;
WebSocketExtensionData data;
// initialize
PerMessageDeflateServerExtensionHandshaker handshaker =
new PerMessageDeflateServerExtensionHandshaker(6, true, 10, true, true);
parameters = new HashMap<String, String>();
parameters.put(CLIENT_MAX_WINDOW, null);
parameters.put(SERVER_MAX_WINDOW, "12");
parameters.put(CLIENT_NO_CONTEXT, null);
parameters.put(SERVER_NO_CONTEXT, null);
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
// test
assertNotNull(extension);
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
// execute
data = extension.newReponseData();
// test
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
assertTrue(data.parameters().containsKey(CLIENT_MAX_WINDOW));
assertTrue(data.parameters().get(CLIENT_MAX_WINDOW).equals("10"));
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
assertTrue(data.parameters().get(SERVER_MAX_WINDOW).equals("12"));
assertTrue(data.parameters().containsKey(CLIENT_MAX_WINDOW));
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
// initialize
parameters = new HashMap<String, String>();
parameters.put(SERVER_MAX_WINDOW, "12");
parameters.put(SERVER_NO_CONTEXT, null);
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
// test
assertNotNull(extension);
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
// execute
data = extension.newReponseData();
// test
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
assertEquals(2, data.parameters().size());
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
assertTrue(data.parameters().get(SERVER_MAX_WINDOW).equals("12"));
assertTrue(data.parameters().containsKey(SERVER_NO_CONTEXT));
// initialize
parameters = new HashMap<String, String>();
// execute
extension = handshaker.handshakeExtension(
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
// test
assertNotNull(extension);
// execute
data = extension.newReponseData();
// test
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
assertTrue(data.parameters().isEmpty());
}
}

View File

@ -0,0 +1,196 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.http.websocketx.extensions.compression;
import static io.netty.handler.codec.http.websocketx.extensions.compression.
PerMessageDeflateServerExtensionHandshaker.*;
import static io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionTestUtil.*;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionUtil;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler;
import io.netty.handler.codec.http.HttpHeaders.Names;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
public class WebSocketServerCompressionHandlerTest {
@Test
public void testNormalSuccess() {
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION);
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
Assert.assertTrue(exts.get(0).parameters().isEmpty());
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
}
@Test
public void testClientWindowSizeSuccess() {
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, false, 10, false, false)));
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + CLIENT_MAX_WINDOW);
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
Assert.assertEquals("10", exts.get(0).parameters().get(CLIENT_MAX_WINDOW));
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
}
@Test
public void testClientWindowSizeUnavailable() {
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, false, 10, false, false)));
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION);
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
Assert.assertTrue(exts.get(0).parameters().isEmpty());
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
}
@Test
public void testServerWindowSizeSuccess() {
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, true, 15, false, false)));
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + SERVER_MAX_WINDOW + "=10");
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
Assert.assertEquals("10", exts.get(0).parameters().get(SERVER_MAX_WINDOW));
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
}
@Test
public void testServerWindowSizeDisable() {
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, false, 15, false, false)));
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + SERVER_MAX_WINDOW + "=10");
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
Assert.assertFalse(res2.headers().contains(Names.SEC_WEBSOCKET_EXTENSIONS));
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) == null);
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) == null);
}
@Test
public void testServerNoContext() {
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + SERVER_NO_CONTEXT);
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
Assert.assertFalse(res2.headers().contains(Names.SEC_WEBSOCKET_EXTENSIONS));
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) == null);
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) == null);
}
@Test
public void testClientNoContext() {
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + CLIENT_NO_CONTEXT);
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
Assert.assertTrue(exts.get(0).parameters().isEmpty());
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
}
@Test
public void testServerWindowSizeDisableThenFallback() {
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, false, 15, false, false)));
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + SERVER_MAX_WINDOW + "=10, " +
PERMESSAGE_DEFLATE_EXTENSION);
ch.writeInbound(req);
HttpResponse res = newUpgradeResponse(null);
ch.writeOutbound(res);
HttpResponse res2 = ch.readOutbound();
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
Assert.assertTrue(exts.get(0).parameters().isEmpty());
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
}
}

View File

@ -66,7 +66,7 @@ public final class ZlibCodecFactory {
public static ZlibEncoder newZlibEncoder(ZlibWrapper wrapper, int compressionLevel, int windowBits, int memLevel) {
if (PlatformDependent.javaVersion() < 7 || noJdkZlibEncoder ||
windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
return new JZlibEncoder(wrapper, compressionLevel, windowBits, memLevel);
} else {
return new JdkZlibEncoder(wrapper, compressionLevel);
@ -91,7 +91,7 @@ public final class ZlibCodecFactory {
public static ZlibEncoder newZlibEncoder(int compressionLevel, int windowBits, int memLevel, byte[] dictionary) {
if (PlatformDependent.javaVersion() < 7 || noJdkZlibEncoder ||
windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
return new JZlibEncoder(compressionLevel, windowBits, memLevel, dictionary);
} else {
return new JdkZlibEncoder(compressionLevel, dictionary);

View File

@ -33,6 +33,7 @@ import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@ -113,6 +114,7 @@ public final class WebSocketClient {
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator(8192),
new WebSocketClientCompressionHandler(),
handler);
}
});

View File

@ -20,6 +20,7 @@ import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.ssl.SslContext;
/**
@ -40,6 +41,7 @@ public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel
}
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65536));
pipeline.addLast(new WebSocketServerCompressionHandler());
pipeline.addLast(new WebSocketServerHandler());
}
}