Permalink
Browse files

Adding WebSocket server example

  • Loading branch information...
MattTuttle committed Mar 16, 2015
1 parent 504b099 commit 874ea4c74968a00048a1a1c7a734c530fe59d60c
Showing with 149 additions and 81 deletions.
  1. +3 −0 build.hxml
  2. +1 −1 hxnet/base/Protocol.hx
  3. +1 −1 hxnet/interfaces/Protocol.hx
  4. +109 −77 hxnet/protocols/WebSocket.hx
  5. +2 −2 hxnet/tcp/Server.hx
  6. +33 −0 samples/websocket/Main.hx
View
@@ -0,0 +1,3 @@
-neko echo.n
-main Main
-cp samples/websocket
View
@@ -81,7 +81,7 @@ class Protocol implements hxnet.interfaces.Protocol
return finish;
}
public function makeConnection(cnx:Connection) { this.cnx = cnx; }
public function makeConnection(cnx:Connection, isClient:Bool) { this.cnx = cnx; }
public function loseConnection(?reason:String) { this.cnx = null; }
@@ -5,7 +5,7 @@ import haxe.io.Input;
interface Protocol
{
public function isConnected():Bool;
public function makeConnection(cnx:Connection):Void;
public function makeConnection(cnx:Connection, isClient:Bool):Void;
public function loseConnection(?reason:String):Void;
public function dataReceived(input:Input):Void;
}
@@ -1,6 +1,7 @@
package hxnet.protocols;
import haxe.crypto.BaseCode;
import haxe.crypto.Base64;
import haxe.crypto.Sha1;
import haxe.io.Bytes;
import haxe.io.BytesOutput;
import haxe.io.Input;
@@ -13,12 +14,14 @@ import neko.Lib;
import cpp.Lib;
#end
using StringTools;
/**
* WebSocket protocol (RFC 6455)
*/
class WebSocket extends hxnet.base.Protocol
{
private static inline var WEBSOCKET_VERSION = 13;
private static inline var WEBSOCKET_VERSION = "13";
private static inline var OPCODE_CONTINUE = 0x0;
private static inline var OPCODE_TEXT = 0x1;
@@ -27,51 +30,54 @@ class WebSocket extends hxnet.base.Protocol
private static inline var OPCODE_PING = 0x9;
private static inline var OPCODE_PONG = 0xA;
private var headersSent:Bool = false;
private var host:String;
private var url:String;
private var port:Int;
private var key:String;
private var origin:String;
/**
* Construct the WebSocket protocol
*/
public function new(url:String, host:String, port:Int, origin:String, key:String="key")
{
super();
this.host = host;
this.url = url;
this.port = port;
this.key = key;
this.origin = origin;
_host = host;
_url = url;
_port = port;
_key = Base64.encode(Bytes.ofString(key));
_origin = origin;
_headers = new Array<String>();
}
function setHeader(key:String, value:String)
{
_headers.push(key + ": " + value);
}
function writeHeader(http:String)
{
_headers.insert(0, http);
cnx.writeBytes(Bytes.ofString(_headers.join("\r\n") + "\r\n\r\n"));
_headers = new Array<String>();
_useHttp = false;
}
/**
* Upon connecting with another WebSocket send handshake
* @param cnx The remote connection
*/
override public function makeConnection(cnx:Connection)
override public function makeConnection(cnx:Connection, isClient:Bool)
{
super.makeConnection(cnx);
super.makeConnection(cnx, isClient);
var headers = new Array<String>();
headers.push("GET " + url + " HTTP/1.1");
headers.push("Host: " + host + ":" + port);
headers.push("Upgrade: websocket");
headers.push("Connection: Upgrade");
headers.push("Sec-WebSocket-Key: " + encodeBase64(key));
headers.push("Sec-WebSocket-Version: " + WEBSOCKET_VERSION);
headers.push("Origin: " + origin);
// send headers
var header = headers.join("\r\n") + "\r\n\r\n";
cnx.writeBytes(Bytes.ofString(header));
headersSent = true;
if (isClient)
{
setHeader("Host", _host + ":" + _port);
setHeader("Upgrade", "websocket");
setHeader("Connection", "Upgrade");
setHeader("Sec-WebSocket-Key", _key);
setHeader("Sec-WebSocket-Version", WEBSOCKET_VERSION);
setHeader("Origin", _origin);
// send headers
writeHeader("GET " + _url + " HTTP/1.1");
}
}
/**
@@ -80,36 +86,50 @@ class WebSocket extends hxnet.base.Protocol
*/
override public function dataReceived(input:Input)
{
if (headersSent)
if (_useHttp) // http protocol
{
var line:String;
while((line = input.readLine()) != "")
{
// trace(line);
var colon = line.indexOf(":");
if (colon != -1)
{
var key = line.substr(0, colon).trim();
var value = line.substr(colon + 1).trim();
if (key == "Sec-WebSocket-Key")
{
var accept = Base64.encode(Sha1.make(Bytes.ofString(value + MAGIC_STRING)));
setHeader("Upgrade", "websocket");
setHeader("Connection", "upgrade");
setHeader("Sec-WebSocket-Accept", accept);
}
}
}
headersSent = false;
writeHeader("HTTP/1.1 101 Switching Protocols");
}
// loop until we get text or binary data
var frame = recvFrame(input);
switch (frame.opcode)
else // websocket protocol
{
case OPCODE_CONTINUE: // continuation
// return frame.bytes.toString();
throw "Continuation should be handled by recvFrame()";
case OPCODE_TEXT: // text
recvText(frame.bytes.toString());
case OPCODE_BINARY: // binary
recvBinary(frame.bytes);
case OPCODE_CLOSE: // close
cnx.close();
case OPCODE_PING: // ping
sendFrame(OPCODE_PONG); // send pong
case OPCODE_PONG: // pong
// do nothing
default:
throw "Unsupported websocket opcode: " + frame.opcode;
// loop until we get text or binary data
var frame = recvFrame(input);
switch (frame.opcode)
{
case OPCODE_CONTINUE: // continuation
// return frame.bytes.toString();
throw "Continuation should be handled by recvFrame()";
case OPCODE_TEXT: // text
recvText(frame.bytes.toString());
case OPCODE_BINARY: // binary
recvBinary(frame.bytes);
case OPCODE_CLOSE: // close
cnx.close();
case OPCODE_PING: // ping
sendFrame(OPCODE_PONG); // send pong
case OPCODE_PONG: // pong
// do nothing
default:
throw "Unsupported websocket opcode: " + frame.opcode;
}
}
}
@@ -146,17 +166,31 @@ class WebSocket extends hxnet.base.Protocol
*/
private function sendFrame(opcode:Int, ?bytes:haxe.io.Bytes)
{
var bytes = new BytesOutput();
var length = 0;
var out = new BytesOutput();
var length = (bytes == null) ? 0 : bytes.length;
opcode |= 0x80;
bytes.writeByte(opcode);
out.writeByte(opcode);
if (length < 0x7E)
{
out.writeByte(length);
}
else if (length < 0xFFFF)
{
out.writeByte(0x7E);
out.writeByte(length >> 8 & 0xFF);
out.writeByte(length & 0xFF);
}
else
{
throw "Can't send data this large";
}
if (bytes != null)
{
length = bytes.length;
out.writeBytes(bytes, 0, bytes.length);
}
bytes.writeByte(length);
cnx.writeBytes(bytes.getBytes());
cnx.writeBytes(out.getBytes());
}
/**
@@ -167,7 +201,7 @@ class WebSocket extends hxnet.base.Protocol
var opcode = input.readByte();
var len = input.readByte();
var final = opcode & 0x80 != 0;
var final = (opcode & 0x80) != 0; // check byte 0
opcode = opcode & 0x0F;
var mask = len >> 7 == 1;
len = len & 0x7F;
@@ -184,11 +218,14 @@ class WebSocket extends hxnet.base.Protocol
var lenByte1 = input.readByte();
var lenByte2 = input.readByte();
var lenByte3 = input.readByte();
var lenByte4 = input.readByte();
var lenByte5 = input.readByte();
var lenByte6 = input.readByte();
var lenByte7 = input.readByte();
len = (lenByte0 << 24) + (lenByte1 << 16) + (lenByte2 << 8) + lenByte3;
}
var maskKey = (mask ? input.read(4) : null);
var payload = input.read(len);
if (mask)
@@ -202,22 +239,17 @@ class WebSocket extends hxnet.base.Protocol
return {
opcode: opcode,
mask: mask,
final: final,
bytes: payload
};
}
/**
* Helper function to encode content into base 64
*/
private function encodeBase64(content:String):String
{
var suffix = switch (content.length % 3) {
case 2: "=";
case 1: "==";
default: "";
};
return BaseCode.encode(content, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") + suffix;
}
private var _host:String;
private var _url:String;
private var _port:Int;
private var _key:String;
private var _origin:String;
private var _headers:Array<String>;
private var _useHttp:Bool = true;
static private var MAGIC_STRING:String = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
}
View
@@ -47,7 +47,7 @@ class Server implements hxnet.interfaces.Server
}
}
public function update(timeout:Float=1)
public function update(?timeout:Float=null)
{
var len = buffer.length,
bytesReceived:Int,
@@ -64,7 +64,7 @@ class Server implements hxnet.interfaces.Server
cnx = factory.buildProtocol();
var connection = new Connection(client);
client.custom = cnx;
cnx.makeConnection(connection);
cnx.makeConnection(connection, false);
}
else
{
View
@@ -0,0 +1,33 @@
import hxnet.tcp.Server;
class Echo extends hxnet.protocols.WebSocket
{
override private function recvText(text:String) {
sendText(text);
}
override private function recvBinary(data:haxe.io.Bytes) {
sendBinary(data);
}
}
class Main
{
public function new()
{
server = new Server(new hxnet.base.Factory(Echo), 4000, "localhost");
server.listen();
while (true)
{
server.update();
}
}
static public function main()
{
new Main();
}
private var server:Server;
}

0 comments on commit 874ea4c

Please sign in to comment.