Clean up the new WebSocket package

This commit is contained in:
Trustin Lee 2012-01-19 13:12:45 +09:00
parent 59e5f2f262
commit c1aa8b4c7b
31 changed files with 414 additions and 436 deletions

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 io.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 io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers; import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.base64.Base64; import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponse;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
@ -32,35 +33,44 @@ import io.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;
public WebSocketClientHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol, /**
* Base constructor
*
* @param webSocketUrl
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
* sent to this URL.
* @param version
* Version of web socket specification to use to connect to the server
* @param subprotocol
* Sub protocol request sent to the server.
* @param customHeaders
* Map of custom headers to add to the client request
*/
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;
} }
/** /**
@ -70,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 io.netty.buffer.ChannelBuffers; import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaders.Names; import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values; import io.netty.handler.codec.http.HttpHeaders.Values;
@ -55,14 +56,14 @@ 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);
} }
@ -87,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;
@ -110,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);
@ -123,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();
@ -140,8 +141,8 @@ 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) {
@ -152,9 +153,11 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
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;
} }
/** /**
@ -180,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)) {
@ -205,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;
@ -237,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 io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaders.Names; import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values; import io.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
@ -133,9 +134,11 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
} }
} }
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;
} }
/** /**
@ -158,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());
@ -178,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 io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.DefaultHttpRequest;
import io.netty.handler.codec.http.HttpHeaders.Names; import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values; import io.netty.handler.codec.http.HttpHeaders.Values;
@ -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
@ -132,9 +133,12 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
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,7 +31,7 @@ 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
@ -39,16 +39,16 @@ public class WebSocketClientHandshakerFactory {
* Custom HTTP headers to send during the handshake * 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 " + version.toString() + " not supported."); throw new WebSocketHandshakeException("Protocol version " + version.toString() + " not supported.");

View File

@ -18,7 +18,7 @@ package io.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,10 +17,13 @@ package io.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 io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers; import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.base64.Base64; import io.netty.handler.codec.base64.Base64;
import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequest;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
@ -30,55 +33,54 @@ import io.netty.util.CharsetUtil;
*/ */
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);
public void setSubProtocols(String subProtocols) { }
this.subProtocols = subProtocols; return ret;
} }
/** /**
@ -88,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
* *
@ -99,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
@ -111,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 io.netty.handler.codec.http.websocketx; package io.netty.handler.codec.http.websocketx;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION; import static io.netty.handler.codec.http.HttpHeaders.Names.*;
import static io.netty.handler.codec.http.HttpHeaders.Names.ORIGIN; import static io.netty.handler.codec.http.HttpHeaders.Values.*;
import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1; import static io.netty.handler.codec.http.HttpVersion.*;
import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY2;
import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_LOCATION;
import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_ORIGIN;
import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_PROTOCOL;
import static io.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_LOCATION;
import static io.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_ORIGIN;
import static io.netty.handler.codec.http.HttpHeaders.Names.WEBSOCKET_PROTOCOL;
import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.security.NoSuchAlgorithmException;
import io.netty.buffer.ChannelBuffer; import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers; import io.netty.buffer.ChannelBuffers;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseEncoder; import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpHeaders.Values;
import io.netty.logging.InternalLogger; import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory; import io.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);
} }
/** /**
@ -117,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 WS Version 00 server 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
@ -146,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.
@ -162,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));
} }
} }
@ -179,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;
} }
/** /**
@ -193,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,23 +15,21 @@
*/ */
package io.netty.handler.codec.http.websocketx; package io.netty.handler.codec.http.websocketx;
import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; import static io.netty.handler.codec.http.HttpHeaders.Values.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static io.netty.handler.codec.http.HttpVersion.*;
import java.security.NoSuchAlgorithmException;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.HttpChunkAggregator;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseEncoder; import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.logging.InternalLogger; import io.netty.logging.InternalLogger;
import io.netty.logging.InternalLoggerFactory; import io.netty.logging.InternalLoggerFactory;
import io.netty.util.CharsetUtil; import io.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 8 server 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 8 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,8 +18,6 @@ package io.netty.handler.codec.http.websocketx;
import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET; import static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import java.security.NoSuchAlgorithmException;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
@ -50,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
@ -58,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;
} }
@ -106,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 server 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;
} }
/** /**
@ -159,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;
} }
@ -63,16 +63,16 @@ public class WebSocketServerHandshakerFactory {
if (version != null) { if (version != null) {
if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) { if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
// Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification). // Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
return new WebSocketServerHandshaker13(webSocketURL, subProtocols, this.allowExtensions); return new WebSocketServerHandshaker13(webSocketURL, subprotocols, allowExtensions);
} else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) { } else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
// Version 8 of the wire protocol - version 10 of the draft hybi specification. // Version 8 of the wire protocol - version 10 of the draft hybi specification.
return new WebSocketServerHandshaker08(webSocketURL, subProtocols, this.allowExtensions); return new WebSocketServerHandshaker08(webSocketURL, subprotocols, 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);
} }
} }
@ -83,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 io.netty.handler.codec.http.websocketx;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import io.netty.buffer.ChannelBuffer;
import io.netty.buffer.ChannelBuffers;
import io.netty.handler.codec.base64.Base64;
import io.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

@ -57,6 +57,6 @@ public enum WebSocketVersion {
} else if (this == V13) { } else if (this == V13) {
return "13"; return "13";
} }
throw new IllegalArgumentException(this.toString() + " does not have a HttpHeaderValue."); throw new IllegalStateException("Unknown web socket version: " + this);
} }
} }

View File

@ -76,7 +76,7 @@ public class WebSocketServerHandshaker00Test {
req.setContent(buffer); req.setContent(buffer);
WebSocketServerHandshaker00 handsaker = new WebSocketServerHandshaker00("ws://example.com/chat", "chat"); WebSocketServerHandshaker00 handsaker = new WebSocketServerHandshaker00("ws://example.com/chat", "chat");
handsaker.performOpeningHandshake(channelMock, req); handsaker.handshake(channelMock, req);
Assert.assertEquals("ws://example.com/chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_LOCATION)); Assert.assertEquals("ws://example.com/chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_LOCATION));
Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL)); Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));

View File

@ -69,7 +69,7 @@ public class WebSocketServerHandshaker08Test {
req.setHeader(Names.SEC_WEBSOCKET_VERSION, "8"); req.setHeader(Names.SEC_WEBSOCKET_VERSION, "8");
WebSocketServerHandshaker08 handsaker = new WebSocketServerHandshaker08("ws://example.com/chat", "chat", false); WebSocketServerHandshaker08 handsaker = new WebSocketServerHandshaker08("ws://example.com/chat", "chat", false);
handsaker.performOpeningHandshake(channelMock, req); handsaker.handshake(channelMock, req);
Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT)); Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT));
Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL)); Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));

View File

@ -68,7 +68,7 @@ public class WebSocketServerHandshaker13Test {
req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat"); req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat");
req.setHeader(Names.SEC_WEBSOCKET_VERSION, "13"); req.setHeader(Names.SEC_WEBSOCKET_VERSION, "13");
WebSocketServerHandshaker13 handsaker = new WebSocketServerHandshaker13("ws://example.com/chat", "chat", false); WebSocketServerHandshaker13 handsaker = new WebSocketServerHandshaker13("ws://example.com/chat", "chat", false);
handsaker.performOpeningHandshake(channelMock, req); handsaker.handshake(channelMock, req);
Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT)); Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT));
Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL)); Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));

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

@ -97,21 +97,17 @@ public class WebSocketClient {
Channel ch = future.getChannel(); Channel ch = future.getChannel();
handshaker.performOpeningHandshake(ch); handshaker.handshake(ch).awaitUninterruptibly();
Thread.sleep(1000);
// Send 10 messages and wait for responses // Send 10 messages and wait for responses
System.out.println("WebSocket Client sending message"); System.out.println("WebSocket Client sending message");
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
ch.write(new TextWebSocketFrame("Message #" + i)); ch.write(new TextWebSocketFrame("Message #" + i));
} }
Thread.sleep(1000);
// Ping // Ping
System.out.println("WebSocket Client sending ping"); System.out.println("WebSocket Client sending ping");
ch.write(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 }))); ch.write(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[]{1, 2, 3, 4, 5, 6})));
Thread.sleep(1000);
// Close // Close
System.out.println("WebSocket Client sending close"); System.out.println("WebSocket Client sending close");

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()));