WebSocket is closed without an error on protocol violations (#9116)
Motivation: Incorrect WebSockets closure affects our production system. Enforced 'close socket on any protocol violation' prevents our custom termination sequence from execution. Huge number of parameters is a nightmare both in usage and in support (decoders configuration). Modification: - Fix violations handling - send proper response codes. - Fix for messages leak. - Introduce decoder's option to disable default behavior (send close frame) on protocol violations. - Encapsulate WebSocket response codes - WebSocketCloseStatus. - Encapsulate decoder's configuration into a separate class - WebSocketDecoderConfig. Result: Fixes #8295.
This commit is contained in:
parent
aaf5ec1fbb
commit
d9db218291
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -33,7 +33,31 @@ public class CloseWebSocketFrame extends WebSocketFrame {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty close frame with closing getStatus code and reason text
|
||||
* Creates a new empty close frame with closing status code and reason text
|
||||
*
|
||||
* @param status
|
||||
* Status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For
|
||||
* example, <tt>1000</tt> indicates normal closure.
|
||||
*/
|
||||
public CloseWebSocketFrame(WebSocketCloseStatus status) {
|
||||
this(status.code(), status.reasonText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty close frame with closing status code and reason text
|
||||
*
|
||||
* @param status
|
||||
* Status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For
|
||||
* example, <tt>1000</tt> indicates normal closure.
|
||||
* @param reasonText
|
||||
* Reason text. Set to null if no text.
|
||||
*/
|
||||
public CloseWebSocketFrame(WebSocketCloseStatus status, String reasonText) {
|
||||
this(status.code(), reasonText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty close frame with closing status code and reason text
|
||||
*
|
||||
* @param statusCode
|
||||
* Integer status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For
|
||||
@ -46,7 +70,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new close frame with no losing getStatus code and no reason text
|
||||
* Creates a new close frame with no losing status code and no reason text
|
||||
*
|
||||
* @param finalFragment
|
||||
* flag indicating if this frame is the final fragment
|
||||
@ -105,7 +129,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
|
||||
|
||||
/**
|
||||
* Returns the closing status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. If
|
||||
* a getStatus code is set, -1 is returned.
|
||||
* a status code is set, -1 is returned.
|
||||
*/
|
||||
public int statusCode() {
|
||||
ByteBuf binaryData = content();
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2019 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.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
|
||||
/**
|
||||
* An {@link DecoderException} which is thrown when the received {@link WebSocketFrame} data could not be decoded by
|
||||
* an inbound handler.
|
||||
*/
|
||||
public final class CorruptedWebSocketFrameException extends CorruptedFrameException {
|
||||
|
||||
private static final long serialVersionUID = 3918055132492988338L;
|
||||
|
||||
private final WebSocketCloseStatus closeStatus;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public CorruptedWebSocketFrameException() {
|
||||
this(WebSocketCloseStatus.PROTOCOL_ERROR, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public CorruptedWebSocketFrameException(WebSocketCloseStatus status, String message, Throwable cause) {
|
||||
super(message == null ? status.reasonText() : message, cause);
|
||||
closeStatus = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public CorruptedWebSocketFrameException(WebSocketCloseStatus status, String message) {
|
||||
this(status, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
public CorruptedWebSocketFrameException(WebSocketCloseStatus status, Throwable cause) {
|
||||
this(status, null, cause);
|
||||
}
|
||||
|
||||
public WebSocketCloseStatus closeStatus() {
|
||||
return closeStatus;
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -36,7 +36,6 @@
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.util.ByteProcessor;
|
||||
|
||||
/**
|
||||
@ -79,7 +78,8 @@ final class Utf8Validator implements ByteProcessor {
|
||||
codep = 0;
|
||||
if (state != UTF8_ACCEPT) {
|
||||
state = UTF8_ACCEPT;
|
||||
throw new CorruptedFrameException("bytes are not UTF-8");
|
||||
throw new CorruptedWebSocketFrameException(
|
||||
WebSocketCloseStatus.INVALID_PAYLOAD_DATA, "bytes are not UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +93,8 @@ final class Utf8Validator implements ByteProcessor {
|
||||
|
||||
if (state == UTF8_REJECT) {
|
||||
checking = false;
|
||||
throw new CorruptedFrameException("bytes are not UTF-8");
|
||||
throw new CorruptedWebSocketFrameException(
|
||||
WebSocketCloseStatus.INVALID_PAYLOAD_DATA, "bytes are not UTF-8");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -19,6 +19,7 @@ import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ReplayingDecoder;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -52,6 +53,17 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<Void> implements W
|
||||
this.maxFrameSize = maxFrameSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client
|
||||
* sends a frame size larger than {@code maxFrameSize}, the channel will be closed.
|
||||
*
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public WebSocket00FrameDecoder(WebSocketDecoderConfig decoderConfig) {
|
||||
this.maxFrameSize = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig").maxFramePayloadLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
// Discard all data received if closing handshake was received before.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -71,7 +71,11 @@ public class WebSocket07FrameDecoder extends WebSocket08FrameDecoder {
|
||||
* helps check for denial of services attacks.
|
||||
*/
|
||||
public WebSocket07FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength) {
|
||||
this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false);
|
||||
this(WebSocketDecoderConfig.newBuilder()
|
||||
.expectMaskedFrames(expectMaskedFrames)
|
||||
.allowExtensions(allowExtensions)
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,6 +95,21 @@ public class WebSocket07FrameDecoder extends WebSocket08FrameDecoder {
|
||||
*/
|
||||
public WebSocket07FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength,
|
||||
boolean allowMaskMismatch) {
|
||||
super(expectMaskedFrames, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
|
||||
this(WebSocketDecoderConfig.newBuilder()
|
||||
.expectMaskedFrames(expectMaskedFrames)
|
||||
.allowExtensions(allowExtensions)
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.allowMaskMismatch(allowMaskMismatch)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public WebSocket07FrameDecoder(WebSocketDecoderConfig decoderConfig) {
|
||||
super(decoderConfig);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -58,8 +58,8 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
@ -93,10 +93,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
private static final byte OPCODE_PING = 0x9;
|
||||
private static final byte OPCODE_PONG = 0xA;
|
||||
|
||||
private final long maxFramePayloadLength;
|
||||
private final boolean allowExtensions;
|
||||
private final boolean expectMaskedFrames;
|
||||
private final boolean allowMaskMismatch;
|
||||
private final WebSocketDecoderConfig config;
|
||||
|
||||
private int fragmentedFramesCount;
|
||||
private boolean frameFinalFlag;
|
||||
@ -142,10 +139,22 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
*/
|
||||
public WebSocket08FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength,
|
||||
boolean allowMaskMismatch) {
|
||||
this.expectMaskedFrames = expectMaskedFrames;
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
this.allowExtensions = allowExtensions;
|
||||
this.maxFramePayloadLength = maxFramePayloadLength;
|
||||
this(WebSocketDecoderConfig.newBuilder()
|
||||
.expectMaskedFrames(expectMaskedFrames)
|
||||
.allowExtensions(allowExtensions)
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.allowMaskMismatch(allowMaskMismatch)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public WebSocket08FrameDecoder(WebSocketDecoderConfig decoderConfig) {
|
||||
this.config = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -184,13 +193,13 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
frameMasked = (b & 0x80) != 0;
|
||||
framePayloadLen1 = b & 0x7F;
|
||||
|
||||
if (frameRsv != 0 && !allowExtensions) {
|
||||
protocolViolation(ctx, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
|
||||
if (frameRsv != 0 && !config.allowExtensions()) {
|
||||
protocolViolation(ctx, in, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allowMaskMismatch && expectMaskedFrames != frameMasked) {
|
||||
protocolViolation(ctx, "received a frame that is not masked as expected");
|
||||
if (!config.allowMaskMismatch() && config.expectMaskedFrames() != frameMasked) {
|
||||
protocolViolation(ctx, in, "received a frame that is not masked as expected");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -198,20 +207,20 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
|
||||
// control frames MUST NOT be fragmented
|
||||
if (!frameFinalFlag) {
|
||||
protocolViolation(ctx, "fragmented control frame");
|
||||
protocolViolation(ctx, in, "fragmented control frame");
|
||||
return;
|
||||
}
|
||||
|
||||
// control frames MUST have payload 125 octets or less
|
||||
if (framePayloadLen1 > 125) {
|
||||
protocolViolation(ctx, "control frame with payload length > 125 octets");
|
||||
protocolViolation(ctx, in, "control frame with payload length > 125 octets");
|
||||
return;
|
||||
}
|
||||
|
||||
// check for reserved control frame opcodes
|
||||
if (!(frameOpcode == OPCODE_CLOSE || frameOpcode == OPCODE_PING
|
||||
|| frameOpcode == OPCODE_PONG)) {
|
||||
protocolViolation(ctx, "control frame using reserved opcode " + frameOpcode);
|
||||
protocolViolation(ctx, in, "control frame using reserved opcode " + frameOpcode);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -219,26 +228,26 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
// body MUST be a 2-byte unsigned integer (in network byte
|
||||
// order) representing a getStatus code
|
||||
if (frameOpcode == 8 && framePayloadLen1 == 1) {
|
||||
protocolViolation(ctx, "received close control frame with payload len 1");
|
||||
protocolViolation(ctx, in, "received close control frame with payload len 1");
|
||||
return;
|
||||
}
|
||||
} else { // data frame
|
||||
// check for reserved data frame opcodes
|
||||
if (!(frameOpcode == OPCODE_CONT || frameOpcode == OPCODE_TEXT
|
||||
|| frameOpcode == OPCODE_BINARY)) {
|
||||
protocolViolation(ctx, "data frame using reserved opcode " + frameOpcode);
|
||||
protocolViolation(ctx, in, "data frame using reserved opcode " + frameOpcode);
|
||||
return;
|
||||
}
|
||||
|
||||
// check opcode vs message fragmentation state 1/2
|
||||
if (fragmentedFramesCount == 0 && frameOpcode == OPCODE_CONT) {
|
||||
protocolViolation(ctx, "received continuation data frame outside fragmented message");
|
||||
protocolViolation(ctx, in, "received continuation data frame outside fragmented message");
|
||||
return;
|
||||
}
|
||||
|
||||
// check opcode vs message fragmentation state 2/2
|
||||
if (fragmentedFramesCount != 0 && frameOpcode != OPCODE_CONT && frameOpcode != OPCODE_PING) {
|
||||
protocolViolation(ctx,
|
||||
protocolViolation(ctx, in,
|
||||
"received non-continuation data frame while inside fragmented message");
|
||||
return;
|
||||
}
|
||||
@ -254,7 +263,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
}
|
||||
framePayloadLength = in.readUnsignedShort();
|
||||
if (framePayloadLength < 126) {
|
||||
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
|
||||
protocolViolation(ctx, in, "invalid data frame length (not using minimal length encoding)");
|
||||
return;
|
||||
}
|
||||
} else if (framePayloadLen1 == 127) {
|
||||
@ -266,15 +275,16 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
// just check if it's negative?
|
||||
|
||||
if (framePayloadLength < 65536) {
|
||||
protocolViolation(ctx, "invalid data frame length (not using minimal length encoding)");
|
||||
protocolViolation(ctx, in, "invalid data frame length (not using minimal length encoding)");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
framePayloadLength = framePayloadLen1;
|
||||
}
|
||||
|
||||
if (framePayloadLength > maxFramePayloadLength) {
|
||||
protocolViolation(ctx, "Max frame length of " + maxFramePayloadLength + " has been exceeded.");
|
||||
if (framePayloadLength > config.maxFramePayloadLength()) {
|
||||
protocolViolation(ctx, in, WebSocketCloseStatus.MESSAGE_TOO_BIG,
|
||||
"Max frame length of " + config.maxFramePayloadLength() + " has been exceeded.");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -408,18 +418,33 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
}
|
||||
}
|
||||
|
||||
private void protocolViolation(ChannelHandlerContext ctx, String reason) {
|
||||
protocolViolation(ctx, new CorruptedFrameException(reason));
|
||||
private void protocolViolation(ChannelHandlerContext ctx, ByteBuf in, String reason) {
|
||||
protocolViolation(ctx, in, WebSocketCloseStatus.PROTOCOL_ERROR, reason);
|
||||
}
|
||||
|
||||
private void protocolViolation(ChannelHandlerContext ctx, CorruptedFrameException ex) {
|
||||
private void protocolViolation(ChannelHandlerContext ctx, ByteBuf in, WebSocketCloseStatus status, String reason) {
|
||||
protocolViolation(ctx, in, new CorruptedWebSocketFrameException(status, reason));
|
||||
}
|
||||
|
||||
private void protocolViolation(ChannelHandlerContext ctx, ByteBuf in, CorruptedWebSocketFrameException ex) {
|
||||
state = State.CORRUPT;
|
||||
if (ctx.channel().isActive()) {
|
||||
int readableBytes = in.readableBytes();
|
||||
if (readableBytes > 0) {
|
||||
// Fix for memory leak, caused by ByteToMessageDecoder#channelRead:
|
||||
// buffer 'cumulation' is released ONLY when no more readable bytes available.
|
||||
in.skipBytes(readableBytes);
|
||||
}
|
||||
if (ctx.channel().isActive() && config.closeOnProtocolViolation()) {
|
||||
Object closeMessage;
|
||||
if (receivedClosingHandshake) {
|
||||
closeMessage = Unpooled.EMPTY_BUFFER;
|
||||
} else {
|
||||
closeMessage = new CloseWebSocketFrame(1002, null);
|
||||
WebSocketCloseStatus closeStatus = ex.closeStatus();
|
||||
String reasonText = ex.getMessage();
|
||||
if (reasonText == null) {
|
||||
reasonText = closeStatus.reasonText();
|
||||
}
|
||||
closeMessage = new CloseWebSocketFrame(closeStatus, reasonText);
|
||||
}
|
||||
ctx.writeAndFlush(closeMessage).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
@ -441,7 +466,7 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
return;
|
||||
}
|
||||
if (buffer.readableBytes() == 1) {
|
||||
protocolViolation(ctx, "Invalid close frame body");
|
||||
protocolViolation(ctx, buffer, WebSocketCloseStatus.INVALID_PAYLOAD_DATA, "Invalid close frame body");
|
||||
}
|
||||
|
||||
// Save reader index
|
||||
@ -450,17 +475,16 @@ public class WebSocket08FrameDecoder extends ByteToMessageDecoder
|
||||
|
||||
// Must have 2 byte integer within the valid range
|
||||
int statusCode = buffer.readShort();
|
||||
if (statusCode >= 0 && statusCode <= 999 || statusCode >= 1004 && statusCode <= 1006
|
||||
|| statusCode >= 1015 && statusCode <= 2999) {
|
||||
protocolViolation(ctx, "Invalid close frame getStatus code: " + statusCode);
|
||||
if (!WebSocketCloseStatus.isValidStatusCode(statusCode)) {
|
||||
protocolViolation(ctx, buffer, "Invalid close frame getStatus code: " + statusCode);
|
||||
}
|
||||
|
||||
// May have UTF-8 message
|
||||
if (buffer.isReadable()) {
|
||||
try {
|
||||
new Utf8Validator().check(buffer);
|
||||
} catch (CorruptedFrameException ex) {
|
||||
protocolViolation(ctx, ex);
|
||||
} catch (CorruptedWebSocketFrameException ex) {
|
||||
protocolViolation(ctx, buffer, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -91,6 +91,21 @@ public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder {
|
||||
*/
|
||||
public WebSocket13FrameDecoder(boolean expectMaskedFrames, boolean allowExtensions, int maxFramePayloadLength,
|
||||
boolean allowMaskMismatch) {
|
||||
super(expectMaskedFrames, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
|
||||
this(WebSocketDecoderConfig.newBuilder()
|
||||
.expectMaskedFrames(expectMaskedFrames)
|
||||
.allowExtensions(allowExtensions)
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.allowMaskMismatch(allowMaskMismatch)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public WebSocket13FrameDecoder(WebSocketDecoderConfig decoderConfig) {
|
||||
super(decoderConfig);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2019 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 static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
/**
|
||||
* WebSocket status codes specified in RFC-6455.
|
||||
* <pre>
|
||||
*
|
||||
* RFC-6455 The WebSocket Protocol, December 2011:
|
||||
* <a href="https://tools.ietf.org/html/rfc6455#section-7.4.1"
|
||||
* >https://tools.ietf.org/html/rfc6455#section-7.4.1</a>
|
||||
*
|
||||
* WebSocket Protocol Registries, April 2019:
|
||||
* <a href="https://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number"
|
||||
* >https://www.iana.org/assignments/websocket/websocket.xhtml</a>
|
||||
*
|
||||
* 7.4.1. Defined Status Codes
|
||||
*
|
||||
* Endpoints MAY use the following pre-defined status codes when sending
|
||||
* a Close frame.
|
||||
*
|
||||
* 1000
|
||||
*
|
||||
* 1000 indicates a normal closure, meaning that the purpose for
|
||||
* which the connection was established has been fulfilled.
|
||||
*
|
||||
* 1001
|
||||
*
|
||||
* 1001 indicates that an endpoint is "going away", such as a server
|
||||
* going down or a browser having navigated away from a page.
|
||||
*
|
||||
* 1002
|
||||
*
|
||||
* 1002 indicates that an endpoint is terminating the connection due
|
||||
* to a protocol error.
|
||||
*
|
||||
* 1003
|
||||
*
|
||||
* 1003 indicates that an endpoint is terminating the connection
|
||||
* because it has received a type of data it cannot accept (e.g., an
|
||||
* endpoint that understands only text data MAY send this if it
|
||||
* receives a binary message).
|
||||
*
|
||||
* 1004
|
||||
*
|
||||
* Reserved. The specific meaning might be defined in the future.
|
||||
*
|
||||
* 1005
|
||||
*
|
||||
* 1005 is a reserved value and MUST NOT be set as a status code in a
|
||||
* Close control frame by an endpoint. It is designated for use in
|
||||
* applications expecting a status code to indicate that no status
|
||||
* code was actually present.
|
||||
*
|
||||
* 1006
|
||||
*
|
||||
* 1006 is a reserved value and MUST NOT be set as a status code in a
|
||||
* Close control frame by an endpoint. It is designated for use in
|
||||
* applications expecting a status code to indicate that the
|
||||
* connection was closed abnormally, e.g., without sending or
|
||||
* receiving a Close control frame.
|
||||
*
|
||||
* 1007
|
||||
*
|
||||
* 1007 indicates that an endpoint is terminating the connection
|
||||
* because it has received data within a message that was not
|
||||
* consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
|
||||
* data within a text message).
|
||||
*
|
||||
* 1008
|
||||
*
|
||||
* 1008 indicates that an endpoint is terminating the connection
|
||||
* because it has received a message that violates its policy. This
|
||||
* is a generic status code that can be returned when there is no
|
||||
* other more suitable status code (e.g., 1003 or 1009) or if there
|
||||
* is a need to hide specific details about the policy.
|
||||
*
|
||||
* 1009
|
||||
*
|
||||
* 1009 indicates that an endpoint is terminating the connection
|
||||
* because it has received a message that is too big for it to
|
||||
* process.
|
||||
*
|
||||
* 1010
|
||||
*
|
||||
* 1010 indicates that an endpoint (client) is terminating the
|
||||
* connection because it has expected the server to negotiate one or
|
||||
* more extension, but the server didn't return them in the response
|
||||
* message of the WebSocket handshake. The list of extensions that
|
||||
* are needed SHOULD appear in the /reason/ part of the Close frame.
|
||||
* Note that this status code is not used by the server, because it
|
||||
* can fail the WebSocket handshake instead.
|
||||
*
|
||||
* 1011
|
||||
*
|
||||
* 1011 indicates that a server is terminating the connection because
|
||||
* it encountered an unexpected condition that prevented it from
|
||||
* fulfilling the request.
|
||||
*
|
||||
* 1012 (IANA Registry, Non RFC-6455)
|
||||
*
|
||||
* 1012 indicates that the service is restarted. a client may reconnect,
|
||||
* and if it choses to do, should reconnect using a randomized delay
|
||||
* of 5 - 30 seconds.
|
||||
*
|
||||
* 1013 (IANA Registry, Non RFC-6455)
|
||||
*
|
||||
* 1013 indicates that the service is experiencing overload. a client
|
||||
* should only connect to a different IP (when there are multiple for the
|
||||
* target) or reconnect to the same IP upon user action.
|
||||
*
|
||||
* 1014 (IANA Registry, Non RFC-6455)
|
||||
*
|
||||
* The server was acting as a gateway or proxy and received an invalid
|
||||
* response from the upstream server. This is similar to 502 HTTP Status Code.
|
||||
*
|
||||
* 1015
|
||||
*
|
||||
* 1015 is a reserved value and MUST NOT be set as a status code in a
|
||||
* Close control frame by an endpoint. It is designated for use in
|
||||
* applications expecting a status code to indicate that the
|
||||
* connection was closed due to a failure to perform a TLS handshake
|
||||
* (e.g., the server certificate can't be verified).
|
||||
*
|
||||
*
|
||||
* 7.4.2. Reserved Status Code Ranges
|
||||
*
|
||||
* 0-999
|
||||
*
|
||||
* Status codes in the range 0-999 are not used.
|
||||
*
|
||||
* 1000-2999
|
||||
*
|
||||
* Status codes in the range 1000-2999 are reserved for definition by
|
||||
* this protocol, its future revisions, and extensions specified in a
|
||||
* permanent and readily available public specification.
|
||||
*
|
||||
* 3000-3999
|
||||
*
|
||||
* Status codes in the range 3000-3999 are reserved for use by
|
||||
* libraries, frameworks, and applications. These status codes are
|
||||
* registered directly with IANA. The interpretation of these codes
|
||||
* is undefined by this protocol.
|
||||
*
|
||||
* 4000-4999
|
||||
*
|
||||
* Status codes in the range 4000-4999 are reserved for private use
|
||||
* and thus can't be registered. Such codes can be used by prior
|
||||
* agreements between WebSocket applications. The interpretation of
|
||||
* these codes is undefined by this protocol.
|
||||
* </pre>
|
||||
* <p>
|
||||
* While {@link WebSocketCloseStatus} is enum-like structure, its instances should NOT be compared by reference.
|
||||
* Instead, either {@link #equals(Object)} should be used or direct comparison of {@link #code()} value.
|
||||
*/
|
||||
public final class WebSocketCloseStatus implements Comparable<WebSocketCloseStatus> {
|
||||
|
||||
public static final WebSocketCloseStatus NORMAL_CLOSURE =
|
||||
new WebSocketCloseStatus(1000, "Bye");
|
||||
|
||||
public static final WebSocketCloseStatus ENDPOINT_UNAVAILABLE =
|
||||
new WebSocketCloseStatus(1001, "Endpoint unavailable");
|
||||
|
||||
public static final WebSocketCloseStatus PROTOCOL_ERROR =
|
||||
new WebSocketCloseStatus(1002, "Protocol error");
|
||||
|
||||
public static final WebSocketCloseStatus INVALID_MESSAGE_TYPE =
|
||||
new WebSocketCloseStatus(1003, "Invalid message type");
|
||||
|
||||
public static final WebSocketCloseStatus INVALID_PAYLOAD_DATA =
|
||||
new WebSocketCloseStatus(1007, "Invalid payload data");
|
||||
|
||||
public static final WebSocketCloseStatus POLICY_VIOLATION =
|
||||
new WebSocketCloseStatus(1008, "Policy violation");
|
||||
|
||||
public static final WebSocketCloseStatus MESSAGE_TOO_BIG =
|
||||
new WebSocketCloseStatus(1009, "Message too big");
|
||||
|
||||
public static final WebSocketCloseStatus MANDATORY_EXTENSION =
|
||||
new WebSocketCloseStatus(1010, "Mandatory extension");
|
||||
|
||||
public static final WebSocketCloseStatus INTERNAL_SERVER_ERROR =
|
||||
new WebSocketCloseStatus(1011, "Internal server error");
|
||||
|
||||
public static final WebSocketCloseStatus SERVICE_RESTART =
|
||||
new WebSocketCloseStatus(1012, "Service Restart");
|
||||
|
||||
public static final WebSocketCloseStatus TRY_AGAIN_LATER =
|
||||
new WebSocketCloseStatus(1013, "Try Again Later");
|
||||
|
||||
public static final WebSocketCloseStatus BAD_GATEWAY =
|
||||
new WebSocketCloseStatus(1014, "Bad Gateway");
|
||||
|
||||
// 1004, 1005, 1006, 1015 are reserved and should never be used by user
|
||||
//public static final WebSocketCloseStatus SPECIFIC_MEANING = register(1004, "...");
|
||||
//public static final WebSocketCloseStatus EMPTY = register(1005, "Empty");
|
||||
//public static final WebSocketCloseStatus ABNORMAL_CLOSURE = register(1006, "Abnormal closure");
|
||||
//public static final WebSocketCloseStatus TLS_HANDSHAKE_FAILED(1015, "TLS handshake failed");
|
||||
|
||||
private final int statusCode;
|
||||
private final String reasonText;
|
||||
private String text;
|
||||
|
||||
public WebSocketCloseStatus(int statusCode, String reasonText) {
|
||||
if (!isValidStatusCode(statusCode)) {
|
||||
throw new IllegalArgumentException(
|
||||
"WebSocket close status code does NOT comply with RFC-6455: " + statusCode);
|
||||
}
|
||||
this.statusCode = statusCode;
|
||||
this.reasonText = checkNotNull(reasonText, "reasonText");
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public String reasonText() {
|
||||
return reasonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Order of {@link WebSocketCloseStatus} only depends on {@link #code()}.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(WebSocketCloseStatus o) {
|
||||
return code() - o.code();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality of {@link WebSocketCloseStatus} only depends on {@link #code()}.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (null == o || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WebSocketCloseStatus that = (WebSocketCloseStatus) o;
|
||||
|
||||
return statusCode == that.statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String text = this.text;
|
||||
if (text == null) {
|
||||
// E.g.: "1000 Bye", "1009 Message too big"
|
||||
this.text = text = code() + " " + reasonText();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
public static boolean isValidStatusCode(int code) {
|
||||
return code < 0 ||
|
||||
1000 <= code && code <= 1003 ||
|
||||
1007 <= code && code <= 1014 ||
|
||||
3000 <= code;
|
||||
}
|
||||
|
||||
public static WebSocketCloseStatus valueOf(int code) {
|
||||
switch (code) {
|
||||
case 1000:
|
||||
return NORMAL_CLOSURE;
|
||||
case 1001:
|
||||
return ENDPOINT_UNAVAILABLE;
|
||||
case 1002:
|
||||
return PROTOCOL_ERROR;
|
||||
case 1003:
|
||||
return INVALID_MESSAGE_TYPE;
|
||||
case 1007:
|
||||
return INVALID_PAYLOAD_DATA;
|
||||
case 1008:
|
||||
return POLICY_VIOLATION;
|
||||
case 1009:
|
||||
return MESSAGE_TOO_BIG;
|
||||
case 1010:
|
||||
return MANDATORY_EXTENSION;
|
||||
case 1011:
|
||||
return INTERNAL_SERVER_ERROR;
|
||||
case 1012:
|
||||
return SERVICE_RESTART;
|
||||
case 1013:
|
||||
return TRY_AGAIN_LATER;
|
||||
case 1014:
|
||||
return BAD_GATEWAY;
|
||||
default:
|
||||
return new WebSocketCloseStatus(code, "Close status #" + code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2019 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.util.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public final class WebSocketDecoderConfig {
|
||||
|
||||
private final int maxFramePayloadLength;
|
||||
private final boolean expectMaskedFrames;
|
||||
private final boolean allowMaskMismatch;
|
||||
private final boolean allowExtensions;
|
||||
private final boolean closeOnProtocolViolation;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param maxFramePayloadLength
|
||||
* Maximum length of a frame's payload. Setting this to an appropriate value for you application
|
||||
* helps check for denial of services attacks.
|
||||
* @param expectMaskedFrames
|
||||
* Web socket servers must set this to true processed incoming masked payload. Client implementations
|
||||
* must set this to false.
|
||||
* @param allowMaskMismatch
|
||||
* Allows to loosen the masking requirement on received frames. When this is set to false then also
|
||||
* frames which are not masked properly according to the standard will still be accepted.
|
||||
* @param allowExtensions
|
||||
* Flag to allow reserved extension bits to be used or not
|
||||
* @param closeOnProtocolViolation
|
||||
* Flag to send close frame immediately on any protocol violation.ion.
|
||||
*/
|
||||
private WebSocketDecoderConfig(int maxFramePayloadLength, boolean expectMaskedFrames, boolean allowMaskMismatch,
|
||||
boolean allowExtensions, boolean closeOnProtocolViolation) {
|
||||
this.maxFramePayloadLength = maxFramePayloadLength;
|
||||
this.expectMaskedFrames = expectMaskedFrames;
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
this.allowExtensions = allowExtensions;
|
||||
this.closeOnProtocolViolation = closeOnProtocolViolation;
|
||||
}
|
||||
|
||||
public int maxFramePayloadLength() {
|
||||
return maxFramePayloadLength;
|
||||
}
|
||||
|
||||
public boolean expectMaskedFrames() {
|
||||
return expectMaskedFrames;
|
||||
}
|
||||
|
||||
public boolean allowMaskMismatch() {
|
||||
return allowMaskMismatch;
|
||||
}
|
||||
|
||||
public boolean allowExtensions() {
|
||||
return allowExtensions;
|
||||
}
|
||||
|
||||
public boolean closeOnProtocolViolation() {
|
||||
return closeOnProtocolViolation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WebSocketDecoderConfig" +
|
||||
" [maxFramePayloadLength=" + maxFramePayloadLength +
|
||||
", expectMaskedFrames=" + expectMaskedFrames +
|
||||
", allowMaskMismatch=" + allowMaskMismatch +
|
||||
", allowExtensions=" + allowExtensions +
|
||||
", closeOnProtocolViolation=" + closeOnProtocolViolation +
|
||||
"]";
|
||||
}
|
||||
|
||||
public Builder toBuilder() {
|
||||
return new Builder(this);
|
||||
}
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private int maxFramePayloadLength = 65536;
|
||||
private boolean expectMaskedFrames = true;
|
||||
private boolean allowMaskMismatch;
|
||||
private boolean allowExtensions;
|
||||
private boolean closeOnProtocolViolation = true;
|
||||
|
||||
private Builder() {
|
||||
/* No-op */
|
||||
}
|
||||
|
||||
private Builder(WebSocketDecoderConfig decoderConfig) {
|
||||
ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
|
||||
maxFramePayloadLength = decoderConfig.maxFramePayloadLength();
|
||||
expectMaskedFrames = decoderConfig.expectMaskedFrames();
|
||||
allowMaskMismatch = decoderConfig.allowMaskMismatch();
|
||||
allowExtensions = decoderConfig.allowExtensions();
|
||||
closeOnProtocolViolation = decoderConfig.closeOnProtocolViolation();
|
||||
}
|
||||
|
||||
public Builder maxFramePayloadLength(int maxFramePayloadLength) {
|
||||
this.maxFramePayloadLength = maxFramePayloadLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder expectMaskedFrames(boolean expectMaskedFrames) {
|
||||
this.expectMaskedFrames = expectMaskedFrames;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allowMaskMismatch(boolean allowMaskMismatch) {
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allowExtensions(boolean allowExtensions) {
|
||||
this.allowExtensions = allowExtensions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder closeOnProtocolViolation(boolean closeOnProtocolViolation) {
|
||||
this.closeOnProtocolViolation = closeOnProtocolViolation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WebSocketDecoderConfig build() {
|
||||
return new WebSocketDecoderConfig(
|
||||
maxFramePayloadLength, expectMaskedFrames, allowMaskMismatch,
|
||||
allowExtensions, closeOnProtocolViolation);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -35,7 +35,7 @@ import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.internal.EmptyArrays;
|
||||
import io.netty.util.internal.ThrowableUtil;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
|
||||
@ -56,7 +56,7 @@ public abstract class WebSocketServerHandshaker {
|
||||
|
||||
private final WebSocketVersion version;
|
||||
|
||||
private final int maxFramePayloadLength;
|
||||
private final WebSocketDecoderConfig decoderConfig;
|
||||
|
||||
private String selectedSubprotocol;
|
||||
|
||||
@ -81,6 +81,26 @@ public abstract class WebSocketServerHandshaker {
|
||||
protected WebSocketServerHandshaker(
|
||||
WebSocketVersion version, String uri, String subprotocols,
|
||||
int maxFramePayloadLength) {
|
||||
this(version, uri, subprotocols, WebSocketDecoderConfig.newBuilder()
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
* @param version
|
||||
* the protocol version
|
||||
* @param uri
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||
* sent to this URL.
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols. Null if sub protocols not supported.
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
protected WebSocketServerHandshaker(
|
||||
WebSocketVersion version, String uri, String subprotocols, WebSocketDecoderConfig decoderConfig) {
|
||||
this.version = version;
|
||||
this.uri = uri;
|
||||
if (subprotocols != null) {
|
||||
@ -92,7 +112,7 @@ public abstract class WebSocketServerHandshaker {
|
||||
} else {
|
||||
this.subprotocols = EmptyArrays.EMPTY_STRINGS;
|
||||
}
|
||||
this.maxFramePayloadLength = maxFramePayloadLength;
|
||||
this.decoderConfig = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,7 +144,16 @@ public abstract class WebSocketServerHandshaker {
|
||||
* @return The maximum length for a frame's payload
|
||||
*/
|
||||
public int maxFramePayloadLength() {
|
||||
return maxFramePayloadLength;
|
||||
return decoderConfig.maxFramePayloadLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this decoder configuration.
|
||||
*
|
||||
* @return This decoder configuration.
|
||||
*/
|
||||
public WebSocketDecoderConfig decoderConfig() {
|
||||
return decoderConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -60,7 +60,24 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
* reduce denial of service attacks using long data frames.
|
||||
*/
|
||||
public WebSocketServerHandshaker00(String webSocketURL, String subprotocols, int maxFramePayloadLength) {
|
||||
super(WebSocketVersion.V00, webSocketURL, subprotocols, maxFramePayloadLength);
|
||||
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
* @param webSocketURL
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||
* sent to this URL.
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public WebSocketServerHandshaker00(String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
|
||||
super(WebSocketVersion.V00, webSocketURL, subprotocols, decoderConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -189,7 +206,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
|
||||
@Override
|
||||
protected WebSocketFrameDecoder newWebsocketDecoder() {
|
||||
return new WebSocket00FrameDecoder(maxFramePayloadLength());
|
||||
return new WebSocket00FrameDecoder(decoderConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -37,9 +37,6 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
|
||||
|
||||
public static final String WEBSOCKET_07_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
private final boolean allowExtensions;
|
||||
private final boolean allowMaskMismatch;
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
@ -79,9 +76,21 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
|
||||
public WebSocketServerHandshaker07(
|
||||
String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength,
|
||||
boolean allowMaskMismatch) {
|
||||
super(WebSocketVersion.V07, webSocketURL, subprotocols, maxFramePayloadLength);
|
||||
this.allowExtensions = allowExtensions;
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
|
||||
.allowExtensions(allowExtensions)
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.allowMaskMismatch(allowMaskMismatch)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public WebSocketServerHandshaker07(String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
|
||||
super(WebSocketVersion.V07, webSocketURL, subprotocols, decoderConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,7 +168,7 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker {
|
||||
|
||||
@Override
|
||||
protected WebSocketFrameDecoder newWebsocketDecoder() {
|
||||
return new WebSocket07FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch);
|
||||
return new WebSocket07FrameDecoder(decoderConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -37,9 +37,6 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||
|
||||
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
private final boolean allowExtensions;
|
||||
private final boolean allowMaskMismatch;
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
@ -79,9 +76,27 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||
public WebSocketServerHandshaker08(
|
||||
String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength,
|
||||
boolean allowMaskMismatch) {
|
||||
super(WebSocketVersion.V08, webSocketURL, subprotocols, maxFramePayloadLength);
|
||||
this.allowExtensions = allowExtensions;
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
|
||||
.allowExtensions(allowExtensions)
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.allowMaskMismatch(allowMaskMismatch)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
* @param webSocketURL
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath".
|
||||
* Subsequent web socket frames will be sent to this URL.
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public WebSocketServerHandshaker08(
|
||||
String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
|
||||
super(WebSocketVersion.V08, webSocketURL, subprotocols, decoderConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,7 +173,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||
|
||||
@Override
|
||||
protected WebSocketFrameDecoder newWebsocketDecoder() {
|
||||
return new WebSocket08FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch);
|
||||
return new WebSocket08FrameDecoder(decoderConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -36,9 +36,6 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||
|
||||
public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
private final boolean allowExtensions;
|
||||
private final boolean allowMaskMismatch;
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
@ -78,9 +75,27 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||
public WebSocketServerHandshaker13(
|
||||
String webSocketURL, String subprotocols, boolean allowExtensions, int maxFramePayloadLength,
|
||||
boolean allowMaskMismatch) {
|
||||
super(WebSocketVersion.V13, webSocketURL, subprotocols, maxFramePayloadLength);
|
||||
this.allowExtensions = allowExtensions;
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
|
||||
.allowExtensions(allowExtensions)
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.allowMaskMismatch(allowMaskMismatch)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
* @param webSocketURL
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web
|
||||
* socket frames will be sent to this URL.
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols
|
||||
* @param decoderConfig
|
||||
* Frames decoder configuration.
|
||||
*/
|
||||
public WebSocketServerHandshaker13(
|
||||
String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
|
||||
super(WebSocketVersion.V13, webSocketURL, subprotocols, decoderConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,7 +171,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||
|
||||
@Override
|
||||
protected WebSocketFrameDecoder newWebsocketDecoder() {
|
||||
return new WebSocket13FrameDecoder(true, allowExtensions, maxFramePayloadLength(), allowMaskMismatch);
|
||||
return new WebSocket13FrameDecoder(decoderConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -25,6 +25,7 @@ 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.internal.ObjectUtil;
|
||||
|
||||
/**
|
||||
* Auto-detects the version of the Web Socket protocol in use and creates a new proper
|
||||
@ -36,11 +37,7 @@ public class WebSocketServerHandshakerFactory {
|
||||
|
||||
private final String subprotocols;
|
||||
|
||||
private final boolean allowExtensions;
|
||||
|
||||
private final int maxFramePayloadLength;
|
||||
|
||||
private final boolean allowMaskMismatch;
|
||||
private final WebSocketDecoderConfig decoderConfig;
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
@ -98,11 +95,29 @@ public class WebSocketServerHandshakerFactory {
|
||||
public WebSocketServerHandshakerFactory(
|
||||
String webSocketURL, String subprotocols, boolean allowExtensions,
|
||||
int maxFramePayloadLength, boolean allowMaskMismatch) {
|
||||
this(webSocketURL, subprotocols, WebSocketDecoderConfig.newBuilder()
|
||||
.allowExtensions(allowExtensions)
|
||||
.maxFramePayloadLength(maxFramePayloadLength)
|
||||
.allowMaskMismatch(allowMaskMismatch)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
*
|
||||
* @param webSocketURL
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath".
|
||||
* Subsequent web socket frames will be sent to this URL.
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols. Null if sub protocols not supported.
|
||||
* @param decoderConfig
|
||||
* Frames decoder options.
|
||||
*/
|
||||
public WebSocketServerHandshakerFactory(
|
||||
String webSocketURL, String subprotocols, WebSocketDecoderConfig decoderConfig) {
|
||||
this.webSocketURL = webSocketURL;
|
||||
this.subprotocols = subprotocols;
|
||||
this.allowExtensions = allowExtensions;
|
||||
this.maxFramePayloadLength = maxFramePayloadLength;
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
this.decoderConfig = ObjectUtil.checkNotNull(decoderConfig, "decoderConfig");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,21 +133,21 @@ public class WebSocketServerHandshakerFactory {
|
||||
if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
|
||||
// Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
|
||||
return new WebSocketServerHandshaker13(
|
||||
webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
|
||||
webSocketURL, subprotocols, decoderConfig);
|
||||
} else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
|
||||
// Version 8 of the wire protocol - version 10 of the draft hybi specification.
|
||||
return new WebSocketServerHandshaker08(
|
||||
webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
|
||||
webSocketURL, subprotocols, decoderConfig);
|
||||
} else if (version.equals(WebSocketVersion.V07.toHttpHeaderValue())) {
|
||||
// Version 8 of the wire protocol - version 07 of the draft hybi specification.
|
||||
return new WebSocketServerHandshaker07(
|
||||
webSocketURL, subprotocols, allowExtensions, maxFramePayloadLength, allowMaskMismatch);
|
||||
webSocketURL, subprotocols, decoderConfig);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Assume version 00 where version header was not specified
|
||||
return new WebSocketServerHandshaker00(webSocketURL, subprotocols, maxFramePayloadLength);
|
||||
return new WebSocketServerHandshaker00(webSocketURL, subprotocols, decoderConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -106,11 +106,9 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
|
||||
|
||||
private final String websocketPath;
|
||||
private final String subprotocols;
|
||||
private final boolean allowExtensions;
|
||||
private final int maxFramePayloadLength;
|
||||
private final boolean allowMaskMismatch;
|
||||
private final boolean checkStartsWith;
|
||||
private final long handshakeTimeoutMillis;
|
||||
private final WebSocketDecoderConfig decoderConfig;
|
||||
|
||||
public WebSocketServerProtocolHandler(String websocketPath) {
|
||||
this(websocketPath, DEFAULT_HANDSHAKE_TIMEOUT_MS);
|
||||
@ -190,14 +188,23 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
|
||||
public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean allowExtensions,
|
||||
int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith,
|
||||
boolean dropPongFrames, long handshakeTimeoutMillis) {
|
||||
this(websocketPath, subprotocols, checkStartsWith, dropPongFrames, handshakeTimeoutMillis,
|
||||
WebSocketDecoderConfig.newBuilder()
|
||||
.maxFramePayloadLength(maxFrameSize)
|
||||
.allowMaskMismatch(allowMaskMismatch)
|
||||
.allowExtensions(allowExtensions)
|
||||
.build());
|
||||
}
|
||||
|
||||
public WebSocketServerProtocolHandler(String websocketPath, String subprotocols, boolean checkStartsWith,
|
||||
boolean dropPongFrames, long handshakeTimeoutMillis,
|
||||
WebSocketDecoderConfig decoderConfig) {
|
||||
super(dropPongFrames);
|
||||
this.websocketPath = websocketPath;
|
||||
this.subprotocols = subprotocols;
|
||||
this.allowExtensions = allowExtensions;
|
||||
maxFramePayloadLength = maxFrameSize;
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
this.checkStartsWith = checkStartsWith;
|
||||
this.handshakeTimeoutMillis = checkPositive(handshakeTimeoutMillis, "handshakeTimeoutMillis");
|
||||
this.decoderConfig = checkNotNull(decoderConfig, "decoderConfig");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -206,11 +213,8 @@ public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {
|
||||
if (cp.get(WebSocketServerProtocolHandshakeHandler.class) == null) {
|
||||
// Add the WebSocketHandshakeHandler before this one.
|
||||
ctx.pipeline().addBefore(ctx.name(), WebSocketServerProtocolHandshakeHandler.class.getName(),
|
||||
new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols,
|
||||
allowExtensions, maxFramePayloadLength,
|
||||
allowMaskMismatch,
|
||||
checkStartsWith,
|
||||
handshakeTimeoutMillis));
|
||||
new WebSocketServerProtocolHandshakeHandler(
|
||||
websocketPath, subprotocols, checkStartsWith, handshakeTimeoutMillis, decoderConfig));
|
||||
}
|
||||
if (cp.get(Utf8FrameValidator.class) == null) {
|
||||
// Add the UFT8 checking before this one.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -48,43 +48,19 @@ class WebSocketServerProtocolHandshakeHandler implements ChannelInboundHandler {
|
||||
|
||||
private final String websocketPath;
|
||||
private final String subprotocols;
|
||||
private final boolean allowExtensions;
|
||||
private final int maxFramePayloadSize;
|
||||
private final boolean allowMaskMismatch;
|
||||
private final boolean checkStartsWith;
|
||||
private final long handshakeTimeoutMillis;
|
||||
private final WebSocketDecoderConfig decoderConfig;
|
||||
private ChannelHandlerContext ctx;
|
||||
private ChannelPromise handshakePromise;
|
||||
|
||||
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
|
||||
boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch) {
|
||||
this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch,
|
||||
DEFAULT_HANDSHAKE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
|
||||
boolean allowExtensions, int maxFrameSize,
|
||||
boolean allowMaskMismatch, long handshakeTimeoutMillis) {
|
||||
this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch,
|
||||
false, handshakeTimeoutMillis);
|
||||
}
|
||||
|
||||
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
|
||||
boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch, boolean checkStartsWith) {
|
||||
this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch,
|
||||
checkStartsWith, DEFAULT_HANDSHAKE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
WebSocketServerProtocolHandshakeHandler(String websocketPath, String subprotocols,
|
||||
boolean allowExtensions, int maxFrameSize, boolean allowMaskMismatch,
|
||||
boolean checkStartsWith, long handshakeTimeoutMillis) {
|
||||
boolean checkStartsWith, long handshakeTimeoutMillis, WebSocketDecoderConfig decoderConfig) {
|
||||
this.websocketPath = websocketPath;
|
||||
this.subprotocols = subprotocols;
|
||||
this.allowExtensions = allowExtensions;
|
||||
maxFramePayloadSize = maxFrameSize;
|
||||
this.allowMaskMismatch = allowMaskMismatch;
|
||||
this.checkStartsWith = checkStartsWith;
|
||||
this.handshakeTimeoutMillis = checkPositive(handshakeTimeoutMillis, "handshakeTimeoutMillis");
|
||||
this.decoderConfig = checkNotNull(decoderConfig, "decoderConfig");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,8 +84,7 @@ class WebSocketServerProtocolHandshakeHandler implements ChannelInboundHandler {
|
||||
}
|
||||
|
||||
final WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
|
||||
getWebSocketLocation(ctx.pipeline(), req, websocketPath), subprotocols,
|
||||
allowExtensions, maxFramePayloadSize, allowMaskMismatch);
|
||||
getWebSocketLocation(ctx.pipeline(), req, websocketPath), subprotocols, decoderConfig);
|
||||
final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req);
|
||||
final ChannelPromise localHandshakePromise = handshakePromise;
|
||||
if (handshaker == null) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
* Copyright 2019 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
|
||||
@ -53,6 +53,67 @@ public class WebSocket08EncoderDecoderTest {
|
||||
strTestData = s.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebSocketProtocolViolation() {
|
||||
// Given
|
||||
initTestData();
|
||||
|
||||
int maxPayloadLength = 255;
|
||||
String errorMessage = "Max frame length of " + maxPayloadLength + " has been exceeded.";
|
||||
WebSocketCloseStatus expectedStatus = WebSocketCloseStatus.MESSAGE_TOO_BIG;
|
||||
|
||||
// With auto-close
|
||||
WebSocketDecoderConfig config = WebSocketDecoderConfig.newBuilder()
|
||||
.maxFramePayloadLength(maxPayloadLength)
|
||||
.closeOnProtocolViolation(true)
|
||||
.build();
|
||||
EmbeddedChannel inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(config));
|
||||
EmbeddedChannel outChannel = new EmbeddedChannel(new WebSocket08FrameEncoder(true));
|
||||
|
||||
executeProtocolViolationTest(outChannel, inChannel, maxPayloadLength + 1, expectedStatus, errorMessage);
|
||||
|
||||
CloseWebSocketFrame response = inChannel.readOutbound();
|
||||
Assert.assertNotNull(response);
|
||||
Assert.assertEquals(expectedStatus.code(), response.statusCode());
|
||||
Assert.assertEquals(errorMessage, response.reasonText());
|
||||
|
||||
// Without auto-close
|
||||
config = WebSocketDecoderConfig.newBuilder()
|
||||
.maxFramePayloadLength(maxPayloadLength)
|
||||
.closeOnProtocolViolation(false)
|
||||
.build();
|
||||
inChannel = new EmbeddedChannel(new WebSocket08FrameDecoder(config));
|
||||
outChannel = new EmbeddedChannel(new WebSocket08FrameEncoder(true));
|
||||
|
||||
executeProtocolViolationTest(outChannel, inChannel, maxPayloadLength + 1, expectedStatus, errorMessage);
|
||||
|
||||
response = inChannel.readOutbound();
|
||||
Assert.assertNull(response);
|
||||
|
||||
// Release test data
|
||||
binTestData.release();
|
||||
Assert.assertFalse(inChannel.finish());
|
||||
Assert.assertFalse(outChannel.finish());
|
||||
}
|
||||
|
||||
private void executeProtocolViolationTest(EmbeddedChannel outChannel, EmbeddedChannel inChannel,
|
||||
int testDataLength, WebSocketCloseStatus expectedStatus, String errorMessage) {
|
||||
CorruptedWebSocketFrameException corrupted = null;
|
||||
|
||||
try {
|
||||
testBinaryWithLen(outChannel, inChannel, testDataLength);
|
||||
} catch (CorruptedWebSocketFrameException e) {
|
||||
corrupted = e;
|
||||
}
|
||||
|
||||
BinaryWebSocketFrame exceedingFrame = inChannel.readInbound();
|
||||
Assert.assertNull(exceedingFrame);
|
||||
|
||||
Assert.assertNotNull(corrupted);
|
||||
Assert.assertEquals(expectedStatus, corrupted.closeStatus());
|
||||
Assert.assertEquals(errorMessage, corrupted.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWebSocketEncodingAndDecoding() {
|
||||
initTestData();
|
||||
@ -108,16 +169,7 @@ public class WebSocket08EncoderDecoderTest {
|
||||
String testStr = strTestData.substring(0, testDataLength);
|
||||
outChannel.writeOutbound(new TextWebSocketFrame(testStr));
|
||||
|
||||
// Transfer encoded data into decoder
|
||||
// Loop because there might be multiple frames (gathering write)
|
||||
while (true) {
|
||||
ByteBuf encoded = outChannel.readOutbound();
|
||||
if (encoded != null) {
|
||||
inChannel.writeInbound(encoded);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
transfer(outChannel, inChannel);
|
||||
|
||||
Object decoded = inChannel.readInbound();
|
||||
Assert.assertNotNull(decoded);
|
||||
@ -132,16 +184,7 @@ public class WebSocket08EncoderDecoderTest {
|
||||
binTestData.setIndex(0, testDataLength); // Send only len bytes
|
||||
outChannel.writeOutbound(new BinaryWebSocketFrame(binTestData));
|
||||
|
||||
// Transfer encoded data into decoder
|
||||
// Loop because there might be multiple frames (gathering write)
|
||||
while (true) {
|
||||
ByteBuf encoded = outChannel.readOutbound();
|
||||
if (encoded != null) {
|
||||
inChannel.writeInbound(encoded);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
transfer(outChannel, inChannel);
|
||||
|
||||
Object decoded = inChannel.readInbound();
|
||||
Assert.assertNotNull(decoded);
|
||||
@ -154,4 +197,16 @@ public class WebSocket08EncoderDecoderTest {
|
||||
}
|
||||
binFrame.release();
|
||||
}
|
||||
|
||||
private void transfer(EmbeddedChannel outChannel, EmbeddedChannel inChannel) {
|
||||
// Transfer encoded data into decoder
|
||||
// Loop because there might be multiple frames (gathering write)
|
||||
for (;;) {
|
||||
ByteBuf encoded = outChannel.readOutbound();
|
||||
if (encoded == null) {
|
||||
return;
|
||||
}
|
||||
inChannel.writeInbound(encoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2019 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 java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static io.netty.handler.codec.http.websocketx.WebSocketCloseStatus.*;
|
||||
|
||||
public class WebSocketCloseStatusTest {
|
||||
|
||||
private final List<WebSocketCloseStatus> validCodes = Arrays.asList(
|
||||
NORMAL_CLOSURE,
|
||||
ENDPOINT_UNAVAILABLE,
|
||||
PROTOCOL_ERROR,
|
||||
INVALID_MESSAGE_TYPE,
|
||||
INVALID_PAYLOAD_DATA,
|
||||
POLICY_VIOLATION,
|
||||
MESSAGE_TOO_BIG,
|
||||
MANDATORY_EXTENSION,
|
||||
INTERNAL_SERVER_ERROR,
|
||||
SERVICE_RESTART,
|
||||
TRY_AGAIN_LATER,
|
||||
BAD_GATEWAY
|
||||
);
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
Assert.assertEquals("1000 Bye", NORMAL_CLOSURE.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKnownStatuses() {
|
||||
Assert.assertSame(NORMAL_CLOSURE, valueOf(1000));
|
||||
Assert.assertSame(ENDPOINT_UNAVAILABLE, valueOf(1001));
|
||||
Assert.assertSame(PROTOCOL_ERROR, valueOf(1002));
|
||||
Assert.assertSame(INVALID_MESSAGE_TYPE, valueOf(1003));
|
||||
Assert.assertSame(INVALID_PAYLOAD_DATA, valueOf(1007));
|
||||
Assert.assertSame(POLICY_VIOLATION, valueOf(1008));
|
||||
Assert.assertSame(MESSAGE_TOO_BIG, valueOf(1009));
|
||||
Assert.assertSame(MANDATORY_EXTENSION, valueOf(1010));
|
||||
Assert.assertSame(INTERNAL_SERVER_ERROR, valueOf(1011));
|
||||
Assert.assertSame(SERVICE_RESTART, valueOf(1012));
|
||||
Assert.assertSame(TRY_AGAIN_LATER, valueOf(1013));
|
||||
Assert.assertSame(BAD_GATEWAY, valueOf(1014));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNaturalOrder() {
|
||||
Assert.assertThat(PROTOCOL_ERROR, Matchers.greaterThan(NORMAL_CLOSURE));
|
||||
Assert.assertThat(PROTOCOL_ERROR, Matchers.greaterThan(valueOf(1001)));
|
||||
Assert.assertThat(PROTOCOL_ERROR, Matchers.comparesEqualTo(PROTOCOL_ERROR));
|
||||
Assert.assertThat(PROTOCOL_ERROR, Matchers.comparesEqualTo(valueOf(1002)));
|
||||
Assert.assertThat(PROTOCOL_ERROR, Matchers.lessThan(INVALID_MESSAGE_TYPE));
|
||||
Assert.assertThat(PROTOCOL_ERROR, Matchers.lessThan(valueOf(1007)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserDefinedStatuses() {
|
||||
// Given, when
|
||||
WebSocketCloseStatus feedTimeot = new WebSocketCloseStatus(6033, "Feed timed out");
|
||||
WebSocketCloseStatus untradablePrice = new WebSocketCloseStatus(6034, "Untradable price");
|
||||
|
||||
// Then
|
||||
Assert.assertNotSame(feedTimeot, valueOf(6033));
|
||||
Assert.assertEquals(feedTimeot.code(), 6033);
|
||||
Assert.assertEquals(feedTimeot.reasonText(), "Feed timed out");
|
||||
|
||||
Assert.assertNotSame(untradablePrice, valueOf(6034));
|
||||
Assert.assertEquals(untradablePrice.code(), 6034);
|
||||
Assert.assertEquals(untradablePrice.reasonText(), "Untradable price");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRfc6455CodeValidation() {
|
||||
// Given
|
||||
List<Integer> knownCodes = Arrays.asList(
|
||||
NORMAL_CLOSURE.code(),
|
||||
ENDPOINT_UNAVAILABLE.code(),
|
||||
PROTOCOL_ERROR.code(),
|
||||
INVALID_MESSAGE_TYPE.code(),
|
||||
INVALID_PAYLOAD_DATA.code(),
|
||||
POLICY_VIOLATION.code(),
|
||||
MESSAGE_TOO_BIG.code(),
|
||||
MANDATORY_EXTENSION.code(),
|
||||
INTERNAL_SERVER_ERROR.code(),
|
||||
SERVICE_RESTART.code(),
|
||||
TRY_AGAIN_LATER.code(),
|
||||
BAD_GATEWAY.code()
|
||||
);
|
||||
|
||||
SortedSet<Integer> invalidCodes = new TreeSet<Integer>();
|
||||
|
||||
// When
|
||||
for (int statusCode = Short.MIN_VALUE; statusCode < Short.MAX_VALUE; statusCode++) {
|
||||
if (!isValidStatusCode(statusCode)) {
|
||||
invalidCodes.add(statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
// Then
|
||||
Assert.assertEquals(0, invalidCodes.first().intValue());
|
||||
Assert.assertEquals(2999, invalidCodes.last().intValue());
|
||||
Assert.assertEquals(3000 - validCodes.size(), invalidCodes.size());
|
||||
|
||||
invalidCodes.retainAll(knownCodes);
|
||||
Assert.assertEquals(invalidCodes, Collections.emptySet());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user