[#4212] Backport WebSocket Extension handlers for client and server.
Motivation: We have websocket extension support (with compression) in old master. We should port this to 4.1 Modifications: Backport relevant code. Result: websocket extension support (with compression) is now in 4.1.
This commit is contained in:
parent
b39380ad83
commit
dc615ecaaf
@ -20,7 +20,7 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* Web Socket text frame with assumed UTF-8 encoding
|
||||
* Web Socket text frame
|
||||
*/
|
||||
public class TextWebSocketFrame extends WebSocketFrame {
|
||||
|
||||
@ -45,7 +45,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
||||
* Creates a new text frame with the specified binary data. The final fragment flag is set to true.
|
||||
*
|
||||
* @param binaryData
|
||||
* the content of the frame. Must be UTF-8 encoded
|
||||
* the content of the frame.
|
||||
*/
|
||||
public TextWebSocketFrame(ByteBuf binaryData) {
|
||||
super(binaryData);
|
||||
@ -81,7 +81,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
||||
* @param rsv
|
||||
* reserved bits used for protocol extensions
|
||||
* @param binaryData
|
||||
* the content of the frame. Must be UTF-8 encoded
|
||||
* the content of the frame.
|
||||
*/
|
||||
public TextWebSocketFrame(boolean finalFragment, int rsv, ByteBuf binaryData) {
|
||||
super(finalFragment, rsv, binaryData);
|
||||
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerAdapter;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder.State;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.buffer.ByteBufUtil.readBytes;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class Utf8FrameValidator extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private int fragmentedFramesCount;
|
||||
private Utf8Validator utf8Validator;
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof WebSocketFrame) {
|
||||
WebSocketFrame frame = (WebSocketFrame) msg;
|
||||
|
||||
// Processing for possible fragmented messages for text and binary
|
||||
// frames
|
||||
if (((WebSocketFrame) msg).isFinalFragment()) {
|
||||
// Final frame of the sequence. Apparently ping frames are
|
||||
// allowed in the middle of a fragmented message
|
||||
if (!(frame instanceof PingWebSocketFrame)) {
|
||||
fragmentedFramesCount = 0;
|
||||
|
||||
// Check text for UTF8 correctness
|
||||
if ((frame instanceof TextWebSocketFrame) ||
|
||||
(utf8Validator != null && utf8Validator.isChecking())) {
|
||||
// Check UTF-8 correctness for this payload
|
||||
checkUTF8String(ctx, frame.content());
|
||||
|
||||
// This does a second check to make sure UTF-8
|
||||
// correctness for entire text message
|
||||
utf8Validator.finish();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not final frame so we can expect more frames in the
|
||||
// fragmented sequence
|
||||
if (fragmentedFramesCount == 0) {
|
||||
// First text or binary frame for a fragmented set
|
||||
if (frame instanceof TextWebSocketFrame) {
|
||||
checkUTF8String(ctx, frame.content());
|
||||
}
|
||||
} else {
|
||||
// Subsequent frames - only check if init frame is text
|
||||
if (utf8Validator != null && utf8Validator.isChecking()) {
|
||||
checkUTF8String(ctx, frame.content());
|
||||
}
|
||||
}
|
||||
|
||||
// Increment counter
|
||||
fragmentedFramesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
|
||||
private void checkUTF8String(ChannelHandlerContext ctx, ByteBuf buffer) {
|
||||
try {
|
||||
if (utf8Validator == null) {
|
||||
utf8Validator = new Utf8Validator();
|
||||
}
|
||||
utf8Validator.check(buffer);
|
||||
} catch (CorruptedFrameException ex) {
|
||||
if (ctx.channel().isActive()) {
|
||||
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -107,7 +107,6 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
private byte[] maskingKey;
|
||||
private int framePayloadLen1;
|
||||
private boolean receivedClosingHandshake;
|
||||
private Utf8Validator utf8Validator;
|
||||
private State state = State.READING_FIRST;
|
||||
|
||||
/**
|
||||
@ -340,33 +339,8 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
// allowed in the middle of a fragmented message
|
||||
if (frameOpcode != OPCODE_PING) {
|
||||
fragmentedFramesCount = 0;
|
||||
|
||||
// Check text for UTF8 correctness
|
||||
if (frameOpcode == OPCODE_TEXT ||
|
||||
utf8Validator != null && utf8Validator.isChecking()) {
|
||||
// Check UTF-8 correctness for this payload
|
||||
checkUTF8String(ctx, payloadBuffer);
|
||||
|
||||
// This does a second check to make sure UTF-8
|
||||
// correctness for entire text message
|
||||
utf8Validator.finish();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not final frame so we can expect more frames in the
|
||||
// fragmented sequence
|
||||
if (fragmentedFramesCount == 0) {
|
||||
// First text or binary frame for a fragmented set
|
||||
if (frameOpcode == OPCODE_TEXT) {
|
||||
checkUTF8String(ctx, payloadBuffer);
|
||||
}
|
||||
} else {
|
||||
// Subsequent frames - only check if init frame is text
|
||||
if (utf8Validator != null && utf8Validator.isChecking()) {
|
||||
checkUTF8String(ctx, payloadBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Increment counter
|
||||
fragmentedFramesCount++;
|
||||
}
|
||||
@ -460,17 +434,6 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
}
|
||||
}
|
||||
|
||||
private void checkUTF8String(ChannelHandlerContext ctx, ByteBuf buffer) {
|
||||
try {
|
||||
if (utf8Validator == null) {
|
||||
utf8Validator = new Utf8Validator();
|
||||
}
|
||||
utf8Validator.check(buffer);
|
||||
} catch (CorruptedFrameException ex) {
|
||||
protocolViolation(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/** */
|
||||
protected void checkCloseFrameBody(
|
||||
ChannelHandlerContext ctx, ByteBuf buffer) {
|
||||
|
@ -183,5 +183,10 @@ public class WebSocketClientProtocolHandler extends WebSocketProtocolHandler {
|
||||
ctx.pipeline().addBefore(ctx.name(), WebSocketClientProtocolHandshakeHandler.class.getName(),
|
||||
new WebSocketClientProtocolHandshakeHandler(handshaker));
|
||||
}
|
||||
if (cp.get(Utf8FrameValidator.class) == null) {
|
||||
// Add the UFT8 checking before this one.
|
||||
ctx.pipeline().addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
|
||||
new Utf8FrameValidator());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,6 +105,11 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
|
||||
new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols,
|
||||
allowExtensions, maxFramePayloadLength, allowMaskMismatch));
|
||||
}
|
||||
if (cp.get(Utf8FrameValidator.class) == null) {
|
||||
// Add the UFT8 checking before this one.
|
||||
ctx.pipeline().addBefore(ctx.name(), Utf8FrameValidator.class.getName(),
|
||||
new Utf8FrameValidator());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.CodecException;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This handler negotiates and initializes the WebSocket Extensions.
|
||||
*
|
||||
* This implementation negotiates the extension with the server in a defined order,
|
||||
* ensures that the successfully negotiated extensions are consistent between them,
|
||||
* and initializes the channel pipeline with the extension decoder and encoder.
|
||||
*
|
||||
* Find a basic implementation for compression extensions at
|
||||
* <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler</tt>.
|
||||
*/
|
||||
public class WebSocketClientExtensionHandler extends ChannelDuplexHandler {
|
||||
|
||||
private final List<WebSocketClientExtensionHandshaker> extensionHandshakers;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param extensionHandshakers
|
||||
* The extension handshaker in priority order. A handshaker could be repeated many times
|
||||
* with fallback configuration.
|
||||
*/
|
||||
public WebSocketClientExtensionHandler(WebSocketClientExtensionHandshaker... extensionHandshakers) {
|
||||
if (extensionHandshakers == null) {
|
||||
throw new NullPointerException("extensionHandshakers");
|
||||
}
|
||||
if (extensionHandshakers.length == 0) {
|
||||
throw new IllegalArgumentException("extensionHandshakers must contains at least one handshaker");
|
||||
}
|
||||
this.extensionHandshakers = Arrays.asList(extensionHandshakers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (msg instanceof HttpRequest && WebSocketExtensionUtil.isWebsocketUpgrade((HttpRequest) msg)) {
|
||||
HttpRequest request = (HttpRequest) msg;
|
||||
String headerValue = request.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
|
||||
|
||||
for (WebSocketClientExtensionHandshaker extentionHandshaker : extensionHandshakers) {
|
||||
WebSocketExtensionData extensionData = extentionHandshaker.newRequestData();
|
||||
headerValue = WebSocketExtensionUtil.appendExtension(headerValue,
|
||||
extensionData.name(), extensionData.parameters());
|
||||
}
|
||||
|
||||
request.headers().set(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS, headerValue);
|
||||
}
|
||||
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg)
|
||||
throws Exception {
|
||||
if (msg instanceof HttpResponse) {
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
|
||||
if (WebSocketExtensionUtil.isWebsocketUpgrade(response)) {
|
||||
String extensionsHeader = response.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
|
||||
|
||||
if (extensionsHeader != null) {
|
||||
List<WebSocketExtensionData> extensions =
|
||||
WebSocketExtensionUtil.extractExtensions(extensionsHeader);
|
||||
List<WebSocketClientExtension> validExtensions =
|
||||
new ArrayList<WebSocketClientExtension>(extensions.size());
|
||||
int rsv = 0;
|
||||
|
||||
for (WebSocketExtensionData extensionData : extensions) {
|
||||
Iterator<WebSocketClientExtensionHandshaker> extensionHandshakersIterator =
|
||||
extensionHandshakers.iterator();
|
||||
WebSocketClientExtension validExtension = null;
|
||||
|
||||
while (validExtension == null && extensionHandshakersIterator.hasNext()) {
|
||||
WebSocketClientExtensionHandshaker extensionHandshaker =
|
||||
extensionHandshakersIterator.next();
|
||||
validExtension = extensionHandshaker.handshakeExtension(extensionData);
|
||||
}
|
||||
|
||||
if (validExtension != null && ((validExtension.rsv() & rsv) == 0)) {
|
||||
rsv = rsv | validExtension.rsv();
|
||||
validExtensions.add(validExtension);
|
||||
} else {
|
||||
throw new CodecException(
|
||||
"invalid WebSocket Extension handhshake for \"" + extensionsHeader + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
for (WebSocketClientExtension validExtension : validExtensions) {
|
||||
WebSocketExtensionDecoder decoder = validExtension.newExtensionDecoder();
|
||||
WebSocketExtensionEncoder encoder = validExtension.newExtensionEncoder();
|
||||
ctx.pipeline().addAfter(ctx.name(), decoder.getClass().getName(), decoder);
|
||||
ctx.pipeline().addAfter(ctx.name(), encoder.getClass().getName(), encoder);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.pipeline().remove(ctx.name());
|
||||
}
|
||||
}
|
||||
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
}
|
||||
|
@ -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,106 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx.extensions;
|
||||
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpMessage;
|
||||
import io.netty.util.internal.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class WebSocketExtensionUtil {
|
||||
|
||||
private static final char EXTENSION_SEPARATOR = ',';
|
||||
private static final char PARAMETER_SEPARATOR = ';';
|
||||
private static final char PARAMETER_EQUAL = '=';
|
||||
|
||||
private static final Pattern PARAMETER = Pattern.compile("^([^=]+)(=[\\\"]?([^\\\"]+)[\\\"]?)?$");
|
||||
|
||||
static boolean isWebsocketUpgrade(HttpMessage httpMessage) {
|
||||
if (httpMessage == null) {
|
||||
throw new NullPointerException("httpMessage");
|
||||
}
|
||||
return httpMessage.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.UPGRADE, true) &&
|
||||
httpMessage.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true);
|
||||
}
|
||||
|
||||
public static List<WebSocketExtensionData> extractExtensions(String extensionHeader) {
|
||||
String[] rawExtensions = StringUtil.split(extensionHeader, EXTENSION_SEPARATOR);
|
||||
if (rawExtensions.length > 0) {
|
||||
List<WebSocketExtensionData> extensions = new ArrayList<WebSocketExtensionData>(rawExtensions.length);
|
||||
for (String rawExtension : rawExtensions) {
|
||||
String[] extensionParameters = StringUtil.split(rawExtension, PARAMETER_SEPARATOR);
|
||||
String name = extensionParameters[0].trim();
|
||||
Map<String, String> parameters;
|
||||
if (extensionParameters.length > 1) {
|
||||
parameters = new HashMap<String, String>(extensionParameters.length - 1);
|
||||
for (int i = 1; i < extensionParameters.length; i++) {
|
||||
String parameter = extensionParameters[i].trim();
|
||||
Matcher parameterMatcher = PARAMETER.matcher(parameter);
|
||||
if (parameterMatcher.matches() && parameterMatcher.group(1) != null) {
|
||||
parameters.put(parameterMatcher.group(1), parameterMatcher.group(3));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parameters = Collections.<String, String>emptyMap();
|
||||
}
|
||||
extensions.add(new WebSocketExtensionData(name, parameters));
|
||||
}
|
||||
return extensions;
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
static String appendExtension(String currentHeaderValue, String extensionName,
|
||||
Map<String, String> extensionParameters) {
|
||||
|
||||
StringBuilder newHeaderValue = new StringBuilder(
|
||||
currentHeaderValue != null ? currentHeaderValue.length() : 0 + extensionName.length() + 1);
|
||||
if (currentHeaderValue != null && !currentHeaderValue.trim().isEmpty()) {
|
||||
newHeaderValue.append(currentHeaderValue);
|
||||
newHeaderValue.append(EXTENSION_SEPARATOR);
|
||||
}
|
||||
newHeaderValue.append(extensionName);
|
||||
boolean isFirst = true;
|
||||
for (Entry<String, String> extensionParameter : extensionParameters.entrySet()) {
|
||||
if (isFirst) {
|
||||
newHeaderValue.append(PARAMETER_SEPARATOR);
|
||||
} else {
|
||||
isFirst = false;
|
||||
}
|
||||
newHeaderValue.append(extensionParameter.getKey());
|
||||
if (extensionParameter.getValue() != null) {
|
||||
newHeaderValue.append(PARAMETER_EQUAL);
|
||||
newHeaderValue.append(extensionParameter.getValue());
|
||||
}
|
||||
}
|
||||
return newHeaderValue.toString();
|
||||
}
|
||||
|
||||
private WebSocketExtensionUtil() {
|
||||
// Unused
|
||||
}
|
||||
}
|
@ -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,141 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx.extensions;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This handler negotiates and initializes the WebSocket Extensions.
|
||||
*
|
||||
* It negotiates the extensions based on the client desired order,
|
||||
* ensures that the successfully negotiated extensions are consistent between them,
|
||||
* and initializes the channel pipeline with the extension decoder and encoder.
|
||||
*
|
||||
* Find a basic implementation for compression extensions at
|
||||
* <tt>io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler</tt>.
|
||||
*/
|
||||
public class WebSocketServerExtensionHandler extends ChannelDuplexHandler {
|
||||
|
||||
private final List<WebSocketServerExtensionHandshaker> extensionHandshakers;
|
||||
|
||||
private List<WebSocketServerExtension> validExtensions;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param extensionHandshakers
|
||||
* The extension handshaker in priority order. A handshaker could be repeated many times
|
||||
* with fallback configuration.
|
||||
*/
|
||||
public WebSocketServerExtensionHandler(WebSocketServerExtensionHandshaker... extensionHandshakers) {
|
||||
if (extensionHandshakers == null) {
|
||||
throw new NullPointerException("extensionHandshakers");
|
||||
}
|
||||
if (extensionHandshakers.length == 0) {
|
||||
throw new IllegalArgumentException("extensionHandshakers must contains at least one handshaker");
|
||||
}
|
||||
this.extensionHandshakers = Arrays.asList(extensionHandshakers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg)
|
||||
throws Exception {
|
||||
if (msg instanceof HttpRequest) {
|
||||
HttpRequest request = (HttpRequest) msg;
|
||||
|
||||
if (WebSocketExtensionUtil.isWebsocketUpgrade(request)) {
|
||||
String extensionsHeader = request.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
|
||||
|
||||
if (extensionsHeader != null) {
|
||||
List<WebSocketExtensionData> extensions =
|
||||
WebSocketExtensionUtil.extractExtensions(extensionsHeader);
|
||||
int rsv = 0;
|
||||
|
||||
for (WebSocketExtensionData extensionData : extensions) {
|
||||
Iterator<WebSocketServerExtensionHandshaker> extensionHandshakersIterator =
|
||||
extensionHandshakers.iterator();
|
||||
WebSocketServerExtension validExtension = null;
|
||||
|
||||
while (validExtension == null && extensionHandshakersIterator.hasNext()) {
|
||||
WebSocketServerExtensionHandshaker extensionHandshaker =
|
||||
extensionHandshakersIterator.next();
|
||||
validExtension = extensionHandshaker.handshakeExtension(extensionData);
|
||||
}
|
||||
|
||||
if (validExtension != null && ((validExtension.rsv() & rsv) == 0)) {
|
||||
if (validExtensions == null) {
|
||||
validExtensions = new ArrayList<WebSocketServerExtension>(1);
|
||||
}
|
||||
rsv = rsv | validExtension.rsv();
|
||||
validExtensions.add(validExtension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (msg instanceof HttpResponse &&
|
||||
WebSocketExtensionUtil.isWebsocketUpgrade((HttpResponse) msg) && validExtensions != null) {
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
String headerValue = response.headers().getAsString(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS);
|
||||
|
||||
for (WebSocketServerExtension extension : validExtensions) {
|
||||
WebSocketExtensionData extensionData = extension.newReponseData();
|
||||
headerValue = WebSocketExtensionUtil.appendExtension(headerValue,
|
||||
extensionData.name(), extensionData.parameters());
|
||||
}
|
||||
|
||||
promise.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
for (WebSocketServerExtension extension : validExtensions) {
|
||||
WebSocketExtensionDecoder decoder = extension.newExtensionDecoder();
|
||||
WebSocketExtensionEncoder encoder = extension.newExtensionEncoder();
|
||||
ctx.pipeline().addAfter(ctx.name(), decoder.getClass().getName(), decoder);
|
||||
ctx.pipeline().addAfter(ctx.name(), encoder.getClass().getName(), encoder);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.pipeline().remove(ctx.name());
|
||||
}
|
||||
});
|
||||
|
||||
if (headerValue != null) {
|
||||
response.headers().set(HttpHeaderNames.SEC_WEBSOCKET_EXTENSIONS, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
}
|
@ -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,144 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CodecException;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.compression.ZlibCodecFactory;
|
||||
import io.netty.handler.codec.compression.ZlibWrapper;
|
||||
import io.netty.handler.codec.http.DefaultHttpContent;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionDecoder;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Deflate implementation of a payload decompressor for
|
||||
* <tt>io.netty.handler.codec.http.websocketx.WebSocketFrame</tt>.
|
||||
*/
|
||||
abstract class DeflateDecoder extends WebSocketExtensionDecoder {
|
||||
|
||||
static final byte[] FRAME_TAIL = new byte[] {0x00, 0x00, (byte) 0xff, (byte) 0xff};
|
||||
|
||||
private final boolean noContext;
|
||||
|
||||
private EmbeddedChannel decoder;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param noContext true to disable context takeover.
|
||||
*/
|
||||
public DeflateDecoder(boolean noContext) {
|
||||
this.noContext = noContext;
|
||||
}
|
||||
|
||||
protected abstract boolean appendFrameTail(WebSocketFrame msg);
|
||||
|
||||
protected abstract int newRsv(WebSocketFrame msg);
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, WebSocketFrame msg, List<Object> out) throws Exception {
|
||||
if (decoder == null) {
|
||||
if (!(msg instanceof TextWebSocketFrame) && !(msg instanceof BinaryWebSocketFrame)) {
|
||||
throw new CodecException("unexpected initial frame type: " + msg.getClass().getName());
|
||||
}
|
||||
decoder = new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.NONE));
|
||||
}
|
||||
|
||||
boolean readable = msg.content().isReadable();
|
||||
decoder.writeInbound(msg.content().retain());
|
||||
if (appendFrameTail(msg)) {
|
||||
decoder.writeInbound(Unpooled.wrappedBuffer(FRAME_TAIL));
|
||||
}
|
||||
|
||||
CompositeByteBuf compositeUncompressedContent = ctx.alloc().compositeBuffer();
|
||||
for (;;) {
|
||||
ByteBuf partUncompressedContent = decoder.readInbound();
|
||||
if (partUncompressedContent == null) {
|
||||
break;
|
||||
}
|
||||
if (!partUncompressedContent.isReadable()) {
|
||||
partUncompressedContent.release();
|
||||
continue;
|
||||
}
|
||||
compositeUncompressedContent.addComponent(partUncompressedContent);
|
||||
compositeUncompressedContent.writerIndex(compositeUncompressedContent.writerIndex() +
|
||||
partUncompressedContent.readableBytes());
|
||||
}
|
||||
// Correctly handle empty frames
|
||||
// See https://github.com/netty/netty/issues/4348
|
||||
if (readable && compositeUncompressedContent.numComponents() <= 0) {
|
||||
compositeUncompressedContent.release();
|
||||
throw new CodecException("cannot read uncompressed buffer");
|
||||
}
|
||||
|
||||
if (msg.isFinalFragment() && noContext) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
WebSocketFrame outMsg;
|
||||
if (msg instanceof TextWebSocketFrame) {
|
||||
outMsg = new TextWebSocketFrame(msg.isFinalFragment(), newRsv(msg), compositeUncompressedContent);
|
||||
} else if (msg instanceof BinaryWebSocketFrame) {
|
||||
outMsg = new BinaryWebSocketFrame(msg.isFinalFragment(), newRsv(msg), compositeUncompressedContent);
|
||||
} else if (msg instanceof ContinuationWebSocketFrame) {
|
||||
outMsg = new ContinuationWebSocketFrame(msg.isFinalFragment(), newRsv(msg),
|
||||
compositeUncompressedContent);
|
||||
} else {
|
||||
throw new CodecException("unexpected frame type: " + msg.getClass().getName());
|
||||
}
|
||||
out.add(outMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
cleanup();
|
||||
super.handlerRemoved(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
cleanup();
|
||||
super.channelInactive(ctx);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
if (decoder != null) {
|
||||
// Clean-up the previous encoder if not cleaned up correctly.
|
||||
if (decoder.finish()) {
|
||||
for (;;) {
|
||||
ByteBuf buf = decoder.readOutbound();
|
||||
if (buf == null) {
|
||||
break;
|
||||
}
|
||||
// Release the buffer
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
decoder = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||
|
||||
import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateDecoder.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CodecException;
|
||||
import io.netty.handler.codec.compression.ZlibCodecFactory;
|
||||
import io.netty.handler.codec.compression.ZlibWrapper;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.ContinuationWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtensionEncoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Deflate implementation of a payload compressor for
|
||||
* <tt>io.netty.handler.codec.http.websocketx.WebSocketFrame</tt>.
|
||||
*/
|
||||
abstract class DeflateEncoder extends WebSocketExtensionEncoder {
|
||||
|
||||
private final int compressionLevel;
|
||||
private final int windowSize;
|
||||
private final boolean noContext;
|
||||
|
||||
private EmbeddedChannel encoder;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param compressionLevel compression level of the compressor.
|
||||
* @param windowSize maximum size of the window compressor buffer.
|
||||
* @param noContext true to disable context takeover.
|
||||
*/
|
||||
public DeflateEncoder(int compressionLevel, int windowSize, boolean noContext) {
|
||||
this.compressionLevel = compressionLevel;
|
||||
this.windowSize = windowSize;
|
||||
this.noContext = noContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param msg the current frame.
|
||||
* @return the rsv bits to set in the compressed frame.
|
||||
*/
|
||||
protected abstract int rsv(WebSocketFrame msg);
|
||||
|
||||
/**
|
||||
* @param msg the current frame.
|
||||
* @return true if compressed payload tail needs to be removed.
|
||||
*/
|
||||
protected abstract boolean removeFrameTail(WebSocketFrame msg);
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, WebSocketFrame msg,
|
||||
List<Object> out) throws Exception {
|
||||
if (encoder == null) {
|
||||
encoder = new EmbeddedChannel(ZlibCodecFactory.newZlibEncoder(
|
||||
ZlibWrapper.NONE, compressionLevel, windowSize, 8));
|
||||
}
|
||||
|
||||
encoder.writeOutbound(msg.content().retain());
|
||||
|
||||
CompositeByteBuf fullCompressedContent = ctx.alloc().compositeBuffer();
|
||||
for (;;) {
|
||||
ByteBuf partCompressedContent = encoder.readOutbound();
|
||||
if (partCompressedContent == null) {
|
||||
break;
|
||||
}
|
||||
if (!partCompressedContent.isReadable()) {
|
||||
partCompressedContent.release();
|
||||
continue;
|
||||
}
|
||||
fullCompressedContent.addComponent(partCompressedContent);
|
||||
fullCompressedContent.writerIndex(fullCompressedContent.writerIndex() +
|
||||
partCompressedContent.readableBytes());
|
||||
}
|
||||
if (fullCompressedContent.numComponents() <= 0) {
|
||||
fullCompressedContent.release();
|
||||
throw new CodecException("cannot read compressed buffer");
|
||||
}
|
||||
|
||||
if (msg.isFinalFragment() && noContext) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
ByteBuf compressedContent;
|
||||
if (removeFrameTail(msg)) {
|
||||
int realLength = fullCompressedContent.readableBytes() - FRAME_TAIL.length;
|
||||
compressedContent = fullCompressedContent.slice(0, realLength);
|
||||
} else {
|
||||
compressedContent = fullCompressedContent;
|
||||
}
|
||||
|
||||
WebSocketFrame outMsg;
|
||||
if (msg instanceof TextWebSocketFrame) {
|
||||
outMsg = new TextWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
|
||||
} else if (msg instanceof BinaryWebSocketFrame) {
|
||||
outMsg = new BinaryWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
|
||||
} else if (msg instanceof ContinuationWebSocketFrame) {
|
||||
outMsg = new ContinuationWebSocketFrame(msg.isFinalFragment(), rsv(msg), compressedContent);
|
||||
} else {
|
||||
throw new CodecException("unexpected frame type: " + msg.getClass().getName());
|
||||
}
|
||||
out.add(outMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
cleanup();
|
||||
super.handlerRemoved(ctx);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
if (encoder != null) {
|
||||
// Clean-up the previous encoder if not cleaned up correctly.
|
||||
if (encoder.finish()) {
|
||||
for (;;) {
|
||||
ByteBuf buf = encoder.readOutbound();
|
||||
if (buf == null) {
|
||||
break;
|
||||
}
|
||||
// Release the buffer
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
encoder = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -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.parseInt(parameter.getValue());
|
||||
} else {
|
||||
succeed = false;
|
||||
}
|
||||
} else if (SERVER_MAX_WINDOW.equalsIgnoreCase(parameter.getKey())) {
|
||||
// acknowledged server_window_size_bits
|
||||
serverWindowSize = Integer.parseInt(parameter.getValue());
|
||||
if (clientWindowSize > MAX_WINDOW_SIZE || clientWindowSize < MIN_WINDOW_SIZE) {
|
||||
succeed = false;
|
||||
}
|
||||
} else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
|
||||
// allowed client_no_context_takeover
|
||||
if (allowClientNoContext) {
|
||||
clientNoContext = true;
|
||||
} else {
|
||||
succeed = false;
|
||||
}
|
||||
} else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
|
||||
// acknowledged server_no_context_takeover
|
||||
if (requestedServerNoContext) {
|
||||
serverNoContext = true;
|
||||
} else {
|
||||
succeed = false;
|
||||
}
|
||||
} else {
|
||||
// unknown parameter
|
||||
succeed = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((requestedServerNoContext && !serverNoContext) ||
|
||||
requestedServerWindowSize != serverWindowSize) {
|
||||
succeed = false;
|
||||
}
|
||||
|
||||
if (succeed) {
|
||||
return new PermessageDeflateExtension(serverNoContext, serverWindowSize,
|
||||
clientNoContext, clientWindowSize);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final class PermessageDeflateExtension implements WebSocketClientExtension {
|
||||
|
||||
private final boolean serverNoContext;
|
||||
private final int serverWindowSize;
|
||||
private final boolean clientNoContext;
|
||||
private final int clientWindowSize;
|
||||
|
||||
@Override
|
||||
public int rsv() {
|
||||
return RSV1;
|
||||
}
|
||||
|
||||
public PermessageDeflateExtension(boolean serverNoContext, int serverWindowSize,
|
||||
boolean clientNoContext, int clientWindowSize) {
|
||||
this.serverNoContext = serverNoContext;
|
||||
this.serverWindowSize = serverWindowSize;
|
||||
this.clientNoContext = clientNoContext;
|
||||
this.clientWindowSize = clientWindowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketExtensionEncoder newExtensionEncoder() {
|
||||
return new PerMessageDeflateEncoder(compressionLevel, serverWindowSize, serverNoContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketExtensionDecoder newExtensionDecoder() {
|
||||
return new PerMessageDeflateDecoder(clientNoContext);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.parseInt(parameter.getValue());
|
||||
if (serverWindowSize > MAX_WINDOW_SIZE || serverWindowSize < MIN_WINDOW_SIZE) {
|
||||
deflateEnabled = false;
|
||||
}
|
||||
} else {
|
||||
deflateEnabled = false;
|
||||
}
|
||||
} else if (CLIENT_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
|
||||
// use preferred clientNoContext because client is compatible with customization
|
||||
clientNoContext = preferredClientNoContext;
|
||||
} else if (SERVER_NO_CONTEXT.equalsIgnoreCase(parameter.getKey())) {
|
||||
// use server no context if allowed
|
||||
if (allowServerNoContext) {
|
||||
serverNoContext = true;
|
||||
} else {
|
||||
deflateEnabled = false;
|
||||
}
|
||||
} else {
|
||||
// unknown parameter
|
||||
deflateEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (deflateEnabled) {
|
||||
return new PermessageDeflateExtension(compressionLevel, serverNoContext,
|
||||
serverWindowSize, clientNoContext, clientWindowSize);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class PermessageDeflateExtension implements WebSocketServerExtension {
|
||||
|
||||
private final int compressionLevel;
|
||||
private final boolean serverNoContext;
|
||||
private final int serverWindowSize;
|
||||
private final boolean clientNoContext;
|
||||
private final int clientWindowSize;
|
||||
|
||||
public PermessageDeflateExtension(int compressionLevel, boolean serverNoContext,
|
||||
int serverWindowSize, boolean clientNoContext, int clientWindowSize) {
|
||||
this.compressionLevel = compressionLevel;
|
||||
this.serverNoContext = serverNoContext;
|
||||
this.serverWindowSize = serverWindowSize;
|
||||
this.clientNoContext = clientNoContext;
|
||||
this.clientWindowSize = clientWindowSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int rsv() {
|
||||
return RSV1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketExtensionEncoder newExtensionEncoder() {
|
||||
return new PerMessageDeflateEncoder(compressionLevel, clientWindowSize, clientNoContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketExtensionDecoder newExtensionDecoder() {
|
||||
return new PerMessageDeflateDecoder(serverNoContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocketExtensionData newReponseData() {
|
||||
HashMap<String, String> parameters = new HashMap<String, String>(4);
|
||||
if (serverNoContext) {
|
||||
parameters.put(SERVER_NO_CONTEXT, null);
|
||||
}
|
||||
if (clientNoContext) {
|
||||
parameters.put(CLIENT_NO_CONTEXT, null);
|
||||
}
|
||||
if (serverWindowSize != MAX_WINDOW_SIZE) {
|
||||
parameters.put(SERVER_MAX_WINDOW, Integer.toString(serverWindowSize));
|
||||
}
|
||||
if (clientWindowSize != MAX_WINDOW_SIZE) {
|
||||
parameters.put(CLIENT_MAX_WINDOW, Integer.toString(clientWindowSize));
|
||||
}
|
||||
return new WebSocketExtensionData(PERMESSAGE_DEFLATE_EXTENSION, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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,123 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx.extensions.compression;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.compression.ZlibCodecFactory;
|
||||
import io.netty.handler.codec.compression.ZlibWrapper;
|
||||
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.WebSocketExtension;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PerFrameDeflateDecoderTest {
|
||||
|
||||
private static final Random random = new Random();
|
||||
|
||||
@Test
|
||||
public void testCompressedFrame() {
|
||||
EmbeddedChannel encoderChannel = new EmbeddedChannel(
|
||||
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.NONE, 9, 15, 8));
|
||||
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
|
||||
|
||||
// initialize
|
||||
byte[] payload = new byte[300];
|
||||
random.nextBytes(payload);
|
||||
|
||||
encoderChannel.writeOutbound(Unpooled.wrappedBuffer(payload));
|
||||
ByteBuf compressedPayload = encoderChannel.readOutbound();
|
||||
|
||||
BinaryWebSocketFrame compressedFrame = new BinaryWebSocketFrame(true,
|
||||
WebSocketExtension.RSV1 | WebSocketExtension.RSV3,
|
||||
compressedPayload.slice(0, compressedPayload.readableBytes() - 4));
|
||||
|
||||
// execute
|
||||
decoderChannel.writeInbound(compressedFrame);
|
||||
BinaryWebSocketFrame uncompressedFrame = decoderChannel.readInbound();
|
||||
|
||||
// test
|
||||
assertNotNull(uncompressedFrame);
|
||||
assertNotNull(uncompressedFrame.content());
|
||||
assertTrue(uncompressedFrame instanceof BinaryWebSocketFrame);
|
||||
assertEquals(WebSocketExtension.RSV3, uncompressedFrame.rsv());
|
||||
assertEquals(300, uncompressedFrame.content().readableBytes());
|
||||
|
||||
byte[] finalPayload = new byte[300];
|
||||
uncompressedFrame.content().readBytes(finalPayload);
|
||||
assertTrue(Arrays.equals(finalPayload, payload));
|
||||
uncompressedFrame.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalFrame() {
|
||||
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
|
||||
|
||||
// initialize
|
||||
byte[] payload = new byte[300];
|
||||
random.nextBytes(payload);
|
||||
|
||||
BinaryWebSocketFrame frame = new BinaryWebSocketFrame(true,
|
||||
WebSocketExtension.RSV3, Unpooled.wrappedBuffer(payload));
|
||||
|
||||
// execute
|
||||
decoderChannel.writeInbound(frame);
|
||||
BinaryWebSocketFrame newFrame = decoderChannel.readInbound();
|
||||
|
||||
// test
|
||||
assertNotNull(newFrame);
|
||||
assertNotNull(newFrame.content());
|
||||
assertTrue(newFrame instanceof BinaryWebSocketFrame);
|
||||
assertEquals(WebSocketExtension.RSV3, newFrame.rsv());
|
||||
assertEquals(300, newFrame.content().readableBytes());
|
||||
|
||||
byte[] finalPayload = new byte[300];
|
||||
newFrame.content().readBytes(finalPayload);
|
||||
assertTrue(Arrays.equals(finalPayload, payload));
|
||||
newFrame.release();
|
||||
}
|
||||
|
||||
// See https://github.com/netty/netty/issues/4348
|
||||
@Test
|
||||
public void testCompressedEmptyFrame() {
|
||||
EmbeddedChannel encoderChannel = new EmbeddedChannel(
|
||||
ZlibCodecFactory.newZlibEncoder(ZlibWrapper.NONE, 9, 15, 8));
|
||||
EmbeddedChannel decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
|
||||
|
||||
encoderChannel.writeOutbound(Unpooled.EMPTY_BUFFER);
|
||||
ByteBuf compressedPayload = encoderChannel.readOutbound();
|
||||
BinaryWebSocketFrame compressedFrame =
|
||||
new BinaryWebSocketFrame(true, WebSocketExtension.RSV1 | WebSocketExtension.RSV3, compressedPayload);
|
||||
|
||||
// execute
|
||||
decoderChannel.writeInbound(compressedFrame);
|
||||
BinaryWebSocketFrame uncompressedFrame = decoderChannel.readInbound();
|
||||
|
||||
// test
|
||||
assertNotNull(uncompressedFrame);
|
||||
assertNotNull(uncompressedFrame.content());
|
||||
assertTrue(uncompressedFrame instanceof BinaryWebSocketFrame);
|
||||
assertEquals(WebSocketExtension.RSV3, uncompressedFrame.rsv());
|
||||
assertEquals(0, uncompressedFrame.content().readableBytes());
|
||||
uncompressedFrame.release();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -66,7 +66,7 @@ public final class ZlibCodecFactory {
|
||||
|
||||
public static ZlibEncoder newZlibEncoder(ZlibWrapper wrapper, int compressionLevel, int windowBits, int memLevel) {
|
||||
if (PlatformDependent.javaVersion() < 7 || noJdkZlibEncoder ||
|
||||
windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
|
||||
windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
|
||||
return new JZlibEncoder(wrapper, compressionLevel, windowBits, memLevel);
|
||||
} else {
|
||||
return new JdkZlibEncoder(wrapper, compressionLevel);
|
||||
@ -91,7 +91,7 @@ public final class ZlibCodecFactory {
|
||||
|
||||
public static ZlibEncoder newZlibEncoder(int compressionLevel, int windowBits, int memLevel, byte[] dictionary) {
|
||||
if (PlatformDependent.javaVersion() < 7 || noJdkZlibEncoder ||
|
||||
windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
|
||||
windowBits != DEFAULT_JDK_WINDOW_SIZE || memLevel != DEFAULT_JDK_MEM_LEVEL) {
|
||||
return new JZlibEncoder(compressionLevel, windowBits, memLevel, dictionary);
|
||||
} else {
|
||||
return new JdkZlibEncoder(compressionLevel, dictionary);
|
||||
|
@ -33,6 +33,7 @@ import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
@ -113,6 +114,7 @@ public final class WebSocketClient {
|
||||
p.addLast(
|
||||
new HttpClientCodec(),
|
||||
new HttpObjectAggregator(8192),
|
||||
new WebSocketClientCompressionHandler(),
|
||||
handler);
|
||||
}
|
||||
});
|
||||
|
@ -20,6 +20,7 @@ import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
|
||||
/**
|
||||
@ -40,6 +41,7 @@ public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel
|
||||
}
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new HttpObjectAggregator(65536));
|
||||
pipeline.addLast(new WebSocketServerCompressionHandler());
|
||||
pipeline.addLast(new WebSocketServerHandler());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user