Clean up the new WebSocket package
This commit is contained in:
parent
59e5f2f262
commit
c1aa8b4c7b
@ -27,7 +27,7 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
|
||||
* Creates a new empty binary frame.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public BinaryWebSocketFrame(ChannelBuffer binaryData) {
|
||||
this.setBinaryData(binaryData);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,9 +51,9 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
|
||||
* the content of the frame.
|
||||
*/
|
||||
public BinaryWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
this.setBinaryData(binaryData);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,7 +26,7 @@ public class CloseWebSocketFrame extends WebSocketFrame {
|
||||
* Creates a new empty close frame.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public CloseWebSocketFrame(boolean finalFragment, int rsv) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -31,7 +31,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
|
||||
* Creates a new empty continuation frame.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public ContinuationWebSocketFrame(ChannelBuffer binaryData) {
|
||||
this.setBinaryData(binaryData);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,9 +55,9 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
|
||||
* the content of the frame.
|
||||
*/
|
||||
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
this.setBinaryData(binaryData);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
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
|
||||
*/
|
||||
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData, String aggregatedText) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
this.setBinaryData(binaryData);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
setBinaryData(binaryData);
|
||||
this.aggregatedText = aggregatedText;
|
||||
}
|
||||
|
||||
@ -90,19 +90,19 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
|
||||
* text content of the frame.
|
||||
*/
|
||||
public ContinuationWebSocketFrame(boolean finalFragment, int rsv, String text) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
this.setText(text);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text data in this frame
|
||||
*/
|
||||
public String getText() {
|
||||
if (this.getBinaryData() == null) {
|
||||
if (getBinaryData() == 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) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
} else {
|
||||
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
|
||||
setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,8 @@ public class PingWebSocketFrame extends WebSocketFrame {
|
||||
* Creates a new empty ping frame.
|
||||
*/
|
||||
public PingWebSocketFrame() {
|
||||
this.setFinalFragment(true);
|
||||
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
setFinalFragment(true);
|
||||
setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,7 +38,7 @@ public class PingWebSocketFrame extends WebSocketFrame {
|
||||
* the content of the frame.
|
||||
*/
|
||||
public PingWebSocketFrame(ChannelBuffer binaryData) {
|
||||
this.setBinaryData(binaryData);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,9 +52,9 @@ public class PingWebSocketFrame extends WebSocketFrame {
|
||||
* the content of the frame.
|
||||
*/
|
||||
public PingWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
this.setBinaryData(binaryData);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -27,7 +27,7 @@ public class PongWebSocketFrame extends WebSocketFrame {
|
||||
* Creates a new empty pong frame.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
public PongWebSocketFrame(ChannelBuffer binaryData) {
|
||||
this.setBinaryData(binaryData);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,9 +51,9 @@ public class PongWebSocketFrame extends WebSocketFrame {
|
||||
* the content of the frame.
|
||||
*/
|
||||
public PongWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
this.setBinaryData(binaryData);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -28,7 +28,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
||||
* Creates a new empty text frame.
|
||||
*/
|
||||
public TextWebSocketFrame() {
|
||||
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,9 +39,9 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
||||
*/
|
||||
public TextWebSocketFrame(String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
} 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
|
||||
*/
|
||||
public TextWebSocketFrame(ChannelBuffer binaryData) {
|
||||
this.setBinaryData(binaryData);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,12 +66,12 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
||||
* String to put in the frame
|
||||
*/
|
||||
public TextWebSocketFrame(boolean finalFragment, int rsv, String text) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
if (text == null || text.isEmpty()) {
|
||||
this.setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
setBinaryData(ChannelBuffers.EMPTY_BUFFER);
|
||||
} 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
|
||||
*/
|
||||
public TextWebSocketFrame(boolean finalFragment, int rsv, ChannelBuffer binaryData) {
|
||||
this.setFinalFragment(finalFragment);
|
||||
this.setRsv(rsv);
|
||||
this.setBinaryData(binaryData);
|
||||
setFinalFragment(finalFragment);
|
||||
setRsv(rsv);
|
||||
setBinaryData(binaryData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text data in this frame
|
||||
*/
|
||||
public String getText() {
|
||||
if (this.getBinaryData() == null) {
|
||||
if (getBinaryData() == 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) {
|
||||
throw new NullPointerException("text");
|
||||
}
|
||||
this.setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
|
||||
setBinaryData(ChannelBuffers.copiedBuffer(text, CharsetUtil.UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,7 +60,7 @@ final class UTF8Output {
|
||||
public void write(int b) {
|
||||
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];
|
||||
|
||||
|
@ -33,7 +33,7 @@ import io.netty.handler.codec.replay.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 boolean receivedClosingHandshake;
|
||||
@ -70,7 +70,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
|
||||
return decodeBinaryFrame(type, buffer);
|
||||
} else {
|
||||
// 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);
|
||||
|
||||
if (type == ((byte) 0xFF) && frameSize == 0) {
|
||||
if (type == (byte) 0xFF && frameSize == 0) {
|
||||
receivedClosingHandshake = true;
|
||||
return new CloseWebSocketFrame();
|
||||
}
|
||||
@ -100,7 +100,7 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
|
||||
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 rbytes = actualReadableBytes();
|
||||
int delimPos = buffer.indexOf(ridx, ridx + rbytes, (byte) 0xFF);
|
||||
|
@ -75,8 +75,8 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
|
||||
private int framePayloadBytesRead;
|
||||
private ChannelBuffer maskingKey;
|
||||
|
||||
private boolean allowExtensions;
|
||||
private boolean maskedPayload;
|
||||
private final boolean allowExtensions;
|
||||
private final boolean maskedPayload;
|
||||
private boolean receivedClosingHandshake;
|
||||
|
||||
public enum State {
|
||||
@ -129,12 +129,12 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
|
||||
boolean frameMasked = (b & 0x80) != 0;
|
||||
int framePayloadLen1 = b & 0x7F;
|
||||
|
||||
if (frameRsv != 0 && !this.allowExtensions) {
|
||||
if (frameRsv != 0 && !allowExtensions) {
|
||||
protocolViolation(channel, "RSV != 0 and no extension negotiated, RSV:" + frameRsv);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.maskedPayload && !frameMasked) {
|
||||
if (maskedPayload && !frameMasked) {
|
||||
protocolViolation(channel, "unmasked client to server frame");
|
||||
return null;
|
||||
}
|
||||
@ -211,7 +211,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
|
||||
|
||||
checkpoint(State.MASKING_KEY);
|
||||
case MASKING_KEY:
|
||||
if (this.maskedPayload) {
|
||||
if (maskedPayload) {
|
||||
maskingKey = buffer.readBytes(4);
|
||||
}
|
||||
checkpoint(State.PAYLOAD);
|
||||
@ -236,7 +236,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
|
||||
framePayload = channel.getConfig().getBufferFactory().getBuffer(toFrameLength(framePayloadLength));
|
||||
}
|
||||
framePayload.writeBytes(payloadBuffer);
|
||||
framePayloadBytesRead = framePayloadBytesRead + rbytes;
|
||||
framePayloadBytesRead += rbytes;
|
||||
|
||||
// Return null to wait for more bytes to arrive
|
||||
return null;
|
||||
@ -258,7 +258,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
|
||||
}
|
||||
|
||||
// Unmask data if needed
|
||||
if (this.maskedPayload) {
|
||||
if (maskedPayload) {
|
||||
unmask(framePayload);
|
||||
}
|
||||
|
||||
@ -269,7 +269,7 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
|
||||
} else if (frameOpcode == OPCODE_PONG) {
|
||||
return new PongWebSocketFrame(frameFinalFlag, frameRsv, framePayload);
|
||||
} else if (frameOpcode == OPCODE_CLOSE) {
|
||||
this.receivedClosingHandshake = true;
|
||||
receivedClosingHandshake = true;
|
||||
return new CloseWebSocketFrame(frameFinalFlag, frameRsv);
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
|
||||
private static final byte OPCODE_PING = 0x9;
|
||||
private static final byte OPCODE_PONG = 0xA;
|
||||
|
||||
private boolean maskPayload;
|
||||
private final boolean maskPayload;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -82,7 +82,7 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
|
||||
@Override
|
||||
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
|
||||
|
||||
byte[] mask = null;
|
||||
byte[] mask;
|
||||
|
||||
if (msg instanceof WebSocketFrame) {
|
||||
WebSocketFrame frame = (WebSocketFrame) msg;
|
||||
@ -118,7 +118,7 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
|
||||
if (frame.isFinalFragment()) {
|
||||
b0 |= 1 << 7;
|
||||
}
|
||||
b0 |= (frame.getRsv() % 8) << 4;
|
||||
b0 |= frame.getRsv() % 8 << 4;
|
||||
b0 |= opcode % 128;
|
||||
|
||||
ChannelBuffer header;
|
||||
@ -129,27 +129,27 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
|
||||
+ length);
|
||||
}
|
||||
|
||||
int maskLength = this.maskPayload ? 4 : 0;
|
||||
int maskLength = maskPayload ? 4 : 0;
|
||||
if (length <= 125) {
|
||||
header = ChannelBuffers.buffer(2 + maskLength);
|
||||
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);
|
||||
} else if (length <= 0xFFFF) {
|
||||
header = ChannelBuffers.buffer(4 + maskLength);
|
||||
header.writeByte(b0);
|
||||
header.writeByte(this.maskPayload ? 0xFE : 126);
|
||||
header.writeByte((length >>> 8) & 0xFF);
|
||||
header.writeByte(maskPayload ? 0xFE : 126);
|
||||
header.writeByte(length >>> 8 & 0xFF);
|
||||
header.writeByte(length & 0xFF);
|
||||
} else {
|
||||
header = ChannelBuffers.buffer(10 + maskLength);
|
||||
header.writeByte(b0);
|
||||
header.writeByte(this.maskPayload ? 0xFF : 127);
|
||||
header.writeByte(maskPayload ? 0xFF : 127);
|
||||
header.writeLong(length);
|
||||
}
|
||||
|
||||
// Write payload
|
||||
if (this.maskPayload) {
|
||||
if (maskPayload) {
|
||||
Integer random = (int) (Math.random() * Integer.MAX_VALUE);
|
||||
mask = ByteBuffer.allocate(4).putInt(random).array();
|
||||
header.writeBytes(mask);
|
||||
|
@ -23,6 +23,7 @@ import java.util.Map;
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.util.CharsetUtil;
|
||||
@ -32,35 +33,44 @@ import io.netty.util.CharsetUtil;
|
||||
*/
|
||||
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) {
|
||||
this.webSocketURL = webSocketURL;
|
||||
this.webSocketUrl = webSocketUrl;
|
||||
this.version = version;
|
||||
this.subProtocolRequest = subProtocol;
|
||||
expectedSubprotocol = subprotocol;
|
||||
this.customHeaders = customHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI to the web socket. e.g. "ws://myhost.com/path"
|
||||
*/
|
||||
public URI getWebSocketURL() {
|
||||
return webSocketURL;
|
||||
}
|
||||
|
||||
protected void setWebSocketURL(URI webSocketURL) {
|
||||
this.webSocketURL = webSocketURL;
|
||||
public URI getWebSocketUrl() {
|
||||
return webSocketUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,133 +80,50 @@ public abstract class WebSocketClientHandshaker {
|
||||
return version;
|
||||
}
|
||||
|
||||
protected void setVersion(WebSocketVersion version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to indicate if the opening handshake is complete
|
||||
*/
|
||||
public boolean isOpeningHandshakeCompleted() {
|
||||
return openingHandshakeCompleted;
|
||||
public boolean isHandshakeComplete() {
|
||||
return handshakeComplete;
|
||||
}
|
||||
|
||||
protected void setOpenningHandshakeCompleted(boolean openningHandshakeCompleted) {
|
||||
this.openingHandshakeCompleted = openningHandshakeCompleted;
|
||||
protected void setHandshakeComplete() {
|
||||
handshakeComplete = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sub protocol request sent to the server as specified in the constructor
|
||||
*/
|
||||
public String getSubProtocolRequest() {
|
||||
return subProtocolRequest;
|
||||
}
|
||||
|
||||
protected void setSubProtocolRequest(String subProtocolRequest) {
|
||||
this.subProtocolRequest = subProtocolRequest;
|
||||
public String getExpectedSubprotocol() {
|
||||
return expectedSubprotocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sub protocol response and sent by the server. Only available after end of handshake.
|
||||
*/
|
||||
public String getSubProtocolResponse() {
|
||||
return subProtocolResponse;
|
||||
public String getActualSubprotocol() {
|
||||
return actualSubprotocol;
|
||||
}
|
||||
|
||||
protected void setSubProtocolResponse(String subProtocolResponse) {
|
||||
this.subProtocolResponse = subProtocolResponse;
|
||||
protected void setActualSubprotocol(String actualSubprotocol) {
|
||||
this.actualSubprotocol = actualSubprotocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the opening handshake
|
||||
* Begins the opening handshake
|
||||
*
|
||||
* @param 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
|
||||
* Channel
|
||||
* @param response
|
||||
* HTTP response containing the closing handshake details
|
||||
*/
|
||||
public abstract void performClosingHandshake(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);
|
||||
}
|
||||
public abstract void finishHandshake(Channel channel, HttpResponse response);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import java.util.Map;
|
||||
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.handler.codec.http.DefaultHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||
@ -55,14 +56,14 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
* sent to this URL.
|
||||
* @param version
|
||||
* Version of web socket specification to use to connect to the server
|
||||
* @param subProtocol
|
||||
* @param subprotocol
|
||||
* Sub protocol request sent to the server.
|
||||
* @param customHeaders
|
||||
* 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) {
|
||||
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
|
||||
*/
|
||||
@Override
|
||||
public void performOpeningHandshake(Channel channel) {
|
||||
public ChannelFuture handshake(Channel channel) {
|
||||
// Make keys
|
||||
int spaces1 = createRandomNumber(1, 12);
|
||||
int spaces2 = createRandomNumber(1, 12);
|
||||
int spaces1 = WebSocketUtil.randomNumber(1, 12);
|
||||
int spaces2 = WebSocketUtil.randomNumber(1, 12);
|
||||
|
||||
int max1 = Integer.MAX_VALUE / spaces1;
|
||||
int max2 = Integer.MAX_VALUE / spaces2;
|
||||
|
||||
int number1 = createRandomNumber(0, max1);
|
||||
int number2 = createRandomNumber(0, max2);
|
||||
int number1 = WebSocketUtil.randomNumber(0, max1);
|
||||
int number2 = WebSocketUtil.randomNumber(0, max2);
|
||||
|
||||
int product1 = number1 * spaces1;
|
||||
int product2 = number2 * spaces2;
|
||||
@ -110,7 +111,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
key1 = insertSpaces(key1, spaces1);
|
||||
key2 = insertSpaces(key2, spaces2);
|
||||
|
||||
byte[] key3 = createRandomBytes(8);
|
||||
byte[] key3 = WebSocketUtil.randomBytes(8);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(4);
|
||||
buffer.putInt(number1);
|
||||
@ -123,10 +124,10 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
System.arraycopy(number1Array, 0, challenge, 0, 4);
|
||||
System.arraycopy(number2Array, 0, challenge, 4, 4);
|
||||
System.arraycopy(key3, 0, challenge, 8, 8);
|
||||
this.expectedChallengeResponseBytes = md5(challenge);
|
||||
expectedChallengeResponseBytes = WebSocketUtil.md5(challenge);
|
||||
|
||||
// Get path
|
||||
URI wsURL = this.getWebSocketURL();
|
||||
URI wsURL = getWebSocketUrl();
|
||||
String path = wsURL.getPath();
|
||||
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
|
||||
path = wsURL.getPath() + "?" + wsURL.getQuery();
|
||||
@ -140,8 +141,8 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
|
||||
request.addHeader(Names.SEC_WEBSOCKET_KEY1, key1);
|
||||
request.addHeader(Names.SEC_WEBSOCKET_KEY2, key2);
|
||||
if (this.getSubProtocolRequest() != null && !this.getSubProtocolRequest().equals("")) {
|
||||
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, this.getSubProtocolRequest());
|
||||
if (getExpectedSubprotocol() != null && !getExpectedSubprotocol().equals("")) {
|
||||
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, getExpectedSubprotocol());
|
||||
}
|
||||
|
||||
if (customHeaders != null) {
|
||||
@ -152,9 +153,11 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
|
||||
request.setContent(ChannelBuffers.copiedBuffer(key3));
|
||||
|
||||
channel.write(request);
|
||||
ChannelFuture future = channel.write(request);
|
||||
|
||||
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket00FrameEncoder());
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,7 +183,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
* @throws WebSocketHandshakeException
|
||||
*/
|
||||
@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");
|
||||
|
||||
if (!response.getStatus().equals(status)) {
|
||||
@ -205,28 +208,28 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
}
|
||||
|
||||
String protocol = response.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
|
||||
this.setSubProtocolResponse(protocol);
|
||||
setActualSubprotocol(protocol);
|
||||
|
||||
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket00FrameDecoder());
|
||||
|
||||
this.setOpenningHandshakeCompleted(true);
|
||||
setHandshakeComplete();
|
||||
}
|
||||
|
||||
private String insertRandomCharacters(String key) {
|
||||
int count = createRandomNumber(1, 12);
|
||||
int count = WebSocketUtil.randomNumber(1, 12);
|
||||
|
||||
char[] randomChars = new char[count];
|
||||
int randCount = 0;
|
||||
while (randCount < count) {
|
||||
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;
|
||||
randCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
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 part2 = key.substring(split);
|
||||
key = part1 + randomChars[i] + part2;
|
||||
@ -237,7 +240,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
||||
|
||||
private String insertSpaces(String key, int spaces) {
|
||||
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 part2 = key.substring(split);
|
||||
key = part1 + " " + part2;
|
||||
|
@ -19,6 +19,7 @@ import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.handler.codec.http.DefaultHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||
@ -50,7 +51,7 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
||||
|
||||
private static final String protocol = null;
|
||||
|
||||
private boolean allowExtensions;
|
||||
private final boolean allowExtensions;
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location and version to initiate
|
||||
@ -60,16 +61,16 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
||||
* sent to this URL.
|
||||
* @param version
|
||||
* Version of web socket specification to use to connect to the server
|
||||
* @param subProtocol
|
||||
* @param subprotocol
|
||||
* Sub protocol request sent to the server.
|
||||
* @param allowExtensions
|
||||
* Allow extensions to be used in the reserved bits of the web socket frame
|
||||
* @param customHeaders
|
||||
* 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) {
|
||||
super(webSocketURL, version, subProtocol, customHeaders);
|
||||
super(webSocketURL, version, subprotocol, customHeaders);
|
||||
this.allowExtensions = allowExtensions;
|
||||
}
|
||||
|
||||
@ -94,25 +95,25 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
||||
* Channel into which we can write our request
|
||||
*/
|
||||
@Override
|
||||
public void performOpeningHandshake(Channel channel) {
|
||||
public ChannelFuture handshake(Channel channel) {
|
||||
// Get path
|
||||
URI wsURL = this.getWebSocketURL();
|
||||
URI wsURL = getWebSocketUrl();
|
||||
String path = wsURL.getPath();
|
||||
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
|
||||
path = wsURL.getPath() + "?" + wsURL.getQuery();
|
||||
}
|
||||
|
||||
// Get 16 bit nonce and base 64 encode it
|
||||
byte[] nonce = createRandomBytes(16);
|
||||
String key = base64Encode(nonce);
|
||||
byte[] nonce = WebSocketUtil.randomBytes(16);
|
||||
String key = WebSocketUtil.base64(nonce);
|
||||
|
||||
String acceptSeed = key + MAGIC_GUID;
|
||||
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||
this.expectedChallengeResponseString = base64Encode(sha1);
|
||||
byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||
expectedChallengeResponseString = WebSocketUtil.base64(sha1);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("WS Version 08 Client Handshake key: %s. Expected response: %s.", key,
|
||||
this.expectedChallengeResponseString));
|
||||
expectedChallengeResponseString));
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,8 +161,8 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
||||
* @throws WebSocketHandshakeException
|
||||
*/
|
||||
@Override
|
||||
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
|
||||
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
|
||||
public void finishHandshake(Channel channel, HttpResponse response) {
|
||||
final HttpResponseStatus status = HttpResponseStatus.SWITCHING_PROTOCOLS;
|
||||
|
||||
if (!response.getStatus().equals(status)) {
|
||||
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);
|
||||
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,
|
||||
this.expectedChallengeResponseString));
|
||||
expectedChallengeResponseString));
|
||||
}
|
||||
|
||||
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
|
||||
new WebSocket08FrameDecoder(false, this.allowExtensions));
|
||||
new WebSocket08FrameDecoder(false, allowExtensions));
|
||||
|
||||
this.setOpenningHandshakeCompleted(true);
|
||||
setHandshakeComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.handler.codec.http.DefaultHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||
@ -50,7 +51,7 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||
|
||||
private static final String protocol = null;
|
||||
|
||||
private boolean allowExtensions;
|
||||
private final boolean allowExtensions;
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location and version to initiate
|
||||
@ -60,16 +61,16 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||
* sent to this URL.
|
||||
* @param version
|
||||
* Version of web socket specification to use to connect to the server
|
||||
* @param subProtocol
|
||||
* @param subprotocol
|
||||
* Sub protocol request sent to the server.
|
||||
* @param allowExtensions
|
||||
* Allow extensions to be used in the reserved bits of the web socket frame
|
||||
* @param customHeaders
|
||||
* 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) {
|
||||
super(webSocketURL, version, subProtocol, customHeaders);
|
||||
super(webSocketURL, version, subprotocol, customHeaders);
|
||||
this.allowExtensions = allowExtensions;
|
||||
}
|
||||
|
||||
@ -94,25 +95,25 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||
* Channel into which we can write our request
|
||||
*/
|
||||
@Override
|
||||
public void performOpeningHandshake(Channel channel) {
|
||||
public ChannelFuture handshake(Channel channel) {
|
||||
// Get path
|
||||
URI wsURL = this.getWebSocketURL();
|
||||
URI wsURL = getWebSocketUrl();
|
||||
String path = wsURL.getPath();
|
||||
if (wsURL.getQuery() != null && wsURL.getQuery().length() > 0) {
|
||||
path = wsURL.getPath() + "?" + wsURL.getQuery();
|
||||
}
|
||||
|
||||
// Get 16 bit nonce and base 64 encode it
|
||||
byte[] nonce = createRandomBytes(16);
|
||||
String key = base64Encode(nonce);
|
||||
byte[] nonce = WebSocketUtil.randomBytes(16);
|
||||
String key = WebSocketUtil.base64(nonce);
|
||||
|
||||
String acceptSeed = key + MAGIC_GUID;
|
||||
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||
this.expectedChallengeResponseString = base64Encode(sha1);
|
||||
byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||
expectedChallengeResponseString = WebSocketUtil.base64(sha1);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("WS Version 13 Client Handshake key: %s. Expected response: %s.", key,
|
||||
this.expectedChallengeResponseString));
|
||||
expectedChallengeResponseString));
|
||||
}
|
||||
|
||||
// Format request
|
||||
@ -132,9 +133,12 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||
request.addHeader(header, customHeaders.get(header));
|
||||
}
|
||||
}
|
||||
channel.write(request);
|
||||
|
||||
ChannelFuture future = channel.write(request);
|
||||
|
||||
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,8 +161,8 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||
* @throws WebSocketHandshakeException
|
||||
*/
|
||||
@Override
|
||||
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
|
||||
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
|
||||
public void finishHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
|
||||
final HttpResponseStatus status = HttpResponseStatus.SWITCHING_PROTOCOLS;
|
||||
|
||||
if (!response.getStatus().equals(status)) {
|
||||
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);
|
||||
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,
|
||||
this.expectedChallengeResponseString));
|
||||
expectedChallengeResponseString));
|
||||
}
|
||||
|
||||
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
|
||||
new WebSocket13FrameDecoder(false, this.allowExtensions));
|
||||
new WebSocket13FrameDecoder(false, allowExtensions));
|
||||
|
||||
this.setOpenningHandshakeCompleted(true);
|
||||
setHandshakeComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ public class WebSocketClientHandshakerFactory {
|
||||
* sent to this URL.
|
||||
* @param version
|
||||
* 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.
|
||||
* @param allowExtensions
|
||||
* 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
|
||||
* @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 {
|
||||
if (version == WebSocketVersion.V13) {
|
||||
return new WebSocketClientHandshaker13(webSocketURL, version, subProtocol, allowExtensions, customHeaders);
|
||||
return new WebSocketClientHandshaker13(webSocketURL, version, subprotocol, allowExtensions, customHeaders);
|
||||
}
|
||||
if (version == WebSocketVersion.V08) {
|
||||
return new WebSocketClientHandshaker08(webSocketURL, version, subProtocol, allowExtensions, customHeaders);
|
||||
return new WebSocketClientHandshaker08(webSocketURL, version, subprotocol, allowExtensions, customHeaders);
|
||||
}
|
||||
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.");
|
||||
|
@ -18,7 +18,7 @@ package io.netty.handler.codec.http.websocketx;
|
||||
/**
|
||||
* Exception during handshaking process
|
||||
*/
|
||||
public class WebSocketHandshakeException extends Exception {
|
||||
public class WebSocketHandshakeException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
@ -17,10 +17,13 @@ package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.util.CharsetUtil;
|
||||
@ -30,55 +33,54 @@ import io.netty.util.CharsetUtil;
|
||||
*/
|
||||
public abstract class WebSocketServerHandshaker {
|
||||
|
||||
private String webSocketURL;
|
||||
private final String webSocketUrl;
|
||||
|
||||
private String subProtocols;
|
||||
private final String[] subprotocols;
|
||||
|
||||
private String[] subProtocolsArray;
|
||||
|
||||
private WebSocketVersion version = WebSocketVersion.UNKNOWN;
|
||||
private final WebSocketVersion version;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* sent to this URL.
|
||||
* @param subProtocols
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols. Null if sub protocols not supported.
|
||||
*/
|
||||
public WebSocketServerHandshaker(String webSocketURL, String subProtocols) {
|
||||
this.webSocketURL = webSocketURL;
|
||||
this.subProtocols = subProtocols;
|
||||
|
||||
if (this.subProtocols != null) {
|
||||
this.subProtocolsArray = subProtocols.split(",");
|
||||
for (int i = 0; i < this.subProtocolsArray.length; i++) {
|
||||
this.subProtocolsArray[i] = this.subProtocolsArray[i].trim();
|
||||
protected WebSocketServerHandshaker(
|
||||
WebSocketVersion version, String webSocketUrl, String subprotocols) {
|
||||
this.version = version;
|
||||
this.webSocketUrl = webSocketUrl;
|
||||
if (subprotocols != null) {
|
||||
String[] subprotocolArray = subprotocols.split(",");
|
||||
for (int i = 0; i < subprotocolArray.length; i++) {
|
||||
subprotocolArray[i] = subprotocolArray[i].trim();
|
||||
}
|
||||
this.subprotocols = subprotocolArray;
|
||||
} else {
|
||||
this.subprotocols = new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the web socket
|
||||
*/
|
||||
public String getWebSocketURL() {
|
||||
return webSocketURL;
|
||||
}
|
||||
|
||||
public void setWebSocketURL(String webSocketURL) {
|
||||
this.webSocketURL = webSocketURL;
|
||||
public String getWebSocketUrl() {
|
||||
return webSocketUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the CSV of supported sub protocols
|
||||
*/
|
||||
public String getSubProtocols() {
|
||||
return subProtocols;
|
||||
}
|
||||
|
||||
public void setSubProtocols(String subProtocols) {
|
||||
this.subProtocols = subProtocols;
|
||||
public Set<String> getSubprotocols() {
|
||||
Set<String> ret = new LinkedHashSet<String>();
|
||||
for (String p: this.subprotocols) {
|
||||
ret.add(p);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,10 +90,6 @@ public abstract class WebSocketServerHandshaker {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(WebSocketVersion version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the opening handshake
|
||||
*
|
||||
@ -99,9 +97,8 @@ public abstract class WebSocketServerHandshaker {
|
||||
* Channel
|
||||
* @param req
|
||||
* HTTP Request
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public abstract void performOpeningHandshake(Channel channel, HttpRequest req);
|
||||
public abstract ChannelFuture handshake(Channel channel, HttpRequest req);
|
||||
|
||||
/**
|
||||
* Performs the closing handshake
|
||||
@ -111,71 +108,27 @@ public abstract class WebSocketServerHandshaker {
|
||||
* @param frame
|
||||
* Closing Frame that was received
|
||||
*/
|
||||
public abstract void performClosingHandshake(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);
|
||||
}
|
||||
public abstract ChannelFuture close(Channel channel, CloseWebSocketFrame frame);
|
||||
|
||||
/**
|
||||
* Selects the first matching supported sub protocol
|
||||
*
|
||||
* @param requestedSubProtocol
|
||||
* @param requestedSubprotocols
|
||||
* CSV of protocols to be supported. e.g. "chat, superchat"
|
||||
* @return First matching supported sub protocol. Null if not found.
|
||||
*/
|
||||
protected String selectSubProtocol(String requestedSubProtocol) {
|
||||
if (requestedSubProtocol == null || this.subProtocolsArray == null) {
|
||||
protected String selectSubprotocol(String requestedSubprotocols) {
|
||||
if (requestedSubprotocols == null || subprotocols.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] requesteSubProtocolsArray = requestedSubProtocol.split(",");
|
||||
for (int i = 0; i < requesteSubProtocolsArray.length; i++) {
|
||||
String requesteSubProtocol = requesteSubProtocolsArray[i].trim();
|
||||
String[] requesteSubprotocolArray = requestedSubprotocols.split(",");
|
||||
for (String p: requesteSubprotocolArray) {
|
||||
String requestedSubprotocol = p.trim();
|
||||
|
||||
for (String supportedSubProtocol : this.subProtocolsArray) {
|
||||
if (requesteSubProtocol.equals(supportedSubProtocol)) {
|
||||
return requesteSubProtocol;
|
||||
for (String supportedSubprotocol: subprotocols) {
|
||||
if (requestedSubprotocol.equals(supportedSubprotocol)) {
|
||||
return requestedSubprotocol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,34 +15,24 @@
|
||||
*/
|
||||
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.ORIGIN;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Names.SEC_WEBSOCKET_KEY1;
|
||||
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 static io.netty.handler.codec.http.HttpHeaders.Names.*;
|
||||
import static io.netty.handler.codec.http.HttpHeaders.Values.*;
|
||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
|
||||
import io.netty.buffer.ChannelBuffer;
|
||||
import io.netty.buffer.ChannelBuffers;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
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.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.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
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.InternalLoggerFactory;
|
||||
|
||||
@ -66,11 +56,11 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
* @param webSocketURL
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||
* sent to this URL.
|
||||
* @param subProtocols
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols
|
||||
*/
|
||||
public WebSocketServerHandshaker00(String webSocketURL, String subProtocols) {
|
||||
super(webSocketURL, subProtocols);
|
||||
public WebSocketServerHandshaker00(String webSocketURL, String subprotocols) {
|
||||
super(WebSocketVersion.V00, webSocketURL, subprotocols);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,20 +107,18 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
* Channel
|
||||
* @param req
|
||||
* HTTP request
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
@Override
|
||||
public void performOpeningHandshake(Channel channel, HttpRequest req) {
|
||||
public ChannelFuture handshake(Channel channel, HttpRequest req) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Channel %s WS Version 00 server handshake", channel.getId()));
|
||||
}
|
||||
this.setVersion(WebSocketVersion.V00);
|
||||
|
||||
// Serve the WebSocket handshake request.
|
||||
if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
|
||||
|| !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
|
||||
@ -146,10 +134,10 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
if (isHixie76) {
|
||||
// New handshake method with a challenge:
|
||||
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);
|
||||
if (protocol != null) {
|
||||
res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubProtocol(protocol));
|
||||
res.addHeader(SEC_WEBSOCKET_PROTOCOL, selectSubprotocol(protocol));
|
||||
}
|
||||
|
||||
// Calculate the answer of the challenge.
|
||||
@ -162,15 +150,15 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
input.writeInt(a);
|
||||
input.writeInt(b);
|
||||
input.writeLong(c);
|
||||
ChannelBuffer output = ChannelBuffers.wrappedBuffer(this.md5(input.array()));
|
||||
ChannelBuffer output = ChannelBuffers.wrappedBuffer(WebSocketUtil.md5(input.array()));
|
||||
res.setContent(output);
|
||||
} else {
|
||||
// Old Hixie 75 handshake method with no challenge:
|
||||
res.addHeader(WEBSOCKET_ORIGIN, req.getHeader(ORIGIN));
|
||||
res.addHeader(WEBSOCKET_LOCATION, this.getWebSocketURL());
|
||||
res.addHeader(WEBSOCKET_LOCATION, getWebSocketUrl());
|
||||
String protocol = req.getHeader(WEBSOCKET_PROTOCOL);
|
||||
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.replace(HttpRequestDecoder.class, "wsdecoder", new WebSocket00FrameDecoder());
|
||||
|
||||
channel.write(res);
|
||||
ChannelFuture future = channel.write(res);
|
||||
|
||||
p.replace(HttpResponseEncoder.class, "wsencoder", new WebSocket00FrameEncoder());
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -193,7 +183,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
||||
* Web Socket frame that was received
|
||||
*/
|
||||
@Override
|
||||
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
|
||||
channel.write(frame);
|
||||
public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
|
||||
return channel.write(frame);
|
||||
}
|
||||
}
|
||||
|
@ -15,23 +15,21 @@
|
||||
*/
|
||||
package io.netty.handler.codec.http.websocketx;
|
||||
|
||||
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 static io.netty.handler.codec.http.HttpHeaders.Values.*;
|
||||
import static io.netty.handler.codec.http.HttpVersion.*;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
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.HttpChunkAggregator;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||
import io.netty.logging.InternalLogger;
|
||||
import io.netty.logging.InternalLoggerFactory;
|
||||
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";
|
||||
|
||||
private boolean allowExtensions;
|
||||
private final boolean allowExtensions;
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
@ -57,13 +55,13 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||
* @param webSocketURL
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||
* sent to this URL.
|
||||
* @param subProtocols
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols
|
||||
* @param allowExtensions
|
||||
* Allow extensions to be used in the reserved bits of the web socket frame
|
||||
*/
|
||||
public WebSocketServerHandshaker08(String webSocketURL, String subProtocols, boolean allowExtensions) {
|
||||
super(webSocketURL, subProtocols);
|
||||
public WebSocketServerHandshaker08(String webSocketURL, String subprotocols, boolean allowExtensions) {
|
||||
super(WebSocketVersion.V08, webSocketURL, subprotocols);
|
||||
this.allowExtensions = allowExtensions;
|
||||
}
|
||||
|
||||
@ -105,48 +103,46 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||
* Channel
|
||||
* @param req
|
||||
* HTTP request
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
@Override
|
||||
public void performOpeningHandshake(Channel channel, HttpRequest req) {
|
||||
public ChannelFuture handshake(Channel channel, HttpRequest req) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
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"));
|
||||
this.setVersion(WebSocketVersion.V08);
|
||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
|
||||
|
||||
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
|
||||
if (key == null) {
|
||||
res.setStatus(HttpResponseStatus.BAD_REQUEST);
|
||||
return;
|
||||
throw new WebSocketHandshakeException("not a WebSocket request: missing key");
|
||||
}
|
||||
String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
|
||||
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||
String accept = base64Encode(sha1);
|
||||
byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||
String accept = WebSocketUtil.base64(sha1);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
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.CONNECTION, Names.UPGRADE);
|
||||
res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
|
||||
String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
|
||||
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.
|
||||
ChannelPipeline p = channel.getPipeline();
|
||||
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));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,9 +154,10 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||
* Web Socket frame that was received
|
||||
*/
|
||||
@Override
|
||||
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
|
||||
public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
|
||||
ChannelFuture f = channel.write(frame);
|
||||
f.addListener(ChannelFutureListener.CLOSE);
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.HttpVersion.HTTP_1_1;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
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";
|
||||
|
||||
private boolean allowExtensions;
|
||||
private final boolean allowExtensions;
|
||||
|
||||
/**
|
||||
* Constructor specifying the destination web socket location
|
||||
@ -58,13 +56,13 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||
* @param webSocketURL
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||
* sent to this URL.
|
||||
* @param subProtocols
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols
|
||||
* @param allowExtensions
|
||||
* Allow extensions to be used in the reserved bits of the web socket frame
|
||||
*/
|
||||
public WebSocketServerHandshaker13(String webSocketURL, String subProtocols, boolean allowExtensions) {
|
||||
super(webSocketURL, subProtocols);
|
||||
public WebSocketServerHandshaker13(String webSocketURL, String subprotocols, boolean allowExtensions) {
|
||||
super(WebSocketVersion.V13, webSocketURL, subprotocols);
|
||||
this.allowExtensions = allowExtensions;
|
||||
}
|
||||
|
||||
@ -106,48 +104,46 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||
* Channel
|
||||
* @param req
|
||||
* HTTP request
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
@Override
|
||||
public void performOpeningHandshake(Channel channel, HttpRequest req) {
|
||||
public ChannelFuture handshake(Channel channel, HttpRequest req) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
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"));
|
||||
this.setVersion(WebSocketVersion.V13);
|
||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.SWITCHING_PROTOCOLS);
|
||||
|
||||
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
|
||||
if (key == null) {
|
||||
res.setStatus(HttpResponseStatus.BAD_REQUEST);
|
||||
return;
|
||||
throw new WebSocketHandshakeException("not a WebSocket request: missing key");
|
||||
}
|
||||
String acceptSeed = key + WEBSOCKET_13_ACCEPT_GUID;
|
||||
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||
String accept = base64Encode(sha1);
|
||||
byte[] sha1 = WebSocketUtil.sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||
String accept = WebSocketUtil.base64(sha1);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
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.CONNECTION, Names.UPGRADE);
|
||||
res.addHeader(Names.SEC_WEBSOCKET_ACCEPT, accept);
|
||||
String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
|
||||
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.
|
||||
ChannelPipeline p = channel.getPipeline();
|
||||
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));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,9 +155,10 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||
* Web Socket frame that was received
|
||||
*/
|
||||
@Override
|
||||
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
|
||||
public ChannelFuture close(Channel channel, CloseWebSocketFrame frame) {
|
||||
ChannelFuture f = channel.write(frame);
|
||||
f.addListener(ChannelFutureListener.CLOSE);
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ public class WebSocketServerHandshakerFactory {
|
||||
|
||||
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
|
||||
@ -40,14 +40,14 @@ public class WebSocketServerHandshakerFactory {
|
||||
* @param webSocketURL
|
||||
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||
* sent to this URL.
|
||||
* @param subProtocols
|
||||
* @param subprotocols
|
||||
* CSV of supported protocols. Null if sub protocols not supported.
|
||||
* @param allowExtensions
|
||||
* 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.subProtocols = subProtocols;
|
||||
this.subprotocols = subprotocols;
|
||||
this.allowExtensions = allowExtensions;
|
||||
}
|
||||
|
||||
@ -63,16 +63,16 @@ public class WebSocketServerHandshakerFactory {
|
||||
if (version != null) {
|
||||
if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
|
||||
// Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
|
||||
return new WebSocketServerHandshaker13(webSocketURL, subProtocols, this.allowExtensions);
|
||||
return new WebSocketServerHandshaker13(webSocketURL, subprotocols, allowExtensions);
|
||||
} else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
|
||||
// Version 8 of the wire protocol - version 10 of the draft hybi specification.
|
||||
return new WebSocketServerHandshaker08(webSocketURL, subProtocols, this.allowExtensions);
|
||||
return new WebSocketServerHandshaker08(webSocketURL, subprotocols, allowExtensions);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// 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
|
||||
*/
|
||||
public void sendUnsupportedWebSocketVersionResponse(Channel channel) {
|
||||
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(101,
|
||||
"Switching Protocols"));
|
||||
HttpResponse res = new DefaultHttpResponse(
|
||||
HttpVersion.HTTP_1_1,
|
||||
HttpResponseStatus.SWITCHING_PROTOCOLS);
|
||||
res.setStatus(HttpResponseStatus.UPGRADE_REQUIRED);
|
||||
res.setHeader(Names.SEC_WEBSOCKET_VERSION, WebSocketVersion.V13.toHttpHeaderValue());
|
||||
channel.write(res);
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -57,6 +57,6 @@ public enum WebSocketVersion {
|
||||
} else if (this == V13) {
|
||||
return "13";
|
||||
}
|
||||
throw new IllegalArgumentException(this.toString() + " does not have a HttpHeaderValue.");
|
||||
throw new IllegalStateException("Unknown web socket version: " + this);
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ public class WebSocketServerHandshaker00Test {
|
||||
req.setContent(buffer);
|
||||
|
||||
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("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
|
||||
|
@ -69,7 +69,7 @@ public class WebSocketServerHandshaker08Test {
|
||||
req.setHeader(Names.SEC_WEBSOCKET_VERSION, "8");
|
||||
|
||||
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("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
|
||||
|
@ -68,7 +68,7 @@ public class WebSocketServerHandshaker13Test {
|
||||
req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat");
|
||||
req.setHeader(Names.SEC_WEBSOCKET_VERSION, "13");
|
||||
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("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
|
||||
|
@ -76,7 +76,7 @@ public class AutobahnServerHandler extends SimpleChannelUpstreamHandler {
|
||||
if (this.handshaker == null) {
|
||||
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
|
||||
} 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()));
|
||||
|
||||
if (frame instanceof CloseWebSocketFrame) {
|
||||
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame);
|
||||
this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
|
||||
} else if (frame instanceof PingWebSocketFrame) {
|
||||
ctx.getChannel().write(
|
||||
new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
|
||||
|
@ -97,21 +97,17 @@ public class WebSocketClient {
|
||||
|
||||
Channel ch = future.getChannel();
|
||||
|
||||
handshaker.performOpeningHandshake(ch);
|
||||
|
||||
Thread.sleep(1000);
|
||||
handshaker.handshake(ch).awaitUninterruptibly();
|
||||
|
||||
// Send 10 messages and wait for responses
|
||||
System.out.println("WebSocket Client sending message");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ch.write(new TextWebSocketFrame("Message #" + i));
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Ping
|
||||
System.out.println("WebSocket Client sending ping");
|
||||
ch.write(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[] { 1, 2, 3, 4, 5, 6 })));
|
||||
Thread.sleep(1000);
|
||||
ch.write(new PingWebSocketFrame(ChannelBuffers.copiedBuffer(new byte[]{1, 2, 3, 4, 5, 6})));
|
||||
|
||||
// Close
|
||||
System.out.println("WebSocket Client sending close");
|
||||
|
@ -52,8 +52,8 @@ public class WebSocketClientHandler extends SimpleChannelUpstreamHandler {
|
||||
@Override
|
||||
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
|
||||
Channel ch = ctx.getChannel();
|
||||
if (!handshaker.isOpeningHandshakeCompleted()) {
|
||||
handshaker.performClosingHandshake(ch, (HttpResponse) e.getMessage());
|
||||
if (!handshaker.isHandshakeComplete()) {
|
||||
handshaker.finishHandshake(ch, (HttpResponse) e.getMessage());
|
||||
System.out.println("WebSocket Client connected!");
|
||||
return;
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
|
||||
if (this.handshaker == null) {
|
||||
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
|
||||
} 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
|
||||
if (frame instanceof CloseWebSocketFrame) {
|
||||
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame);
|
||||
this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
|
||||
return;
|
||||
} else if (frame instanceof PingWebSocketFrame) {
|
||||
ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));
|
||||
|
@ -96,7 +96,7 @@ public class WebSocketSslServerHandler extends SimpleChannelUpstreamHandler {
|
||||
if (this.handshaker == null) {
|
||||
wsFactory.sendUnsupportedWebSocketVersionResponse(ctx.getChannel());
|
||||
} 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
|
||||
if (frame instanceof CloseWebSocketFrame) {
|
||||
this.handshaker.performClosingHandshake(ctx.getChannel(), (CloseWebSocketFrame) frame);
|
||||
this.handshaker.close(ctx.getChannel(), (CloseWebSocketFrame) frame);
|
||||
return;
|
||||
} else if (frame instanceof PingWebSocketFrame) {
|
||||
ctx.getChannel().write(new PongWebSocketFrame(frame.getBinaryData()));
|
||||
|
Loading…
Reference in New Issue
Block a user