Add WebSocket Extension handlers for client and server.
Add permessage-deflate and deflate-frame WebSocket extension implementations. Motivation: Need to compress HTTP WebSocket frames payload. Modifications: - Move UTF8 checking of WebSocketFrames from frame decoder to and external handler. - Change ZlibCodecFactory in order to use ZLib implentation instead of JDK if windowSize or memLevel is different from the default one. - Add WebSocketServerExtensionHandler and WebSocketClientExtensionHandler to handle WebSocket Extension headers. - Add DeflateFrame and PermessageDeflate extension implementations.
This commit is contained in:
parent
cbe20faf6c
commit
282d6e73b8
@ -269,6 +269,10 @@ public interface HttpHeaders extends TextHeaders {
|
|||||||
* {@code "Sec-WebSocket-Accept"}
|
* {@code "Sec-WebSocket-Accept"}
|
||||||
*/
|
*/
|
||||||
public static final AsciiString SEC_WEBSOCKET_ACCEPT = new AsciiString("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"}
|
* {@code "Server"}
|
||||||
*/
|
*/
|
||||||
|
@ -20,7 +20,7 @@ import io.netty.buffer.Unpooled;
|
|||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web Socket text frame with assumed UTF-8 encoding
|
* Web Socket text frame
|
||||||
*/
|
*/
|
||||||
public class TextWebSocketFrame extends WebSocketFrame {
|
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.
|
* Creates a new text frame with the specified binary data. The final fragment flag is set to true.
|
||||||
*
|
*
|
||||||
* @param binaryData
|
* @param binaryData
|
||||||
* the content of the frame. Must be UTF-8 encoded
|
* the content of the frame.
|
||||||
*/
|
*/
|
||||||
public TextWebSocketFrame(ByteBuf binaryData) {
|
public TextWebSocketFrame(ByteBuf binaryData) {
|
||||||
super(binaryData);
|
super(binaryData);
|
||||||
@ -81,7 +81,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
|||||||
* @param rsv
|
* @param rsv
|
||||||
* reserved bits used for protocol extensions
|
* reserved bits used for protocol extensions
|
||||||
* @param binaryData
|
* @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) {
|
public TextWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
|
||||||
super(finalFragment, rsv, binaryData);
|
super(finalFragment, rsv, binaryData);
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -104,7 +104,6 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
|||||||
private byte[] maskingKey;
|
private byte[] maskingKey;
|
||||||
private int framePayloadLen1;
|
private int framePayloadLen1;
|
||||||
private boolean receivedClosingHandshake;
|
private boolean receivedClosingHandshake;
|
||||||
private Utf8Validator utf8Validator;
|
|
||||||
private State state = State.READING_FIRST;
|
private State state = State.READING_FIRST;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -315,33 +314,8 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
|||||||
// allowed in the middle of a fragmented message
|
// allowed in the middle of a fragmented message
|
||||||
if (frameOpcode != OPCODE_PING) {
|
if (frameOpcode != OPCODE_PING) {
|
||||||
fragmentedFramesCount = 0;
|
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 {
|
} 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
|
// Increment counter
|
||||||
fragmentedFramesCount++;
|
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(
|
protected void checkCloseFrameBody(
|
||||||
ChannelHandlerContext ctx, ByteBuf buffer) {
|
ChannelHandlerContext ctx, ByteBuf buffer) {
|
||||||
|
@ -145,5 +145,10 @@ public class WebSocketClientProtocolHandler extends WebSocketProtocolHandler {
|
|||||||
ctx.pipeline().addBefore(ctx.name(), WebSocketClientProtocolHandshakeHandler.class.getName(),
|
ctx.pipeline().addBefore(ctx.name(), WebSocketClientProtocolHandshakeHandler.class.getName(),
|
||||||
new WebSocketClientProtocolHandshakeHandler(handshaker));
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,11 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
|
|||||||
new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols,
|
new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols,
|
||||||
allowExtensions, maxFramePayloadLength));
|
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
|
@Override
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
* <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler</tt>.
|
||||||
|
*/
|
||||||
|
public class WebSocketClientExtensionHandler extends ChannelHandlerAdapter {
|
||||||
|
|
||||||
|
private final List<WebSocketClientExtensionHandshaker> extensionHandshakers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param extensionHandshakers
|
||||||
|
* The extension handshaker in priority order. A handshaker could be repeated many times
|
||||||
|
* with fallback configuration.
|
||||||
|
*/
|
||||||
|
public WebSocketClientExtensionHandler(WebSocketClientExtensionHandshaker... extensionHandshakers) {
|
||||||
|
if (extensionHandshakers == null) {
|
||||||
|
throw new NullPointerException("extensionHandshakers");
|
||||||
|
}
|
||||||
|
if (extensionHandshakers.length == 0) {
|
||||||
|
throw new IllegalArgumentException("extensionHandshakers must contains at least one handshaker");
|
||||||
|
}
|
||||||
|
this.extensionHandshakers = Arrays.asList(extensionHandshakers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
if (msg instanceof HttpRequest && WebSocketExtensionUtil.isWebsocketUpgrade((HttpRequest) msg)) {
|
||||||
|
HttpRequest request = (HttpRequest) msg;
|
||||||
|
String headerValue = request.headers().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<WebSocketExtensionData> extensions =
|
||||||
|
WebSocketExtensionUtil.extractExtensions(extensionsHeader);
|
||||||
|
List<WebSocketClientExtension> validExtensions =
|
||||||
|
new ArrayList<WebSocketClientExtension>(extensions.size());
|
||||||
|
int rsv = 0;
|
||||||
|
|
||||||
|
for (WebSocketExtensionData extensionData : extensions) {
|
||||||
|
Iterator<WebSocketClientExtensionHandshaker> extensionHandshakersIterator =
|
||||||
|
extensionHandshakers.iterator();
|
||||||
|
WebSocketClientExtension validExtension = null;
|
||||||
|
|
||||||
|
while (validExtension == null && extensionHandshakersIterator.hasNext()) {
|
||||||
|
WebSocketClientExtensionHandshaker extensionHandshaker =
|
||||||
|
extensionHandshakersIterator.next();
|
||||||
|
validExtension = extensionHandshaker.handshakeExtension(extensionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validExtension != null && ((validExtension.rsv() & rsv) == 0)) {
|
||||||
|
rsv = rsv | validExtension.rsv();
|
||||||
|
validExtensions.add(validExtension);
|
||||||
|
} else {
|
||||||
|
throw new CodecException(
|
||||||
|
"invalid WebSocket Extension handhshake for \"" + extensionsHeader + "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (WebSocketClientExtension validExtension : validExtensions) {
|
||||||
|
WebSocketExtensionDecoder decoder = validExtension.newExtensionDecoder();
|
||||||
|
WebSocketExtensionEncoder encoder = validExtension.newExtensionEncoder();
|
||||||
|
ctx.pipeline().addAfter(ctx.name(), decoder.getClass().getName(), decoder);
|
||||||
|
ctx.pipeline().addAfter(ctx.name(), encoder.getClass().getName(), encoder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.pipeline().remove(ctx.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.channelRead(ctx, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A WebSocket Extension data from the <tt>Sec-WebSocket-Extensions</tt> header.
|
||||||
|
*
|
||||||
|
* See <tt>io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_EXTENSIONS</tt>.
|
||||||
|
*/
|
||||||
|
public final class WebSocketExtensionData {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Map<String, String> parameters;
|
||||||
|
|
||||||
|
public WebSocketExtensionData(String name, Map<String, String> parameters) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("name");
|
||||||
|
}
|
||||||
|
if (parameters == null) {
|
||||||
|
throw new NullPointerException("parameters");
|
||||||
|
}
|
||||||
|
this.name = name;
|
||||||
|
this.parameters = Collections.unmodifiableMap(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the extension name.
|
||||||
|
*/
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the extension optional parameters.
|
||||||
|
*/
|
||||||
|
public Map<String, String> parameters() {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenient class for <tt>io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension</tt> decoder.
|
||||||
|
*/
|
||||||
|
public abstract class WebSocketExtensionDecoder extends MessageToMessageDecoder<WebSocketFrame> {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenient class for <tt>io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension</tt> encoder.
|
||||||
|
*/
|
||||||
|
public abstract class WebSocketExtensionEncoder extends MessageToMessageEncoder<WebSocketFrame> {
|
||||||
|
|
||||||
|
}
|
@ -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<WebSocketExtensionData> extractExtensions(String extensionHeader) {
|
||||||
|
String[] rawExtensions = StringUtil.split(extensionHeader, EXTENSION_SEPARATOR);
|
||||||
|
if (rawExtensions.length > 0) {
|
||||||
|
List<WebSocketExtensionData> extensions = new ArrayList<WebSocketExtensionData>(rawExtensions.length);
|
||||||
|
for (String rawExtension : rawExtensions) {
|
||||||
|
String[] extensionParameters = StringUtil.split(rawExtension, PARAMETER_SEPARATOR);
|
||||||
|
String name = extensionParameters[0].trim();
|
||||||
|
Map<String, String> parameters;
|
||||||
|
if (extensionParameters.length > 1) {
|
||||||
|
parameters = new HashMap<String, String>(extensionParameters.length - 1);
|
||||||
|
for (int i = 1; i < extensionParameters.length; i++) {
|
||||||
|
String parameter = extensionParameters[i].trim();
|
||||||
|
Matcher parameterMatcher = PARAMETER.matcher(parameter);
|
||||||
|
if (parameterMatcher.matches() && parameterMatcher.group(1) != null) {
|
||||||
|
parameters.put(parameterMatcher.group(1), parameterMatcher.group(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parameters = Collections.<String, String>emptyMap();
|
||||||
|
}
|
||||||
|
extensions.add(new WebSocketExtensionData(name, parameters));
|
||||||
|
}
|
||||||
|
return extensions;
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String appendExtension(String currentHeaderValue, String extensionName,
|
||||||
|
Map<String, String> extensionParameters) {
|
||||||
|
|
||||||
|
StringBuilder newHeaderValue = new StringBuilder(
|
||||||
|
currentHeaderValue != null ? currentHeaderValue.length() : 0 + extensionName.length() + 1);
|
||||||
|
if (currentHeaderValue != null && !currentHeaderValue.trim().isEmpty()) {
|
||||||
|
newHeaderValue.append(currentHeaderValue);
|
||||||
|
newHeaderValue.append(EXTENSION_SEPARATOR);
|
||||||
|
}
|
||||||
|
newHeaderValue.append(extensionName);
|
||||||
|
boolean isFirst = true;
|
||||||
|
for (Entry<String, String> extensionParameter : extensionParameters.entrySet()) {
|
||||||
|
if (isFirst) {
|
||||||
|
newHeaderValue.append(PARAMETER_SEPARATOR);
|
||||||
|
} else {
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
newHeaderValue.append(extensionParameter.getKey());
|
||||||
|
if (extensionParameter.getValue() != null) {
|
||||||
|
newHeaderValue.append(PARAMETER_EQUAL);
|
||||||
|
newHeaderValue.append(extensionParameter.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newHeaderValue.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebSocketExtensionUtil() {
|
||||||
|
// Unused
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
* <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler</tt>.
|
||||||
|
*/
|
||||||
|
public class WebSocketServerExtensionHandler extends ChannelHandlerAdapter {
|
||||||
|
|
||||||
|
private final List<WebSocketServerExtensionHandshaker> extensionHandshakers;
|
||||||
|
|
||||||
|
private List<WebSocketServerExtension> validExtensions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param extensionHandshakers
|
||||||
|
* The extension handshaker in priority order. A handshaker could be repeated many times
|
||||||
|
* with fallback configuration.
|
||||||
|
*/
|
||||||
|
public WebSocketServerExtensionHandler(WebSocketServerExtensionHandshaker... extensionHandshakers) {
|
||||||
|
if (extensionHandshakers == null) {
|
||||||
|
throw new NullPointerException("extensionHandshakers");
|
||||||
|
}
|
||||||
|
if (extensionHandshakers.length == 0) {
|
||||||
|
throw new IllegalArgumentException("extensionHandshakers must contains at least one handshaker");
|
||||||
|
}
|
||||||
|
this.extensionHandshakers = Arrays.asList(extensionHandshakers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg)
|
||||||
|
throws Exception {
|
||||||
|
if (msg instanceof HttpRequest) {
|
||||||
|
HttpRequest request = (HttpRequest) msg;
|
||||||
|
|
||||||
|
if (WebSocketExtensionUtil.isWebsocketUpgrade(request)) {
|
||||||
|
String extensionsHeader = request.headers().get(HttpHeaders.Names.SEC_WEBSOCKET_EXTENSIONS);
|
||||||
|
|
||||||
|
if (extensionsHeader != null) {
|
||||||
|
List<WebSocketExtensionData> extensions =
|
||||||
|
WebSocketExtensionUtil.extractExtensions(extensionsHeader);
|
||||||
|
int rsv = 0;
|
||||||
|
|
||||||
|
for (WebSocketExtensionData extensionData : extensions) {
|
||||||
|
Iterator<WebSocketServerExtensionHandshaker> extensionHandshakersIterator =
|
||||||
|
extensionHandshakers.iterator();
|
||||||
|
WebSocketServerExtension validExtension = null;
|
||||||
|
|
||||||
|
while (validExtension == null && extensionHandshakersIterator.hasNext()) {
|
||||||
|
WebSocketServerExtensionHandshaker extensionHandshaker =
|
||||||
|
extensionHandshakersIterator.next();
|
||||||
|
validExtension = extensionHandshaker.handshakeExtension(extensionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validExtension != null && ((validExtension.rsv() & rsv) == 0)) {
|
||||||
|
if (validExtensions == null) {
|
||||||
|
validExtensions = new ArrayList<WebSocketServerExtension>(1);
|
||||||
|
}
|
||||||
|
rsv = rsv | validExtension.rsv();
|
||||||
|
validExtensions.add(validExtension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.channelRead(ctx, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||||
|
if (msg instanceof HttpResponse &&
|
||||||
|
WebSocketExtensionUtil.isWebsocketUpgrade((HttpResponse) msg) && validExtensions != null) {
|
||||||
|
HttpResponse response = (HttpResponse) msg;
|
||||||
|
String headerValue = response.headers().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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handshakes a client extension based on this server capabilities.
|
||||||
|
*/
|
||||||
|
public interface WebSocketServerExtensionHandshaker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handshake based on client request. It must failed with <tt>null</tt> if server cannot handle it.
|
||||||
|
*
|
||||||
|
* @param extensionData
|
||||||
|
* the extension configuration sent by the client.
|
||||||
|
* @return an initialized extension if handshake phase succeed or null if failed.
|
||||||
|
*/
|
||||||
|
WebSocketServerExtension handshakeExtension(WebSocketExtensionData extensionData);
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
* <tt>io.netty.handler.codec.http.websocketx.WebSocketFrame</tt>.
|
||||||
|
*/
|
||||||
|
abstract class DeflateDecoder extends WebSocketExtensionDecoder {
|
||||||
|
|
||||||
|
static final byte[] FRAME_TAIL = new byte[] {0x00, 0x00, (byte) 0xff, (byte) 0xff};
|
||||||
|
|
||||||
|
private final boolean noContext;
|
||||||
|
|
||||||
|
private EmbeddedChannel decoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param noContext true to disable context takeover.
|
||||||
|
*/
|
||||||
|
public DeflateDecoder(boolean noContext) {
|
||||||
|
this.noContext = noContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean appendFrameTail(WebSocketFrame msg);
|
||||||
|
|
||||||
|
protected abstract int newRsv(WebSocketFrame msg);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
|
||||||
|
if (decoder == null) {
|
||||||
|
if (!(msg instanceof TextWebSocketFrame) && !(msg instanceof BinaryWebSocketFrame)) {
|
||||||
|
throw new CodecException("unexpected initial frame type: " + msg.getClass().getName());
|
||||||
|
}
|
||||||
|
decoder = new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.NONE));
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder.writeInbound(msg.content().retain());
|
||||||
|
if (appendFrameTail(msg)) {
|
||||||
|
decoder.writeInbound(Unpooled.wrappedBuffer(FRAME_TAIL));
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositeByteBuf compositeUncompressedContent = ctx.alloc().compositeBuffer();
|
||||||
|
for (;;) {
|
||||||
|
ByteBuf partUncompressedContent = decoder.readInbound();
|
||||||
|
if (partUncompressedContent == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!partUncompressedContent.isReadable()) {
|
||||||
|
partUncompressedContent.release();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
compositeUncompressedContent.addComponent(partUncompressedContent);
|
||||||
|
compositeUncompressedContent.writerIndex(compositeUncompressedContent.writerIndex() +
|
||||||
|
partUncompressedContent.readableBytes());
|
||||||
|
}
|
||||||
|
if (compositeUncompressedContent.numComponents() <= 0) {
|
||||||
|
compositeUncompressedContent.release();
|
||||||
|
throw new CodecException("cannot read uncompressed buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.isFinalFragment() && noContext) {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketFrame outMsg;
|
||||||
|
if (msg instanceof TextWebSocketFrame) {
|
||||||
|
outMsg = new TextWebSocketFrame(msg.isFinalFragment(), newRsv(msg), compositeUncompressedContent);
|
||||||
|
} else if (msg instanceof BinaryWebSocketFrame) {
|
||||||
|
outMsg = new BinaryWebSocketFrame(msg.isFinalFragment(), newRsv(msg), compositeUncompressedContent);
|
||||||
|
} else if (msg instanceof ContinuationWebSocketFrame) {
|
||||||
|
outMsg = new ContinuationWebSocketFrame(msg.isFinalFragment(), newRsv(msg),
|
||||||
|
compositeUncompressedContent);
|
||||||
|
} else {
|
||||||
|
throw new CodecException("unexpected frame type: " + msg.getClass().getName());
|
||||||
|
}
|
||||||
|
out.add(outMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
cleanup();
|
||||||
|
super.handlerRemoved(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
cleanup();
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanup() {
|
||||||
|
if (decoder != null) {
|
||||||
|
// Clean-up the previous encoder if not cleaned up correctly.
|
||||||
|
if (decoder.finish()) {
|
||||||
|
for (;;) {
|
||||||
|
ByteBuf buf = decoder.readOutbound();
|
||||||
|
if (buf == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Release the buffer
|
||||||
|
buf.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateDecoder.*;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
import io.netty.handler.codec.CodecException;
|
||||||
|
import io.netty.handler.codec.compression.ZlibCodecFactory;
|
||||||
|
import io.netty.handler.codec.compression.ZlibWrapper;
|
||||||
|
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deflate implementation of a payload compressor for
|
||||||
|
* <tt>io.netty.handler.codec.http.websocketx.WebSocketFrame</tt>.
|
||||||
|
*/
|
||||||
|
abstract class DeflateEncoder extends WebSocketExtensionEncoder {
|
||||||
|
|
||||||
|
private final int compressionLevel;
|
||||||
|
private final int windowSize;
|
||||||
|
private final boolean noContext;
|
||||||
|
|
||||||
|
private EmbeddedChannel encoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param compressionLevel compression level of the compressor.
|
||||||
|
* @param windowSize maximum size of the window compressor buffer.
|
||||||
|
* @param noContext true to disable context takeover.
|
||||||
|
*/
|
||||||
|
public DeflateEncoder(int compressionLevel, int windowSize, boolean noContext) {
|
||||||
|
this.compressionLevel = compressionLevel;
|
||||||
|
this.windowSize = windowSize;
|
||||||
|
this.noContext = noContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param msg the current frame.
|
||||||
|
* @return the rsv bits to set in the compressed frame.
|
||||||
|
*/
|
||||||
|
protected abstract int rsv(WebSocketFrame msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param msg the current frame.
|
||||||
|
* @return true if compressed payload tail needs to be removed.
|
||||||
|
*/
|
||||||
|
protected abstract boolean removeFrameTail(WebSocketFrame msg);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
|
||||||
|
List<Object> out) throws Exception {
|
||||||
|
if (encoder == null) {
|
||||||
|
encoder = new EmbeddedChannel(ZlibCodecFactory.newZlibEncoder(
|
||||||
|
ZlibWrapper.NONE, compressionLevel, windowSize, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder.writeOutbound(msg.content().retain());
|
||||||
|
|
||||||
|
CompositeByteBuf fullCompressedContent = ctx.alloc().compositeBuffer();
|
||||||
|
for (;;) {
|
||||||
|
ByteBuf partCompressedContent = encoder.readOutbound();
|
||||||
|
if (partCompressedContent == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!partCompressedContent.isReadable()) {
|
||||||
|
partCompressedContent.release();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fullCompressedContent.addComponent(partCompressedContent);
|
||||||
|
fullCompressedContent.writerIndex(fullCompressedContent.writerIndex() +
|
||||||
|
partCompressedContent.readableBytes());
|
||||||
|
}
|
||||||
|
if (fullCompressedContent.numComponents() <= 0) {
|
||||||
|
fullCompressedContent.release();
|
||||||
|
throw new CodecException("cannot read compressed buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.isFinalFragment() && noContext) {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuf compressedContent;
|
||||||
|
if (removeFrameTail(msg)) {
|
||||||
|
int realLength = fullCompressedContent.readableBytes() - FRAME_TAIL.length;
|
||||||
|
compressedContent = fullCompressedContent.slice(0, realLength);
|
||||||
|
} else {
|
||||||
|
compressedContent = fullCompressedContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketFrame outMsg;
|
||||||
|
if (msg instanceof TextWebSocketFrame) {
|
||||||
|
outMsg = new TextWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
|
||||||
|
} else if (msg instanceof BinaryWebSocketFrame) {
|
||||||
|
outMsg = new BinaryWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
|
||||||
|
} else if (msg instanceof ContinuationWebSocketFrame) {
|
||||||
|
outMsg = new ContinuationWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
|
||||||
|
} else {
|
||||||
|
throw new CodecException("unexpected frame type: " + msg.getClass().getName());
|
||||||
|
}
|
||||||
|
out.add(outMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
cleanup();
|
||||||
|
super.handlerRemoved(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
cleanup();
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanup() {
|
||||||
|
if (encoder != null) {
|
||||||
|
// Clean-up the previous encoder if not cleaned up correctly.
|
||||||
|
if (encoder.finish()) {
|
||||||
|
for (;;) {
|
||||||
|
ByteBuf buf = encoder.readOutbound();
|
||||||
|
if (buf == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Release the buffer
|
||||||
|
buf.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.compression.
|
||||||
|
DeflateFrameServerExtensionHandshaker.*;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-06.txt">perframe-deflate</a>
|
||||||
|
* handshake implementation.
|
||||||
|
*/
|
||||||
|
public final class DeflateFrameClientExtensionHandshaker implements WebSocketClientExtensionHandshaker {
|
||||||
|
|
||||||
|
private final int compressionLevel;
|
||||||
|
private final boolean useWebkitExtensionName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with default configuration.
|
||||||
|
*/
|
||||||
|
public DeflateFrameClientExtensionHandshaker(boolean useWebkitExtensionName) {
|
||||||
|
this(6, useWebkitExtensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with custom configuration.
|
||||||
|
*
|
||||||
|
* @param compressionLevel
|
||||||
|
* Compression level between 0 and 9 (default is 6).
|
||||||
|
*/
|
||||||
|
public DeflateFrameClientExtensionHandshaker(int compressionLevel, boolean useWebkitExtensionName) {
|
||||||
|
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||||
|
}
|
||||||
|
this.compressionLevel = compressionLevel;
|
||||||
|
this.useWebkitExtensionName = useWebkitExtensionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionData newRequestData() {
|
||||||
|
return new WebSocketExtensionData(
|
||||||
|
useWebkitExtensionName ? X_WEBKIT_DEFLATE_FRAME_EXTENSION : DEFLATE_FRAME_EXTENSION,
|
||||||
|
Collections.<String, String>emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketClientExtension handshakeExtension(WebSocketExtensionData extensionData) {
|
||||||
|
if (!X_WEBKIT_DEFLATE_FRAME_EXTENSION.equals(extensionData.name()) &&
|
||||||
|
!DEFLATE_FRAME_EXTENSION.equals(extensionData.name())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensionData.parameters().isEmpty()) {
|
||||||
|
return new DeflateFrameClientExtension(compressionLevel);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeflateFrameClientExtension implements WebSocketClientExtension {
|
||||||
|
|
||||||
|
private final int compressionLevel;
|
||||||
|
|
||||||
|
public DeflateFrameClientExtension(int compressionLevel) {
|
||||||
|
this.compressionLevel = compressionLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int rsv() {
|
||||||
|
return RSV1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionEncoder newExtensionEncoder() {
|
||||||
|
return new PerFrameDeflateEncoder(compressionLevel, 15, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionDecoder newExtensionDecoder() {
|
||||||
|
return new PerFrameDeflateDecoder(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandshaker;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-06.txt">perframe-deflate</a>
|
||||||
|
* handshake implementation.
|
||||||
|
*/
|
||||||
|
public final class DeflateFrameServerExtensionHandshaker implements WebSocketServerExtensionHandshaker {
|
||||||
|
|
||||||
|
static final String X_WEBKIT_DEFLATE_FRAME_EXTENSION = "x-webkit-deflate-frame";
|
||||||
|
static final String DEFLATE_FRAME_EXTENSION = "deflate-frame";
|
||||||
|
|
||||||
|
private final int compressionLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with default configuration.
|
||||||
|
*/
|
||||||
|
public DeflateFrameServerExtensionHandshaker() {
|
||||||
|
this(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with custom configuration.
|
||||||
|
*
|
||||||
|
* @param compressionLevel
|
||||||
|
* Compression level between 0 and 9 (default is 6).
|
||||||
|
*/
|
||||||
|
public DeflateFrameServerExtensionHandshaker(int compressionLevel) {
|
||||||
|
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||||
|
}
|
||||||
|
this.compressionLevel = compressionLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketServerExtension handshakeExtension(WebSocketExtensionData extensionData) {
|
||||||
|
if (!X_WEBKIT_DEFLATE_FRAME_EXTENSION.equals(extensionData.name()) &&
|
||||||
|
!DEFLATE_FRAME_EXTENSION.equals(extensionData.name())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensionData.parameters().isEmpty()) {
|
||||||
|
return new DeflateFrameServerExtension(compressionLevel, extensionData.name());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DeflateFrameServerExtension implements WebSocketServerExtension {
|
||||||
|
|
||||||
|
private final String extensionName;
|
||||||
|
private final int compressionLevel;
|
||||||
|
|
||||||
|
public DeflateFrameServerExtension(int compressionLevel, String extensionName) {
|
||||||
|
this.extensionName = extensionName;
|
||||||
|
this.compressionLevel = compressionLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int rsv() {
|
||||||
|
return RSV1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionEncoder newExtensionEncoder() {
|
||||||
|
return new PerFrameDeflateEncoder(compressionLevel, 15, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionDecoder newExtensionDecoder() {
|
||||||
|
return new PerFrameDeflateDecoder(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionData newReponseData() {
|
||||||
|
return new WebSocketExtensionData(extensionName, Collections.<String, String>emptyMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-frame implementation of deflate decompressor.
|
||||||
|
*/
|
||||||
|
class PerFrameDeflateDecoder extends DeflateDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param noContext true to disable context takeover.
|
||||||
|
*/
|
||||||
|
public PerFrameDeflateDecoder(boolean noContext) {
|
||||||
|
super(noContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptInboundMessage(Object msg) throws Exception {
|
||||||
|
return (msg instanceof TextWebSocketFrame ||
|
||||||
|
msg instanceof BinaryWebSocketFrame ||
|
||||||
|
msg instanceof ContinuationWebSocketFrame) &&
|
||||||
|
(((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int newRsv(WebSocketFrame msg) {
|
||||||
|
return msg.rsv() ^ WebSocketExtension.RSV1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean appendFrameTail(WebSocketFrame msg) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-frame implementation of deflate compressor.
|
||||||
|
*/
|
||||||
|
class PerFrameDeflateEncoder extends DeflateEncoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param compressionLevel compression level of the compressor.
|
||||||
|
* @param windowSize maximum size of the window compressor buffer.
|
||||||
|
* @param noContext true to disable context takeover.
|
||||||
|
*/
|
||||||
|
public PerFrameDeflateEncoder(int compressionLevel, int windowSize, boolean noContext) {
|
||||||
|
super(compressionLevel, windowSize, noContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptOutboundMessage(Object msg) throws Exception {
|
||||||
|
return (msg instanceof TextWebSocketFrame ||
|
||||||
|
msg instanceof BinaryWebSocketFrame ||
|
||||||
|
msg instanceof ContinuationWebSocketFrame) &&
|
||||||
|
((WebSocketFrame) msg).content().readableBytes() > 0 &&
|
||||||
|
(((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int rsv(WebSocketFrame msg) {
|
||||||
|
return msg.rsv() | WebSocketExtension.RSV1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeFrameTail(WebSocketFrame msg) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.compression.
|
||||||
|
PerMessageDeflateServerExtensionHandshaker.*;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18">permessage-deflate</a>
|
||||||
|
* handshake implementation.
|
||||||
|
*/
|
||||||
|
public final class PerMessageDeflateClientExtensionHandshaker implements WebSocketClientExtensionHandshaker {
|
||||||
|
|
||||||
|
private final int compressionLevel;
|
||||||
|
private final boolean allowClientWindowSize;
|
||||||
|
private final int requestedServerWindowSize;
|
||||||
|
private final boolean allowClientNoContext;
|
||||||
|
private final boolean requestedServerNoContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with default configuration.
|
||||||
|
*/
|
||||||
|
public PerMessageDeflateClientExtensionHandshaker() {
|
||||||
|
this(6, false, MAX_WINDOW_SIZE, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with custom configuration.
|
||||||
|
*
|
||||||
|
* @param compressionLevel
|
||||||
|
* Compression level between 0 and 9 (default is 6).
|
||||||
|
* @param allowClientWindowSize
|
||||||
|
* allows WebSocket server to customize the client inflater window size
|
||||||
|
* (default is false).
|
||||||
|
* @param requestedServerWindowSize
|
||||||
|
* indicates the requested sever window size to use if server inflater is customizable.
|
||||||
|
* @param allowClientNoContext
|
||||||
|
* allows WebSocket server to activate client_no_context_takeover
|
||||||
|
* (default is false).
|
||||||
|
* @param requestedServerNoContext
|
||||||
|
* indicates if client needs to activate server_no_context_takeover
|
||||||
|
* if server is compatible with (default is false).
|
||||||
|
*/
|
||||||
|
public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
|
||||||
|
boolean allowClientWindowSize, int requestedServerWindowSize,
|
||||||
|
boolean allowClientNoContext, boolean requestedServerNoContext) {
|
||||||
|
if (requestedServerWindowSize > MAX_WINDOW_SIZE || requestedServerWindowSize < MIN_WINDOW_SIZE) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"requestedServerWindowSize: " + requestedServerWindowSize + " (expected: 8-15)");
|
||||||
|
}
|
||||||
|
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||||
|
}
|
||||||
|
this.compressionLevel = compressionLevel;
|
||||||
|
this.allowClientWindowSize = allowClientWindowSize;
|
||||||
|
this.requestedServerWindowSize = requestedServerWindowSize;
|
||||||
|
this.allowClientNoContext = allowClientNoContext;
|
||||||
|
this.requestedServerNoContext = requestedServerNoContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionData newRequestData() {
|
||||||
|
HashMap<String, String> parameters = new HashMap<String, String>(4);
|
||||||
|
if (requestedServerWindowSize != MAX_WINDOW_SIZE) {
|
||||||
|
parameters.put(SERVER_NO_CONTEXT, null);
|
||||||
|
}
|
||||||
|
if (allowClientNoContext) {
|
||||||
|
parameters.put(CLIENT_NO_CONTEXT, null);
|
||||||
|
}
|
||||||
|
if (requestedServerWindowSize != MAX_WINDOW_SIZE) {
|
||||||
|
parameters.put(SERVER_MAX_WINDOW, Integer.toString(requestedServerWindowSize));
|
||||||
|
}
|
||||||
|
if (allowClientWindowSize) {
|
||||||
|
parameters.put(CLIENT_MAX_WINDOW, null);
|
||||||
|
}
|
||||||
|
return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketClientExtension handshakeExtension(WebSocketExtensionData extensionData) {
|
||||||
|
if (!PERMESSAGE_DEFLATE_EXTENSION.equals(extensionData.name())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean succeed = true;
|
||||||
|
int clientWindowSize = MAX_WINDOW_SIZE;
|
||||||
|
int serverWindowSize = MAX_WINDOW_SIZE;
|
||||||
|
boolean serverNoContext = false;
|
||||||
|
boolean clientNoContext = false;
|
||||||
|
|
||||||
|
Iterator<Entry<String, String>> parametersIterator =
|
||||||
|
extensionData.parameters().entrySet().iterator();
|
||||||
|
while (succeed && parametersIterator.hasNext()) {
|
||||||
|
Entry<String, String> parameter = parametersIterator.next();
|
||||||
|
|
||||||
|
if (CLIENT_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
|
||||||
|
// allowed client_window_size_bits
|
||||||
|
if (allowClientWindowSize) {
|
||||||
|
clientWindowSize = Integer.valueOf(parameter.getValue());
|
||||||
|
} else {
|
||||||
|
succeed = false;
|
||||||
|
}
|
||||||
|
} else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
|
||||||
|
// acknowledged server_window_size_bits
|
||||||
|
serverWindowSize = Integer.parseInt(parameter.getValue());
|
||||||
|
if (clientWindowSize > MAX_WINDOW_SIZE || clientWindowSize < MIN_WINDOW_SIZE) {
|
||||||
|
succeed = false;
|
||||||
|
}
|
||||||
|
} else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
|
||||||
|
// allowed client_no_context_takeover
|
||||||
|
if (allowClientNoContext) {
|
||||||
|
clientNoContext = true;
|
||||||
|
} else {
|
||||||
|
succeed = false;
|
||||||
|
}
|
||||||
|
} else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
|
||||||
|
// acknowledged server_no_context_takeover
|
||||||
|
if (requestedServerNoContext) {
|
||||||
|
serverNoContext = true;
|
||||||
|
} else {
|
||||||
|
succeed = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// unknown parameter
|
||||||
|
succeed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((requestedServerNoContext && !serverNoContext) ||
|
||||||
|
requestedServerWindowSize != serverWindowSize) {
|
||||||
|
succeed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (succeed) {
|
||||||
|
return new PermessageDeflateExtension(serverNoContext, serverWindowSize,
|
||||||
|
clientNoContext, clientWindowSize);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PermessageDeflateExtension implements WebSocketClientExtension {
|
||||||
|
|
||||||
|
private final boolean serverNoContext;
|
||||||
|
private final int serverWindowSize;
|
||||||
|
private final boolean clientNoContext;
|
||||||
|
private final int clientWindowSize;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int rsv() {
|
||||||
|
return RSV1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PermessageDeflateExtension(boolean serverNoContext, int serverWindowSize,
|
||||||
|
boolean clientNoContext, int clientWindowSize) {
|
||||||
|
this.serverNoContext = serverNoContext;
|
||||||
|
this.serverWindowSize = serverWindowSize;
|
||||||
|
this.clientNoContext = clientNoContext;
|
||||||
|
this.clientWindowSize = clientWindowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionEncoder newExtensionEncoder() {
|
||||||
|
return new PerMessageDeflateEncoder(compressionLevel, serverWindowSize, serverNoContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionDecoder newExtensionDecoder() {
|
||||||
|
return new PerMessageDeflateDecoder(clientNoContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-message implementation of deflate decompressor.
|
||||||
|
*/
|
||||||
|
class PerMessageDeflateDecoder extends DeflateDecoder {
|
||||||
|
|
||||||
|
private boolean compressing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param noContext true to disable context takeover.
|
||||||
|
*/
|
||||||
|
public PerMessageDeflateDecoder(boolean noContext) {
|
||||||
|
super(noContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptInboundMessage(Object msg) throws Exception {
|
||||||
|
return ((msg instanceof TextWebSocketFrame ||
|
||||||
|
msg instanceof BinaryWebSocketFrame) &&
|
||||||
|
(((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) > 0) ||
|
||||||
|
(msg instanceof ContinuationWebSocketFrame && compressing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int newRsv(WebSocketFrame msg) {
|
||||||
|
return (((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) > 0 ?
|
||||||
|
msg.rsv() ^ WebSocketExtension.RSV1 : msg.rsv();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean appendFrameTail(WebSocketFrame msg) {
|
||||||
|
return msg.isFinalFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,
|
||||||
|
List<Object> out) throws Exception {
|
||||||
|
super.decode(ctx, msg, out);
|
||||||
|
|
||||||
|
if (msg.isFinalFragment()) {
|
||||||
|
compressing = false;
|
||||||
|
} else if (msg instanceof TextWebSocketFrame || msg instanceof BinaryWebSocketFrame) {
|
||||||
|
compressing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-message implementation of deflate compressor.
|
||||||
|
*/
|
||||||
|
class PerMessageDeflateEncoder extends DeflateEncoder {
|
||||||
|
|
||||||
|
private boolean compressing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param compressionLevel compression level of the compressor.
|
||||||
|
* @param windowSize maximum size of the window compressor buffer.
|
||||||
|
* @param noContext true to disable context takeover.
|
||||||
|
*/
|
||||||
|
public PerMessageDeflateEncoder(int compressionLevel, int windowSize, boolean noContext) {
|
||||||
|
super(compressionLevel, windowSize, noContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptOutboundMessage(Object msg) throws Exception {
|
||||||
|
return ((msg instanceof TextWebSocketFrame ||
|
||||||
|
msg instanceof BinaryWebSocketFrame) &&
|
||||||
|
(((WebSocketFrame) msg).rsv() & WebSocketExtension.RSV1) == 0) ||
|
||||||
|
(msg instanceof ContinuationWebSocketFrame && compressing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int rsv(WebSocketFrame msg) {
|
||||||
|
return msg instanceof TextWebSocketFrame || msg instanceof BinaryWebSocketFrame ?
|
||||||
|
msg.rsv() | WebSocketExtension.RSV1 : msg.rsv();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeFrameTail(WebSocketFrame msg) {
|
||||||
|
return msg.isFinalFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
|
||||||
|
List<Object> out) throws Exception {
|
||||||
|
super.encode(ctx, msg, out);
|
||||||
|
|
||||||
|
if (msg.isFinalFragment()) {
|
||||||
|
compressing = false;
|
||||||
|
} else if (msg instanceof TextWebSocketFrame || msg instanceof BinaryWebSocketFrame) {
|
||||||
|
compressing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandshaker;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18">permessage-deflate</a>
|
||||||
|
* handshake implementation.
|
||||||
|
*/
|
||||||
|
public final class PerMessageDeflateServerExtensionHandshaker implements WebSocketServerExtensionHandshaker {
|
||||||
|
|
||||||
|
public static final int MIN_WINDOW_SIZE = 8;
|
||||||
|
public static final int MAX_WINDOW_SIZE = 15;
|
||||||
|
|
||||||
|
static final String PERMESSAGE_DEFLATE_EXTENSION = "permessage-deflate";
|
||||||
|
static final String CLIENT_MAX_WINDOW = "client_max_window_bits";
|
||||||
|
static final String SERVER_MAX_WINDOW = "server_max_window_bits";
|
||||||
|
static final String CLIENT_NO_CONTEXT = "client_no_context_takeover";
|
||||||
|
static final String SERVER_NO_CONTEXT = "server_no_context_takeover";
|
||||||
|
|
||||||
|
private final int compressionLevel;
|
||||||
|
private final boolean allowServerWindowSize;
|
||||||
|
private final int preferredClientWindowSize;
|
||||||
|
private final boolean allowServerNoContext;
|
||||||
|
private final boolean preferredClientNoContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with default configuration.
|
||||||
|
*/
|
||||||
|
public PerMessageDeflateServerExtensionHandshaker() {
|
||||||
|
this(6, false, MAX_WINDOW_SIZE, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with custom configuration.
|
||||||
|
*
|
||||||
|
* @param compressionLevel
|
||||||
|
* Compression level between 0 and 9 (default is 6).
|
||||||
|
* @param allowServerWindowSize
|
||||||
|
* allows WebSocket client to customize the server inflater window size
|
||||||
|
* (default is false).
|
||||||
|
* @param preferredClientWindowSize
|
||||||
|
* indicates the preferred client window size to use if client inflater is customizable.
|
||||||
|
* @param allowServerNoContext
|
||||||
|
* allows WebSocket client to activate server_no_context_takeover
|
||||||
|
* (default is false).
|
||||||
|
* @param preferredClientNoContext
|
||||||
|
* indicates if server prefers to activate client_no_context_takeover
|
||||||
|
* if client is compatible with (default is false).
|
||||||
|
*/
|
||||||
|
public PerMessageDeflateServerExtensionHandshaker(int compressionLevel,
|
||||||
|
boolean allowServerWindowSize, int preferredClientWindowSize,
|
||||||
|
boolean allowServerNoContext, boolean preferredClientNoContext) {
|
||||||
|
if (preferredClientWindowSize > MAX_WINDOW_SIZE || preferredClientWindowSize < MIN_WINDOW_SIZE) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"preferredServerWindowSize: " + preferredClientWindowSize + " (expected: 8-15)");
|
||||||
|
}
|
||||||
|
if (compressionLevel < 0 || compressionLevel > 9) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
|
||||||
|
}
|
||||||
|
this.compressionLevel = compressionLevel;
|
||||||
|
this.allowServerWindowSize = allowServerWindowSize;
|
||||||
|
this.preferredClientWindowSize = preferredClientWindowSize;
|
||||||
|
this.allowServerNoContext = allowServerNoContext;
|
||||||
|
this.preferredClientNoContext = preferredClientNoContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketServerExtension handshakeExtension(WebSocketExtensionData extensionData) {
|
||||||
|
if (!PERMESSAGE_DEFLATE_EXTENSION.equals(extensionData.name())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean deflateEnabled = true;
|
||||||
|
int clientWindowSize = MAX_WINDOW_SIZE;
|
||||||
|
int serverWindowSize = MAX_WINDOW_SIZE;
|
||||||
|
boolean serverNoContext = false;
|
||||||
|
boolean clientNoContext = false;
|
||||||
|
|
||||||
|
Iterator<Entry<String, String>> parametersIterator =
|
||||||
|
extensionData.parameters().entrySet().iterator();
|
||||||
|
while (deflateEnabled && parametersIterator.hasNext()) {
|
||||||
|
Entry<String, String> parameter = parametersIterator.next();
|
||||||
|
|
||||||
|
if (CLIENT_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
|
||||||
|
// use preferred clientWindowSize because client is compatible with customization
|
||||||
|
clientWindowSize = preferredClientWindowSize;
|
||||||
|
} else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
|
||||||
|
// use provided windowSize if it is allowed
|
||||||
|
if (allowServerWindowSize) {
|
||||||
|
serverWindowSize = Integer.valueOf(parameter.getValue());
|
||||||
|
if (serverWindowSize > MAX_WINDOW_SIZE || serverWindowSize < MIN_WINDOW_SIZE) {
|
||||||
|
deflateEnabled = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deflateEnabled = false;
|
||||||
|
}
|
||||||
|
} else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
|
||||||
|
// use preferred clientNoContext because client is compatible with customization
|
||||||
|
clientNoContext = preferredClientNoContext;
|
||||||
|
} else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
|
||||||
|
// use server no context if allowed
|
||||||
|
if (allowServerNoContext) {
|
||||||
|
serverNoContext = true;
|
||||||
|
} else {
|
||||||
|
deflateEnabled = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// unknown parameter
|
||||||
|
deflateEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deflateEnabled) {
|
||||||
|
return new PermessageDeflateExtension(compressionLevel, serverNoContext,
|
||||||
|
serverWindowSize, clientNoContext, clientWindowSize);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PermessageDeflateExtension implements WebSocketServerExtension {
|
||||||
|
|
||||||
|
private final int compressionLevel;
|
||||||
|
private final boolean serverNoContext;
|
||||||
|
private final int serverWindowSize;
|
||||||
|
private final boolean clientNoContext;
|
||||||
|
private final int clientWindowSize;
|
||||||
|
|
||||||
|
public PermessageDeflateExtension(int compressionLevel, boolean serverNoContext,
|
||||||
|
int serverWindowSize, boolean clientNoContext, int clientWindowSize) {
|
||||||
|
this.compressionLevel = compressionLevel;
|
||||||
|
this.serverNoContext = serverNoContext;
|
||||||
|
this.serverWindowSize = serverWindowSize;
|
||||||
|
this.clientNoContext = clientNoContext;
|
||||||
|
this.clientWindowSize = clientWindowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int rsv() {
|
||||||
|
return RSV1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionEncoder newExtensionEncoder() {
|
||||||
|
return new PerMessageDeflateEncoder(compressionLevel, clientWindowSize, clientNoContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionDecoder newExtensionDecoder() {
|
||||||
|
return new PerMessageDeflateDecoder(serverNoContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WebSocketExtensionData newReponseData() {
|
||||||
|
HashMap<String, String> parameters = new HashMap<String, String>(4);
|
||||||
|
if (serverNoContext) {
|
||||||
|
parameters.put(SERVER_NO_CONTEXT, null);
|
||||||
|
}
|
||||||
|
if (clientNoContext) {
|
||||||
|
parameters.put(CLIENT_NO_CONTEXT, null);
|
||||||
|
}
|
||||||
|
if (serverWindowSize != MAX_WINDOW_SIZE) {
|
||||||
|
parameters.put(SERVER_MAX_WINDOW, Integer.toString(serverWindowSize));
|
||||||
|
}
|
||||||
|
if (clientWindowSize != MAX_WINDOW_SIZE) {
|
||||||
|
parameters.put(CLIENT_MAX_WINDOW, Integer.toString(clientWindowSize));
|
||||||
|
}
|
||||||
|
return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientExtensionHandler</tt>
|
||||||
|
* to handle the most common WebSocket Compression Extensions.
|
||||||
|
*
|
||||||
|
* See <tt>io.netty.example.http.websocketx.client.WebSocketClient</tt> for usage.
|
||||||
|
*/
|
||||||
|
public class WebSocketClientCompressionHandler extends WebSocketClientExtensionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with default configuration.
|
||||||
|
*/
|
||||||
|
public WebSocketClientCompressionHandler() {
|
||||||
|
super(new PerMessageDeflateClientExtensionHandshaker(),
|
||||||
|
new DeflateFrameClientExtensionHandshaker(false),
|
||||||
|
new DeflateFrameClientExtensionHandshaker(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerExtensionHandler</tt>
|
||||||
|
* to handle the most common WebSocket Compression Extensions.
|
||||||
|
*
|
||||||
|
* See <tt>io.netty.example.http.websocketx.html5.WebSocketServer</tt> for usage.
|
||||||
|
*/
|
||||||
|
public class WebSocketServerCompressionHandler extends WebSocketServerExtensionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with default configuration.
|
||||||
|
*/
|
||||||
|
public WebSocketServerCompressionHandler() {
|
||||||
|
super(new PerMessageDeflateServerExtensionHandshaker(),
|
||||||
|
new DeflateFrameServerExtensionHandshaker());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encoder, decoder, handshakers to handle most common WebSocket Compression Extensions.
|
||||||
|
* <p>
|
||||||
|
* This package supports different web socket extensions.
|
||||||
|
* The specification currently supported are:
|
||||||
|
* <ul>
|
||||||
|
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-18">permessage-deflate</a></li>
|
||||||
|
* <li><a href="https://tools.ietf.org/id/draft-tyoshino-hybi-websocket-perframe-deflate-06.txt">
|
||||||
|
* perframe-deflate and x-webkit-deflate-frame</a></li>
|
||||||
|
* </ul>
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* See <tt>io.netty.example.http.websocketx.client.WebSocketClient</tt> and
|
||||||
|
* <tt>io.netty.example.http.websocketx.html5.WebSocketServer</tt> for usage.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encoder, decoder, handshakers to handle
|
||||||
|
* <a href="http://tools.ietf.org/html/rfc6455#section-9.1">WebSocket Extensions</a>.
|
||||||
|
*
|
||||||
|
* See <tt>WebSocketServerExtensionHandler</tt> for more details.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions;
|
@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionTestUtil.*;
|
||||||
|
import static org.easymock.EasyMock.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
import io.netty.handler.codec.CodecException;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||||
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class WebSocketClientExtensionHandlerTest {
|
||||||
|
|
||||||
|
WebSocketClientExtensionHandshaker mainHandshakerMock =
|
||||||
|
createMock("mainHandshaker", WebSocketClientExtensionHandshaker.class);
|
||||||
|
WebSocketClientExtensionHandshaker fallbackHandshakerMock =
|
||||||
|
createMock("fallbackHandshaker", WebSocketClientExtensionHandshaker.class);
|
||||||
|
WebSocketClientExtension mainExtensionMock =
|
||||||
|
createMock("mainExtension", WebSocketClientExtension.class);
|
||||||
|
WebSocketClientExtension fallbackExtensionMock =
|
||||||
|
createMock("fallbackExtension", WebSocketClientExtension.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainSuccess() {
|
||||||
|
// initialize
|
||||||
|
expect(mainHandshakerMock.newRequestData()).
|
||||||
|
andReturn(new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(
|
||||||
|
anyObject(WebSocketExtensionData.class))).andReturn(mainExtensionMock).once();
|
||||||
|
replay(mainHandshakerMock);
|
||||||
|
|
||||||
|
expect(fallbackHandshakerMock.newRequestData()).
|
||||||
|
andReturn(new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
|
||||||
|
replay(fallbackHandshakerMock);
|
||||||
|
|
||||||
|
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
|
||||||
|
expect(mainExtensionMock.newExtensionEncoder()).andReturn(new DummyEncoder()).once();
|
||||||
|
expect(mainExtensionMock.newExtensionDecoder()).andReturn(new DummyDecoder()).once();
|
||||||
|
replay(mainExtensionMock);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
|
||||||
|
mainHandshakerMock, fallbackHandshakerMock));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(null);
|
||||||
|
ch.writeOutbound(req);
|
||||||
|
|
||||||
|
HttpRequest req2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse("main");
|
||||||
|
ch.writeInbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readInbound();
|
||||||
|
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(2, reqExts.size());
|
||||||
|
assertEquals("main", reqExts.get(0).name());
|
||||||
|
assertEquals("fallback", reqExts.get(1).name());
|
||||||
|
|
||||||
|
assertEquals(1, resExts.size());
|
||||||
|
assertEquals("main", resExts.get(0).name());
|
||||||
|
assertTrue(resExts.get(0).parameters().isEmpty());
|
||||||
|
assertTrue(ch.pipeline().get(DummyDecoder.class) != null);
|
||||||
|
assertTrue(ch.pipeline().get(DummyEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFallbackSuccess() {
|
||||||
|
// initialize
|
||||||
|
expect(mainHandshakerMock.newRequestData()).
|
||||||
|
andReturn(new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(
|
||||||
|
anyObject(WebSocketExtensionData.class))).andReturn(null).once();
|
||||||
|
replay(mainHandshakerMock);
|
||||||
|
|
||||||
|
expect(fallbackHandshakerMock.newRequestData()).
|
||||||
|
andReturn(new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(
|
||||||
|
anyObject(WebSocketExtensionData.class))).andReturn(fallbackExtensionMock).once();
|
||||||
|
replay(fallbackHandshakerMock);
|
||||||
|
|
||||||
|
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
|
||||||
|
expect(fallbackExtensionMock.newExtensionEncoder()).andReturn(new DummyEncoder()).once();
|
||||||
|
expect(fallbackExtensionMock.newExtensionDecoder()).andReturn(new DummyDecoder()).once();
|
||||||
|
replay(fallbackExtensionMock);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
|
||||||
|
mainHandshakerMock, fallbackHandshakerMock));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(null);
|
||||||
|
ch.writeOutbound(req);
|
||||||
|
|
||||||
|
HttpRequest req2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse("fallback");
|
||||||
|
ch.writeInbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readInbound();
|
||||||
|
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(2, reqExts.size());
|
||||||
|
assertEquals("main", reqExts.get(0).name());
|
||||||
|
assertEquals("fallback", reqExts.get(1).name());
|
||||||
|
|
||||||
|
assertEquals(1, resExts.size());
|
||||||
|
assertEquals("fallback", resExts.get(0).name());
|
||||||
|
assertTrue(resExts.get(0).parameters().isEmpty());
|
||||||
|
assertTrue(ch.pipeline().get(DummyDecoder.class) != null);
|
||||||
|
assertTrue(ch.pipeline().get(DummyEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAllSuccess() {
|
||||||
|
// initialize
|
||||||
|
expect(mainHandshakerMock.newRequestData()).
|
||||||
|
andReturn(new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(
|
||||||
|
webSocketExtensionDataEqual("main"))).andReturn(mainExtensionMock).anyTimes();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(
|
||||||
|
webSocketExtensionDataEqual("fallback"))).andReturn(null).anyTimes();
|
||||||
|
replay(mainHandshakerMock);
|
||||||
|
|
||||||
|
expect(fallbackHandshakerMock.newRequestData()).
|
||||||
|
andReturn(new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(
|
||||||
|
webSocketExtensionDataEqual("main"))).andReturn(null).anyTimes();
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(
|
||||||
|
webSocketExtensionDataEqual("fallback"))).andReturn(fallbackExtensionMock).anyTimes();
|
||||||
|
replay(fallbackHandshakerMock);
|
||||||
|
|
||||||
|
DummyEncoder mainEncoder = new DummyEncoder();
|
||||||
|
DummyDecoder mainDecoder = new DummyDecoder();
|
||||||
|
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
|
||||||
|
expect(mainExtensionMock.newExtensionEncoder()).andReturn(mainEncoder).once();
|
||||||
|
expect(mainExtensionMock.newExtensionDecoder()).andReturn(mainDecoder).once();
|
||||||
|
replay(mainExtensionMock);
|
||||||
|
|
||||||
|
Dummy2Encoder fallbackEncoder = new Dummy2Encoder();
|
||||||
|
Dummy2Decoder fallbackDecoder = new Dummy2Decoder();
|
||||||
|
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV2).anyTimes();
|
||||||
|
expect(fallbackExtensionMock.newExtensionEncoder()).andReturn(fallbackEncoder).once();
|
||||||
|
expect(fallbackExtensionMock.newExtensionDecoder()).andReturn(fallbackDecoder).once();
|
||||||
|
replay(fallbackExtensionMock);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
|
||||||
|
mainHandshakerMock, fallbackHandshakerMock));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(null);
|
||||||
|
ch.writeOutbound(req);
|
||||||
|
|
||||||
|
HttpRequest req2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse("main, fallback");
|
||||||
|
ch.writeInbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readInbound();
|
||||||
|
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(2, reqExts.size());
|
||||||
|
assertEquals("main", reqExts.get(0).name());
|
||||||
|
assertEquals("fallback", reqExts.get(1).name());
|
||||||
|
|
||||||
|
assertEquals(2, resExts.size());
|
||||||
|
assertEquals("main", resExts.get(0).name());
|
||||||
|
assertEquals("fallback", resExts.get(1).name());
|
||||||
|
assertTrue(ch.pipeline().context(mainEncoder) != null);
|
||||||
|
assertTrue(ch.pipeline().context(mainDecoder) != null);
|
||||||
|
assertTrue(ch.pipeline().context(fallbackEncoder) != null);
|
||||||
|
assertTrue(ch.pipeline().context(fallbackDecoder) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = CodecException.class)
|
||||||
|
public void testIfMainAndFallbackUseRSV1WillFail() {
|
||||||
|
// initialize
|
||||||
|
expect(mainHandshakerMock.newRequestData()).
|
||||||
|
andReturn(new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(
|
||||||
|
webSocketExtensionDataEqual("main"))).andReturn(mainExtensionMock).anyTimes();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(
|
||||||
|
webSocketExtensionDataEqual("fallback"))).andReturn(null).anyTimes();
|
||||||
|
replay(mainHandshakerMock);
|
||||||
|
|
||||||
|
expect(fallbackHandshakerMock.newRequestData()).
|
||||||
|
andReturn(new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(
|
||||||
|
webSocketExtensionDataEqual("main"))).andReturn(null).anyTimes();
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(
|
||||||
|
webSocketExtensionDataEqual("fallback"))).andReturn(fallbackExtensionMock).anyTimes();
|
||||||
|
replay(fallbackHandshakerMock);
|
||||||
|
|
||||||
|
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
|
||||||
|
replay(mainExtensionMock);
|
||||||
|
|
||||||
|
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
|
||||||
|
replay(fallbackExtensionMock);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
|
||||||
|
mainHandshakerMock, fallbackHandshakerMock));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(null);
|
||||||
|
ch.writeOutbound(req);
|
||||||
|
|
||||||
|
HttpRequest req2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
req2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse("main, fallback");
|
||||||
|
ch.writeInbound(res);
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(2, reqExts.size());
|
||||||
|
assertEquals("main", reqExts.get(0).name());
|
||||||
|
assertEquals("fallback", reqExts.get(1).name());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License, version
|
||||||
|
* 2.0 (the "License"); you may not use this file except in compliance with the
|
||||||
|
* License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations under
|
||||||
|
* the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.easymock.EasyMock;
|
||||||
|
import org.easymock.IArgumentMatcher;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.http.DefaultHttpRequest;
|
||||||
|
import io.netty.handler.codec.http.DefaultHttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
|
public final class WebSocketExtensionTestUtil {
|
||||||
|
|
||||||
|
public static HttpRequest newUpgradeRequest(String ext) {
|
||||||
|
HttpRequest req = ReferenceCountUtil.releaseLater(new DefaultHttpRequest(
|
||||||
|
HttpVersion.HTTP_1_1, HttpMethod.GET, "/chat"));
|
||||||
|
|
||||||
|
req.headers().set(Names.HOST, "server.example.com");
|
||||||
|
req.headers().set(Names.UPGRADE, Values.WEBSOCKET.toString().toLowerCase());
|
||||||
|
req.headers().set(Names.CONNECTION, "Upgrade");
|
||||||
|
req.headers().set(Names.ORIGIN, "http://example.com");
|
||||||
|
if (ext != null) {
|
||||||
|
req.headers().set(Names.SEC_WEBSOCKET_EXTENSIONS, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HttpResponse newUpgradeResponse(String ext) {
|
||||||
|
HttpResponse res = ReferenceCountUtil.releaseLater(new DefaultHttpResponse(
|
||||||
|
HttpVersion.HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS));
|
||||||
|
|
||||||
|
res.headers().set(Names.HOST, "server.example.com");
|
||||||
|
res.headers().set(Names.UPGRADE, Values.WEBSOCKET.toString().toLowerCase());
|
||||||
|
res.headers().set(Names.CONNECTION, "Upgrade");
|
||||||
|
res.headers().set(Names.ORIGIN, "http://example.com");
|
||||||
|
if (ext != null) {
|
||||||
|
res.headers().set(Names.SEC_WEBSOCKET_EXTENSIONS, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WebSocketExtensionData webSocketExtensionDataEqual(String name) {
|
||||||
|
EasyMock.reportMatcher(new WebSocketExtensionDataMatcher(name));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WebSocketExtensionDataMatcher implements IArgumentMatcher {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public WebSocketExtensionDataMatcher(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendTo(StringBuffer buf) {
|
||||||
|
buf.append("WebSocketExtensionData with name=" + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(Object o) {
|
||||||
|
return o instanceof WebSocketExtensionData &&
|
||||||
|
name.equals(((WebSocketExtensionData) o).name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebSocketExtensionTestUtil() {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DummyEncoder extends WebSocketExtensionEncoder {
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
|
||||||
|
List<Object> out) throws Exception {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DummyDecoder extends WebSocketExtensionDecoder {
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,
|
||||||
|
List<Object> out) throws Exception {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Dummy2Encoder extends WebSocketExtensionEncoder {
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
|
||||||
|
List<Object> out) throws Exception {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Dummy2Decoder extends WebSocketExtensionDecoder {
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg,
|
||||||
|
List<Object> out) throws Exception {
|
||||||
|
// unused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionTestUtil.*;
|
||||||
|
import static org.easymock.EasyMock.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
import io.netty.handler.codec.CodecException;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||||
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class WebSocketServerExtensionHandlerTest {
|
||||||
|
|
||||||
|
WebSocketServerExtensionHandshaker mainHandshakerMock =
|
||||||
|
createMock("mainHandshaker", WebSocketServerExtensionHandshaker.class);
|
||||||
|
WebSocketServerExtensionHandshaker fallbackHandshakerMock =
|
||||||
|
createMock("fallbackHandshaker", WebSocketServerExtensionHandshaker.class);
|
||||||
|
WebSocketServerExtension mainExtensionMock =
|
||||||
|
createMock("mainExtension", WebSocketServerExtension.class);
|
||||||
|
WebSocketServerExtension fallbackExtensionMock =
|
||||||
|
createMock("fallbackExtension", WebSocketServerExtension.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainSuccess() {
|
||||||
|
// initialize
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("main"))).
|
||||||
|
andReturn(mainExtensionMock).anyTimes();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("fallback"))).
|
||||||
|
andReturn(null).anyTimes();
|
||||||
|
replay(mainHandshakerMock);
|
||||||
|
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("fallback"))).
|
||||||
|
andReturn(fallbackExtensionMock).anyTimes();
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("main"))).
|
||||||
|
andReturn(null).anyTimes();
|
||||||
|
replay(fallbackHandshakerMock);
|
||||||
|
|
||||||
|
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
|
||||||
|
expect(mainExtensionMock.newReponseData()).andReturn(
|
||||||
|
new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(mainExtensionMock.newExtensionEncoder()).andReturn(new DummyEncoder()).once();
|
||||||
|
expect(mainExtensionMock.newExtensionDecoder()).andReturn(new DummyDecoder()).once();
|
||||||
|
replay(mainExtensionMock);
|
||||||
|
|
||||||
|
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
|
||||||
|
replay(fallbackExtensionMock);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
|
||||||
|
mainHandshakerMock, fallbackHandshakerMock));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest("main, fallback");
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(1, resExts.size());
|
||||||
|
assertEquals("main", resExts.get(0).name());
|
||||||
|
assertTrue(resExts.get(0).parameters().isEmpty());
|
||||||
|
assertTrue(ch.pipeline().get(DummyDecoder.class) != null);
|
||||||
|
assertTrue(ch.pipeline().get(DummyEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompatibleExtensionTogetherSuccess() {
|
||||||
|
// initialize
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("main"))).
|
||||||
|
andReturn(mainExtensionMock).anyTimes();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("fallback"))).
|
||||||
|
andReturn(null).anyTimes();
|
||||||
|
replay(mainHandshakerMock);
|
||||||
|
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("fallback"))).
|
||||||
|
andReturn(fallbackExtensionMock).anyTimes();
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("main"))).
|
||||||
|
andReturn(null).anyTimes();
|
||||||
|
replay(fallbackHandshakerMock);
|
||||||
|
|
||||||
|
expect(mainExtensionMock.rsv()).andReturn(WebSocketExtension.RSV1).anyTimes();
|
||||||
|
expect(mainExtensionMock.newReponseData()).andReturn(
|
||||||
|
new WebSocketExtensionData("main", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(mainExtensionMock.newExtensionEncoder()).andReturn(new DummyEncoder()).once();
|
||||||
|
expect(mainExtensionMock.newExtensionDecoder()).andReturn(new DummyDecoder()).once();
|
||||||
|
replay(mainExtensionMock);
|
||||||
|
|
||||||
|
expect(fallbackExtensionMock.rsv()).andReturn(WebSocketExtension.RSV2).anyTimes();
|
||||||
|
expect(fallbackExtensionMock.newReponseData()).andReturn(
|
||||||
|
new WebSocketExtensionData("fallback", Collections.<String, String>emptyMap())).once();
|
||||||
|
expect(fallbackExtensionMock.newExtensionEncoder()).andReturn(new Dummy2Encoder()).once();
|
||||||
|
expect(fallbackExtensionMock.newExtensionDecoder()).andReturn(new Dummy2Decoder()).once();
|
||||||
|
replay(fallbackExtensionMock);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
|
||||||
|
mainHandshakerMock, fallbackHandshakerMock));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest("main, fallback");
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(2, resExts.size());
|
||||||
|
assertEquals("main", resExts.get(0).name());
|
||||||
|
assertEquals("fallback", resExts.get(1).name());
|
||||||
|
assertTrue(ch.pipeline().get(DummyDecoder.class) != null);
|
||||||
|
assertTrue(ch.pipeline().get(DummyEncoder.class) != null);
|
||||||
|
assertTrue(ch.pipeline().get(Dummy2Decoder.class) != null);
|
||||||
|
assertTrue(ch.pipeline().get(Dummy2Encoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoneExtensionMatchingSuccess() {
|
||||||
|
// initialize
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("unknown"))).
|
||||||
|
andReturn(null).anyTimes();
|
||||||
|
expect(mainHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("unknown2"))).
|
||||||
|
andReturn(null).anyTimes();
|
||||||
|
replay(mainHandshakerMock);
|
||||||
|
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("unknown"))).
|
||||||
|
andReturn(null).anyTimes();
|
||||||
|
expect(fallbackHandshakerMock.handshakeExtension(webSocketExtensionDataEqual("unknown2"))).
|
||||||
|
andReturn(null).anyTimes();
|
||||||
|
replay(fallbackHandshakerMock);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
|
||||||
|
mainHandshakerMock, fallbackHandshakerMock));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest("unknown, unknown2");
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertFalse(res2.headers().contains(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.compression.
|
||||||
|
DeflateFrameServerExtensionHandshaker.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class DeflateFrameClientExtensionHandshakerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWebkitDeflateFrameData() {
|
||||||
|
DeflateFrameClientExtensionHandshaker handshaker =
|
||||||
|
new DeflateFrameClientExtensionHandshaker(true);
|
||||||
|
|
||||||
|
WebSocketExtensionData data = handshaker.newRequestData();
|
||||||
|
|
||||||
|
assertEquals(X_WEBKIT_DEFLATE_FRAME_EXTENSION, data.name());
|
||||||
|
assertTrue(data.parameters().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeflateFrameData() {
|
||||||
|
DeflateFrameClientExtensionHandshaker handshaker =
|
||||||
|
new DeflateFrameClientExtensionHandshaker(false);
|
||||||
|
|
||||||
|
WebSocketExtensionData data = handshaker.newRequestData();
|
||||||
|
|
||||||
|
assertEquals(DEFLATE_FRAME_EXTENSION, data.name());
|
||||||
|
assertTrue(data.parameters().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNormalHandshake() {
|
||||||
|
DeflateFrameClientExtensionHandshaker handshaker =
|
||||||
|
new DeflateFrameClientExtensionHandshaker(false);
|
||||||
|
|
||||||
|
WebSocketClientExtension extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, Collections.<String, String>emptyMap()));
|
||||||
|
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketClientExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerFrameDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerFrameDeflateEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailedHandshake() {
|
||||||
|
// initialize
|
||||||
|
DeflateFrameClientExtensionHandshaker handshaker =
|
||||||
|
new DeflateFrameClientExtensionHandshaker(false);
|
||||||
|
|
||||||
|
Map<String, String> parameters = new HashMap<String, String>();
|
||||||
|
parameters.put("invalid", "12");
|
||||||
|
|
||||||
|
// execute
|
||||||
|
WebSocketClientExtension extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, parameters));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNull(extension);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.compression.
|
||||||
|
DeflateFrameServerExtensionHandshaker.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class DeflateFrameServerExtensionHandshakerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNormalHandshake() {
|
||||||
|
// initialize
|
||||||
|
DeflateFrameServerExtensionHandshaker handshaker =
|
||||||
|
new DeflateFrameServerExtensionHandshaker();
|
||||||
|
|
||||||
|
// execute
|
||||||
|
WebSocketServerExtension extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, Collections.<String, String>emptyMap()));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerFrameDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerFrameDeflateEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWebkitHandshake() {
|
||||||
|
// initialize
|
||||||
|
DeflateFrameServerExtensionHandshaker handshaker =
|
||||||
|
new DeflateFrameServerExtensionHandshaker();
|
||||||
|
|
||||||
|
// execute
|
||||||
|
WebSocketServerExtension extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(X_WEBKIT_DEFLATE_FRAME_EXTENSION, Collections.<String, String>emptyMap()));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerFrameDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerFrameDeflateEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailedHandshake() {
|
||||||
|
// initialize
|
||||||
|
DeflateFrameServerExtensionHandshaker handshaker =
|
||||||
|
new DeflateFrameServerExtensionHandshaker();
|
||||||
|
|
||||||
|
Map<String, String> parameters;
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
parameters.put("unknown", "11");
|
||||||
|
|
||||||
|
// execute
|
||||||
|
WebSocketServerExtension extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(DEFLATE_FRAME_EXTENSION, parameters));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNull(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.compression.
|
||||||
|
PerMessageDeflateServerExtensionHandshaker.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtension;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class PerMessageDeflateClientExtensionHandshakerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNormalData() {
|
||||||
|
PerMessageDeflateClientExtensionHandshaker handshaker =
|
||||||
|
new PerMessageDeflateClientExtensionHandshaker();
|
||||||
|
|
||||||
|
WebSocketExtensionData data = handshaker.newRequestData();
|
||||||
|
|
||||||
|
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
|
||||||
|
assertTrue(data.parameters().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomData() {
|
||||||
|
PerMessageDeflateClientExtensionHandshaker handshaker =
|
||||||
|
new PerMessageDeflateClientExtensionHandshaker(6, true, 10, true, true);
|
||||||
|
|
||||||
|
WebSocketExtensionData data = handshaker.newRequestData();
|
||||||
|
|
||||||
|
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
|
||||||
|
assertTrue(data.parameters().containsKey(CLIENT_MAX_WINDOW));
|
||||||
|
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
|
||||||
|
assertTrue(data.parameters().get(SERVER_MAX_WINDOW).equals("10"));
|
||||||
|
assertTrue(data.parameters().containsKey(CLIENT_MAX_WINDOW));
|
||||||
|
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNormalHandshake() {
|
||||||
|
PerMessageDeflateClientExtensionHandshaker handshaker =
|
||||||
|
new PerMessageDeflateClientExtensionHandshaker();
|
||||||
|
|
||||||
|
WebSocketClientExtension extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, Collections.<String, String>emptyMap()));
|
||||||
|
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketClientExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomHandshake() {
|
||||||
|
WebSocketClientExtension extension;
|
||||||
|
Map<String, String> parameters;
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
PerMessageDeflateClientExtensionHandshaker handshaker =
|
||||||
|
new PerMessageDeflateClientExtensionHandshaker(6, true, 10, true, true);
|
||||||
|
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
parameters.put(CLIENT_MAX_WINDOW, "12");
|
||||||
|
parameters.put(SERVER_MAX_WINDOW, "10");
|
||||||
|
parameters.put(CLIENT_NO_CONTEXT, null);
|
||||||
|
parameters.put(SERVER_NO_CONTEXT, null);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketClientExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
parameters.put(SERVER_MAX_WINDOW, "10");
|
||||||
|
parameters.put(SERVER_NO_CONTEXT, null);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketClientExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNull(extension);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.compression.
|
||||||
|
PerMessageDeflateServerExtensionHandshaker.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtension;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class PerMessageDeflateServerExtensionHandshakerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNormalHandshake() {
|
||||||
|
WebSocketServerExtension extension;
|
||||||
|
WebSocketExtensionData data;
|
||||||
|
Map<String, String> parameters;
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
PerMessageDeflateServerExtensionHandshaker handshaker =
|
||||||
|
new PerMessageDeflateServerExtensionHandshaker();
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, Collections.<String, String>emptyMap()));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
data = extension.newReponseData();
|
||||||
|
|
||||||
|
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
|
||||||
|
assertTrue(data.parameters().isEmpty());
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
parameters.put(CLIENT_MAX_WINDOW, null);
|
||||||
|
parameters.put(CLIENT_NO_CONTEXT, null);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, Collections.<String, String>emptyMap()));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
data = extension.newReponseData();
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
|
||||||
|
assertTrue(data.parameters().isEmpty());
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
parameters.put(SERVER_MAX_WINDOW, "12");
|
||||||
|
parameters.put(SERVER_NO_CONTEXT, null);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNull(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomHandshake() {
|
||||||
|
WebSocketServerExtension extension;
|
||||||
|
Map<String, String> parameters;
|
||||||
|
WebSocketExtensionData data;
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
PerMessageDeflateServerExtensionHandshaker handshaker =
|
||||||
|
new PerMessageDeflateServerExtensionHandshaker(6, true, 10, true, true);
|
||||||
|
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
parameters.put(CLIENT_MAX_WINDOW, null);
|
||||||
|
parameters.put(SERVER_MAX_WINDOW, "12");
|
||||||
|
parameters.put(CLIENT_NO_CONTEXT, null);
|
||||||
|
parameters.put(SERVER_NO_CONTEXT, null);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
data = extension.newReponseData();
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
|
||||||
|
assertTrue(data.parameters().containsKey(CLIENT_MAX_WINDOW));
|
||||||
|
assertTrue(data.parameters().get(CLIENT_MAX_WINDOW).equals("10"));
|
||||||
|
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
|
||||||
|
assertTrue(data.parameters().get(SERVER_MAX_WINDOW).equals("12"));
|
||||||
|
assertTrue(data.parameters().containsKey(CLIENT_MAX_WINDOW));
|
||||||
|
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
parameters.put(SERVER_MAX_WINDOW, "12");
|
||||||
|
parameters.put(SERVER_NO_CONTEXT, null);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
assertEquals(WebSocketServerExtension.RSV1, extension.rsv());
|
||||||
|
assertTrue(extension.newExtensionDecoder() instanceof PerMessageDeflateDecoder);
|
||||||
|
assertTrue(extension.newExtensionEncoder() instanceof PerMessageDeflateEncoder);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
data = extension.newReponseData();
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
|
||||||
|
assertEquals(2, data.parameters().size());
|
||||||
|
assertTrue(data.parameters().containsKey(SERVER_MAX_WINDOW));
|
||||||
|
assertTrue(data.parameters().get(SERVER_MAX_WINDOW).equals("12"));
|
||||||
|
assertTrue(data.parameters().containsKey(SERVER_NO_CONTEXT));
|
||||||
|
|
||||||
|
// initialize
|
||||||
|
parameters = new HashMap<String, String>();
|
||||||
|
|
||||||
|
// execute
|
||||||
|
extension = handshaker.handshakeExtension(
|
||||||
|
new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters));
|
||||||
|
// test
|
||||||
|
assertNotNull(extension);
|
||||||
|
|
||||||
|
// execute
|
||||||
|
data = extension.newReponseData();
|
||||||
|
|
||||||
|
// test
|
||||||
|
assertEquals(PERMESSAGE_DEFLATE_EXTENSION, data.name());
|
||||||
|
assertTrue(data.parameters().isEmpty());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.compression.
|
||||||
|
PerMessageDeflateServerExtensionHandshaker.*;
|
||||||
|
import static io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionTestUtil.*;
|
||||||
|
import io.netty.channel.embedded.EmbeddedChannel;
|
||||||
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionData;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionUtil;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class WebSocketServerCompressionHandlerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNormalSuccess() {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION);
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
|
||||||
|
Assert.assertTrue(exts.get(0).parameters().isEmpty());
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientWindowSizeSuccess() {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
|
||||||
|
new PerMessageDeflateServerExtensionHandshaker(6, false, 10, false, false)));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + CLIENT_MAX_WINDOW);
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
|
||||||
|
Assert.assertEquals("10", exts.get(0).parameters().get(CLIENT_MAX_WINDOW));
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientWindowSizeUnavailable() {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
|
||||||
|
new PerMessageDeflateServerExtensionHandshaker(6, false, 10, false, false)));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION);
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
|
||||||
|
Assert.assertTrue(exts.get(0).parameters().isEmpty());
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerWindowSizeSuccess() {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
|
||||||
|
new PerMessageDeflateServerExtensionHandshaker(6, true, 15, false, false)));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + SERVER_MAX_WINDOW + "=10");
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
|
||||||
|
Assert.assertEquals("10", exts.get(0).parameters().get(SERVER_MAX_WINDOW));
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerWindowSizeDisable() {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
|
||||||
|
new PerMessageDeflateServerExtensionHandshaker(6, false, 15, false, false)));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + SERVER_MAX_WINDOW + "=10");
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
|
||||||
|
Assert.assertFalse(res2.headers().contains(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) == null);
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerNoContext() {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + SERVER_NO_CONTEXT);
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
|
||||||
|
Assert.assertFalse(res2.headers().contains(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) == null);
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClientNoContext() {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + CLIENT_NO_CONTEXT);
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
|
||||||
|
Assert.assertTrue(exts.get(0).parameters().isEmpty());
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerWindowSizeDisableThenFallback() {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
|
||||||
|
new PerMessageDeflateServerExtensionHandshaker(6, false, 15, false, false)));
|
||||||
|
|
||||||
|
HttpRequest req = newUpgradeRequest(PERMESSAGE_DEFLATE_EXTENSION + "; " + SERVER_MAX_WINDOW + "=10, " +
|
||||||
|
PERMESSAGE_DEFLATE_EXTENSION);
|
||||||
|
ch.writeInbound(req);
|
||||||
|
|
||||||
|
HttpResponse res = newUpgradeResponse(null);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
HttpResponse res2 = ch.readOutbound();
|
||||||
|
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.extractExtensions(
|
||||||
|
res2.headers().get(Names.SEC_WEBSOCKET_EXTENSIONS));
|
||||||
|
|
||||||
|
Assert.assertEquals(PERMESSAGE_DEFLATE_EXTENSION, exts.get(0).name());
|
||||||
|
Assert.assertTrue(exts.get(0).parameters().isEmpty());
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateDecoder.class) != null);
|
||||||
|
Assert.assertTrue(ch.pipeline().get(PerMessageDeflateEncoder.class) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -26,6 +26,9 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
|
|||||||
public final class ZlibCodecFactory {
|
public final class ZlibCodecFactory {
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ZlibCodecFactory.class);
|
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;
|
private static final boolean noJdkZlibDecoder;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -58,7 +61,8 @@ public final class ZlibCodecFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ZlibEncoder newZlibEncoder(ZlibWrapper wrapper, int compressionLevel, int windowBits, int memLevel) {
|
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);
|
return new JZlibEncoder(wrapper, compressionLevel, windowBits, memLevel);
|
||||||
} else {
|
} else {
|
||||||
return new JdkZlibEncoder(wrapper, compressionLevel);
|
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) {
|
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);
|
return new JZlibEncoder(compressionLevel, windowBits, memLevel, dictionary);
|
||||||
} else {
|
} else {
|
||||||
return new JdkZlibEncoder(compressionLevel, dictionary);
|
return new JdkZlibEncoder(compressionLevel, dictionary);
|
||||||
|
@ -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.WebSocketClientHandshakerFactory;
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
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.SslContext;
|
||||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||||
|
|
||||||
@ -111,6 +112,7 @@ public final class WebSocketClient {
|
|||||||
p.addLast(
|
p.addLast(
|
||||||
new HttpClientCodec(),
|
new HttpClientCodec(),
|
||||||
new HttpObjectAggregator(8192),
|
new HttpObjectAggregator(8192),
|
||||||
|
new WebSocketClientCompressionHandler(),
|
||||||
handler);
|
handler);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,7 @@ import io.netty.channel.ChannelPipeline;
|
|||||||
import io.netty.channel.socket.SocketChannel;
|
import io.netty.channel.socket.SocketChannel;
|
||||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||||
import io.netty.handler.codec.http.HttpServerCodec;
|
import io.netty.handler.codec.http.HttpServerCodec;
|
||||||
|
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,6 +41,7 @@ public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel
|
|||||||
}
|
}
|
||||||
pipeline.addLast(new HttpServerCodec());
|
pipeline.addLast(new HttpServerCodec());
|
||||||
pipeline.addLast(new HttpObjectAggregator(65536));
|
pipeline.addLast(new HttpObjectAggregator(65536));
|
||||||
|
pipeline.addLast(new WebSocketServerCompressionHandler());
|
||||||
pipeline.addLast(new WebSocketServerHandler());
|
pipeline.addLast(new WebSocketServerHandler());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user