Issue #250. Implement web socket close frame status code and reason text.

This commit is contained in:
vibul 2012-04-09 14:33:45 +10:00
parent 778f4a3cbc
commit dd14b8d9e8
3 changed files with 139 additions and 5 deletions

View File

@ -15,7 +15,9 @@
*/
package io.netty.handler.codec.http.websocketx;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.util.CharsetUtil;
/**
* Web Socket Frame for closing the connection
@ -30,7 +32,20 @@ public class CloseWebSocketFrame extends WebSocketFrame {
}
/**
* Creates a new close frame
* 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
* example, <tt>1000</tt> indicates normal closure.
* @param reasonText
* Reason text. Set to null if no text.
*/
public CloseWebSocketFrame(int statusCode, String reasonText) {
this(true, 0, statusCode, reasonText);
}
/**
* 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
@ -38,8 +53,93 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* reserved bits used for protocol extensions
*/
public CloseWebSocketFrame(boolean finalFragment, int rsv) {
this(finalFragment, rsv, null);
}
/**
* Creates a new close frame with closing status code and reason text
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param statusCode
* Integer 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(boolean finalFragment, int rsv, int statusCode, String reasonText) {
setFinalFragment(finalFragment);
setRsv(rsv);
byte[] reasonBytes = new byte[0];
if (reasonText != null) {
reasonBytes = reasonText.getBytes(CharsetUtil.UTF_8);
}
ChannelBuffer binaryData = ChannelBuffers.buffer(2 + reasonBytes.length);
binaryData.writeShort(statusCode);
if (reasonBytes.length > 0) {
binaryData.writeBytes(reasonBytes);
}
binaryData.readerIndex(0);
setBinaryData(binaryData);
}
/**
* Creates a new close frame
*
* @param finalFragment
* flag indicating if this frame is the final fragment
* @param rsv
* reserved bits used for protocol extensions
* @param binaryData
* the content of the frame. Must be 2 byte integer followed by optional UTF-8 encoded string.
*/
public CloseWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
setFinalFragment(finalFragment);
setRsv(rsv);
if (binaryData == null) {
setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else {
setBinaryData(binaryData);
}
}
/**
* Returns the closing status code as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. If
* a status code is set, -1 is returned.
*/
public int getStatusCode() {
ChannelBuffer binaryData = this.getBinaryData();
if (binaryData == null || binaryData.capacity() == 0) {
return -1;
}
binaryData.readerIndex(0);
int statusCode = binaryData.readShort();
binaryData.readerIndex(0);
return statusCode;
}
/**
* Returns the reason text as per <a href="http://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a> If a reason
* text is not supplied, an empty string is returned.
*/
public String getReasonText() {
ChannelBuffer binaryData = this.getBinaryData();
if (binaryData == null || binaryData.capacity() <= 2) {
return "";
}
binaryData.readerIndex(2);
String reasonText = binaryData.toString(CharsetUtil.UTF_8);
binaryData.readerIndex(0);
return reasonText;
}
@Override

View File

@ -284,8 +284,9 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
} else if (frameOpcode == OPCODE_PONG) {
return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_CLOSE) {
checkCloseFrameBody(channel, framePayload);
receivedClosingHandshake = true;
return new CloseWebSocketFrame(frameFinalFlag, frameRsv);
return new CloseWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
}
// Processing for possible fragmented messages for text and binary
@ -391,4 +392,38 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
protocolViolation(channel, "invalid UTF-8 bytes");
}
}
protected void checkCloseFrameBody(Channel channel, ChannelBuffer buffer) throws CorruptedFrameException {
if (buffer == null || buffer.capacity() == 0) {
return;
}
if (buffer.capacity() == 1) {
protocolViolation(channel, "Invalid close frame body");
}
// Save reader index
int idx = buffer.readerIndex();
buffer.readerIndex(0);
// Must have 2 byte integer within the valid range
int statusCode = buffer.readShort();
if ((statusCode >= 0 && statusCode <= 999) || (statusCode >= 1004 && statusCode <= 1006)
|| (statusCode >= 1012 && statusCode <= 2999)) {
protocolViolation(channel, "Invalid close frame status code: " + statusCode);
}
// May have UTF-8 message
if (buffer.readableBytes() > 0) {
byte[] b = new byte[buffer.readableBytes()];
buffer.readBytes(b);
try {
new UTF8Output(b);
} catch (UTF8Exception ex) {
protocolViolation(channel, "Invalid close frame reason text. Invalid UTF-8 bytes");
}
}
// Restore reader index
buffer.readerIndex(idx);
}
}

View File

@ -82,11 +82,10 @@ public class AutobahnServerHandler extends SimpleChannelUpstreamHandler {
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
if (logger.isDebugEnabled()) {
logger.debug(String
.format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName()));
logger.debug(String.format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass()
.getSimpleName()));
}
if (frame instanceof CloseWebSocketFrame) {
this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
} else if (frame instanceof PingWebSocketFrame) {