Clean up the new WebSocket package

This commit is contained in:
Trustin Lee 2012-01-19 13:12:28 +09:00
parent 4759a791b3
commit fafeae7aa3
29 changed files with 431 additions and 453 deletions

View File

@ -76,7 +76,7 @@ public class AutobahnServerHandler extends SimpleChannelUpstreamHandler {
if (this.handshaker == null) { if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
} else { } else {
this.handshaker.performOpeningHandshake(ctx.getChannel(), req); this.handshaker.handshake(ctx.getChannel(), req);
} }
} }
@ -85,7 +85,7 @@ public class AutobahnServerHandler extends SimpleChannelUpstreamHandler {
.format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName())); .format("Channel %s received %s", ctx.getChannel().getId(), frame.getClass().getSimpleName()));
if (frame instanceof CloseWebSocketFrame) { if (frame instanceof CloseWebSocketFrame) {
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame); this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
} else if (frame instanceof PingWebSocketFrame) { } else if (frame instanceof PingWebSocketFrame) {
ctx.getChannel().write( ctx.getChannel().write(
new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData())); new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));

View File

@ -100,7 +100,7 @@ public class WebSocketClient {
Channel ch = future.getChannel(); Channel ch = future.getChannel();
handshaker.performOpeningHandshake(ch); handshaker.handshake(ch);
Thread.sleep(1000); Thread.sleep(1000);

View File

@ -52,8 +52,8 @@ public class WebSocketClientHandler extends SimpleChannelUpstreamHandler {
@Override @Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Channel ch = ctx.getChannel(); Channel ch = ctx.getChannel();
if (!handshaker.isOpeningHandshakeCompleted()) { if (!handshaker.isHandshakeComplete()) {
handshaker.performClosingHandshake(ch, (HttpResponse) e.getMessage()); handshaker.finishHandshake(ch, (HttpResponse) e.getMessage());
System.out.println("WebSocket Client connected!"); System.out.println("WebSocket Client connected!");
return; return;
} }

View File

@ -96,7 +96,7 @@ public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
if (this.handshaker == null) { if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
} else { } else {
this.handshaker.performOpeningHandshake(ctx.getChannel(), req); this.handshaker.handshake(ctx.getChannel(), req);
} }
} }
@ -104,7 +104,7 @@ public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
// Check for closing frame // Check for closing frame
if (frame instanceof CloseWebSocketFrame) { if (frame instanceof CloseWebSocketFrame) {
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame); this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
return; return;
} else if (frame instanceof PingWebSocketFrame) { } else if (frame instanceof PingWebSocketFrame) {
ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData())); ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));

View File

@ -96,7 +96,7 @@ public class WebSocketSslServerHandler extends SimpleChannelUpstreamHandler {
if (this.handshaker == null) { if (this.handshaker == null) {
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel()); wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
} else { } else {
this.handshaker.performOpeningHandshake(ctx.getChannel(), req); this.handshaker.handshake(ctx.getChannel(), req);
} }
} }
@ -104,7 +104,7 @@ public class WebSocketSslServerHandler extends SimpleChannelUpstreamHandler {
// Check for closing frame // Check for closing frame
if (frame instanceof CloseWebSocketFrame) { if (frame instanceof CloseWebSocketFrame) {
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame); this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
return; return;
} else if (frame instanceof PingWebSocketFrame) { } else if (frame instanceof PingWebSocketFrame) {
ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData())); ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));

View File

@ -27,7 +27,7 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
* Creates a new empty binary frame. * Creates a new empty binary frame.
*/ */
public BinaryWebSocketFrame() { public BinaryWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
@ -37,7 +37,7 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public BinaryWebSocketFrame(ChannelBuffer binaryData) { public BinaryWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
/** /**
@ -51,9 +51,9 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
@Override @Override

View File

@ -26,7 +26,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* Creates a new empty close frame. * Creates a new empty close frame.
*/ */
public CloseWebSocketFrame() { public CloseWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
@ -38,8 +38,8 @@ public class CloseWebSocketFrame extends WebSocketFrame {
* reserved bits used for protocol extensions * reserved bits used for protocol extensions
*/ */
public CloseWebSocketFrame(boolean finalFragment, int rsv) { public CloseWebSocketFrame(boolean finalFragment, int rsv) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
} }
@Override @Override

View File

@ -31,7 +31,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* Creates a new empty continuation frame. * Creates a new empty continuation frame.
*/ */
public ContinuationWebSocketFrame() { public ContinuationWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
@ -41,7 +41,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public ContinuationWebSocketFrame(ChannelBuffer binaryData) { public ContinuationWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
/** /**
@ -55,9 +55,9 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
/** /**
@ -73,9 +73,9 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* Aggregated text set by decoder on the final continuation frame of a fragmented text message * Aggregated text set by decoder on the final continuation frame of a fragmented text message
*/ */
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) { public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
this.setBinaryData(binaryData); setBinaryData(binaryData);
this.aggregatedText = aggregatedText; this.aggregatedText = aggregatedText;
} }
@ -90,19 +90,19 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
* text content of the frame. * text content of the frame.
*/ */
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) { public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
this.setText(text); setText(text);
} }
/** /**
* Returns the text data in this frame * Returns the text data in this frame
*/ */
public String getText() { public String getText() {
if (this.getBinaryData() == null) { if (getBinaryData() == null) {
return null; return null;
} }
return this.getBinaryData().toString(CharsetUtil.UTF_8); return getBinaryData().toString(CharsetUtil.UTF_8);
} }
/** /**
@ -113,9 +113,9 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
*/ */
public void setText(String text) { public void setText(String text) {
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else { } else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8)); setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
} }
} }

View File

