Use websocket wire protocol version rather than specification version
This commit is contained in:
parent
f01d8a4841
commit
812a79fd52
@ -25,8 +25,8 @@ import io.netty.bootstrap.ServerBootstrap;
|
|||||||
import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Web Socket echo server for running the <a href="http://www.tavendo.de/autobahn/testsuite.html">autobahn</a>
|
* A Web Socket echo server for running the <a href="http://www.tavendo.de/autobahn/testsuite.html">autobahn</a> test
|
||||||
* test suite
|
* suite
|
||||||
*/
|
*/
|
||||||
public class WebSocketServer {
|
public class WebSocketServer {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
@ -91,7 +91,8 @@ public class WebSocketServerHandler extends SimpleChannelUpstreamHandler {
|
|||||||
new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
|
new PongWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
|
||||||
} else if (frame instanceof TextWebSocketFrame) {
|
} else if (frame instanceof TextWebSocketFrame) {
|
||||||
// String text = ((TextWebSocketFrame) frame).getText();
|
// String text = ((TextWebSocketFrame) frame).getText();
|
||||||
ctx.getChannel().write(new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
|
ctx.getChannel().write(
|
||||||
|
new TextWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
|
||||||
} else if (frame instanceof BinaryWebSocketFrame) {
|
} else if (frame instanceof BinaryWebSocketFrame) {
|
||||||
ctx.getChannel().write(
|
ctx.getChannel().write(
|
||||||
new BinaryWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
|
new BinaryWebSocketFrame(frame.isFinalFragment(), frame.getRsv(), frame.getBinaryData()));
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
*
|
*
|
||||||
* <p>08. Go to test suite directory: <tt>cd testsuite/websockets</tt>
|
* <p>08. Go to test suite directory: <tt>cd testsuite/websockets</tt>
|
||||||
*
|
*
|
||||||
* <p>09. Edit <tt>fuzzing_clinet_spec.json</tt> and set the version to 10 or 17.
|
* <p>09. Edit <tt>fuzzing_clinet_spec.json</tt> and set the hybi specification version to 10 or 17 (RFC 6455).
|
||||||
* <code>
|
* <code>
|
||||||
* {
|
* {
|
||||||
* "options": {"failByDrop": false},
|
* "options": {"failByDrop": false},
|
||||||
|
@ -28,7 +28,7 @@ import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
|
|||||||
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
|
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
|
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A HTTP client demo app
|
* A HTTP client demo app
|
||||||
@ -55,11 +55,11 @@ public class App {
|
|||||||
MyCallbackHandler callbackHandler = new MyCallbackHandler();
|
MyCallbackHandler callbackHandler = new MyCallbackHandler();
|
||||||
WebSocketClientFactory factory = new WebSocketClientFactory();
|
WebSocketClientFactory factory = new WebSocketClientFactory();
|
||||||
|
|
||||||
// Connect with spec version 17. You can change it to V10 or V00.
|
// Connect with RFC 6455. You can change it to V10 or V00.
|
||||||
// If you change it to V00, ping is not supported and remember to change HttpResponseDecoder to
|
// If you change it to V00, ping is not supported and remember to change HttpResponseDecoder to
|
||||||
// WebSocketHttpResponseDecoder in the pipeline.
|
// WebSocketHttpResponseDecoder in the pipeline.
|
||||||
WebSocketClient client = factory.newClient(new URI("ws://localhost:8080/websocket"),
|
WebSocketClient client = factory.newClient(new URI("ws://localhost:8080/websocket"), WebSocketVersion.V13,
|
||||||
WebSocketSpecificationVersion.V17, callbackHandler);
|
callbackHandler, null);
|
||||||
|
|
||||||
// Connect
|
// Connect
|
||||||
System.out.println("WebSocket Client connecting");
|
System.out.println("WebSocket Client connecting");
|
||||||
|
@ -24,7 +24,6 @@ package io.netty.example.http.websocketx.client;
|
|||||||
|
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copied from https://github.com/cgbystrom/netty-tools
|
* Copied from https://github.com/cgbystrom/netty-tools
|
||||||
*
|
*
|
||||||
|
@ -51,14 +51,4 @@ public interface WebSocketClient {
|
|||||||
* @return Write future. Will fire when the data is sent.
|
* @return Write future. Will fire when the data is sent.
|
||||||
*/
|
*/
|
||||||
ChannelFuture send(WebSocketFrame frame);
|
ChannelFuture send(WebSocketFrame frame);
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a custom header to this client request
|
|
||||||
*
|
|
||||||
* @param header
|
|
||||||
* Name of header field to add to request
|
|
||||||
* @param value
|
|
||||||
* Value of header field added to request
|
|
||||||
*/
|
|
||||||
void addCustomHeader(String header, String value);
|
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,10 @@ import io.netty.channel.Channels;
|
|||||||
import io.netty.channel.socket.nio.NioClientSocketChannelFactory;
|
import io.netty.channel.socket.nio.NioClientSocketChannelFactory;
|
||||||
import io.netty.handler.codec.http.HttpRequestEncoder;
|
import io.netty.handler.codec.http.HttpRequestEncoder;
|
||||||
import io.netty.handler.codec.http.HttpResponseDecoder;
|
import io.netty.handler.codec.http.HttpResponseDecoder;
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
|
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,11 +55,12 @@ public class WebSocketClientFactory {
|
|||||||
* Web Socket version to support
|
* Web Socket version to support
|
||||||
* @param callback
|
* @param callback
|
||||||
* Callback interface to receive events
|
* Callback interface to receive events
|
||||||
|
* @param customHeaders
|
||||||
|
* custom HTTP headers to pass during the handshake
|
||||||
* @return A WebSocket client. Call {@link WebSocketClient#connect()} to connect.
|
* @return A WebSocket client. Call {@link WebSocketClient#connect()} to connect.
|
||||||
*/
|
*/
|
||||||
public WebSocketClient newClient(final URI url,
|
public WebSocketClient newClient(final URI url, final WebSocketVersion version, final WebSocketCallback callback,
|
||||||
final WebSocketSpecificationVersion version,
|
Map<String, String> customHeaders) {
|
||||||
final WebSocketCallback callback) {
|
|
||||||
ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory);
|
ClientBootstrap bootstrap = new ClientBootstrap(socketChannelFactory);
|
||||||
|
|
||||||
String protocol = url.getScheme();
|
String protocol = url.getScheme();
|
||||||
@ -66,7 +68,8 @@ public class WebSocketClientFactory {
|
|||||||
throw new IllegalArgumentException("Unsupported protocol: " + protocol);
|
throw new IllegalArgumentException("Unsupported protocol: " + protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
final WebSocketClientHandler clientHandler = new WebSocketClientHandler(bootstrap, url, version, callback);
|
final WebSocketClientHandler clientHandler = new WebSocketClientHandler(bootstrap, url, version, callback,
|
||||||
|
customHeaders);
|
||||||
|
|
||||||
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
|
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ package io.netty.example.http.websocketx.client;
|
|||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import io.netty.bootstrap.ClientBootstrap;
|
import io.netty.bootstrap.ClientBootstrap;
|
||||||
@ -39,7 +38,7 @@ import io.netty.handler.codec.http.HttpResponse;
|
|||||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
|
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
|
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
|
||||||
import io.netty.handler.codec.http.websocketx.WebSocketSpecificationVersion;
|
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,20 +54,23 @@ public class WebSocketClientHandler extends SimpleChannelUpstreamHandler impleme
|
|||||||
private final WebSocketCallback callback;
|
private final WebSocketCallback callback;
|
||||||
private Channel channel;
|
private Channel channel;
|
||||||
private WebSocketClientHandshaker handshaker = null;
|
private WebSocketClientHandshaker handshaker = null;
|
||||||
private final WebSocketSpecificationVersion version;
|
private final WebSocketVersion version;
|
||||||
private Map<String, String> customHeaders = null;
|
private Map<String, String> customHeaders = null;
|
||||||
|
|
||||||
public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketSpecificationVersion version, WebSocketCallback callback) {
|
public WebSocketClientHandler(ClientBootstrap bootstrap, URI url, WebSocketVersion version,
|
||||||
|
WebSocketCallback callback, Map<String, String> customHeaders) {
|
||||||
this.bootstrap = bootstrap;
|
this.bootstrap = bootstrap;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
this.customHeaders = customHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
|
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
|
||||||
channel = e.getChannel();
|
channel = e.getChannel();
|
||||||
this.handshaker = new WebSocketClientHandshakerFactory().newHandshaker(url, version, null, false, customHeaders);
|
this.handshaker = new WebSocketClientHandshakerFactory()
|
||||||
|
.newHandshaker(url, version, null, false, customHeaders);
|
||||||
handshaker.performOpeningHandshake(channel);
|
handshaker.performOpeningHandshake(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,11 +126,4 @@ public class WebSocketClientHandler extends SimpleChannelUpstreamHandler impleme
|
|||||||
public void setUrl(URI url) {
|
public void setUrl(URI url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCustomHeader(String header, String value){
|
|
||||||
if(customHeaders == null){
|
|
||||||
customHeaders = new HashMap<String,String>();
|
|
||||||
}
|
|
||||||
customHeaders.put(header, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,10 @@ import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
|||||||
*
|
*
|
||||||
* http://localhost:8080/websocket
|
* http://localhost:8080/websocket
|
||||||
*
|
*
|
||||||
* Open your browser at http://localhost:8080/, then the demo page will be
|
* Open your browser at http://localhost:8080/, then the demo page will be loaded and a Web Socket connection will be
|
||||||
* loaded and a Web Socket connection will be made automatically.
|
* made automatically.
|
||||||
*
|
*
|
||||||
* This server illustrates support for the different web socket specification
|
* This server illustrates support for the different web socket specification versions and will work with:
|
||||||
* versions and will work with:
|
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
|
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
|
||||||
@ -63,6 +62,7 @@ public class WebSocketServer {
|
|||||||
// Bind and start to accept incoming connections.
|
// Bind and start to accept incoming connections.
|
||||||
bootstrap.bind(new InetSocketAddress(8080));
|
bootstrap.bind(new InetSocketAddress(8080));
|
||||||
|
|
||||||
System.out.println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/");
|
System.out
|
||||||
|
.println("Web Socket Server started on 8080. Open your browser and navigate to http://localhost:8080/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import io.netty.buffer.ChannelBuffer;
|
|||||||
import io.netty.buffer.ChannelBuffers;
|
import io.netty.buffer.ChannelBuffers;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the demo HTML page which is served at http://localhost:8080/
|
* Generates the demo HTML page which is served at http://localhost:8080/
|
||||||
*/
|
*/
|
||||||
@ -28,40 +27,66 @@ public class WebSocketServerIndexPage {
|
|||||||
private static final String NEWLINE = "\r\n";
|
private static final String NEWLINE = "\r\n";
|
||||||
|
|
||||||
public static ChannelBuffer getContent(String webSocketLocation) {
|
public static ChannelBuffer getContent(String webSocketLocation) {
|
||||||
return ChannelBuffers.copiedBuffer(
|
return ChannelBuffers
|
||||||
"<html><head><title>Web Socket Test</title></head>" + NEWLINE +
|
.copiedBuffer(
|
||||||
"<body>" + NEWLINE +
|
"<html><head><title>Web Socket Test</title></head>"
|
||||||
"<script type=\"text/javascript\">" + NEWLINE +
|
+ NEWLINE
|
||||||
"var socket;" + NEWLINE +
|
+ "<body>"
|
||||||
"if (!window.WebSocket) {" + NEWLINE +
|
+ NEWLINE
|
||||||
" window.WebSocket = window.MozWebSocket;" + NEWLINE +
|
+ "<script type=\"text/javascript\">"
|
||||||
"}" + NEWLINE +
|
+ NEWLINE
|
||||||
"if (window.WebSocket) {" + NEWLINE +
|
+ "var socket;"
|
||||||
" socket = new WebSocket(\"" + webSocketLocation + "\");" + NEWLINE +
|
+ NEWLINE
|
||||||
" socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };" + NEWLINE +
|
+ "if (!window.WebSocket) {"
|
||||||
" socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };" + NEWLINE +
|
+ NEWLINE
|
||||||
" socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };" + NEWLINE +
|
+ " window.WebSocket = window.MozWebSocket;"
|
||||||
"} else {" + NEWLINE +
|
+ NEWLINE
|
||||||
" alert(\"Your browser does not support Web Socket.\");" + NEWLINE +
|
+ "}"
|
||||||
"}" + NEWLINE +
|
+ NEWLINE
|
||||||
NEWLINE +
|
+ "if (window.WebSocket) {"
|
||||||
"function send(message) {" + NEWLINE +
|
+ NEWLINE
|
||||||
" if (!window.WebSocket) { return; }" + NEWLINE +
|
+ " socket = new WebSocket(\""
|
||||||
" if (socket.readyState == WebSocket.OPEN) {" + NEWLINE +
|
+ webSocketLocation
|
||||||
" socket.send(message);" + NEWLINE +
|
+ "\");"
|
||||||
" } else {" + NEWLINE +
|
+ NEWLINE
|
||||||
" alert(\"The socket is not open.\");" + NEWLINE +
|
+ " socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };"
|
||||||
" }" + NEWLINE +
|
+ NEWLINE
|
||||||
"}" + NEWLINE +
|
+ " socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };"
|
||||||
"</script>" + NEWLINE +
|
+ NEWLINE
|
||||||
"<form onsubmit=\"return false;\">" + NEWLINE +
|
+ " socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };"
|
||||||
"<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>" +
|
+ NEWLINE
|
||||||
"<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />" + NEWLINE +
|
+ "} else {"
|
||||||
"<h3>Output</h3>" + NEWLINE +
|
+ NEWLINE
|
||||||
"<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>" + NEWLINE +
|
+ " alert(\"Your browser does not support Web Socket.\");"
|
||||||
"</form>" + NEWLINE +
|
+ NEWLINE
|
||||||
"</body>" + NEWLINE +
|
+ "}"
|
||||||
"</html>" + NEWLINE,
|
+ NEWLINE
|
||||||
|
+ NEWLINE
|
||||||
|
+ "function send(message) {"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " if (!window.WebSocket) { return; }"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " if (socket.readyState == WebSocket.OPEN) {"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " socket.send(message);"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " } else {"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " alert(\"The socket is not open.\");"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " }"
|
||||||
|
+ NEWLINE
|
||||||
|
+ "}"
|
||||||
|
+ NEWLINE
|
||||||
|
+ "</script>"
|
||||||
|
+ NEWLINE
|
||||||
|
+ "<form onsubmit=\"return false;\">"
|
||||||
|
+ NEWLINE
|
||||||
|
+ "<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>"
|
||||||
|
+ "<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />"
|
||||||
|
+ NEWLINE + "<h3>Output</h3>" + NEWLINE
|
||||||
|
+ "<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>"
|
||||||
|
+ NEWLINE + "</form>" + NEWLINE + "</body>" + NEWLINE + "</html>" + NEWLINE,
|
||||||
CharsetUtil.US_ASCII);
|
CharsetUtil.US_ASCII);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,10 @@ import io.netty.channel.socket.nio.NioServerSocketChannelFactory;
|
|||||||
*
|
*
|
||||||
* https://localhost:8081/websocket
|
* https://localhost:8081/websocket
|
||||||
*
|
*
|
||||||
* Open your browser at https://localhost:8081/, then the demo page will be
|
* Open your browser at https://localhost:8081/, then the demo page will be loaded and a Web Socket connection will be
|
||||||
* loaded and a Web Socket connection will be made automatically.
|
* made automatically.
|
||||||
*
|
*
|
||||||
* This server illustrates support for the different web socket specification
|
* This server illustrates support for the different web socket specification versions and will work with:
|
||||||
* versions and will work with:
|
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
|
* <li>Safari 5+ (draft-ietf-hybi-thewebsocketprotocol-00)
|
||||||
@ -75,6 +74,7 @@ public class WebSocketSslServer {
|
|||||||
// Bind and start to accept incoming connections.
|
// Bind and start to accept incoming connections.
|
||||||
bootstrap.bind(new InetSocketAddress(8081));
|
bootstrap.bind(new InetSocketAddress(8081));
|
||||||
|
|
||||||
System.out.println("Web Socket Server started on 8081. Open your browser and navigate to https://localhost:8081/");
|
System.out
|
||||||
|
.println("Web Socket Server started on 8081. Open your browser and navigate to https://localhost:8081/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import io.netty.buffer.ChannelBuffer;
|
|||||||
import io.netty.buffer.ChannelBuffers;
|
import io.netty.buffer.ChannelBuffers;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the demo HTML page which is served at http://localhost:8080/
|
* Generates the demo HTML page which is served at http://localhost:8080/
|
||||||
*/
|
*/
|
||||||
@ -28,40 +27,66 @@ public class WebSocketSslServerIndexPage {
|
|||||||
private static final String NEWLINE = "\r\n";
|
private static final String NEWLINE = "\r\n";
|
||||||
|
|
||||||
public static ChannelBuffer getContent(String webSocketLocation) {
|
public static ChannelBuffer getContent(String webSocketLocation) {
|
||||||
return ChannelBuffers.copiedBuffer(
|
return ChannelBuffers
|
||||||
"<html><head><title>Web Socket Test</title></head>" + NEWLINE +
|
.copiedBuffer(
|
||||||
"<body>" + NEWLINE +
|
"<html><head><title>Web Socket Test</title></head>"
|
||||||
"<script type=\"text/javascript\">" + NEWLINE +
|
+ NEWLINE
|
||||||
"var socket;" + NEWLINE +
|
+ "<body>"
|
||||||
"if (!window.WebSocket) {" + NEWLINE +
|
+ NEWLINE
|
||||||
" window.WebSocket = window.MozWebSocket;" + NEWLINE +
|
+ "<script type=\"text/javascript\">"
|
||||||
"}" + NEWLINE +
|
+ NEWLINE
|
||||||
"if (window.WebSocket) {" + NEWLINE +
|
+ "var socket;"
|
||||||
" socket = new WebSocket(\"" + webSocketLocation + "\");" + NEWLINE +
|
+ NEWLINE
|
||||||
" socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };" + NEWLINE +
|
+ "if (!window.WebSocket) {"
|
||||||
" socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };" + NEWLINE +
|
+ NEWLINE
|
||||||
" socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };" + NEWLINE +
|
+ " window.WebSocket = window.MozWebSocket;"
|
||||||
"} else {" + NEWLINE +
|
+ NEWLINE
|
||||||
" alert(\"Your browser does not support Web Socket.\");" + NEWLINE +
|
+ "}"
|
||||||
"}" + NEWLINE +
|
+ NEWLINE
|
||||||
NEWLINE +
|
+ "if (window.WebSocket) {"
|
||||||
"function send(message) {" + NEWLINE +
|
+ NEWLINE
|
||||||
" if (!window.WebSocket) { return; }" + NEWLINE +
|
+ " socket = new WebSocket(\""
|
||||||
" if (socket.readyState == WebSocket.OPEN) {" + NEWLINE +
|
+ webSocketLocation
|
||||||
" socket.send(message);" + NEWLINE +
|
+ "\");"
|
||||||
" } else {" + NEWLINE +
|
+ NEWLINE
|
||||||
" alert(\"The socket is not open.\");" + NEWLINE +
|
+ " socket.onmessage = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + '\\n' + event.data };"
|
||||||
" }" + NEWLINE +
|
+ NEWLINE
|
||||||
"}" + NEWLINE +
|
+ " socket.onopen = function(event) { var ta = document.getElementById('responseText'); ta.value = \"Web Socket opened!\"; };"
|
||||||
"</script>" + NEWLINE +
|
+ NEWLINE
|
||||||
"<form onsubmit=\"return false;\">" + NEWLINE +
|
+ " socket.onclose = function(event) { var ta = document.getElementById('responseText'); ta.value = ta.value + \"Web Socket closed\"; };"
|
||||||
"<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>" +
|
+ NEWLINE
|
||||||
"<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />" + NEWLINE +
|
+ "} else {"
|
||||||
"<h3>Output</h3>" + NEWLINE +
|
+ NEWLINE
|
||||||
"<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>" + NEWLINE +
|
+ " alert(\"Your browser does not support Web Socket.\");"
|
||||||
"</form>" + NEWLINE +
|
+ NEWLINE
|
||||||
"</body>" + NEWLINE +
|
+ "}"
|
||||||
"</html>" + NEWLINE,
|
+ NEWLINE
|
||||||
|
+ NEWLINE
|
||||||
|
+ "function send(message) {"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " if (!window.WebSocket) { return; }"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " if (socket.readyState == WebSocket.OPEN) {"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " socket.send(message);"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " } else {"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " alert(\"The socket is not open.\");"
|
||||||
|
+ NEWLINE
|
||||||
|
+ " }"
|
||||||
|
+ NEWLINE
|
||||||
|
+ "}"
|
||||||
|
+ NEWLINE
|
||||||
|
+ "</script>"
|
||||||
|
+ NEWLINE
|
||||||
|
+ "<form onsubmit=\"return false;\">"
|
||||||
|
+ NEWLINE
|
||||||
|
+ "<input type=\"text\" name=\"message\" value=\"Hello, World!\"/>"
|
||||||
|
+ "<input type=\"button\" value=\"Send Web Socket Data\" onclick=\"send(this.form.message.value)\" />"
|
||||||
|
+ NEWLINE + "<h3>Output</h3>" + NEWLINE
|
||||||
|
+ "<textarea id=\"responseText\" style=\"width: 500px; height:300px;\"></textarea>"
|
||||||
|
+ NEWLINE + "</form>" + NEWLINE + "</body>" + NEWLINE + "</html>" + NEWLINE,
|
||||||
CharsetUtil.US_ASCII);
|
CharsetUtil.US_ASCII);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,8 @@ public class WebSocketSslServerSslContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SingletonHolder is loaded on the first execution of
|
* SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to
|
||||||
* Singleton.getInstance() or the first access to SingletonHolder.INSTANCE,
|
* SingletonHolder.INSTANCE, not before.
|
||||||
* not before.
|
|
||||||
*
|
*
|
||||||
* See http://en.wikipedia.org/wiki/Singleton_pattern
|
* See http://en.wikipedia.org/wiki/Singleton_pattern
|
||||||
*/
|
*/
|
||||||
|
@ -31,8 +31,7 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new binary frame with the specified binary data. The final
|
* Creates a new binary frame with the specified binary data. The final fragment flag is set to true.
|
||||||
* fragment flag is set to true.
|
|
||||||
*
|
*
|
||||||
* @param binaryData
|
* @param binaryData
|
||||||
* the content of the frame.
|
* the content of the frame.
|
||||||
@ -42,8 +41,7 @@ public class BinaryWebSocketFrame extends WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new binary frame with the specified binary data and the final
|
* Creates a new binary frame with the specified binary data and the final fragment flag.
|
||||||
* fragment flag.
|
|
||||||
*
|
*
|
||||||
* @param finalFragment
|
* @param finalFragment
|
||||||
* flag indicating if this frame is the final fragment
|
* flag indicating if this frame is the final fragment
|
||||||
|
@ -20,9 +20,8 @@ import io.netty.buffer.ChannelBuffers;
|
|||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web Socket continuation frame containing continuation text or binary data.
|
* Web Socket continuation frame containing continuation text or binary data. This is used for fragmented messages where
|
||||||
* This is used for fragmented messages where the contents of a messages is
|
* the contents of a messages is contained more than 1 frame.
|
||||||
* contained more than 1 frame.
|
|
||||||
*/
|
*/
|
||||||
public class ContinuationWebSocketFrame extends WebSocketFrame {
|
public class ContinuationWebSocketFrame extends WebSocketFrame {
|
||||||
|
|
||||||
@ -36,8 +35,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new continuation frame with the specified binary data. The
|
* Creates a new continuation frame with the specified binary data. The final fragment flag is set to true.
|
||||||
* final fragment flag is set to true.
|
|
||||||
*
|
*
|
||||||
* @param binaryData
|
* @param binaryData
|
||||||
* the content of the frame.
|
* the content of the frame.
|
||||||
@ -72,8 +70,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
|
|||||||
* @param binaryData
|
* @param binaryData
|
||||||
* the content of the frame.
|
* the content of the frame.
|
||||||
* @param aggregatedText
|
* @param aggregatedText
|
||||||
* Aggregated text set by decoder on the final continuation frame
|
* Aggregated text set by decoder on the final continuation frame of a fragmented text message
|
||||||
* 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);
|
this.setFinalFragment(finalFragment);
|
||||||
@ -128,8 +125,7 @@ public class ContinuationWebSocketFrame extends WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aggregated text returned by decoder on the final continuation frame of a
|
* Aggregated text returned by decoder on the final continuation frame of a fragmented text message
|
||||||
* fragmented text message
|
|
||||||
*/
|
*/
|
||||||
public String getAggregatedText() {
|
public String getAggregatedText() {
|
||||||
return aggregatedText;
|
return aggregatedText;
|
||||||
|
@ -32,8 +32,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new text frame with the specified text string. The final
|
* Creates a new text frame with the specified text string. The final fragment flag is set to true.
|
||||||
* fragment flag is set to true.
|
|
||||||
*
|
*
|
||||||
* @param text
|
* @param text
|
||||||
* String to put in the frame
|
* String to put in the frame
|
||||||
@ -47,8 +46,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new text frame with the specified binary data. The final
|
* Creates a new text frame with the specified binary data. The final fragment flag is set to true.
|
||||||
* fragment flag is set to true.
|
|
||||||
*
|
*
|
||||||
* @param binaryData
|
* @param binaryData
|
||||||
* the content of the frame. Must be UTF-8 encoded
|
* the content of the frame. Must be UTF-8 encoded
|
||||||
@ -58,8 +56,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new text frame with the specified text string. The final
|
* Creates a new text frame with the specified text string. The final fragment flag is set to true.
|
||||||
* fragment flag is set to true.
|
|
||||||
*
|
*
|
||||||
* @param finalFragment
|
* @param finalFragment
|
||||||
* flag indicating if this frame is the final fragment
|
* flag indicating if this frame is the final fragment
|
||||||
@ -79,8 +76,7 @@ public class TextWebSocketFrame extends WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new text frame with the specified binary data. The final
|
* Creates a new text frame with the specified binary data. The final fragment flag is set to true.
|
||||||
* fragment flag is set to true.
|
|
||||||
*
|
*
|
||||||
* @param finalFragment
|
* @param finalFragment
|
||||||
* flag indicating if this frame is the final fragment
|
* flag indicating if this frame is the final fragment
|
||||||
|
@ -26,12 +26,20 @@ final class UTF8Output {
|
|||||||
private static final int UTF8_ACCEPT = 0;
|
private static final int UTF8_ACCEPT = 0;
|
||||||
private static final int UTF8_REJECT = 12;
|
private static final int UTF8_REJECT = 12;
|
||||||
|
|
||||||
private static final byte[] TYPES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
private static final byte[] TYPES = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
|
||||||
|
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11,
|
||||||
|
6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
|
||||||
|
|
||||||
private static final byte[] STATES = { 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12,
|
private static final byte[] STATES = { 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12,
|
||||||
12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 };
|
12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24,
|
||||||
|
12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12,
|
||||||
|
12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12,
|
||||||
|
12, 12, 12, 12, 12, 12, 12, 12 };
|
||||||
|
|
||||||
private int state = UTF8_ACCEPT;
|
private int state = UTF8_ACCEPT;
|
||||||
private int codep = 0;
|
private int codep = 0;
|
||||||
|
@ -25,9 +25,9 @@ import io.netty.handler.codec.replay.VoidEnum;
|
|||||||
/**
|
/**
|
||||||
* Decodes {@link ChannelBuffer}s into {@link WebSocketFrame}s.
|
* Decodes {@link ChannelBuffer}s into {@link WebSocketFrame}s.
|
||||||
* <p>
|
* <p>
|
||||||
* For the detailed instruction on adding add Web Socket support to your HTTP
|
* For the detailed instruction on adding add Web Socket support to your HTTP server, take a look into the
|
||||||
* server, take a look into the <tt>WebSocketServer</tt> example located in the
|
* <tt>WebSocketServer</tt> example located in the {@code io.netty.example.http.websocket} package.
|
||||||
* {@code io.netty.example.http.websocket} package.
|
*
|
||||||
* @apiviz.landmark
|
* @apiviz.landmark
|
||||||
* @apiviz.uses io.netty.handler.codec.http.websocket.WebSocketFrame
|
* @apiviz.uses io.netty.handler.codec.http.websocket.WebSocketFrame
|
||||||
*/
|
*/
|
||||||
@ -43,9 +43,8 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of {@code WebSocketFrameDecoder} with the
|
* Creates a new instance of {@code WebSocketFrameDecoder} with the specified {@code maxFrameSize}. If the client
|
||||||
* specified {@code maxFrameSize}. If the client sends a frame size larger
|
* sends a frame size larger than {@code maxFrameSize}, the channel will be closed.
|
||||||
* than {@code maxFrameSize}, the channel will be closed.
|
|
||||||
*
|
*
|
||||||
* @param maxFrameSize
|
* @param maxFrameSize
|
||||||
* the maximum frame size to decode
|
* the maximum frame size to decode
|
||||||
@ -55,7 +54,8 @@ public class WebSocket00FrameDecoder extends ReplayingDecoder<VoidEnum> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum state) throws Exception {
|
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer, VoidEnum state)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
// Discard all data received if closing handshake was received before.
|
// Discard all data received if closing handshake was received before.
|
||||||
if (receivedClosingHandshake) {
|
if (receivedClosingHandshake) {
|
||||||
|
@ -24,9 +24,9 @@ import io.netty.handler.codec.oneone.OneToOneEncoder;
|
|||||||
/**
|
/**
|
||||||
* Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}.
|
* Encodes a {@link WebSocketFrame} into a {@link ChannelBuffer}.
|
||||||
* <p>
|
* <p>
|
||||||
* For the detailed instruction on adding add Web Socket support to your HTTP
|
* For the detailed instruction on adding add Web Socket support to your HTTP server, take a look into the
|
||||||
* server, take a look into the <tt>WebSocketServer</tt> example located in the
|
* <tt>WebSocketServer</tt> example located in the {@code io.netty.example.http.websocket} package.
|
||||||
* {@code io.netty.example.http.websocket} package.
|
*
|
||||||
* @apiviz.landmark
|
* @apiviz.landmark
|
||||||
* @apiviz.uses io.netty.handler.codec.http.websocket.WebSocketFrame
|
* @apiviz.uses io.netty.handler.codec.http.websocket.WebSocketFrame
|
||||||
*/
|
*/
|
||||||
@ -40,7 +40,8 @@ public class WebSocket00FrameEncoder extends OneToOneEncoder {
|
|||||||
if (frame instanceof TextWebSocketFrame) {
|
if (frame instanceof TextWebSocketFrame) {
|
||||||
// Text frame
|
// Text frame
|
||||||
ChannelBuffer data = frame.getBinaryData();
|
ChannelBuffer data = frame.getBinaryData();
|
||||||
ChannelBuffer encoded = channel.getConfig().getBufferFactory().getBuffer(data.order(), data.readableBytes() + 2);
|
ChannelBuffer encoded = channel.getConfig().getBufferFactory()
|
||||||
|
.getBuffer(data.order(), data.readableBytes() + 2);
|
||||||
encoded.writeByte((byte) 0x00);
|
encoded.writeByte((byte) 0x00);
|
||||||
encoded.writeBytes(data, data.readerIndex(), data.readableBytes());
|
encoded.writeBytes(data, data.readerIndex(), data.readableBytes());
|
||||||
encoded.writeByte((byte) 0xFF);
|
encoded.writeByte((byte) 0xFF);
|
||||||
|
@ -50,9 +50,8 @@ import io.netty.logging.InternalLogger;
|
|||||||
import io.netty.logging.InternalLoggerFactory;
|
import io.netty.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes a web socket frame from wire protocol version 8 format. This code was
|
* Decodes a web socket frame from wire protocol version 8 format. This code was forked from <a
|
||||||
* forked from <a href="https://github.com/joewalnes/webbit">webbit</a> and
|
* href="https://github.com/joewalnes/webbit">webbit</a> and modified.
|
||||||
* modified.
|
|
||||||
*/
|
*/
|
||||||
public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDecoder.State> {
|
public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDecoder.State> {
|
||||||
|
|
||||||
@ -88,8 +87,8 @@ public class WebSocket08FrameDecoder extends ReplayingDecoder<WebSocket08FrameDe
|
|||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param maskedPayload
|
* @param maskedPayload
|
||||||
* Web socket servers must set this to true processed incoming
|
* Web socket servers must set this to true processed incoming masked payload. Client implementations
|
||||||
* masked payload. Client implementations must set this to false.
|
* must set this to false.
|
||||||
* @param allowExtensions
|
* @param allowExtensions
|
||||||
* Flag to allow reserved extension bits to be used or not
|
* Flag to allow reserved extension bits to be used or not
|
||||||
*/
|
*/
|
||||||
|
@ -51,9 +51,8 @@ import io.netty.logging.InternalLoggerFactory;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Encodes a web socket frame into wire protocol version 8 format. This code was
|
* Encodes a web socket frame into wire protocol version 8 format. This code was forked from <a
|
||||||
* forked from <a href="https://github.com/joewalnes/webbit">webbit</a> and
|
* href="https://github.com/joewalnes/webbit">webbit</a> and modified.
|
||||||
* modified.
|
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class WebSocket08FrameEncoder extends OneToOneEncoder {
|
public class WebSocket08FrameEncoder extends OneToOneEncoder {
|
||||||
@ -73,8 +72,8 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
|
|||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param maskPayload
|
* @param maskPayload
|
||||||
* Web socket clients must set this to true to mask payload.
|
* Web socket clients must set this to true to mask payload. Server implementations must set this to
|
||||||
* Server implementations must set this to false.
|
* false.
|
||||||
*/
|
*/
|
||||||
public WebSocket08FrameEncoder(boolean maskPayload) {
|
public WebSocket08FrameEncoder(boolean maskPayload) {
|
||||||
this.maskPayload = maskPayload;
|
this.maskPayload = maskPayload;
|
||||||
@ -126,7 +125,8 @@ public class WebSocket08FrameEncoder extends OneToOneEncoder {
|
|||||||
ChannelBuffer body;
|
ChannelBuffer body;
|
||||||
|
|
||||||
if (opcode == OPCODE_PING && length > 125) {
|
if (opcode == OPCODE_PING && length > 125) {
|
||||||
throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was " + length);
|
throw new TooLongFrameException("invalid payload for PING (payload length must be <= 125, was "
|
||||||
|
+ length);
|
||||||
}
|
}
|
||||||
|
|
||||||
int maskLength = this.maskPayload ? 4 : 0;
|
int maskLength = this.maskPayload ? 4 : 0;
|
||||||
|
@ -39,8 +39,7 @@
|
|||||||
package io.netty.handler.codec.http.websocketx;
|
package io.netty.handler.codec.http.websocketx;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes a web socket frame from wire protocol version 13 format.
|
* Decodes a web socket frame from wire protocol version 13 format. V13 is essentially the same as V8.
|
||||||
* V13 is essentially the same as V8.
|
|
||||||
*/
|
*/
|
||||||
public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder {
|
public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder {
|
||||||
|
|
||||||
@ -48,8 +47,8 @@ public class WebSocket13FrameDecoder extends WebSocket08FrameDecoder {
|
|||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param maskedPayload
|
* @param maskedPayload
|
||||||
* Web socket servers must set this to true processed incoming
|
* Web socket servers must set this to true processed incoming masked payload. Client implementations
|
||||||
* masked payload. Client implementations must set this to false.
|
* must set this to false.
|
||||||
* @param allowExtensions
|
* @param allowExtensions
|
||||||
* Flag to allow reserved extension bits to be used or not
|
* Flag to allow reserved extension bits to be used or not
|
||||||
*/
|
*/
|
||||||
|
@ -38,11 +38,9 @@
|
|||||||
|
|
||||||
package io.netty.handler.codec.http.websocketx;
|
package io.netty.handler.codec.http.websocketx;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Encodes a web socket frame into wire protocol version 13 format. V13 is essentially the same
|
* Encodes a web socket frame into wire protocol version 13 format. V13 is essentially the same as V8.
|
||||||
* as V8.
|
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class WebSocket13FrameEncoder extends WebSocket08FrameEncoder {
|
public class WebSocket13FrameEncoder extends WebSocket08FrameEncoder {
|
||||||
@ -51,8 +49,8 @@ public class WebSocket13FrameEncoder extends WebSocket08FrameEncoder {
|
|||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param maskPayload
|
* @param maskPayload
|
||||||
* Web socket clients must set this to true to mask payload.
|
* Web socket clients must set this to true to mask payload. Server implementations must set this to
|
||||||
* Server implementations must set this to false.
|
* false.
|
||||||
*/
|
*/
|
||||||
public WebSocket13FrameEncoder(boolean maskPayload) {
|
public WebSocket13FrameEncoder(boolean maskPayload) {
|
||||||
super(maskPayload);
|
super(maskPayload);
|
||||||
|
@ -34,7 +34,7 @@ public abstract class WebSocketClientHandshaker {
|
|||||||
|
|
||||||
private URI webSocketURL;
|
private URI webSocketURL;
|
||||||
|
|
||||||
private WebSocketSpecificationVersion version = WebSocketSpecificationVersion.UNKNOWN;
|
private WebSocketVersion version = WebSocketVersion.UNKNOWN;
|
||||||
|
|
||||||
private boolean openingHandshakeCompleted = false;
|
private boolean openingHandshakeCompleted = false;
|
||||||
|
|
||||||
@ -50,7 +50,8 @@ public abstract class WebSocketClientHandshaker {
|
|||||||
* @param version
|
* @param version
|
||||||
* @param subProtocol
|
* @param subProtocol
|
||||||
*/
|
*/
|
||||||
public WebSocketClientHandshaker(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, Map<String,String> customHeaders) {
|
public WebSocketClientHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol,
|
||||||
|
Map<String, String> customHeaders) {
|
||||||
this.webSocketURL = webSocketURL;
|
this.webSocketURL = webSocketURL;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.subProtocolRequest = subProtocol;
|
this.subProtocolRequest = subProtocol;
|
||||||
@ -71,11 +72,11 @@ public abstract class WebSocketClientHandshaker {
|
|||||||
/**
|
/**
|
||||||
* Version of the web socket specification that is being used
|
* Version of the web socket specification that is being used
|
||||||
*/
|
*/
|
||||||
public WebSocketSpecificationVersion getVersion() {
|
public WebSocketVersion getVersion() {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setVersion(WebSocketSpecificationVersion version) {
|
protected void setVersion(WebSocketVersion version) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +92,7 @@ public abstract class WebSocketClientHandshaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the sub protocol request sent to the server as specified in the
|
* Returns the sub protocol request sent to the server as specified in the constructor
|
||||||
* constructor
|
|
||||||
*/
|
*/
|
||||||
public String getSubProtocolRequest() {
|
public String getSubProtocolRequest() {
|
||||||
return subProtocolRequest;
|
return subProtocolRequest;
|
||||||
@ -103,8 +103,7 @@ public abstract class WebSocketClientHandshaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the sub protocol response and sent by the server. Only available
|
* Returns the sub protocol response and sent by the server. Only available after end of handshake.
|
||||||
* after end of handshake.
|
|
||||||
*/
|
*/
|
||||||
public String getSubProtocolResponse() {
|
public String getSubProtocolResponse() {
|
||||||
return subProtocolResponse;
|
return subProtocolResponse;
|
||||||
@ -130,7 +129,8 @@ public abstract class WebSocketClientHandshaker {
|
|||||||
* @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) throws WebSocketHandshakeException;
|
public abstract void performClosingHandshake(Channel channel, HttpResponse response)
|
||||||
|
throws WebSocketHandshakeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs an MD5 hash
|
* Performs an MD5 hash
|
||||||
|
@ -35,10 +35,9 @@ import io.netty.handler.codec.http.HttpVersion;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Performs client side opening and closing handshakes for web socket
|
* Performs client side opening and closing handshakes for web socket specification version <a
|
||||||
* specification version <a
|
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" >draft-ietf-hybi-thewebsocketprotocol-
|
||||||
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
|
* 00</a>
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>
|
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* A very large portion of this code was taken from the Netty 3.2 HTTP example.
|
* A very large portion of this code was taken from the Netty 3.2 HTTP example.
|
||||||
@ -49,22 +48,20 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
|||||||
private byte[] expectedChallengeResponseBytes = null;
|
private byte[] expectedChallengeResponseBytes = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor specifying the destination web socket location and version to
|
* Constructor specifying the destination web socket location and version to initiate
|
||||||
* initiate
|
|
||||||
*
|
*
|
||||||
* @param webSocketURL
|
* @param webSocketURL
|
||||||
* URL for web socket communications. e.g
|
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||||
* "ws://myhost.com/mypath". Subsequent web socket frames will be
|
|
||||||
* sent to this URL.
|
* sent to this URL.
|
||||||
* @param version
|
* @param version
|
||||||
* Version of web socket specification to use to connect to the
|
* Version of web socket specification to use to connect to the server
|
||||||
* 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, WebSocketSpecificationVersion version, String subProtocol, Map<String,String> customHeaders) {
|
public WebSocketClientHandshaker00(URI webSocketURL, WebSocketVersion version, String subProtocol,
|
||||||
|
Map<String, String> customHeaders) {
|
||||||
super(webSocketURL, version, subProtocol, customHeaders);
|
super(webSocketURL, version, subProtocol, customHeaders);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -179,8 +176,7 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
|||||||
* @param channel
|
* @param channel
|
||||||
* Channel
|
* Channel
|
||||||
* @param response
|
* @param response
|
||||||
* HTTP response returned from the server for the request sent by
|
* HTTP response returned from the server for the request sent by beginOpeningHandshake00().
|
||||||
* beginOpeningHandshake00().
|
|
||||||
* @throws WebSocketHandshakeException
|
* @throws WebSocketHandshakeException
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@ -193,12 +189,14 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker {
|
|||||||
|
|
||||||
String upgrade = response.getHeader(Names.UPGRADE);
|
String upgrade = response.getHeader(Names.UPGRADE);
|
||||||
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET)) {
|
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET)) {
|
||||||
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE));
|
throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
|
||||||
|
+ response.getHeader(Names.UPGRADE));
|
||||||
}
|
}
|
||||||
|
|
||||||
String connection = response.getHeader(Names.CONNECTION);
|
String connection = response.getHeader(Names.CONNECTION);
|
||||||
if (connection == null || !connection.equals(Values.UPGRADE)) {
|
if (connection == null || !connection.equals(Values.UPGRADE)) {
|
||||||
throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION));
|
throw new WebSocketHandshakeException("Invalid handshake response connection: "
|
||||||
|
+ response.getHeader(Names.CONNECTION));
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] challenge = response.getContent().array();
|
byte[] challenge = response.getContent().array();
|
||||||
|
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* 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.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.handler.codec.http.DefaultHttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||||
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpRequestEncoder;
|
||||||
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpResponseDecoder;
|
||||||
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
|
import io.netty.logging.InternalLogger;
|
||||||
|
import io.netty.logging.InternalLoggerFactory;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Performs client side opening and closing handshakes for web socket specification version <a
|
||||||
|
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" >draft-ietf-hybi-thewebsocketprotocol-
|
||||||
|
* 10</a>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker {
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class);
|
||||||
|
|
||||||
|
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
|
private String expectedChallengeResponseString = null;
|
||||||
|
|
||||||
|
private static final String protocol = null;
|
||||||
|
|
||||||
|
private boolean allowExtensions = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor specifying the destination web socket location and version to initiate
|
||||||
|
*
|
||||||
|
* @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 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,
|
||||||
|
boolean allowExtensions, Map<String, String> customHeaders) {
|
||||||
|
super(webSocketURL, version, subProtocol, customHeaders);
|
||||||
|
this.allowExtensions = allowExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /**
|
||||||
|
* <p>
|
||||||
|
* Sends the opening request to the server:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* GET /chat HTTP/1.1
|
||||||
|
* Host: server.example.com
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||||
|
* Sec-WebSocket-Origin: http://example.com
|
||||||
|
* Sec-WebSocket-Protocol: chat, superchat
|
||||||
|
* Sec-WebSocket-Version: 8
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* Channel into which we can write our request
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void performOpeningHandshake(Channel channel) {
|
||||||
|
// Get path
|
||||||
|
URI wsURL = this.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);
|
||||||
|
|
||||||
|
String acceptSeed = key + MAGIC_GUID;
|
||||||
|
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||||
|
this.expectedChallengeResponseString = base64Encode(sha1);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug(String.format("WS Version 08 Client Handshake key: %s. Expected response: %s.", key,
|
||||||
|
this.expectedChallengeResponseString));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format request
|
||||||
|
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
|
||||||
|
request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
|
||||||
|
request.addHeader(Names.CONNECTION, Values.UPGRADE);
|
||||||
|
request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
|
||||||
|
request.addHeader(Names.HOST, wsURL.getHost());
|
||||||
|
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
|
||||||
|
if (protocol != null && !protocol.equals("")) {
|
||||||
|
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
|
||||||
|
}
|
||||||
|
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8");
|
||||||
|
|
||||||
|
if (customHeaders != null) {
|
||||||
|
for (String header : customHeaders.keySet()) {
|
||||||
|
request.addHeader(header, customHeaders.get(header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.write(request);
|
||||||
|
|
||||||
|
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Process server response:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* HTTP/1.1 101 Switching Protocols
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||||
|
* Sec-WebSocket-Protocol: chat
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* Channel
|
||||||
|
* @param response
|
||||||
|
* HTTP response returned from the server for the request sent by beginOpeningHandshake00().
|
||||||
|
* @throws WebSocketHandshakeException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
|
||||||
|
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
|
||||||
|
|
||||||
|
if (!response.getStatus().equals(status)) {
|
||||||
|
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
String upgrade = response.getHeader(Names.UPGRADE);
|
||||||
|
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
|
||||||
|
throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
|
||||||
|
+ response.getHeader(Names.UPGRADE));
|
||||||
|
}
|
||||||
|
|
||||||
|
String connection = response.getHeader(Names.CONNECTION);
|
||||||
|
if (connection == null || !connection.equals(Values.UPGRADE)) {
|
||||||
|
throw new WebSocketHandshakeException("Invalid handshake response connection: "
|
||||||
|
+ response.getHeader(Names.CONNECTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
|
||||||
|
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
|
||||||
|
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
|
||||||
|
this.expectedChallengeResponseString));
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
|
||||||
|
new WebSocket08FrameDecoder(false, this.allowExtensions));
|
||||||
|
|
||||||
|
this.setOpenningHandshakeCompleted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,192 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.net.URI;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.handler.codec.http.DefaultHttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpRequestEncoder;
|
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseDecoder;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.netty.logging.InternalLogger;
|
|
||||||
import io.netty.logging.InternalLoggerFactory;
|
|
||||||
import io.netty.util.CharsetUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Performs client side opening and closing handshakes for web socket
|
|
||||||
* specification version <a
|
|
||||||
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
|
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class WebSocketClientHandshaker10 extends WebSocketClientHandshaker {
|
|
||||||
|
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker10.class);
|
|
||||||
|
|
||||||
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
||||||
|
|
||||||
private String expectedChallengeResponseString = null;
|
|
||||||
|
|
||||||
private static final String protocol = null;
|
|
||||||
|
|
||||||
private boolean allowExtensions = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor specifying the destination web socket location and version to
|
|
||||||
* initiate
|
|
||||||
*
|
|
||||||
* @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 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 WebSocketClientHandshaker10(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions, Map<String,String> customHeaders) {
|
|
||||||
super(webSocketURL, version, subProtocol, customHeaders);
|
|
||||||
this.allowExtensions = allowExtensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* /**
|
|
||||||
* <p>
|
|
||||||
* Sends the opening request to the server:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* GET /chat HTTP/1.1
|
|
||||||
* Host: server.example.com
|
|
||||||
* Upgrade: websocket
|
|
||||||
* Connection: Upgrade
|
|
||||||
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
|
||||||
* Sec-WebSocket-Origin: http://example.com
|
|
||||||
* Sec-WebSocket-Protocol: chat, superchat
|
|
||||||
* Sec-WebSocket-Version: 8
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* Channel into which we can write our request
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void performOpeningHandshake(Channel channel) {
|
|
||||||
// Get path
|
|
||||||
URI wsURL = this.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);
|
|
||||||
|
|
||||||
String acceptSeed = key + MAGIC_GUID;
|
|
||||||
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
|
||||||
this.expectedChallengeResponseString = base64Encode(sha1);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(String.format("HyBi10 Client Handshake key: %s. Expected response: %s.", key, this.expectedChallengeResponseString));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format request
|
|
||||||
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
|
|
||||||
request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
|
|
||||||
request.addHeader(Names.CONNECTION, Values.UPGRADE);
|
|
||||||
request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
|
|
||||||
request.addHeader(Names.HOST, wsURL.getHost());
|
|
||||||
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
|
|
||||||
if (protocol != null && !protocol.equals("")) {
|
|
||||||
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
|
|
||||||
}
|
|
||||||
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "8");
|
|
||||||
|
|
||||||
if(customHeaders != null){
|
|
||||||
for(String header: customHeaders.keySet()){
|
|
||||||
request.addHeader(header, customHeaders.get(header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.write(request);
|
|
||||||
|
|
||||||
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket08FrameEncoder(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Process server response:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* HTTP/1.1 101 Switching Protocols
|
|
||||||
* Upgrade: websocket
|
|
||||||
* Connection: Upgrade
|
|
||||||
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
|
||||||
* Sec-WebSocket-Protocol: chat
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* Channel
|
|
||||||
* @param response
|
|
||||||
* HTTP response returned from the server for the request sent by
|
|
||||||
* beginOpeningHandshake00().
|
|
||||||
* @throws WebSocketHandshakeException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
|
|
||||||
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
|
|
||||||
|
|
||||||
if (!response.getStatus().equals(status)) {
|
|
||||||
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
String upgrade = response.getHeader(Names.UPGRADE);
|
|
||||||
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
|
|
||||||
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE));
|
|
||||||
}
|
|
||||||
|
|
||||||
String connection = response.getHeader(Names.CONNECTION);
|
|
||||||
if (connection == null || !connection.equals(Values.UPGRADE)) {
|
|
||||||
throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION));
|
|
||||||
}
|
|
||||||
|
|
||||||
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
|
|
||||||
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
|
|
||||||
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, this.expectedChallengeResponseString));
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket08FrameDecoder(false, this.allowExtensions));
|
|
||||||
|
|
||||||
this.setOpenningHandshakeCompleted(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* 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.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.handler.codec.http.DefaultHttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Values;
|
||||||
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
|
import io.netty.handler.codec.http.HttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpRequestEncoder;
|
||||||
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpResponseDecoder;
|
||||||
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
|
import io.netty.logging.InternalLogger;
|
||||||
|
import io.netty.logging.InternalLoggerFactory;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Performs client side opening and closing handshakes for web socket specification version <a
|
||||||
|
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" >draft-ietf-hybi-thewebsocketprotocol-
|
||||||
|
* 17</a>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker {
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class);
|
||||||
|
|
||||||
|
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
|
private String expectedChallengeResponseString = null;
|
||||||
|
|
||||||
|
private static final String protocol = null;
|
||||||
|
|
||||||
|
private boolean allowExtensions = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor specifying the destination web socket location and version to initiate
|
||||||
|
*
|
||||||
|
* @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 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,
|
||||||
|
boolean allowExtensions, Map<String, String> customHeaders) {
|
||||||
|
super(webSocketURL, version, subProtocol, customHeaders);
|
||||||
|
this.allowExtensions = allowExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /**
|
||||||
|
* <p>
|
||||||
|
* Sends the opening request to the server:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* GET /chat HTTP/1.1
|
||||||
|
* Host: server.example.com
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||||
|
* Sec-WebSocket-Origin: http://example.com
|
||||||
|
* Sec-WebSocket-Protocol: chat, superchat
|
||||||
|
* Sec-WebSocket-Version: 13
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* Channel into which we can write our request
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void performOpeningHandshake(Channel channel) {
|
||||||
|
// Get path
|
||||||
|
URI wsURL = this.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);
|
||||||
|
|
||||||
|
String acceptSeed = key + MAGIC_GUID;
|
||||||
|
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||||
|
this.expectedChallengeResponseString = base64Encode(sha1);
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug(String.format("WS Version 13 Client Handshake key: %s. Expected response: %s.", key,
|
||||||
|
this.expectedChallengeResponseString));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format request
|
||||||
|
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
|
||||||
|
request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
|
||||||
|
request.addHeader(Names.CONNECTION, Values.UPGRADE);
|
||||||
|
request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
|
||||||
|
request.addHeader(Names.HOST, wsURL.getHost());
|
||||||
|
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
|
||||||
|
if (protocol != null && !protocol.equals("")) {
|
||||||
|
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
|
||||||
|
}
|
||||||
|
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13");
|
||||||
|
|
||||||
|
if (customHeaders != null) {
|
||||||
|
for (String header : customHeaders.keySet()) {
|
||||||
|
request.addHeader(header, customHeaders.get(header));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel.write(request);
|
||||||
|
|
||||||
|
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Process server response:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* HTTP/1.1 101 Switching Protocols
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||||
|
* Sec-WebSocket-Protocol: chat
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* Channel
|
||||||
|
* @param response
|
||||||
|
* HTTP response returned from the server for the request sent by beginOpeningHandshake00().
|
||||||
|
* @throws WebSocketHandshakeException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
|
||||||
|
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
|
||||||
|
|
||||||
|
if (!response.getStatus().equals(status)) {
|
||||||
|
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
String upgrade = response.getHeader(Names.UPGRADE);
|
||||||
|
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
|
||||||
|
throw new WebSocketHandshakeException("Invalid handshake response upgrade: "
|
||||||
|
+ response.getHeader(Names.UPGRADE));
|
||||||
|
}
|
||||||
|
|
||||||
|
String connection = response.getHeader(Names.CONNECTION);
|
||||||
|
if (connection == null || !connection.equals(Values.UPGRADE)) {
|
||||||
|
throw new WebSocketHandshakeException("Invalid handshake response connection: "
|
||||||
|
+ response.getHeader(Names.CONNECTION));
|
||||||
|
}
|
||||||
|
|
||||||
|
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
|
||||||
|
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
|
||||||
|
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept,
|
||||||
|
this.expectedChallengeResponseString));
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder",
|
||||||
|
new WebSocket13FrameDecoder(false, this.allowExtensions));
|
||||||
|
|
||||||
|
this.setOpenningHandshakeCompleted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,191 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.net.URI;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.handler.codec.http.DefaultHttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders.Values;
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpRequestEncoder;
|
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseDecoder;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.netty.logging.InternalLogger;
|
|
||||||
import io.netty.logging.InternalLoggerFactory;
|
|
||||||
import io.netty.util.CharsetUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Performs client side opening and closing handshakes for web socket
|
|
||||||
* specification version <a
|
|
||||||
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
|
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class WebSocketClientHandshaker17 extends WebSocketClientHandshaker {
|
|
||||||
|
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker17.class);
|
|
||||||
|
|
||||||
public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
||||||
|
|
||||||
private String expectedChallengeResponseString = null;
|
|
||||||
|
|
||||||
private static final String protocol = null;
|
|
||||||
|
|
||||||
private boolean allowExtensions = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor specifying the destination web socket location and version to
|
|
||||||
* initiate
|
|
||||||
*
|
|
||||||
* @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 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 WebSocketClientHandshaker17(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions, Map<String,String> customHeaders) {
|
|
||||||
super(webSocketURL, version, subProtocol, customHeaders);
|
|
||||||
this.allowExtensions = allowExtensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* /**
|
|
||||||
* <p>
|
|
||||||
* Sends the opening request to the server:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* GET /chat HTTP/1.1
|
|
||||||
* Host: server.example.com
|
|
||||||
* Upgrade: websocket
|
|
||||||
* Connection: Upgrade
|
|
||||||
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
|
||||||
* Sec-WebSocket-Origin: http://example.com
|
|
||||||
* Sec-WebSocket-Protocol: chat, superchat
|
|
||||||
* Sec-WebSocket-Version: 13
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* Channel into which we can write our request
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void performOpeningHandshake(Channel channel) {
|
|
||||||
// Get path
|
|
||||||
URI wsURL = this.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);
|
|
||||||
|
|
||||||
String acceptSeed = key + MAGIC_GUID;
|
|
||||||
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
|
||||||
this.expectedChallengeResponseString = base64Encode(sha1);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(String.format("HyBi17 Client Handshake key: %s. Expected response: %s.", key, this.expectedChallengeResponseString));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format request
|
|
||||||
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
|
|
||||||
request.addHeader(Names.UPGRADE, Values.WEBSOCKET.toLowerCase());
|
|
||||||
request.addHeader(Names.CONNECTION, Values.UPGRADE);
|
|
||||||
request.addHeader(Names.SEC_WEBSOCKET_KEY, key);
|
|
||||||
request.addHeader(Names.HOST, wsURL.getHost());
|
|
||||||
request.addHeader(Names.ORIGIN, "http://" + wsURL.getHost());
|
|
||||||
if (protocol != null && !protocol.equals("")) {
|
|
||||||
request.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
|
|
||||||
}
|
|
||||||
request.addHeader(Names.SEC_WEBSOCKET_VERSION, "13");
|
|
||||||
|
|
||||||
if(customHeaders != null){
|
|
||||||
for(String header: customHeaders.keySet()){
|
|
||||||
request.addHeader(header, customHeaders.get(header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
channel.write(request);
|
|
||||||
|
|
||||||
channel.getPipeline().replace(HttpRequestEncoder.class, "ws-encoder", new WebSocket13FrameEncoder(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Process server response:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* HTTP/1.1 101 Switching Protocols
|
|
||||||
* Upgrade: websocket
|
|
||||||
* Connection: Upgrade
|
|
||||||
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
|
||||||
* Sec-WebSocket-Protocol: chat
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* Channel
|
|
||||||
* @param response
|
|
||||||
* HTTP response returned from the server for the request sent by
|
|
||||||
* beginOpeningHandshake00().
|
|
||||||
* @throws WebSocketHandshakeException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void performClosingHandshake(Channel channel, HttpResponse response) throws WebSocketHandshakeException {
|
|
||||||
final HttpResponseStatus status = new HttpResponseStatus(101, "Switching Protocols");
|
|
||||||
|
|
||||||
if (!response.getStatus().equals(status)) {
|
|
||||||
throw new WebSocketHandshakeException("Invalid handshake response status: " + response.getStatus());
|
|
||||||
}
|
|
||||||
|
|
||||||
String upgrade = response.getHeader(Names.UPGRADE);
|
|
||||||
if (upgrade == null || !upgrade.equals(Values.WEBSOCKET.toLowerCase())) {
|
|
||||||
throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + response.getHeader(Names.UPGRADE));
|
|
||||||
}
|
|
||||||
|
|
||||||
String connection = response.getHeader(Names.CONNECTION);
|
|
||||||
if (connection == null || !connection.equals(Values.UPGRADE)) {
|
|
||||||
throw new WebSocketHandshakeException("Invalid handshake response connection: " + response.getHeader(Names.CONNECTION));
|
|
||||||
}
|
|
||||||
|
|
||||||
String accept = response.getHeader(Names.SEC_WEBSOCKET_ACCEPT);
|
|
||||||
if (accept == null || !accept.equals(this.expectedChallengeResponseString)) {
|
|
||||||
throw new WebSocketHandshakeException(String.format("Invalid challenge. Actual: %s. Expected: %s", accept, this.expectedChallengeResponseString));
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.getPipeline().replace(HttpResponseDecoder.class, "ws-decoder", new WebSocket13FrameDecoder(false, this.allowExtensions));
|
|
||||||
|
|
||||||
this.setOpenningHandshakeCompleted(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -27,28 +27,27 @@ public class WebSocketClientHandshakerFactory {
|
|||||||
* Instances a new handshaker
|
* Instances a new handshaker
|
||||||
*
|
*
|
||||||
* @param webSocketURL
|
* @param webSocketURL
|
||||||
* URL for web socket communications. e.g
|
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||||
* "ws://myhost.com/mypath". Subsequent web socket frames will be
|
|
||||||
* sent to this URL.
|
* sent to this URL.
|
||||||
* @param version
|
* @param version
|
||||||
* Version of web socket specification to use to connect to the
|
* Version of web socket specification to use to connect to the server
|
||||||
* server
|
|
||||||
* @param subProtocol
|
* @param subProtocol
|
||||||
* Sub protocol request sent to the server. Null if no
|
* Sub protocol request sent to the server. Null if no sub-protocol support is required.
|
||||||
* sub-protocol support is required.
|
|
||||||
* @param allowExtensions
|
* @param allowExtensions
|
||||||
* Allow extensions to be used in the reserved bits of the web
|
* Allow extensions to be used in the reserved bits of the web socket frame
|
||||||
* socket frame
|
* @param customHeaders
|
||||||
|
* Custom HTTP headers to send during the handshake
|
||||||
* @throws WebSocketHandshakeException
|
* @throws WebSocketHandshakeException
|
||||||
*/
|
*/
|
||||||
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketSpecificationVersion version, String subProtocol, boolean allowExtensions, Map<String,String> customHeaders) throws WebSocketHandshakeException {
|
public WebSocketClientHandshaker newHandshaker(URI webSocketURL, WebSocketVersion version, String subProtocol,
|
||||||
if (version == WebSocketSpecificationVersion.V17) {
|
boolean allowExtensions, Map<String, String> customHeaders) throws WebSocketHandshakeException {
|
||||||
return new WebSocketClientHandshaker17(webSocketURL, version, subProtocol, allowExtensions, customHeaders);
|
if (version == WebSocketVersion.V13) {
|
||||||
|
return new WebSocketClientHandshaker13(webSocketURL, version, subProtocol, allowExtensions, customHeaders);
|
||||||
}
|
}
|
||||||
if (version == WebSocketSpecificationVersion.V10) {
|
if (version == WebSocketVersion.V08) {
|
||||||
return new WebSocketClientHandshaker10(webSocketURL, version, subProtocol, allowExtensions, customHeaders);
|
return new WebSocketClientHandshaker08(webSocketURL, version, subProtocol, allowExtensions, customHeaders);
|
||||||
}
|
}
|
||||||
if (version == WebSocketSpecificationVersion.V00) {
|
if (version == WebSocketVersion.V00) {
|
||||||
return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol, customHeaders);
|
return new WebSocketClientHandshaker00(webSocketURL, version, subProtocol, customHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ import io.netty.buffer.ChannelBuffer;
|
|||||||
public abstract class WebSocketFrame {
|
public abstract class WebSocketFrame {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to indicate if this frame is the final fragment in a message. The
|
* Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the
|
||||||
* first fragment (frame) may also be the final fragment.
|
* final fragment.
|
||||||
*/
|
*/
|
||||||
private boolean finalFragment = true;
|
private boolean finalFragment = true;
|
||||||
|
|
||||||
@ -53,8 +53,8 @@ public abstract class WebSocketFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag to indicate if this frame is the final fragment in a message. The
|
* Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the
|
||||||
* first fragment (frame) may also be the final fragment.
|
* final fragment.
|
||||||
*/
|
*/
|
||||||
public boolean isFinalFragment() {
|
public boolean isFinalFragment() {
|
||||||
return finalFragment;
|
return finalFragment;
|
||||||
|
@ -36,18 +36,16 @@ public abstract class WebSocketServerHandshaker {
|
|||||||
|
|
||||||
private String[] subProtocolsArray = null;
|
private String[] subProtocolsArray = null;
|
||||||
|
|
||||||
private WebSocketSpecificationVersion version = WebSocketSpecificationVersion.UNKNOWN;
|
private WebSocketVersion version = WebSocketVersion.UNKNOWN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor specifying the destination web socket location
|
* Constructor specifying the destination web socket location
|
||||||
*
|
*
|
||||||
* @param webSocketURL
|
* @param webSocketURL
|
||||||
* URL for web socket communications. e.g
|
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||||
* "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
|
* CSV of supported protocols. Null if sub protocols not supported.
|
||||||
* supported.
|
|
||||||
*/
|
*/
|
||||||
public WebSocketServerHandshaker(String webSocketURL, String subProtocols) {
|
public WebSocketServerHandshaker(String webSocketURL, String subProtocols) {
|
||||||
this.webSocketURL = webSocketURL;
|
this.webSocketURL = webSocketURL;
|
||||||
@ -86,11 +84,11 @@ public abstract class WebSocketServerHandshaker {
|
|||||||
/**
|
/**
|
||||||
* Returns the version of the specification being supported
|
* Returns the version of the specification being supported
|
||||||
*/
|
*/
|
||||||
public WebSocketSpecificationVersion getVersion() {
|
public WebSocketVersion getVersion() {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVersion(WebSocketSpecificationVersion version) {
|
public void setVersion(WebSocketVersion version) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +48,9 @@ import io.netty.logging.InternalLoggerFactory;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Performs server side opening and closing handshakes for web socket
|
* Performs server side opening and closing handshakes for web socket specification version <a
|
||||||
* specification version <a
|
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00" >draft-ietf-hybi-thewebsocketprotocol-
|
||||||
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
|
* 00</a>
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>
|
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* A very large portion of this code was taken from the Netty 3.2 HTTP example.
|
* A very large portion of this code was taken from the Netty 3.2 HTTP example.
|
||||||
@ -65,8 +64,7 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
|||||||
* Constructor specifying the destination web socket location
|
* Constructor specifying the destination web socket location
|
||||||
*
|
*
|
||||||
* @param webSocketURL
|
* @param webSocketURL
|
||||||
* URL for web socket communications. e.g
|
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||||
* "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
|
||||||
@ -78,12 +76,9 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
|||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Handle the web socket handshake for the web socket specification <a href=
|
* Handle the web socket handshake for the web socket specification <a href=
|
||||||
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi
|
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">HyBi version 0</a> and lower. This standard
|
||||||
* version 0</a> and lower. This standard is really a rehash of <a
|
* is really a rehash of <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76" >hixie-76</a> and
|
||||||
* href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76"
|
* <a href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75" >hixie-75</a>.
|
||||||
* >hixie-76</a> and <a
|
|
||||||
* href="http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75"
|
|
||||||
* >hixie-75</a>.
|
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
@ -127,12 +122,13 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
|||||||
public void performOpeningHandshake(Channel channel, HttpRequest req) {
|
public void performOpeningHandshake(Channel channel, HttpRequest req) {
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug(String.format("Channel %s web socket spec version 00 handshake", channel.getId()));
|
logger.debug(String.format("Channel %s WS Version 00 server handshake", channel.getId()));
|
||||||
}
|
}
|
||||||
this.setVersion(WebSocketSpecificationVersion.V00);
|
this.setVersion(WebSocketVersion.V00);
|
||||||
|
|
||||||
// Serve the WebSocket handshake request.
|
// Serve the WebSocket handshake request.
|
||||||
if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION)) || !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
|
if (!Values.UPGRADE.equalsIgnoreCase(req.getHeader(CONNECTION))
|
||||||
|
|| !WEBSOCKET.equalsIgnoreCase(req.getHeader(Names.UPGRADE))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +136,8 @@ public class WebSocketServerHandshaker00 extends WebSocketServerHandshaker {
|
|||||||
boolean isHixie76 = req.containsHeader(SEC_WEBSOCKET_KEY1) && req.containsHeader(SEC_WEBSOCKET_KEY2);
|
boolean isHixie76 = req.containsHeader(SEC_WEBSOCKET_KEY1) && req.containsHeader(SEC_WEBSOCKET_KEY2);
|
||||||
|
|
||||||
// Create the WebSocket handshake response.
|
// Create the WebSocket handshake response.
|
||||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
|
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101,
|
||||||
|
isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake"));
|
||||||
res.addHeader(Names.UPGRADE, WEBSOCKET);
|
res.addHeader(Names.UPGRADE, WEBSOCKET);
|
||||||
res.addHeader(CONNECTION, Values.UPGRADE);
|
res.addHeader(CONNECTION, Values.UPGRADE);
|
||||||
|
|
||||||
|
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* 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 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;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Performs server side opening and closing handshakes for web socket specification version <a
|
||||||
|
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10" >draft-ietf-hybi-thewebsocketprotocol-
|
||||||
|
* 10</a>
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker {
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker08.class);
|
||||||
|
|
||||||
|
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
|
private boolean allowExtensions = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor specifying the destination web socket location
|
||||||
|
*
|
||||||
|
* @param webSocketURL
|
||||||
|
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||||
|
* sent to this URL.
|
||||||
|
* @param subProtocols
|
||||||
|
* CSV of supported protocols
|
||||||
|
* @param 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);
|
||||||
|
this.allowExtensions = allowExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Handle the web socket handshake for the web socket specification <a href=
|
||||||
|
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi version 8 to 10</a>. Version 8, 9 and
|
||||||
|
* 10 share the same wire protocol.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Browser request to the server:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* GET /chat HTTP/1.1
|
||||||
|
* Host: server.example.com
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||||
|
* Sec-WebSocket-Origin: http://example.com
|
||||||
|
* Sec-WebSocket-Protocol: chat, superchat
|
||||||
|
* Sec-WebSocket-Version: 8
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Server response:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* HTTP/1.1 101 Switching Protocols
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||||
|
* Sec-WebSocket-Protocol: chat
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* Channel
|
||||||
|
* @param req
|
||||||
|
* HTTP request
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void performOpeningHandshake(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);
|
||||||
|
|
||||||
|
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
|
||||||
|
if (key == null) {
|
||||||
|
res.setStatus(HttpResponseStatus.BAD_REQUEST);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
|
||||||
|
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||||
|
String accept = base64Encode(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.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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo back the closing frame and close the connection
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* Channel
|
||||||
|
* @param frame
|
||||||
|
* Web Socket frame that was received
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
|
||||||
|
ChannelFuture f = channel.write(frame);
|
||||||
|
f.addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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;
|
|
||||||
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.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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Performs server side opening and closing handshakes for web socket
|
|
||||||
* specification version <a
|
|
||||||
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
|
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class WebSocketServerHandshaker10 extends WebSocketServerHandshaker {
|
|
||||||
|
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker10.class);
|
|
||||||
|
|
||||||
public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
||||||
|
|
||||||
private boolean allowExtensions = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor specifying the destination web socket location
|
|
||||||
*
|
|
||||||
* @param webSocketURL
|
|
||||||
* URL for web socket communications. e.g
|
|
||||||
* "ws://myhost.com/mypath". Subsequent web socket frames will be
|
|
||||||
* sent to this URL.
|
|
||||||
* @param subProtocols
|
|
||||||
* CSV of supported protocols
|
|
||||||
* @param allowExtensions
|
|
||||||
* Allow extensions to be used in the reserved bits of the web
|
|
||||||
* socket frame
|
|
||||||
*/
|
|
||||||
public WebSocketServerHandshaker10(String webSocketURL, String subProtocols, boolean allowExtensions) {
|
|
||||||
super(webSocketURL, subProtocols);
|
|
||||||
this.allowExtensions = allowExtensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Handle the web socket handshake for the web socket specification <a href=
|
|
||||||
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-08">HyBi
|
|
||||||
* version 8 to 10</a>. Version 8, 9 and 10 share the same wire protocol.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Browser request to the server:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* GET /chat HTTP/1.1
|
|
||||||
* Host: server.example.com
|
|
||||||
* Upgrade: websocket
|
|
||||||
* Connection: Upgrade
|
|
||||||
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
|
||||||
* Sec-WebSocket-Origin: http://example.com
|
|
||||||
* Sec-WebSocket-Protocol: chat, superchat
|
|
||||||
* Sec-WebSocket-Version: 8
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Server response:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* HTTP/1.1 101 Switching Protocols
|
|
||||||
* Upgrade: websocket
|
|
||||||
* Connection: Upgrade
|
|
||||||
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
|
||||||
* Sec-WebSocket-Protocol: chat
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* Channel
|
|
||||||
* @param req
|
|
||||||
* HTTP request
|
|
||||||
* @throws NoSuchAlgorithmException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void performOpeningHandshake(Channel channel, HttpRequest req) {
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(String.format("Channel %s web socket spec version 10 handshake", channel.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols"));
|
|
||||||
this.setVersion(WebSocketSpecificationVersion.V10);
|
|
||||||
|
|
||||||
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
|
|
||||||
if (key == null) {
|
|
||||||
res.setStatus(HttpResponseStatus.BAD_REQUEST);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String acceptSeed = key + WEBSOCKET_08_ACCEPT_GUID;
|
|
||||||
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
|
||||||
String accept = base64Encode(sha1);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(String.format("HyBi10 Server Handshake key: %s. Response: %s.", key, accept));
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setStatus(new HttpResponseStatus(101, "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));
|
|
||||||
}
|
|
||||||
|
|
||||||
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(HttpResponseEncoder.class, "wsencoder", new WebSocket08FrameEncoder(false));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Echo back the closing frame and close the connection
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* Channel
|
|
||||||
* @param frame
|
|
||||||
* Web Socket frame that was received
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
|
|
||||||
ChannelFuture f = channel.write(frame);
|
|
||||||
f.addListener(ChannelFutureListener.CLOSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* 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 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;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Performs server side opening and closing handshakes for <a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a>
|
||||||
|
* (originally web socket specification version <a
|
||||||
|
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" >draft-ietf-hybi-thewebsocketprotocol-
|
||||||
|
* 17</a>).
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker {
|
||||||
|
|
||||||
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker13.class);
|
||||||
|
|
||||||
|
public static final String WEBSOCKET_17_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
|
private boolean allowExtensions = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor specifying the destination web socket location
|
||||||
|
*
|
||||||
|
* @param webSocketURL
|
||||||
|
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||||
|
* sent to this URL.
|
||||||
|
* @param subProtocols
|
||||||
|
* CSV of supported protocols
|
||||||
|
* @param 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);
|
||||||
|
this.allowExtensions = allowExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Handle the web socket handshake for the web socket specification <a href=
|
||||||
|
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi versions 13-17</a>. Versions 13-17
|
||||||
|
* share the same wire protocol.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Browser request to the server:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* GET /chat HTTP/1.1
|
||||||
|
* Host: server.example.com
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||||
|
* Sec-WebSocket-Origin: http://example.com
|
||||||
|
* Sec-WebSocket-Protocol: chat, superchat
|
||||||
|
* Sec-WebSocket-Version: 13
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Server response:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* HTTP/1.1 101 Switching Protocols
|
||||||
|
* Upgrade: websocket
|
||||||
|
* Connection: Upgrade
|
||||||
|
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||||
|
* Sec-WebSocket-Protocol: chat
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* Channel
|
||||||
|
* @param req
|
||||||
|
* HTTP request
|
||||||
|
* @throws NoSuchAlgorithmException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void performOpeningHandshake(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);
|
||||||
|
|
||||||
|
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
|
||||||
|
if (key == null) {
|
||||||
|
res.setStatus(HttpResponseStatus.BAD_REQUEST);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String acceptSeed = key + WEBSOCKET_17_ACCEPT_GUID;
|
||||||
|
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
||||||
|
String accept = base64Encode(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.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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echo back the closing frame and close the connection
|
||||||
|
*
|
||||||
|
* @param channel
|
||||||
|
* Channel
|
||||||
|
* @param frame
|
||||||
|
* Web Socket frame that was received
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
|
||||||
|
ChannelFuture f = channel.write(frame);
|
||||||
|
f.addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 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;
|
|
||||||
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.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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Performs server side opening and closing handshakes for web socket
|
|
||||||
* specification version <a
|
|
||||||
* href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
|
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class WebSocketServerHandshaker17 extends WebSocketServerHandshaker {
|
|
||||||
|
|
||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketServerHandshaker17.class);
|
|
||||||
|
|
||||||
public static final String WEBSOCKET_17_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
||||||
|
|
||||||
private boolean allowExtensions = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor specifying the destination web socket location
|
|
||||||
*
|
|
||||||
* @param webSocketURL
|
|
||||||
* URL for web socket communications. e.g
|
|
||||||
* "ws://myhost.com/mypath". Subsequent web socket frames will be
|
|
||||||
* sent to this URL.
|
|
||||||
* @param subProtocols
|
|
||||||
* CSV of supported protocols
|
|
||||||
* @param allowExtensions
|
|
||||||
* Allow extensions to be used in the reserved bits of the web
|
|
||||||
* socket frame
|
|
||||||
*/
|
|
||||||
public WebSocketServerHandshaker17(String webSocketURL, String subProtocols, boolean allowExtensions) {
|
|
||||||
super(webSocketURL, subProtocols);
|
|
||||||
this.allowExtensions = allowExtensions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Handle the web socket handshake for the web socket specification <a href=
|
|
||||||
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">HyBi
|
|
||||||
* versions 13-17</a>. Versions 13-17 share the same wire protocol.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Browser request to the server:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* GET /chat HTTP/1.1
|
|
||||||
* Host: server.example.com
|
|
||||||
* Upgrade: websocket
|
|
||||||
* Connection: Upgrade
|
|
||||||
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
|
||||||
* Sec-WebSocket-Origin: http://example.com
|
|
||||||
* Sec-WebSocket-Protocol: chat, superchat
|
|
||||||
* Sec-WebSocket-Version: 13
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* Server response:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* HTTP/1.1 101 Switching Protocols
|
|
||||||
* Upgrade: websocket
|
|
||||||
* Connection: Upgrade
|
|
||||||
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
|
||||||
* Sec-WebSocket-Protocol: chat
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* Channel
|
|
||||||
* @param req
|
|
||||||
* HTTP request
|
|
||||||
* @throws NoSuchAlgorithmException
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void performOpeningHandshake(Channel channel, HttpRequest req) {
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(String.format("Channel %s web socket spec version 17 handshake", channel.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponse res = new DefaultHttpResponse(HTTP_1_1, new HttpResponseStatus(101, "Switching Protocols"));
|
|
||||||
this.setVersion(WebSocketSpecificationVersion.V17);
|
|
||||||
|
|
||||||
String key = req.getHeader(Names.SEC_WEBSOCKET_KEY);
|
|
||||||
if (key == null) {
|
|
||||||
res.setStatus(HttpResponseStatus.BAD_REQUEST);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String acceptSeed = key + WEBSOCKET_17_ACCEPT_GUID;
|
|
||||||
byte[] sha1 = sha1(acceptSeed.getBytes(CharsetUtil.US_ASCII));
|
|
||||||
String accept = base64Encode(sha1);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(String.format("HyBi17 Server Handshake key: %s. Response: %s.", key, accept));
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setStatus(new HttpResponseStatus(101, "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));
|
|
||||||
}
|
|
||||||
|
|
||||||
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(HttpResponseEncoder.class, "wsencoder", new WebSocket13FrameEncoder(false));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Echo back the closing frame and close the connection
|
|
||||||
*
|
|
||||||
* @param channel
|
|
||||||
* Channel
|
|
||||||
* @param frame
|
|
||||||
* Web Socket frame that was received
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void performClosingHandshake(Channel channel, CloseWebSocketFrame frame) {
|
|
||||||
ChannelFuture f = channel.write(frame);
|
|
||||||
f.addListener(ChannelFutureListener.CLOSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -38,15 +38,12 @@ public class WebSocketServerHandshakerFactory {
|
|||||||
* Constructor specifying the destination web socket location
|
* Constructor specifying the destination web socket location
|
||||||
*
|
*
|
||||||
* @param webSocketURL
|
* @param webSocketURL
|
||||||
* URL for web socket communications. e.g
|
* URL for web socket communications. e.g "ws://myhost.com/mypath". Subsequent web socket frames will be
|
||||||
* "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
|
* CSV of supported protocols. Null if sub protocols not supported.
|
||||||
* supported.
|
|
||||||
* @param allowExtensions
|
* @param allowExtensions
|
||||||
* Allow extensions to be used in the reserved bits of the web
|
* Allow extensions to be used in the reserved bits of the web socket frame
|
||||||
* socket frame
|
|
||||||
*/
|
*/
|
||||||
public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) {
|
public WebSocketServerHandshakerFactory(String webSocketURL, String subProtocols, boolean allowExtensions) {
|
||||||
this.webSocketURL = webSocketURL;
|
this.webSocketURL = webSocketURL;
|
||||||
@ -57,21 +54,19 @@ public class WebSocketServerHandshakerFactory {
|
|||||||
/**
|
/**
|
||||||
* Instances a new handshaker
|
* Instances a new handshaker
|
||||||
*
|
*
|
||||||
* @return A new WebSocketServerHandshaker for the requested web socket
|
* @return A new WebSocketServerHandshaker for the requested web socket version. Null if web socket version is not
|
||||||
* version. Null if web socket version is not supported.
|
* supported.
|
||||||
*/
|
*/
|
||||||
public WebSocketServerHandshaker newHandshaker(HttpRequest req) {
|
public WebSocketServerHandshaker newHandshaker(HttpRequest req) {
|
||||||
|
|
||||||
String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
|
String version = req.getHeader(Names.SEC_WEBSOCKET_VERSION);
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
if (version.equals("13")) {
|
if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) {
|
||||||
// Version 13 of the wire protocol - assume version 17 of the
|
// Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
|
||||||
// specification.
|
return new WebSocketServerHandshaker13(webSocketURL, subProtocols, this.allowExtensions);
|
||||||
return new WebSocketServerHandshaker17(webSocketURL, subProtocols, this.allowExtensions);
|
} else if (version.equals(WebSocketVersion.V08.toHttpHeaderValue())) {
|
||||||
} else if (version.equals("8")) {
|
// Version 8 of the wire protocol - version 10 of the draft hybi specification.
|
||||||
// Version 8 of the wire protocol - assume version 10 of the
|
return new WebSocketServerHandshaker08(webSocketURL, subProtocols, this.allowExtensions);
|
||||||
// specification.
|
|
||||||
return new WebSocketServerHandshaker10(webSocketURL, subProtocols, this.allowExtensions);
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Versions of the web socket specification.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* A specification is tied to one wire protocol version but a protocol version
|
|
||||||
* may have use by more than 1 version of the specification.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public enum WebSocketSpecificationVersion {
|
|
||||||
UNKNOWN,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <a href=
|
|
||||||
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
|
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>.
|
|
||||||
*/
|
|
||||||
V00,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <a href=
|
|
||||||
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
|
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
|
|
||||||
*/
|
|
||||||
V10,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <a href=
|
|
||||||
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17"
|
|
||||||
* >draft-ietf-hybi-thewebsocketprotocol- 17</a>
|
|
||||||
*/
|
|
||||||
V17
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Versions of the web socket specification.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* A specification is tied to one wire protocol version but a protocol version may have use by more than 1 version of
|
||||||
|
* the specification.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public enum WebSocketVersion {
|
||||||
|
UNKNOWN,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href= "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00"
|
||||||
|
* >draft-ietf-hybi-thewebsocketprotocol- 00</a>.
|
||||||
|
*/
|
||||||
|
V00,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href= "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10"
|
||||||
|
* >draft-ietf-hybi-thewebsocketprotocol- 10</a>
|
||||||
|
*/
|
||||||
|
V08,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a>. This was originally <a href=
|
||||||
|
* "http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17" >draft-ietf-hybi-thewebsocketprotocol-
|
||||||
|
* 17</a>
|
||||||
|
*/
|
||||||
|
V13;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Value for HTTP Header 'Sec-WebSocket-Version'
|
||||||
|
*/
|
||||||
|
public String toHttpHeaderValue() {
|
||||||
|
if (this == V00) {
|
||||||
|
return "0";
|
||||||
|
} else if (this == V08) {
|
||||||
|
return "8";
|
||||||
|
} else if (this == V13) {
|
||||||
|
return "13";
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(this.toString() + " cannot be converted to a string.");
|
||||||
|
}
|
||||||
|
}
|
@ -23,15 +23,10 @@
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">draft-ietf-hybi-thewebsocketprotocol-00</a></li>
|
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00">draft-ietf-hybi-thewebsocketprotocol-00</a></li>
|
||||||
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10">draft-ietf-hybi-thewebsocketprotocol-10</a></li>
|
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10">draft-ietf-hybi-thewebsocketprotocol-10</a></li>
|
||||||
* <li><a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">draft-ietf-hybi-thewebsocketprotocol-17</a></li>
|
* <li><a href="http://tools.ietf.org/html/rfc6455 ">RFC 6455</a> (originally <a href="http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17">draft-ietf-hybi-thewebsocketprotocol-17</a>)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </p>
|
* </p>
|
||||||
* <p>
|
* <p>
|
||||||
* In the future, as the specification develops, more versions will be supported.
|
|
||||||
* This contrasts the <tt>io.netty.handler.codec.http.websocket</tt> package which only
|
|
||||||
* supports draft-ietf-hybi-thewebsocketprotocol-00.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* For the detailed instruction on adding add Web Socket support to your HTTP
|
* For the detailed instruction on adding add Web Socket support to your HTTP
|
||||||
* server, take a look into the <tt>WebSocketServerX</tt> example located in the
|
* server, take a look into the <tt>WebSocketServerX</tt> example located in the
|
||||||
* {@code io.netty.example.http.websocket} package.
|
* {@code io.netty.example.http.websocket} package.
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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 static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
|
||||||
|
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
||||||
|
import static org.easymock.EasyMock.capture;
|
||||||
|
import static org.easymock.EasyMock.replay;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.DefaultChannelFuture;
|
||||||
|
import io.netty.channel.DefaultChannelPipeline;
|
||||||
|
import io.netty.handler.codec.http.DefaultHttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpChunkAggregator;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders.Names;
|
||||||
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
|
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 org.easymock.Capture;
|
||||||
|
import org.easymock.EasyMock;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class WebSocketServerHandshaker13Test {
|
||||||
|
|
||||||
|
private DefaultChannelPipeline createPipeline() {
|
||||||
|
DefaultChannelPipeline pipeline = new DefaultChannelPipeline();
|
||||||
|
pipeline.addLast("chunkAggregator", new HttpChunkAggregator(42));
|
||||||
|
pipeline.addLast("requestDecoder", new HttpRequestDecoder());
|
||||||
|
pipeline.addLast("responseEncoder", new HttpResponseEncoder());
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPerformOpeningHandshake() {
|
||||||
|
Channel channelMock = EasyMock.createMock(Channel.class);
|
||||||
|
|
||||||
|
DefaultChannelPipeline pipeline = createPipeline();
|
||||||
|
EasyMock.expect(channelMock.getPipeline()).andReturn(pipeline);
|
||||||
|
|
||||||
|
// capture the http response in order to verify the headers
|
||||||
|
Capture<HttpResponse> res = new Capture<HttpResponse>();
|
||||||
|
EasyMock.expect(channelMock.write(capture(res))).andReturn(new DefaultChannelFuture(channelMock, true));
|
||||||
|
|
||||||
|
replay(channelMock);
|
||||||
|
|
||||||
|
HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
|
||||||
|
req.setHeader(Names.HOST, "server.example.com");
|
||||||
|
req.setHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
|
||||||
|
req.setHeader(Names.CONNECTION, "Upgrade");
|
||||||
|
req.setHeader(Names.SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==");
|
||||||
|
req.setHeader(Names.SEC_WEBSOCKET_ORIGIN, "http://example.com");
|
||||||
|
req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat");
|
||||||
|
req.setHeader(Names.SEC_WEBSOCKET_VERSION, "13");
|
||||||
|
|
||||||
|
WebSocketServerHandshaker13 handsaker13 = new WebSocketServerHandshaker13("ws://example.com/chat", "chat",
|
||||||
|
false);
|
||||||
|
handsaker13.performOpeningHandshake(channelMock, req);
|
||||||
|
|
||||||
|
Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT));
|
||||||
|
Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
|
||||||
|
}
|
||||||
|
}
|
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 static io.netty.handler.codec.http.HttpHeaders.Values.WEBSOCKET;
|
|
||||||
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
|
|
||||||
import static org.easymock.EasyMock.capture;
|
|
||||||
import static org.easymock.EasyMock.replay;
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.DefaultChannelFuture;
|
|
||||||
import io.netty.channel.DefaultChannelPipeline;
|
|
||||||
import io.netty.handler.codec.http.DefaultHttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpChunkAggregator;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders.Names;
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
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 org.easymock.Capture;
|
|
||||||
import org.easymock.EasyMock;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class WebSocketServerHandshaker17Test {
|
|
||||||
|
|
||||||
private DefaultChannelPipeline createPipeline() {
|
|
||||||
DefaultChannelPipeline pipeline = new DefaultChannelPipeline();
|
|
||||||
pipeline.addLast("chunkAggregator", new HttpChunkAggregator(42));
|
|
||||||
pipeline.addLast("requestDecoder", new HttpRequestDecoder());
|
|
||||||
pipeline.addLast("responseEncoder", new HttpResponseEncoder());
|
|
||||||
return pipeline;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPerformOpeningHandshake() {
|
|
||||||
Channel channelMock = EasyMock.createMock(Channel.class);
|
|
||||||
|
|
||||||
DefaultChannelPipeline pipeline = createPipeline();
|
|
||||||
EasyMock.expect(channelMock.getPipeline()).andReturn(pipeline);
|
|
||||||
|
|
||||||
// capture the http response in order to verify the headers
|
|
||||||
Capture<HttpResponse> res = new Capture<HttpResponse>();
|
|
||||||
EasyMock.expect(channelMock.write(capture(res))).andReturn(new DefaultChannelFuture(channelMock, true));
|
|
||||||
|
|
||||||
replay(channelMock);
|
|
||||||
|
|
||||||
HttpRequest req = new DefaultHttpRequest(HTTP_1_1, HttpMethod.GET, "/chat");
|
|
||||||
req.setHeader(Names.HOST, "server.example.com");
|
|
||||||
req.setHeader(Names.UPGRADE, WEBSOCKET.toLowerCase());
|
|
||||||
req.setHeader(Names.CONNECTION, "Upgrade");
|
|
||||||
req.setHeader(Names.SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==");
|
|
||||||
req.setHeader(Names.SEC_WEBSOCKET_ORIGIN, "http://example.com");
|
|
||||||
req.setHeader(Names.SEC_WEBSOCKET_PROTOCOL, "chat, superchat");
|
|
||||||
req.setHeader(Names.SEC_WEBSOCKET_VERSION, "13");
|
|
||||||
|
|
||||||
WebSocketServerHandshaker17 handsaker17 = new WebSocketServerHandshaker17("ws://example.com/chat", "chat", false);
|
|
||||||
handsaker17.performOpeningHandshake(channelMock, req);
|
|
||||||
|
|
||||||
Assert.assertEquals("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", res.getValue().getHeader(Names.SEC_WEBSOCKET_ACCEPT));
|
|
||||||
Assert.assertEquals("chat", res.getValue().getHeader(Names.SEC_WEBSOCKET_PROTOCOL));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user