123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
- // Lincense: New BSD Lincense
- // Reference: http://dev.w3.org/html5/websockets/
- // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-31
- package {
- import flash.display.*;
- import flash.events.*;
- import flash.external.*;
- import flash.net.*;
- import flash.system.*;
- import flash.utils.*;
- import mx.core.*;
- import mx.controls.*;
- import mx.events.*;
- import mx.utils.*;
- import com.adobe.net.proxies.RFC2817Socket;
- import com.hurlant.crypto.tls.TLSSocket;
- import com.hurlant.crypto.tls.TLSConfig;
- import com.hurlant.crypto.tls.TLSEngine;
- import com.hurlant.crypto.tls.TLSSecurityParameters;
- [Event(name="message", type="WebSocketMessageEvent")]
- [Event(name="open", type="flash.events.Event")]
- [Event(name="close", type="flash.events.Event")]
- [Event(name="stateChange", type="WebSocketStateEvent")]
- public class WebSocket extends EventDispatcher {
-
- private static var CONNECTING:int = 0;
- private static var OPEN:int = 1;
- private static var CLOSED:int = 2;
-
- //private var rawSocket:RFC2817Socket;
- private var rawSocket:Socket;
- private var tlsSocket:TLSSocket;
- private var tlsConfig:TLSConfig;
- private var socket:Socket;
- private var main:WebSocketMain;
- private var scheme:String;
- private var host:String;
- private var port:uint;
- private var path:String;
- private var origin:String;
- private var protocol:String;
- private var buffer:ByteArray = new ByteArray();
- private var headerState:int = 0;
- private var readyState:int = CONNECTING;
- private var bufferedAmount:int = 0;
- private var headers:String;
- public function WebSocket(
- main:WebSocketMain, url:String, protocol:String,
- proxyHost:String = null, proxyPort:int = 0,
- headers:String = null) {
- this.main = main;
- var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?$/);
- if (!m) main.fatal("invalid url: " + url);
- this.scheme = m[1];
- this.host = m[2];
- this.port = parseInt(m[4] || "80");
- this.path = m[5] || "/";
- this.origin = main.getOrigin();
- this.protocol = protocol;
- // if present and not the empty string, headers MUST end with \r\n
- // headers should be zero or more complete lines, for example
- // "Header1: xxx\r\nHeader2: yyyy\r\n"
- this.headers = headers;
-
- /*
- socket = new RFC2817Socket();
-
- // if no proxy information is supplied, it acts like a normal Socket
- // @see RFC2817Socket::connect
- if (proxyHost != null && proxyPort != 0){
- socket.setProxyInfo(proxyHost, proxyPort);
- }
- */
- ExternalInterface.call("console.log", "[WebSocket] scheme: " + scheme);
- rawSocket = new Socket();
- rawSocket.addEventListener(Event.CLOSE, onSocketClose);
- rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
- rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
- rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
- if (scheme == "wss") {
- tlsConfig= new TLSConfig(TLSEngine.CLIENT,
- null, null, null, null, null,
- TLSSecurityParameters.PROTOCOL_VERSION);
- tlsConfig.trustSelfSignedCertificates = true;
- tlsConfig.ignoreCommonNameMismatch = true;
- tlsSocket = new TLSSocket();
- tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
- socket = (tlsSocket as Socket);
- } else {
- rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
- socket = (rawSocket as Socket);
- }
- rawSocket.connect(host, port);
- }
-
- public function send(data:String):int {
- if (readyState == OPEN) {
- socket.writeByte(0x00);
- socket.writeUTFBytes(data);
- socket.writeByte(0xff);
- socket.flush();
- main.log("sent: " + data);
- return -1;
- } else if (readyState == CLOSED) {
- var bytes:ByteArray = new ByteArray();
- bytes.writeUTFBytes(data);
- bufferedAmount += bytes.length; // not sure whether it should include \x00 and \xff
- // We use return value to let caller know bufferedAmount because we cannot fire
- // stateChange event here which causes weird error:
- // > You are trying to call recursively into the Flash Player which is not allowed.
- return bufferedAmount;
- } else {
- main.fatal("invalid state");
- return 0;
- }
- }
-
- public function close():void {
- main.log("close");
- try {
- socket.close();
- } catch (ex:Error) { }
- readyState = CLOSED;
- // We don't fire any events here because it causes weird error:
- // > You are trying to call recursively into the Flash Player which is not allowed.
- // We do something equivalent in JavaScript WebSocket#close instead.
- }
-
- public function getReadyState():int {
- return readyState;
- }
-
- public function getBufferedAmount():int {
- return bufferedAmount;
- }
-
- private function onSocketConnect(event:Event):void {
- main.log("connected");
- if (scheme == "wss") {
- ExternalInterface.call("console.log", "[WebSocket] starting SSL/TLS");
- tlsSocket.startTLS(rawSocket, host, tlsConfig);
- }
-
- var hostValue:String = host + (port == 80 ? "" : ":" + port);
- var cookie:String = "";
- if (main.getCallerHost() == host) {
- cookie = ExternalInterface.call("function(){return document.cookie}");
- }
- var opt:String = "";
- if (protocol) opt += "WebSocket-Protocol: " + protocol + "\r\n";
- // if caller passes additional headers they must end with "\r\n"
- if (headers) opt += headers;
-
- var req:String = StringUtil.substitute(
- "GET {0} HTTP/1.1\r\n" +
- "Upgrade: WebSocket\r\n" +
- "Connection: Upgrade\r\n" +
- "Host: {1}\r\n" +
- "Origin: {2}\r\n" +
- "Cookie: {4}\r\n" +
- "{3}" +
- "\r\n",
- path, hostValue, origin, opt, cookie);
- main.log("request header:\n" + req);
- socket.writeUTFBytes(req);
- socket.flush();
- }
- private function onSocketClose(event:Event):void {
- main.log("closed");
- readyState = CLOSED;
- notifyStateChange();
- dispatchEvent(new Event("close"));
- }
- private function onSocketIoError(event:IOErrorEvent):void {
- close();
- main.fatal("failed to connect Web Socket server (IoError)");
- }
- private function onSocketSecurityError(event:SecurityErrorEvent):void {
- close();
- main.fatal(
- "failed to connect Web Socket server (SecurityError)\n" +
- "make sure the server is running and Flash socket policy file is correctly placed");
- }
- private function onSocketData(event:ProgressEvent):void {
- var pos:int = buffer.length;
- socket.readBytes(buffer, pos);
- for (; pos < buffer.length; ++pos) {
- if (headerState != 4) {
- // try to find "\r\n\r\n"
- if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
- ++headerState;
- } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
- ++headerState;
- } else {
- headerState = 0;
- }
- if (headerState == 4) {
- var headerStr:String = buffer.readUTFBytes(pos + 1);
- main.log("response header:\n" + headerStr);
- validateHeader(headerStr);
- makeBufferCompact();
- pos = -1;
- readyState = OPEN;
- notifyStateChange();
- dispatchEvent(new Event("open"));
- }
- } else {
- if (buffer[pos] == 0xff) {
- if (buffer.readByte() != 0x00) {
- close();
- main.fatal("data must start with \\x00");
- }
- var data:String = buffer.readUTFBytes(pos - 1);
- main.log("received: " + data);
- dispatchEvent(new WebSocketMessageEvent("message", encodeURIComponent(data)));
- buffer.readByte();
- makeBufferCompact();
- pos = -1;
- }
- }
- }
- }
-
- private function validateHeader(headerStr:String):void {
- var lines:Array = headerStr.split(/\r\n/);
- if (!lines[0].match(/^HTTP\/1.1 101 /)) {
- close();
- main.fatal("bad response: " + lines[0]);
- }
- var header:Object = {};
- for (var i:int = 1; i < lines.length; ++i) {
- if (lines[i].length == 0) continue;
- var m:Array = lines[i].match(/^(\S+): (.*)$/);
- if (!m) {
- close();
- main.fatal("failed to parse response header line: " + lines[i]);
- }
- header[m[1]] = m[2];
- }
- if (header["Upgrade"] != "WebSocket") {
- close();
- main.fatal("invalid Upgrade: " + header["Upgrade"]);
- }
- if (header["Connection"] != "Upgrade") {
- close();
- main.fatal("invalid Connection: " + header["Connection"]);
- }
- var resOrigin:String = header["WebSocket-Origin"].toLowerCase();
- if (resOrigin != origin) {
- close();
- main.fatal("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
- }
- if (protocol && header["WebSocket-Protocol"] != protocol) {
- close();
- main.fatal("protocol doesn't match: '" +
- header["WebSocket-Protocol"] + "' != '" + protocol + "'");
- }
- }
- private function makeBufferCompact():void {
- if (buffer.position == 0) return;
- var nextBuffer:ByteArray = new ByteArray();
- buffer.readBytes(nextBuffer);
- buffer = nextBuffer;
- }
-
- private function notifyStateChange():void {
- dispatchEvent(new WebSocketStateEvent("stateChange", readyState, bufferedAmount));
- }
- }
- }
|