@ -27,8 +27,8 @@ public class PingWebSocketFrame extends WebSocketFrame {
* Creates a new empty ping frame. * Creates a new empty ping frame.
*/ */
public PingWebSocketFrame() { public PingWebSocketFrame() {
this.setFinalFragment(true); setFinalFragment(true);
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
@ -38,7 +38,7 @@ public class PingWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public PingWebSocketFrame(ChannelBuffer binaryData) { public PingWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
/** /**
@ -52,9 +52,9 @@ public class PingWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
@Override @Override

View File

@ -27,7 +27,7 @@ public class PongWebSocketFrame extends WebSocketFrame {
* Creates a new empty pong frame. * Creates a new empty pong frame.
*/ */
public PongWebSocketFrame() { public PongWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
@ -37,7 +37,7 @@ public class PongWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public PongWebSocketFrame(ChannelBuffer binaryData) { public PongWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
/** /**
@ -51,9 +51,9 @@ public class PongWebSocketFrame extends WebSocketFrame {
* the content of the frame. * the content of the frame.
*/ */
public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
@Override @Override

View File

@ -28,7 +28,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
* Creates a new empty text frame. * Creates a new empty text frame.
*/ */
public TextWebSocketFrame() { public TextWebSocketFrame() {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} }
/** /**
@ -39,9 +39,9 @@ public class TextWebSocketFrame extends WebSocketFrame {
*/ */
public TextWebSocketFrame(String text) { public TextWebSocketFrame(String text) {
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else { } else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8)); setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
} }
} }
@ -52,7 +52,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
* the content of the frame. Must be UTF-8 encoded * the content of the frame. Must be UTF-8 encoded
*/ */
public TextWebSocketFrame(ChannelBuffer binaryData) { public TextWebSocketFrame(ChannelBuffer binaryData) {
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
/** /**
@ -66,12 +66,12 @@ public class TextWebSocketFrame extends WebSocketFrame {
* String to put in the frame * String to put in the frame
*/ */
public TextWebSocketFrame(boolean finalFragment, int rsv, String text) { public TextWebSocketFrame(boolean finalFragment, int rsv, String text) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
if (text == null || text.isEmpty()) { if (text == null || text.isEmpty()) {
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER); setBinaryData(ChannelBuffers.EMPTY_BUFFER);
} else { } else {
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8)); setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
} }
} }
@ -86,19 +86,19 @@ public class TextWebSocketFrame extends WebSocketFrame {
* the content of the frame. Must be UTF-8 encoded * the content of the frame. Must be UTF-8 encoded
*/ */
public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) { public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
this.setFinalFragment(finalFragment); setFinalFragment(finalFragment);
this.setRsv(rsv); setRsv(rsv);
this.setBinaryData(binaryData); setBinaryData(binaryData);
} }
/** /**
* Returns the text data in this frame * Returns the text data in this frame
*/ */
public String getText() { public String getText() {
if (this.getBinaryData() == null) { if (getBinaryData() == null) {
return null; return null;
} }
return this.getBinaryData().toString(CharsetUtil.UTF_8); return getBinaryData().toString(CharsetUtil.UTF_8);
} }
/** /**
@ -111,7 +111,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
if (text == null) { if (text == null) {
throw new NullPointerException("text"); throw new NullPointerException("text");
} }
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8)); setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
} }
@Override @Override

View File

@ -60,7 +60,7 @@ final class UTF8Output {
public void write(int b) { public void write(int b) {
byte type = TYPES[b & 0xFF]; byte type = TYPES[b & 0xFF];
codep = (state != UTF8_ACCEPT) ? (b & 0x3f) | (codep << 6) : (0xff >> type) & b; codep = state != UTF8_ACCEPT ? b & 0x3f | codep << 6 : 0xff >> type & b;
state = STATES[state + type]; state = STATES[state + type];

View File

@ -33,7 +33,7 @@ import org.jboss.netty.handler.codec.replay.VoidEnum;
*/ */
public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> { public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
public static final int DEFAULT_MAX_FRAME_SIZE = 16384; private static final int DEFAULT_MAX_FRAME_SIZE = 16384;
private final int maxFrameSize; private final int maxFrameSize;
private boolean receivedClosingHandshake; private boolean receivedClosingHandshake;
@ -70,7 +70,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
return decodeBinaryFrame(type, buffer); return decodeBinaryFrame(type, buffer);
} else { } else {
// Decode a 0xff terminated UTF-8 string // Decode a 0xff terminated UTF-8 string
return decodeTextFrame(type, buffer); return decodeTextFrame(buffer);
} }
} }
@ -92,7 +92,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
} }
} while ((b & 0x80) == 0x80); } while ((b & 0x80) == 0x80);
if (type == ((byte) 0xFF) && frameSize == 0) { if (type == (byte) 0xFF && frameSize == 0) {
receivedClosingHandshake = true; receivedClosingHandshake = true;
return new CloseWebSocketFrame(); return new CloseWebSocketFrame();
} }
@ -100,7 +100,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
return new BinaryWebSocketFrame(buffer.readBytes((int) frameSize)); return new BinaryWebSocketFrame(buffer.readBytes((int) frameSize));
} }
private WebSocketFrame decodeTextFrame(byte type, ChannelBuffer buffer) throws TooLongFrameException { private WebSocketFrame decodeTextFrame(ChannelBuffer buffer) throws TooLongFrameException {
int ridx = buffer.readerIndex(); int ridx = buffer.readerIndex();
int rbytes = actualReadableBytes(); int rbytes = actualReadableBytes();
int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF); int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF);

View File

@ -75,8 +75,8 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
private int framePayloadBytesRead; private int framePayloadBytesRead;
private ChannelBuffer maskingKey; private ChannelBuffer maskingKey;
private boolean allowExtensions; private final boolean allowExtensions;
private boolean maskedPayload; private final boolean maskedPayload;
private boolean receivedClosingHandshake; private boolean receivedClosingHandshake;
public enum State { public enum State {
@ -129,12 +129,12 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
boolean frameMasked = (b & 0x80) != 0; boolean frameMasked = (b & 0x80) != 0;
int framePayloadLen1 = b & 0x7F; int framePayloadLen1 = b & 0x7F;
if (frameRsv != 0 && !this.allowExtensions) { if (frameRsv != 0 && !allowExtensions) {
protocolViolation(channel, "RSV != 0 and no extension negotiated, RSV:" + frameRsv); protocolViolation(channel, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
return null; return null;
} }
if (this.maskedPayload && !frameMasked) { if (maskedPayload && !frameMasked) {
protocolViolation(channel, "unmasked client to server frame"); protocolViolation(channel, "unmasked client to server frame");
return null; return null;
} }
@ -211,7 +211,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
checkpoint(State.MASKING_KEY); checkpoint(State.MASKING_KEY);
case MASKING_KEY: case MASKING_KEY:
if (this.maskedPayload) { if (maskedPayload) {
maskingKey = buffer.readBytes(4); maskingKey = buffer.readBytes(4);
} }
checkpoint(State.PAYLOAD); checkpoint(State.PAYLOAD);
@ -236,7 +236,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
framePayload = channel.getConfig().getBufferFactory().getBuffer(toFrameLength(framePayloadLength)); framePayload = channel.getConfig().getBufferFactory().getBuffer(toFrameLength(framePayloadLength));
} }
framePayload.writeBytes(payloadBuffer); framePayload.writeBytes(payloadBuffer);
framePayloadBytesRead = framePayloadBytesRead + rbytes; framePayloadBytesRead += rbytes;
// Return null to wait for more bytes to arrive // Return null to wait for more bytes to arrive
return null; return null;
@ -258,7 +258,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
} }
// Unmask data if needed // Unmask data if needed
if (this.maskedPayload) { if (maskedPayload) {
unmask(framePayload); unmask(framePayload);
} }
@ -269,7 +269,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
} else if (frameOpcode == OPCODE_PONG) { } else if (frameOpcode == OPCODE_PONG) {
return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload); return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
} else if (frameOpcode == OPCODE_CLOSE) { } else if (frameOpcode == OPCODE_CLOSE) {
this.receivedClosingHandshake = true; receivedClosingHandshake = true;
return new CloseWebSocketFrame(frameFinalFlag, frameRsv); return new CloseWebSocketFrame(frameFinalFlag, frameRsv);
} }

View File

