diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java
index 974d4de5b1..adb837c5e9 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java
@@ -269,6 +269,10 @@ public interface HttpHeaders extends TextHeaders {
* {@code "Sec-WebSocket-Accept"}
*/
public static final AsciiString SEC_WEBSOCKET_ACCEPT = new AsciiString("Sec-WebSocket-Accept");
+ /**
+ * {@code "Sec-WebSocket-Protocol"}
+ */
+ public static final AsciiString SEC_WEBSOCKET_EXTENSIONS = new AsciiString("Sec-WebSocket-Extensions");
/**
* {@code "Server"}
*/
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
index 7dca7ba87f..a6199a7e07 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/TextWebSocketFrame.java
@@ -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);
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/Utf8FrameValidator.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/Utf8FrameValidator.java
new file mode 100644
index 0000000000..4902c3b98e
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/Utf8FrameValidator.java
@@ -0,0 +1,102 @@
+/*
+ * 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.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 ChannelHandlerAdapter {
+
+ 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);
+ }
+ }
+ }
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
index 1339f2dd5d..454415a827 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocket08FrameDecoder.java
@@ -104,7 +104,6 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
private byte[] maskingKey;
private int framePayloadLen1;
private boolean receivedClosingHandshake;
- private Utf8Validator utf8Validator;
private State state = State.READING_FIRST;
/**
@@ -315,33 +314,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++;
}
@@ -407,17 +381,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) {
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java
index fc8e4e38a0..7a55b0903b 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientProtocolHandler.java
@@ -145,5 +145,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());
+ }
}
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java
index 239e28b2c0..62ce893bc9 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandler.java
@@ -96,6 +96,11 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols,
allowExtensions, maxFramePayloadLength));
}
+ if (cp.get(Utf8FrameValidator.class) == null) {
+ // Add the UFT8 checking before this one.
+ ctx.pipeline().addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
+ new Utf8FrameValidator());
+ }
}
@Override
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtension.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtension.java
new file mode 100644
index 0000000000..6917b1fda3
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtension.java
@@ -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 {
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandler.java
new file mode 100644
index 0000000000..41c8f5b0a1
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandler.java
@@ -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.ChannelHandlerAdapter;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.CodecException;
+import io.netty.handler.codec.http.HttpHeaders;
+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
+ * io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler.
+ */
+public class WebSocketClientExtensionHandler extends ChannelHandlerAdapter {
+
+ private final List 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().get(HttpHeaders.Names.SEC_WEBSOCKET_EXTENSIONS);
+
+ for (WebSocketClientExtensionHandshaker extentionHandshaker : extensionHandshakers) {
+ WebSocketExtensionData extensionData = extentionHandshaker.newRequestData();
+ headerValue = WebSocketExtensionUtil.appendExtension(headerValue,
+ extensionData.name(), extensionData.parameters());
+ }
+
+ request.headers().set(HttpHeaders.Names.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().get(HttpHeaders.Names.SEC_WEBSOCKET_EXTENSIONS);
+
+ if (extensionsHeader != null) {
+ List extensions =
+ WebSocketExtensionUtil.extractExtensions(extensionsHeader);
+ List validExtensions =
+ new ArrayList(extensions.size());
+ int rsv = 0;
+
+ for (WebSocketExtensionData extensionData : extensions) {
+ Iterator 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);
+ }
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandshaker.java
new file mode 100644
index 0000000000..70514a326e
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandshaker.java
@@ -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);
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtension.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtension.java
new file mode 100644
index 0000000000..e5605e986e
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtension.java
@@ -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();
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionData.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionData.java
new file mode 100644
index 0000000000..eb3c5864de
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionData.java
@@ -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 Sec-WebSocket-Extensions header.
+ *
+ * See io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_EXTENSIONS.
+ */
+public final class WebSocketExtensionData {
+
+ private final String name;
+ private final Map parameters;
+
+ public WebSocketExtensionData(String name, Map 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 parameters() {
+ return parameters;
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionDecoder.java
new file mode 100644
index 0000000000..0223cb4679
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionDecoder.java
@@ -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 io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension decoder.
+ */
+public abstract class WebSocketExtensionDecoder extends MessageToMessageDecoder {
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionEncoder.java
new file mode 100644
index 0000000000..de505bba12
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionEncoder.java
@@ -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 io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension encoder.
+ */
+public abstract class WebSocketExtensionEncoder extends MessageToMessageEncoder {
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionUtil.java
new file mode 100644
index 0000000000..101d81aa7b
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionUtil.java
@@ -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;
+
+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(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE, true) &&
+ httpMessage.headers().contains(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET, true);
+ }
+
+ public static List extractExtensions(String extensionHeader) {
+ String[] rawExtensions = StringUtil.split(extensionHeader, EXTENSION_SEPARATOR);
+ if (rawExtensions.length > 0) {
+ List extensions = new ArrayList(rawExtensions.length);
+ for (String rawExtension : rawExtensions) {
+ String[] extensionParameters = StringUtil.split(rawExtension, PARAMETER_SEPARATOR);
+ String name = extensionParameters[0].trim();
+ Map parameters;
+ if (extensionParameters.length > 1) {
+ parameters = new HashMap(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.emptyMap();
+ }
+ extensions.add(new WebSocketExtensionData(name, parameters));
+ }
+ return extensions;
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ static String appendExtension(String currentHeaderValue, String extensionName,
+ Map 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 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
+ }
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtension.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtension.java
new file mode 100644
index 0000000000..f3b1e95275
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtension.java
@@ -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();
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandler.java
new file mode 100644
index 0000000000..9466724383
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandler.java
@@ -0,0 +1,142 @@
+/*
+ * 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.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerAdapter;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http.HttpHeaders;
+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
+ * io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler.
+ */
+public class WebSocketServerExtensionHandler extends ChannelHandlerAdapter {
+
+ private final List extensionHandshakers;
+
+ private List 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().get(HttpHeaders.Names.SEC_WEBSOCKET_EXTENSIONS);
+
+ if (extensionsHeader != null) {
+ List extensions =
+ WebSocketExtensionUtil.extractExtensions(extensionsHeader);
+ int rsv = 0;
+
+ for (WebSocketExtensionData extensionData : extensions) {
+ Iterator 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(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().get(HttpHeaders.Names.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(HttpHeaders.Names.SEC_WEBSOCKET_EXTENSIONS, headerValue);
+ }
+ }
+
+ super.write(ctx, msg, promise);
+ }
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandshaker.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandshaker.java
new file mode 100644
index 0000000000..de02e546d1
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandshaker.java
@@ -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 null 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);
+
+}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateDecoder.java
new file mode 100644
index 0000000000..03475f6e95
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateDecoder.java
@@ -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.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
+ * io.netty.handler.codec.http.websocketx.WebSocketFrame.
+ */
+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
+ *
+ * See io.netty.example.http.websocketx.client.WebSocketClient and
+ * io.netty.example.http.websocketx.html5.WebSocketServer for usage.
+ *
+ */
+package io.netty.handler.codec.http.websocketx.extensions.compression;
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/package-info.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/package-info.java
new file mode 100644
index 0000000000..83f16b0edb
--- /dev/null
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/package-info.java
@@ -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
+ * WebSocket Extensions.
+ *
+ * See WebSocketServerExtensionHandler for more details.
+ */
+package io.netty.handler.codec.http.websocketx.extensions;
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandlerTest.java
new file mode 100644
index 0000000000..acaa8e7be7
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketClientExtensionHandlerTest.java
@@ -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.emptyMap())).once();
+ expect(mainHandshakerMock.handshakeExtension(
+ anyObject(WebSocketExtensionData.class))).andReturn(mainExtensionMock).once();
+ replay(mainHandshakerMock);
+
+ expect(fallbackHandshakerMock.newRequestData()).
+ andReturn(new WebSocketExtensionData("fallback", Collections.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 reqExts = WebSocketExtensionUtil.extractExtensions(
+ req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
+
+ HttpResponse res = newUpgradeResponse("main");
+ ch.writeInbound(res);
+
+ HttpResponse res2 = ch.readInbound();
+ List 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.emptyMap())).once();
+ expect(mainHandshakerMock.handshakeExtension(
+ anyObject(WebSocketExtensionData.class))).andReturn(null).once();
+ replay(mainHandshakerMock);
+
+ expect(fallbackHandshakerMock.newRequestData()).
+ andReturn(new WebSocketExtensionData("fallback", Collections.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 reqExts = WebSocketExtensionUtil.extractExtensions(
+ req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
+
+ HttpResponse res = newUpgradeResponse("fallback");
+ ch.writeInbound(res);
+
+ HttpResponse res2 = ch.readInbound();
+ List 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.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.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 reqExts = WebSocketExtensionUtil.extractExtensions(
+ req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
+
+ HttpResponse res = newUpgradeResponse("main, fallback");
+ ch.writeInbound(res);
+
+ HttpResponse res2 = ch.readInbound();
+ List 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.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.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 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());
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionTestUtil.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionTestUtil.java
new file mode 100644
index 0000000000..b802246a61
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketExtensionTestUtil.java
@@ -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 out) throws Exception {
+ // unused
+ }
+ }
+
+ static class DummyDecoder extends WebSocketExtensionDecoder {
+ @Override
+ protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,
+ List out) throws Exception {
+ // unused
+ }
+ }
+
+ static class Dummy2Encoder extends WebSocketExtensionEncoder {
+ @Override
+ protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
+ List out) throws Exception {
+ // unused
+ }
+ }
+
+ static class Dummy2Decoder extends WebSocketExtensionDecoder {
+ @Override
+ protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,
+ List out) throws Exception {
+ // unused
+ }
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandlerTest.java
new file mode 100644
index 0000000000..cf1bd420ec
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/WebSocketServerExtensionHandlerTest.java
@@ -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.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 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.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.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 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));
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateFrameClientExtensionHandshakerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateFrameClientExtensionHandshakerTest.java
new file mode 100644
index 0000000000..d3ee5a4c1f
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateFrameClientExtensionHandshakerTest.java
@@ -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.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 parameters = new HashMap();
+ parameters.put("invalid", "12");
+
+ // execute
+ WebSocketClientExtension extension = handshaker.handshakeExtension(
+ new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, parameters));
+
+ // test
+ assertNull(extension);
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateFrameServerExtensionHandshakerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateFrameServerExtensionHandshakerTest.java
new file mode 100644
index 0000000000..1d6f8ba5af
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateFrameServerExtensionHandshakerTest.java
@@ -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.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.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 parameters;
+ parameters = new HashMap();
+ parameters.put("unknown", "11");
+
+ // execute
+ WebSocketServerExtension extension = handshaker.handshakeExtension(
+ new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, parameters));
+
+ // test
+ assertNull(extension);
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerFrameDeflateDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerFrameDeflateDecoderTest.java
new file mode 100644
index 0000000000..bb61e8a1cf
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerFrameDeflateDecoderTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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();
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerFrameDeflateEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerFrameDeflateEncoderTest.java
new file mode 100644
index 0000000000..5c085e9cee
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerFrameDeflateEncoderTest.java
@@ -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();
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateClientExtensionHandshakerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateClientExtensionHandshakerTest.java
new file mode 100644
index 0000000000..f221100e48
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateClientExtensionHandshakerTest.java
@@ -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.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 parameters;
+
+ // initialize
+ PerMessageDeflateClientExtensionHandshaker handshaker =
+ new PerMessageDeflateClientExtensionHandshaker(6, true, 10, true, true);
+
+ parameters = new HashMap();
+ 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();
+ 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();
+
+ // execute
+ extension = handshaker.handshakeExtension(
+ new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
+
+ // test
+ assertNull(extension);
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateDecoderTest.java
new file mode 100644
index 0000000000..5c02976076
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateDecoderTest.java
@@ -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();
+ }
+
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateEncoderTest.java
new file mode 100644
index 0000000000..66ae9627e9
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateEncoderTest.java
@@ -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();
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateServerExtensionHandshakerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateServerExtensionHandshakerTest.java
new file mode 100644
index 0000000000..3b245d8164
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateServerExtensionHandshakerTest.java
@@ -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 parameters;
+
+ // initialize
+ PerMessageDeflateServerExtensionHandshaker handshaker =
+ new PerMessageDeflateServerExtensionHandshaker();
+
+ // execute
+ extension = handshaker.handshakeExtension(
+ new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, Collections.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();
+ parameters.put(CLIENT_MAX_WINDOW, null);
+ parameters.put(CLIENT_NO_CONTEXT, null);
+
+ // execute
+ extension = handshaker.handshakeExtension(
+ new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, Collections.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();
+ 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 parameters;
+ WebSocketExtensionData data;
+
+ // initialize
+ PerMessageDeflateServerExtensionHandshaker handshaker =
+ new PerMessageDeflateServerExtensionHandshaker(6, true, 10, true, true);
+
+ parameters = new HashMap();
+ 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();
+ 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();
+
+ // 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());
+ }
+}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/WebSocketServerCompressionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/WebSocketServerCompressionHandlerTest.java
new file mode 100644
index 0000000000..0b9b48fcee
--- /dev/null
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/extensions/compression/WebSocketServerCompressionHandlerTest.java
@@ -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 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 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 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 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 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 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);
+ }
+
+}
diff --git a/codec/src/main/java/io/netty/handler/codec/compression/ZlibCodecFactory.java b/codec/src/main/java/io/netty/handler/codec/compression/ZlibCodecFactory.java
index db97c24fd8..29a0e4eca3 100644
--- a/codec/src/main/java/io/netty/handler/codec/compression/ZlibCodecFactory.java
+++ b/codec/src/main/java/io/netty/handler/codec/compression/ZlibCodecFactory.java
@@ -26,6 +26,9 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
public final class ZlibCodecFactory {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ZlibCodecFactory.class);
+ private static final int DEFAULT_JDK_WINDOW_SIZE = 15;
+ private static final int DEFAULT_JDK_MEM_LEVEL = 8;
+
private static final boolean noJdkZlibDecoder;
static {
@@ -58,7 +61,8 @@ public final class ZlibCodecFactory {
}
public static ZlibEncoder newZlibEncoder(ZlibWrapper wrapper, int compressionLevel, int windowBits, int memLevel) {
- if (PlatformDependent.javaVersion() < 7) {
+ if (PlatformDependent.javaVersion() < 7 ||
+ windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
return new JZlibEncoder(wrapper, compressionLevel, windowBits, memLevel);
} else {
return new JdkZlibEncoder(wrapper, compressionLevel);
@@ -82,7 +86,8 @@ public final class ZlibCodecFactory {
}
public static ZlibEncoder newZlibEncoder(int compressionLevel, int windowBits, int memLevel, byte[] dictionary) {
- if (PlatformDependent.javaVersion() < 7) {
+ if (PlatformDependent.javaVersion() < 7 ||
+ windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
return new JZlibEncoder(compressionLevel, windowBits, memLevel, dictionary);
} else {
return new JdkZlibEncoder(compressionLevel, dictionary);
diff --git a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java
index 7f954497fd..b706b07e68 100644
--- a/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java
+++ b/example/src/main/java/io/netty/example/http/websocketx/client/WebSocketClient.java
@@ -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.util.SelfSignedCertificate;
@@ -111,6 +112,7 @@ public final class WebSocketClient {
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator(8192),
+ new WebSocketClientCompressionHandler(),
handler);
}
});
diff --git a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerInitializer.java b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerInitializer.java
index f012004c2f..6c7ef4f85a 100644
--- a/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerInitializer.java
+++ b/example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerInitializer.java
@@ -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