@ -66,7 +66,7 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
private static final byte OPCODE_PING = 0x9; private static final byte OPCODE_PING = 0x9;
private static final byte OPCODE_PONG = 0xA; private static final byte OPCODE_PONG = 0xA;
private boolean maskPayload; private final boolean maskPayload;
/** /**
* Constructor * Constructor
@ -82,7 +82,7 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
@Override @Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
byte[] mask = null; byte[] mask;
if (msg instanceof WebSocketFrame) { if (msg instanceof WebSocketFrame) {
WebSocketFrame frame = (WebSocketFrame) msg; WebSocketFrame frame = (WebSocketFrame) msg;
@ -118,7 +118,7 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
if (frame.isFinalFragment()) { if (frame.isFinalFragment()) {
b0 |= 1 << 7; b0 |= 1 << 7;
} }
b0 |= (frame.getRsv() % 8) << 4; b0 |= frame.getRsv() % 8 << 4;
b0 |= opcode % 128; b0 |= opcode % 128;
ChannelBuffer header; ChannelBuffer header;
@ -129,27 +129,27 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
+ length); + length);
} }
int maskLength = this.maskPayload ? 4 : 0; int maskLength = maskPayload ? 4 : 0;
if (length <= 125) { if (length <= 125) {
header = ChannelBuffers.buffer(2 + maskLength); header = ChannelBuffers.buffer(2 + maskLength);
header.writeByte(b0); header.writeByte(b0);
byte b = (byte) (this.maskPayload ? (0x80 | (byte) length) : (byte) length); byte b = (byte) (maskPayload ? 0x80 | (byte) length : (byte) length);
header.writeByte(b); header.writeByte(b);
} else if (length <= 0xFFFF) { } else if (length <= 0xFFFF) {
header = ChannelBuffers.buffer(4 + maskLength); header = ChannelBuffers.buffer(4 + maskLength);
header.writeByte(b0); header.writeByte(b0);
header.writeByte(this.maskPayload ? 0xFE : 126); header.writeByte(maskPayload ? 0xFE : 126);
header.writeByte((length >>> 8) & 0xFF); header.writeByte(length >>> 8 & 0xFF);
header.writeByte(length & 0xFF); header.writeByte(length & 0xFF);
} else { } else {
header = ChannelBuffers.buffer(10 + maskLength); header = ChannelBuffers.buffer(10 + maskLength);
header.writeByte(b0); header.writeByte(b0);
header.writeByte(this.maskPayload ? 0xFF : 127); header.writeByte(maskPayload ? 0xFF : 127);
header.writeLong(length); header.writeLong(length);
} }
// Write payload // Write payload
if (this.maskPayload) { if (maskPayload) {
Integer random = (int) (Math.random() * Integer.MAX_VALUE); Integer random = (int) (Math.random() * Integer.MAX_VALUE);
mask = ByteBuffer.allocate(4).putInt(random).array(); mask = ByteBuffer.allocate(4).putInt(random).array();
header.writeBytes(mask); header.writeBytes(mask);

View File

@ -23,6 +23,7 @@ import java.util.Map;
import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.handler.codec.base64.Base64; import org.jboss.netty.handler.codec.base64.Base64;
import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.util.CharsetUtil; import org.jboss.netty.util.CharsetUtil;
@ -32,48 +33,44 @@ import org.jboss.netty.util.CharsetUtil;
*/ */
public abstract class WebSocketClientHandshaker { public abstract class WebSocketClientHandshaker {
private URI webSocketURL; private final URI webSocketUrl;
private WebSocketVersion version = WebSocketVersion.UNKNOWN; private final WebSocketVersion version;
private boolean openingHandshakeCompleted; private boolean handshakeComplete;
private String subProtocolRequest; private final String expectedSubprotocol;
private String subProtocolResponse; private String actualSubprotocol;
protected Map<String, String> customHeaders; protected final Map<String, String> customHeaders;
/** /**
* Base constructor * Base constructor
* *
* @param webSocketURL * @param webSocketUrl
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the server * Version of web socket specification to use to connect to the server
* @param subProtocol * @param subprotocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
*/ */
public WebSocketClientHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker(URI webSocketUrl, WebSocketVersion version, String subprotocol,
Map<String, String> customHeaders) { Map<String, String> customHeaders) {
this.webSocketURL = webSocketURL; this.webSocketUrl = webSocketUrl;
this.version = version; this.version = version;
this.subProtocolRequest = subProtocol; expectedSubprotocol = subprotocol;
this.customHeaders = customHeaders; this.customHeaders = customHeaders;
} }
/** /**
* Returns the URI to the web socket. e.g. "ws://myhost.com/path" * Returns the URI to the web socket. e.g. "ws://myhost.com/path"
*/ */
public URI getWebSocketURL() { public URI getWebSocketUrl() {
return webSocketURL; return webSocketUrl;
}
protected void setWebSocketURL(URI webSocketURL) {
this.webSocketURL = webSocketURL;
} }
/** /**
@ -83,133 +80,50 @@ public abstract class WebSocketClientHandshaker {
return version; return version;
} }
protected void setVersion(WebSocketVersion version) {
this.version = version;
}
/** /**
* Flag to indicate if the opening handshake is complete * Flag to indicate if the opening handshake is complete
*/ */
public boolean isOpeningHandshakeCompleted() { public boolean isHandshakeComplete() {
return openingHandshakeCompleted; return handshakeComplete;
} }
protected void setOpenningHandshakeCompleted(boolean openningHandshakeCompleted) { protected void setHandshakeComplete() {
this.openingHandshakeCompleted = openningHandshakeCompleted; handshakeComplete = true;
} }
/** /**
* Returns the sub protocol request sent to the server as specified in the constructor * Returns the sub protocol request sent to the server as specified in the constructor
*/ */
public String getSubProtocolRequest() { public String getExpectedSubprotocol() {
return subProtocolRequest; return expectedSubprotocol;
}
protected void setSubProtocolRequest(String subProtocolRequest) {
this.subProtocolRequest = subProtocolRequest;
} }
/** /**
* Returns the sub protocol response and sent by the server. Only available after end of handshake. * Returns the sub protocol response and sent by the server. Only available after end of handshake.
*/ */
public String getSubProtocolResponse() { public String getActualSubprotocol() {
return subProtocolResponse; return actualSubprotocol;
} }
protected void setSubProtocolResponse(String subProtocolResponse) { protected void setActualSubprotocol(String actualSubprotocol) {
this.subProtocolResponse = subProtocolResponse; this.actualSubprotocol = actualSubprotocol;
} }
/** /**
* Performs the opening handshake * Begins the opening handshake
* *
* @param channel * @param channel
* Channel * Channel
*/ */
public abstract void performOpeningHandshake(Channel channel); public abstract ChannelFuture handshake(Channel channel);
/** /**
* Performs the closing handshake * Validates and finishes the opening handshake initiated by {@link #handshake}}.
* *
* @param channel * @param channel
* Channel * Channel
* @param response * @param response
* HTTP response containing the closing handshake details * HTTP response containing the closing handshake details
*/ */
public abstract void performClosingHandshake(Channel channel, HttpResponse response) public abstract void finishHandshake(Channel channel, HttpResponse response);
throws WebSocketHandshakeException;
/**
* Performs an MD5 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
protected byte[] md5(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 not supported on this platform");
}
}
/**
* Performs an SHA-1 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
protected byte[] sha1(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("SHA-1 not supported on this platform");
}
}
/**
* Base 64 encoding
*
* @param bytes
* Bytes to encode
* @return encoded string
*/
protected String base64Encode(byte[] bytes) {
ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
}
/**
* Creates some random bytes
*
* @param size
* Number of random bytes to create
* @return random bytes
*/
protected byte[] createRandomBytes(int size) {
byte[] bytes = new byte[size];
for (int i = 0; i < size; i++) {
bytes[i] = (byte) createRandomNumber(0, 255);
}
return bytes;
}
/**
* Generates a random number
*
* @param min
* Minimum value
* @param max
* Maximum value
* @return Random number
*/
protected int createRandomNumber(int min, int max) {
return (int) (Math.random() * max + min);
}
} }

View File

@ -22,6 +22,7 @@ import java.util.Map;
import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest; import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names; import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values; import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
@ -55,14 +56,15 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the server * Version of web socket specification to use to connect to the server
* @param subProtocol * @param subprotocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
*/ */
public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subprotocol,
Map<String, String> customHeaders) { Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders); super(webSocketURL, version, subprotocol, customHeaders);
} }
/** /**
@ -86,16 +88,16 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
* Channel into which we can write our request * Channel into which we can write our request
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel) { public ChannelFuture handshake(Channel channel) {
// Make keys // Make keys
int spaces1 = createRandomNumber(1, 12); int spaces1 = WebSocketUtil.randomNumber(1, 12);
int spaces2 = createRandomNumber(1, 12); int spaces2 = WebSocketUtil.randomNumber(1, 12);
int max1 = Integer.MAX_VALUE / spaces1; int max1 = Integer.MAX_VALUE / spaces1;
int max2 = Integer.MAX_VALUE / spaces2; int max2 = Integer.MAX_VALUE / spaces2;
int number1 = createRandomNumber(0, max1); int number1 = WebSocketUtil.randomNumber(0, max1);
int number2 = createRandomNumber(0, max2); int number2 = WebSocketUtil.randomNumber(0, max2);
int product1 = number1 * spaces1; int product1 = number1 * spaces1;
int product2 = number2 * spaces2; int product2 = number2 * spaces2;
@ -109,7 +111,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
key1 = insertSpaces(key1, spaces1); key1 = insertSpaces(key1, spaces1);
key2 = insertSpaces(key2, spaces2); key2 = insertSpaces(key2, spaces2);
byte[] key3 = createRandomBytes(8); byte[] key3 = WebSocketUtil.randomBytes(8);
ByteBuffer buffer = ByteBuffer.allocate(4); ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt(number1); buffer.putInt(number1);
@ -122,10 +124,10 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
System.arraycopy(number1Array, 0, challenge, 0, 4); System.arraycopy(number1Array, 0, challenge, 0, 4);
System.arraycopy(number2Array, 0, challenge, 4, 4); System.arraycopy(number2Array, 0, challenge, 4, 4);
System.arraycopy(key3, 0, challenge, 8, 8); System.arraycopy(key3, 0, challenge, 8, 8);
this.expectedChallengeResponseBytes = md5(challenge); expectedChallengeResponseBytes = WebSocketUtil.md5(challenge);
// Get path // Get path
URI wsURL = this.getWebSocketURL(); URI wsURL = getWebSocketUrl();
String path = wsURL.getPath(); String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery(); path = wsURL.getPath() + "?" + wsURL.getQuery();
@ -139,19 +141,23 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost()); request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1); request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1);
request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2); request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2);
if (this.getSubProtocolRequest() != null && !this.getSubProtocolRequest().equals("")) { if (getExpectedSubprotocol() != null && !getExpectedSubprotocol().equals("")) {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.getSubProtocolRequest()); request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, getExpectedSubprotocol());
} }
if (customHeaders != null) { if (customHeaders != null) {
for (String header : customHeaders.keySet()) { for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header)); request.addHeader(header, customHeaders.get(header));
} }
} }
request.setContent(ChannelBuffers.copiedBuffer(key3)); request.setContent(ChannelBuffers.copiedBuffer(key3));
channel.write(request); ChannelFuture future = channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder()); channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder());
return future;
} }
/** /**
@ -177,7 +183,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException { public void finishHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101, "WebSocket Protocol Handshake"); final HttpResponseStatus status = new HttpResponseStatus(101, "WebSocket Protocol Handshake");
if (!response.getStatus().equals(status)) { if (!response.getStatus().equals(status)) {
@ -202,28 +208,28 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
} }
String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
this.setSubProtocolResponse(protocol); setActualSubprotocol(protocol);
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder()); channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder());
this.setOpenningHandshakeCompleted(true); setHandshakeComplete();
} }
private String insertRandomCharacters(String key) { private String insertRandomCharacters(String key) {
int count = createRandomNumber(1, 12); int count = WebSocketUtil.randomNumber(1, 12);
char[] randomChars = new char[count]; char[] randomChars = new char[count];
int randCount = 0; int randCount = 0;
while (randCount < count) { while (randCount < count) {
int rand = (int) (Math.random() * 0x7e + 0x21); int rand = (int) (Math.random() * 0x7e + 0x21);
if (((0x21 < rand) && (rand < 0x2f)) || ((0x3a < rand) && (rand < 0x7e))) { if (0x21 < rand && rand < 0x2f || 0x3a < rand && rand < 0x7e) {
randomChars[randCount] = (char) rand; randomChars[randCount] = (char) rand;
randCount += 1; randCount += 1;
} }
} }
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int split = createRandomNumber(0, key.length()); int split = WebSocketUtil.randomNumber(0, key.length());
String part1 = key.substring(0, split); String part1 = key.substring(0, split);
String part2 = key.substring(split); String part2 = key.substring(split);
key = part1 + randomChars[i] + part2; key = part1 + randomChars[i] + part2;
@ -234,7 +240,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
private String insertSpaces(String key, int spaces) { private String insertSpaces(String key, int spaces) {
for (int i = 0; i < spaces; i++) { for (int i = 0; i < spaces; i++) {
int split = createRandomNumber(1, key.length() - 1); int split = WebSocketUtil.randomNumber(1, key.length() - 1);
String part1 = key.substring(0, split); String part1 = key.substring(0, split);
String part2 = key.substring(split); String part2 = key.substring(split);
key = part1 + " " + part2; key = part1 + " " + part2;

View File

@ -19,6 +19,7 @@ import java.net.URI;
import java.util.Map; import java.util.Map;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest; import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names; import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values; import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
@ -50,7 +51,7 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
private static final String protocol = null; private static final String protocol = null;
private boolean allowExtensions; private final boolean allowExtensions;
/** /**
* Constructor specifying the destination web socket location and version to initiate * Constructor specifying the destination web socket location and version to initiate
@ -60,16 +61,16 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the server * Version of web socket specification to use to connect to the server
* @param subProtocol * @param subprotocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame * Allow extensions to be used in the reserved bits of the web socket frame
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
*/ */
public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker08(URI webSocketURL, WebSocketVersion version, String subprotocol,
boolean allowExtensions, Map<String, String> customHeaders) { boolean allowExtensions, Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders); super(webSocketURL, version, subprotocol, customHeaders);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
@ -94,25 +95,25 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
* Channel into which we can write our request * Channel into which we can write our request
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel) { public ChannelFuture handshake(Channel channel) {
// Get path // Get path
URI wsURL = this.getWebSocketURL(); URI wsURL = getWebSocketUrl();
String path = wsURL.getPath(); String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery(); path = wsURL.getPath() + "?" + wsURL.getQuery();
} }
// Get 16 bit nonce and base 64 encode it // Get 16 bit nonce and base 64 encode it
byte[] nonce = createRandomBytes(16); byte[] nonce = WebSocketUtil.randomBytes(16);
String key = base64Encode(nonce); String key = WebSocketUtil.base64(nonce);
String acceptSeed = key + MAGIC_GUID; String acceptSeed = key + MAGIC_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
this.expectedChallengeResponseString = base64Encode(sha1); expectedChallengeResponseString = WebSocketUtil.base64(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("WS Version 08 Client Handshake key: %s. Expected response: %s.", key, logger.debug(String.format("WS Version 08 Client Handshake key: %s. Expected response: %s.", key,
this.expectedChallengeResponseString)); expectedChallengeResponseString));
} }
// Format request // Format request
@ -126,15 +127,18 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
} }
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8"); request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8");
if (customHeaders != null) { if (customHeaders != null) {
for (String header : customHeaders.keySet()) { for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header)); request.addHeader(header, customHeaders.get(header));
} }
} }
channel.write(request); ChannelFuture future = channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true)); channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true));
return future;
} }
/** /**
@ -157,8 +161,8 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException { public void finishHandshake(Channel channel, HttpResponse response) {
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols"); final HttpResponseStatus status = HttpResponseStatus.SWITCHING_PROTOCOLS;
if (!response.getStatus().equals(status)) { if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus()); throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
@ -177,15 +181,14 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
} }
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT); String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) { if (accept == null || !accept.equals(expectedChallengeResponseString)) {
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
this.expectedChallengeResponseString)); expectedChallengeResponseString));
} }
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
new WebSocket08FrameDecoder(false, this.allowExtensions)); new WebSocket08FrameDecoder(false, allowExtensions));
this.setOpenningHandshakeCompleted(true); setHandshakeComplete();
} }
} }

View File

@ -19,6 +19,7 @@ import java.net.URI;
import java.util.Map; import java.util.Map;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest; import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names; import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values; import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
@ -35,9 +36,9 @@ import org.jboss.netty.util.CharsetUtil;
/** /**
* <p> * <p>
* Performs client side opening and closing handshakes for <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>. * Performs client side opening and closing handshakes for web socket specification version <a
* This was originally <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" >draft-ietf-hybi-thewebsocketprotocol-
* >draft-ietf-hybi-thewebsocketprotocol- 17</a> * 17</a>
* </p> * </p>
*/ */
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
@ -50,7 +51,7 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
private static final String protocol = null; private static final String protocol = null;
private boolean allowExtensions; private final boolean allowExtensions;
/** /**
* Constructor specifying the destination web socket location and version to initiate * Constructor specifying the destination web socket location and version to initiate
@ -60,16 +61,16 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the server * Version of web socket specification to use to connect to the server
* @param subProtocol * @param subprotocol
* Sub protocol request sent to the server. * Sub protocol request sent to the server.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame * Allow extensions to be used in the reserved bits of the web socket frame
* @param customHeaders * @param customHeaders
* Map of custom headers to add to the client request * Map of custom headers to add to the client request
*/ */
public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker13(URI webSocketURL, WebSocketVersion version, String subprotocol,
boolean allowExtensions, Map<String, String> customHeaders) { boolean allowExtensions, Map<String, String> customHeaders) {
super(webSocketURL, version, subProtocol, customHeaders); super(webSocketURL, version, subprotocol, customHeaders);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
@ -94,25 +95,25 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
* Channel into which we can write our request * Channel into which we can write our request
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel) { public ChannelFuture handshake(Channel channel) {
// Get path // Get path
URI wsURL = this.getWebSocketURL(); URI wsURL = getWebSocketUrl();
String path = wsURL.getPath(); String path = wsURL.getPath();
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) { if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
path = wsURL.getPath() + "?" + wsURL.getQuery(); path = wsURL.getPath() + "?" + wsURL.getQuery();
} }
// Get 16 bit nonce and base 64 encode it // Get 16 bit nonce and base 64 encode it
byte[] nonce = createRandomBytes(16); byte[] nonce = WebSocketUtil.randomBytes(16);
String key = base64Encode(nonce); String key = WebSocketUtil.base64(nonce);
String acceptSeed = key + MAGIC_GUID; String acceptSeed = key + MAGIC_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
this.expectedChallengeResponseString = base64Encode(sha1); expectedChallengeResponseString = WebSocketUtil.base64(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("WS Version 13 Client Handshake key: %s. Expected response: %s.", key, logger.debug(String.format("WS Version 13 Client Handshake key: %s. Expected response: %s.", key,
this.expectedChallengeResponseString)); expectedChallengeResponseString));
} }
// Format request // Format request
@ -126,15 +127,18 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol); request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
} }
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13"); request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13");
if (customHeaders != null) { if (customHeaders != null) {
for (String header : customHeaders.keySet()) { for (String header : customHeaders.keySet()) {
request.addHeader(header, customHeaders.get(header)); request.addHeader(header, customHeaders.get(header));
} }
} }
channel.write(request); ChannelFuture future = channel.write(request);
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true)); channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
return future;
} }
/** /**
@ -157,8 +161,8 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException { public void finishHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols"); final HttpResponseStatus status = HttpResponseStatus.SWITCHING_PROTOCOLS;
if (!response.getStatus().equals(status)) { if (!response.getStatus().equals(status)) {
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus()); throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
@ -177,15 +181,14 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
} }
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT); String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) { if (accept == null || !accept.equals(expectedChallengeResponseString)) {
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
this.expectedChallengeResponseString)); expectedChallengeResponseString));
} }
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
new WebSocket13FrameDecoder(false, this.allowExtensions)); new WebSocket13FrameDecoder(false, allowExtensions));
this.setOpenningHandshakeCompleted(true); setHandshakeComplete();
} }
} }

View File

@ -31,27 +31,27 @@ public class WebSocketClientHandshakerFactory {
* sent to this URL. * sent to this URL.
* @param version * @param version
* Version of web socket specification to use to connect to the server * Version of web socket specification to use to connect to the server
* @param subProtocol * @param subprotocol
* Sub protocol request sent to the server. Null if no sub-protocol support is required. * Sub protocol request sent to the server. Null if no sub-protocol support is required.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame * Allow extensions to be used in the reserved bits of the web socket frame
* @param customHeaders * @param customHeaders
* custom HTTP headers * Custom HTTP headers to send during the handshake
* @throws WebSocketHandshakeException * @throws WebSocketHandshakeException
*/ */
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol, public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subprotocol,
boolean allowExtensions, Map<String, String> customHeaders) throws WebSocketHandshakeException { boolean allowExtensions, Map<String, String> customHeaders) throws WebSocketHandshakeException {
if (version == WebSocketVersion.V13) { if (version == WebSocketVersion.V13) {
return new WebSocketClientHandshaker13(webSocketURL, version, subProtocol, allowExtensions, customHeaders); return new WebSocketClientHandshaker13(webSocketURL, version, subprotocol, allowExtensions, customHeaders);
} }
if (version == WebSocketVersion.V08) { if (version == WebSocketVersion.V08) {
return new WebSocketClientHandshaker08(webSocketURL, version, subProtocol, allowExtensions, customHeaders); return new WebSocketClientHandshaker08(webSocketURL, version, subprotocol, allowExtensions, customHeaders);
} }
if (version == WebSocketVersion.V00) { if (version == WebSocketVersion.V00) {
return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol, customHeaders); return new WebSocketClientHandshaker00(webSocketURL, version, subprotocol, customHeaders);
} }
throw new WebSocketHandshakeException("Protocol " + version.toString() + " not supported."); throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported.");
} }
} }

View File

@ -18,7 +18,7 @@ package org.jboss.netty.handler.codec.http.websocketx;
/** /**
* Exception during handshaking process * Exception during handshaking process
*/ */
public class WebSocketHandshakeException extends Exception { public class WebSocketHandshakeException extends RuntimeException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

View File

@ -17,65 +17,70 @@ package org.jboss.netty.handler.codec.http.websocketx;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.LinkedHashSet;
import java.util.Set;
import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.handler.codec.base64.Base64; import org.jboss.netty.handler.codec.base64.Base64;
import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.util.CharsetUtil; import org.jboss.netty.util.CharsetUtil;
/**
* Base class for server side web socket opening and closing handshakes
*/
public abstract class WebSocketServerHandshaker { public abstract class WebSocketServerHandshaker {
private String webSocketURL; private final String webSocketUrl;
private String subProtocols; private final String[] subprotocols;
private String[] subProtocolsArray; private final WebSocketVersion version;
private WebSocketVersion version = WebSocketVersion.UNKNOWN;
/** /**
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
* *
* @param webSocketURL * @param version
* the protocol version
* @param webSocketUrl
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param subProtocols * @param subprotocols
* CSV of supported protocols. Null if sub protocols not supported. * CSV of supported protocols. Null if sub protocols not supported.
*/ */
public WebSocketServerHandshaker(String webSocketURL, String subProtocols) { protected WebSocketServerHandshaker(
this.webSocketURL = webSocketURL; WebSocketVersion version, String webSocketUrl, String subprotocols) {
this.subProtocols = subProtocols; this.version = version;
this.webSocketUrl = webSocketUrl;
if (this.subProtocols != null) { if (subprotocols != null) {
this.subProtocolsArray = subProtocols.split(","); String[] subprotocolArray = subprotocols.split(",");
for (int i = 0; i < this.subProtocolsArray.length; i++) { for (int i = 0; i < subprotocolArray.length; i++) {
this.subProtocolsArray[i] = this.subProtocolsArray[i].trim(); subprotocolArray[i] = subprotocolArray[i].trim();
} }
this.subprotocols = subprotocolArray;
} else {
this.subprotocols = new String[0];
} }
} }
/** /**
* Returns the URL of the web socket * Returns the URL of the web socket
*/ */
public String getWebSocketURL() { public String getWebSocketUrl() {
return webSocketURL; return webSocketUrl;
}
public void setWebSocketURL(String webSocketURL) {
this.webSocketURL = webSocketURL;
} }
/** /**
* Returns the CSV of supported sub protocols * Returns the CSV of supported sub protocols
*/ */
public String getSubProtocols() { public Set<String> getSubprotocols() {
return subProtocols; Set<String> ret = new LinkedHashSet<String>();
for (String p: this.subprotocols) {
ret.add(p);
} }
return ret;
public void setSubProtocols(String subProtocols) {
this.subProtocols = subProtocols;
} }
/** /**
@ -85,10 +90,6 @@ public abstract class WebSocketServerHandshaker {
return version; return version;
} }
public void setVersion(WebSocketVersion version) {
this.version = version;
}
/** /**
* Performs the opening handshake * Performs the opening handshake
* *
@ -96,9 +97,8 @@ public abstract class WebSocketServerHandshaker {
* Channel * Channel
* @param req * @param req
* HTTP Request * HTTP Request
* @throws NoSuchAlgorithmException
*/ */
public abstract void performOpeningHandshake(Channel channel, HttpRequest req); public abstract ChannelFuture handshake(Channel channel, HttpRequest req);
/** /**
* Performs the closing handshake * Performs the closing handshake
@ -108,71 +108,27 @@ public abstract class WebSocketServerHandshaker {
* @param frame * @param frame
* Closing Frame that was received * Closing Frame that was received
*/ */
public abstract void performClosingHandshake(Channel channel, CloseWebSocketFrame frame); public abstract ChannelFuture close(Channel channel, CloseWebSocketFrame frame);
/**
* Performs an MD5 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
protected byte[] md5(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 not supported on this platform");
}
}
/**
* SHA-1 hashing. Instance this we think it is not thread safe
*
* @param bytes
* byte to hash
* @return hashed
*/
protected byte[] sha1(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("SHA-1 not supported on this platform");
}
}
/**
* Base 64 encoding
*
* @param bytes
* Bytes to encode
* @return encoded string
*/
protected String base64Encode(byte[] bytes) {
ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
}
/** /**
* Selects the first matching supported sub protocol * Selects the first matching supported sub protocol
* *
* @param requestedSubProtocol * @param requestedSubprotocols
* CSV of protocols to be supported. e.g. "chat, superchat" * CSV of protocols to be supported. e.g. "chat, superchat"
* @return First matching supported sub protocol. Null if not found. * @return First matching supported sub protocol. Null if not found.
*/ */
protected String selectSubProtocol(String requestedSubProtocol) { protected String selectSubprotocol(String requestedSubprotocols) {
if (requestedSubProtocol == null || this.subProtocolsArray == null) { if (requestedSubprotocols == null || subprotocols.length == 0) {
return null; return null;
} }
String[] requesteSubProtocolsArray = requestedSubProtocol.split(","); String[] requesteSubprotocolArray = requestedSubprotocols.split(",");
for (int i = 0; i < requesteSubProtocolsArray.length; i++) { for (String p: requesteSubprotocolArray) {
String requesteSubProtocol = requesteSubProtocolsArray[i].trim(); String requestedSubprotocol = p.trim();
for (String supportedSubProtocol : this.subProtocolsArray) { for (String supportedSubprotocol: subprotocols) {
if (requesteSubProtocol.equals(supportedSubProtocol)) { if (requestedSubprotocol.equals(supportedSubprotocol)) {
return requesteSubProtocol; return requestedSubprotocol;
} }
} }
} }

View File

@ -15,34 +15,24 @@
*/ */
package org.jboss.netty.handler.codec.http.websocketx; package org.jboss.netty.handler.codec.http.websocketx;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ORIGIN; import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.*;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1; import static org.jboss.netty.handler.codec.http.HttpVersion.*;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY2;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_LOCATION;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_ORIGIN;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_PROTOCOL;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_LOCATION;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_ORIGIN;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_PROTOCOL;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.security.NoSuchAlgorithmException;
import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator; import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder; import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder; import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.logging.InternalLoggerFactory;
@ -66,11 +56,11 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param subProtocols * @param subprotocols
* CSV of supported protocols * CSV of supported protocols
*/ */
public WebSocketServerHandshaker00(String webSocketURL, String subProtocols) { public WebSocketServerHandshaker00(String webSocketURL, String subprotocols) {
super(webSocketURL, subProtocols); super(WebSocketVersion.V00, webSocketURL, subprotocols);
} }
/** /**
@ -91,6 +81,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
* Connection: Upgrade * Connection: Upgrade
* Host: example.com * Host: example.com
* Origin: http://example.com * Origin: http://example.com
* Sec-WebSocket-Protocol: chat, sample
* Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 * Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
* Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 * Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
* *
@ -116,20 +107,18 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
* Channel * Channel
* @param req * @param req
* HTTP request * HTTP request
* @throws NoSuchAlgorithmException
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel, HttpRequest req) { public ChannelFuture handshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s web socket spec version 00 handshake", channel.getId())); logger.debug(String.format("Channel %s WS Version 00 server handshake", channel.getId()));
} }
this.setVersion(WebSocketVersion.V00);
// Serve the WebSocket handshake request. // Serve the WebSocket handshake request.
if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION)) if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
|| !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) { || !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
return; throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
} }
// Hixie 75 does not contain these headers while Hixie 76 does // Hixie 75 does not contain these headers while Hixie 76 does
@ -145,10 +134,10 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
if (isHixie76) { if (isHixie76) {
// New handshake method with a challenge: // New handshake method with a challenge:
res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN)); res.addHeader(SEC_WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
res.addHeader(SEC_WEBSOCKET_LOCATION, this.getWebSocketURL()); res.addHeader(SEC_WEBSOCKET_LOCATION, getWebSocketUrl());
String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL); String protocol = req.getHeader(SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) { if (protocol != null) {
res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubProtocol(protocol)); res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
} }
// Calculate the answer of the challenge. // Calculate the answer of the challenge.
@ -161,15 +150,15 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
input.writeInt(a); input.writeInt(a);
input.writeInt(b); input.writeInt(b);
input.writeLong(c); input.writeLong(c);
ChannelBuffer output = ChannelBuffers.wrappedBuffer(this.md5(input.array())); ChannelBuffer output = ChannelBuffers.wrappedBuffer(WebSocketUtil.md5(input.array()));
res.setContent(output); res.setContent(output);
} else { } else {
// Old Hixie 75 handshake method with no challenge: // Old Hixie 75 handshake method with no challenge:
res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN)); res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
res.addHeader(WEBSOCKET_LOCATION, this.getWebSocketURL()); res.addHeader(WEBSOCKET_LOCATION, getWebSocketUrl());
String protocol = req.getHeader(WEBSOCKET_PROTOCOL); String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
if (protocol != null) { if (protocol != null) {
res.addHeader(WEBSOCKET_PROTOCOL, selectSubProtocol(protocol)); res.addHeader(WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
} }
} }
@ -178,9 +167,11 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
p.remove(HttpChunkAggregator.class); p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket00FrameDecoder()); p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket00FrameDecoder());
channel.write(res); ChannelFuture future = channel.write(res);
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder()); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder());
return future;
} }
/** /**
@ -192,7 +183,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
* Web Socket frame that was received * Web Socket frame that was received
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) { public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
channel.write(frame); return channel.write(frame);
} }
} }

View File

@ -15,10 +15,8 @@
*/ */
package org.jboss.netty.handler.codec.http.websocketx; package org.jboss.netty.handler.codec.http.websocketx;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.*;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static org.jboss.netty.handler.codec.http.HttpVersion.*;
import java.security.NoSuchAlgorithmException;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFuture;
@ -26,12 +24,12 @@ import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse; import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator; import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder; import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseEncoder; import org.jboss.netty.handler.codec.http.HttpResponseEncoder;
import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLogger;
import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.util.CharsetUtil; import org.jboss.netty.util.CharsetUtil;
@ -49,7 +47,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private boolean allowExtensions; private final boolean allowExtensions;
/** /**
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
@ -57,13 +55,13 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param subProtocols * @param subprotocols
* CSV of supported protocols * CSV of supported protocols
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame * Allow extensions to be used in the reserved bits of the web socket frame
*/ */
public WebSocketServerHandshaker08(String webSocketURL, String subProtocols, boolean allowExtensions) { public WebSocketServerHandshaker08(String webSocketURL, String subprotocols, boolean allowExtensions) {
super(webSocketURL, subProtocols); super(WebSocketVersion.V08, webSocketURL, subprotocols);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
@ -105,48 +103,46 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
* Channel * Channel
* @param req * @param req
* HTTP request * HTTP request
* @throws NoSuchAlgorithmException
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel, HttpRequest req) { public ChannelFuture handshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s WS version 08 handshake", channel.getId())); logger.debug(String.format("Channel %s WS Version 8 server handshake", channel.getId()));
} }
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols")); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
this.setVersion(WebSocketVersion.V08);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY); String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
if (key == null) { if (key == null) {
res.setStatus(HttpResponseStatus.BAD_REQUEST); throw new WebSocketHandshakeException("not a WebSocket request: missing key");
return;
} }
String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID; String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
String accept = base64Encode(sha1); String accept = WebSocketUtil.base64(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("WS Version 08 Server Handshake key: %s. Response: %s.", key, accept)); logger.debug(String.format("WS Version 8 Server Handshake key: %s. Response: %s.", key, accept));
} }
res.setStatus(new HttpResponseStatus(101, "Switching Protocols")); res.setStatus(HttpResponseStatus.SWITCHING_PROTOCOLS);
res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase()); res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
res.addHeader(Names.CONNECTION, Names.UPGRADE); res.addHeader(Names.CONNECTION, Names.UPGRADE);
res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept); res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) { if (protocol != null) {
res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol)); res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
} }
channel.write(res); ChannelFuture future = channel.write(res);
// Upgrade the connection and send the handshake response. // Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline(); ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class); p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket08FrameDecoder(true, this.allowExtensions)); p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket08FrameDecoder(true, allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false)); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false));
return future;
} }
/** /**
@ -158,9 +154,10 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
* Web Socket frame that was received * Web Socket frame that was received
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) { public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame); ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
return f;
} }
} }

View File

@ -18,14 +18,12 @@ package org.jboss.netty.handler.codec.http.websocketx;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; import static org.jboss.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.security.NoSuchAlgorithmException;
import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunkAggregator; import org.jboss.netty.handler.codec.http.HttpChunkAggregator;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpRequestDecoder; import org.jboss.netty.handler.codec.http.HttpRequestDecoder;
import org.jboss.netty.handler.codec.http.HttpResponse; import org.jboss.netty.handler.codec.http.HttpResponse;
@ -38,9 +36,10 @@ import org.jboss.netty.util.CharsetUtil;
/** /**
* <p> * <p>
* Performs server side opening and closing handshakes for <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>. * Performs server side opening and closing handshakes for <a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a>
* This was originally <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" * (originally web socket specification version <a
* >draft-ietf-hybi-thewebsocketprotocol- 17</a> * href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" >draft-ietf-hybi-thewebsocketprotocol-
* 17</a>).
* </p> * </p>
*/ */
public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
@ -49,7 +48,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private boolean allowExtensions; private final boolean allowExtensions;
/** /**
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
@ -57,13 +56,13 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param subProtocols * @param subprotocols
* CSV of supported protocols * CSV of supported protocols
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame * Allow extensions to be used in the reserved bits of the web socket frame
*/ */
public WebSocketServerHandshaker13(String webSocketURL, String subProtocols, boolean allowExtensions) { public WebSocketServerHandshaker13(String webSocketURL, String subprotocols, boolean allowExtensions) {
super(webSocketURL, subProtocols); super(WebSocketVersion.V13, webSocketURL, subprotocols);
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
@ -105,48 +104,46 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
* Channel * Channel
* @param req * @param req
* HTTP request * HTTP request
* @throws NoSuchAlgorithmException
*/ */
@Override @Override
public void performOpeningHandshake(Channel channel, HttpRequest req) { public ChannelFuture handshake(Channel channel, HttpRequest req) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Channel %s WS version 13 handshake", channel.getId())); logger.debug(String.format("Channel %s WS Version 13 server handshake", channel.getId()));
} }
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols")); HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
this.setVersion(WebSocketVersion.V13);
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY); String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
if (key == null) { if (key == null) {
res.setStatus(HttpResponseStatus.BAD_REQUEST); throw new WebSocketHandshakeException("not a WebSocket request: missing key");
return;
} }
String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID; String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII)); byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
String accept = base64Encode(sha1); String accept = WebSocketUtil.base64(sha1);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept)); logger.debug(String.format("WS Version 13 Server Handshake key: %s. Response: %s.", key, accept));
} }
res.setStatus(new HttpResponseStatus(101, "Switching Protocols")); res.setStatus(HttpResponseStatus.SWITCHING_PROTOCOLS);
res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase()); res.addHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
res.addHeader(Names.CONNECTION, Names.UPGRADE); res.addHeader(Names.CONNECTION, Names.UPGRADE);
res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept); res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL); String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
if (protocol != null) { if (protocol != null) {
res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.selectSubProtocol(protocol)); res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
} }
channel.write(res); ChannelFuture future = channel.write(res);
// Upgrade the connection and send the handshake response. // Upgrade the connection and send the handshake response.
ChannelPipeline p = channel.getPipeline(); ChannelPipeline p = channel.getPipeline();
p.remove(HttpChunkAggregator.class); p.remove(HttpChunkAggregator.class);
p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, this.allowExtensions)); p.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket13FrameDecoder(true, allowExtensions));
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false)); p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));
return future;
} }
/** /**
@ -158,9 +155,10 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
* Web Socket frame that was received * Web Socket frame that was received
*/ */
@Override @Override
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) { public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
ChannelFuture f = channel.write(frame); ChannelFuture f = channel.write(frame);
f.addListener(ChannelFutureListener.CLOSE); f.addListener(ChannelFutureListener.CLOSE);
return f;
} }
} }

View File

@ -30,9 +30,9 @@ public class WebSocketServerHandshakerFactory {
private final String webSocketURL; private final String webSocketURL;
private final String subProtocols; private final String subprotocols;
private boolean allowExtensions; private final boolean allowExtensions;
/** /**
* Constructor specifying the destination web socket location * Constructor specifying the destination web socket location
@ -40,14 +40,14 @@ public class WebSocketServerHandshakerFactory {
* @param webSocketURL * @param webSocketURL
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be * URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL. * sent to this URL.
* @param subProtocols * @param subprotocols
* CSV of supported protocols. Null if sub protocols not supported. * CSV of supported protocols. Null if sub protocols not supported.
* @param allowExtensions * @param allowExtensions
* Allow extensions to be used in the reserved bits of the web socket frame * Allow extensions to be used in the reserved bits of the web socket frame
*/ */
public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) { public WebSocketServerHandshakerFactory(String webSocketURL, String subprotocols, boolean allowExtensions) {
this.webSocketURL = webSocketURL; this.webSocketURL = webSocketURL;
this.subProtocols = subProtocols; this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions; this.allowExtensions = allowExtensions;
} }
@ -62,19 +62,17 @@ public class WebSocketServerHandshakerFactory {
String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION); String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
if (version != null) { if (version != null) {
if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) { if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
// Version 13 of the wire protocol - assume version 17 of the // Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
// specification. return new WebSocketServerHandshaker13(webSocketURL, subprotocols, allowExtensions);
return new WebSocketServerHandshaker13(webSocketURL, subProtocols, this.allowExtensions);
} else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) { } else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
// Version 8 of the wire protocol - assume version 10 of the // Version 8 of the wire protocol - version 10 of the draft hybi specification.
// specification. return new WebSocketServerHandshaker08(webSocketURL, subprotocols, allowExtensions);
return new WebSocketServerHandshaker08(webSocketURL, subProtocols, this.allowExtensions);
} else { } else {
return null; return null;
} }
} else { } else {
// Assume version 00 where version header was not specified // Assume version 00 where version header was not specified
return new WebSocketServerHandshaker00(webSocketURL, subProtocols); return new WebSocketServerHandshaker00(webSocketURL, subprotocols);
} }
} }
@ -85,8 +83,9 @@ public class WebSocketServerHandshakerFactory {
* Channel * Channel
*/ */
public void sendUnsupportedWebSocketVersionResponse(Channel channel) { public void sendUnsupportedWebSocketVersionResponse(Channel channel) {
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101, HttpResponse res = new DefaultHttpResponse(
"Switching Protocols")); HttpVersion.HTTP_1_1,
HttpResponseStatus.SWITCHING_PROTOCOLS);
res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED); res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);
res.setHeader(Names.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue()); res.setHeader(Names.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue());
channel.write(res); channel.write(res);

View File

@ -0,0 +1,109 @@
/*
* Copyright 2011 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 org.jboss.netty.handler.codec.http.websocketx;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.handler.codec.base64.Base64;
import org.jboss.netty.util.CharsetUtil;
/**
* TODO Document me.
*/
final class WebSocketUtil {
/**
* Performs an MD5 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
static byte[] md5(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("MD5 not supported on this platform");
}
}
/**
* Performs an SHA-1 hash
*
* @param bytes
* Data to hash
* @return Hashed data
*/
static byte[] sha1(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
return md.digest(bytes);
} catch (NoSuchAlgorithmException e) {
throw new InternalError("SHA-1 not supported on this platform");
}
}
/**
* Base 64 encoding
*
* @param bytes
* Bytes to encode
* @return encoded string
*/
static String base64(byte[] bytes) {
ChannelBuffer hashed = ChannelBuffers.wrappedBuffer(bytes);
return Base64.encode(hashed).toString(CharsetUtil.UTF_8);
}
/**
* Creates some random bytes
*
* @param size
* Number of random bytes to create
* @return random bytes
*/
static byte[] randomBytes(int size) {
byte[] bytes = new byte[size];
for (int i = 0; i < size; i++) {
bytes[i] = (byte) randomNumber(0, 255);
}
return bytes;
}
/**
* Generates a random number
*
* @param min
* Minimum value
* @param max
* Maximum value
* @return Random number
*/
static int randomNumber(int min, int max) {
return (int) (Math.random() * max + min);
}
private WebSocketUtil() {
// Unused
}
}

View File

@ -17,7 +17,11 @@ package org.jboss.netty.handler.codec.http.websocketx;
/** /**
* <p> * <p>
* Version of the web socket wire protocol. * Versions of the web socket specification.
* </p>
* <p>
* A specification is tied to one wire protocol version but a protocol version may have use by more than 1 version of
* the specification.
* </p> * </p>
*/ */
public enum WebSocketVersion { public enum WebSocketVersion {
@ -42,6 +46,9 @@ public enum WebSocketVersion {
*/ */
V13; V13;
/**
* @return Value for HTTP Header 'Sec-WebSocket-Version'
*/
public String toHttpHeaderValue() { public String toHttpHeaderValue() {
if (this == V00) { if (this == V00) {
return "0"; return "0";
@ -50,6 +57,6 @@ public enum WebSocketVersion {
} else if (this == V13) { } else if (this == V13) {
return "13"; return "13";
} }
throw new IllegalArgumentException(this.toString() + " cannot be converted to a HttpHeaderValue."); throw new IllegalStateException("Unknown web socket version: " + this);
} }
} }

View File

@ -27,11 +27,6 @@
* </ul> * </ul>
* </p> * </p>
* <p> * <p>
* In the future, as the specification develops, more versions will be supported.
* This contrasts the <tt>org.jboss.netty.handler.codec.http.websocket</tt> package which only
* supports draft-ietf-hybi-thewebsocketprotocol-00.
* </p>
* <p>
* For the detailed instruction on adding add Web Socket support to your HTTP * For the detailed instruction on adding add Web Socket support to your HTTP
* server, take a look into the <tt>WebSocketServerX</tt> example located in the * server, take a look into the <tt>WebSocketServerX</tt> example located in the
* {@code org.jboss.netty.example.http.websocket} package. * {@code org.jboss.netty.example.http.websocket} package.