package com.iwebpp.wspp; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; import android.util.Base64; import com.iwebpp.node.EventEmitter2; import com.iwebpp.node.NodeContext; import com.iwebpp.node.Url; import com.iwebpp.node.Url.UrlObj; import com.iwebpp.node.Util; import com.iwebpp.node.http.HttpServer; import com.iwebpp.node.http.HttppServer; import com.iwebpp.node.http.IncomingMessage; import com.iwebpp.node.http.ServerResponse; import com.iwebpp.node.http.http; import com.iwebpp.node.http.httpp; import com.iwebpp.node.net.AbstractServer; import com.iwebpp.node.net.AbstractSocket; import com.iwebpp.node.others.BasicBean; import com.iwebpp.node.stream.Writable.WriteCB; /** * WebSocket Server implementation */ public class WebSocketServer extends EventEmitter2 { private static final String TAG = "WebSocketServer"; private AbstractServer _server = null; private NodeContext context; private Options options; private String path; private List<WebSocket> clients; // http/httpp server map to websocket servers with Path private static final Map<String, List<String>> _webSocketPaths; static { _webSocketPaths = new Hashtable<String, List<String>>(); } public static class Options { public String host = "0.0.0.0"; public int port = -1; public AbstractServer server = null; public String path = null; public boolean noServer = false; public boolean disableHixie = true; public boolean clientTracking = true; public boolean httpp = false; public boolean https = false; // TBD... public VerifyClient verifyClient = null; public HandleProtocol handleProtocols = null; } public interface ListeningCallback { public void onListening() throws Exception; } public void onconnection(final onconnectionListener cb) throws Exception { if (cb != null) this.on("connection", new Listener(){ @Override public void onEvent(Object raw) throws Exception { WebSocket data = (WebSocket)raw; cb.onConnection(data); } }); } public interface onconnectionListener { public void onConnection(WebSocket socket) throws Exception; } public void onerror(final onerrorListener cb) throws Exception { if (cb != null) this.on("error", new Listener(){ @Override public void onEvent(Object raw) throws Exception { String data = raw!=null ? raw.toString() : ""; cb.onError(data); } }); } public interface onerrorListener { public void onError(String error) throws Exception; } public WebSocketServer(NodeContext ctx, Options options, final ListeningCallback callback) throws Exception { this.context = ctx; /* options = new Options({ host: '0.0.0.0', port: null, server: null, verifyClient: null, handleProtocols: null, path: null, noServer: false, disableHixie: false, clientTracking: true, httpp: false, // default as HTTP not HTTPP https: false, // default as HTTP not HTTPS }).merge(options); */ ///if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) { if (options.port < 0 && options.server==null && !options.noServer) { throw new Exception("'port' or a 'server' must be provided"); } final WebSocketServer self = this; /*if (options.isDefinedAndNonNull('port')) { var httpObj; if (typeof options.value.https === 'object') { httpObj = options.value.httpp ? httpps : https; this._server = httpObj.createServer(options.value.https, function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Not implemented'); }); } else { httpObj = options.value.httpp ? httpp : http; this._server = httpObj.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Not implemented'); }); } this._server.listen(options.value.port, options.value.host, callback); this._closeServer = function() { if (self._server) self._server.close(); }; } else if (options.value.server) { this._server = options.value.server; if (options.value.path) { // take note of the path, to avoid collisions when multiple websocket servers are // listening on the same http server if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) { throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); } if (typeof this._server._webSocketPaths !== 'object') { this._server._webSocketPaths = {}; } this._server._webSocketPaths[options.value.path] = 1; } }*/ if (options.port > 0) { if (options.httpp) { this._server = httpp.createServer(context, new HttpServer.requestListener() { public void onRequest(IncomingMessage req, ServerResponse res) throws Exception { Map<String, List<String>> headers = new Hashtable<String, List<String>>(); headers.put("Content-Type", new ArrayList<String>()); headers.get("Content-Type").add("text/plain"); ///res.writeHead(200, {'Content-Type': 'text/plain'}); res.writeHead(200, headers); res.end("Not implemented", "utf-8", null); } }).listen(options.port, options.host, new HttppServer.ListeningCallback() { @Override public void onListening() throws Exception { callback.onListening(); } }); } else { this._server = http.createServer(context, new HttpServer.requestListener() { public void onRequest(IncomingMessage req, ServerResponse res) throws Exception { Map<String, List<String>> headers = new Hashtable<String, List<String>>(); headers.put("Content-Type", new ArrayList<String>()); headers.get("Content-Type").add("text/plain"); ///res.writeHead(200, {'Content-Type': 'text/plain'}); res.writeHead(200, headers); res.end("Not implemented", "utf-8", null); } }).listen(options.port, options.host, new HttpServer.ListeningCallback() { @Override public void onListening() throws Exception { callback.onListening(); } }); } ///this._server.listen(options.port, options.host, callback); ///this._closeServer = function() { if (self._server) self._server.close(); }; } else if (options.server!=null) { this._server = options.server; if (options.path != null) { // take note of the path, to avoid collisions when multiple websocket servers are // listening on the same http server /*if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) { throw new Error('two instances of WebSocketServer cannot listen on the same http server path'); }*/ if (_webSocketPaths!=null && _webSocketPaths.containsKey(this._server.toString()) && _webSocketPaths.get(this._server.toString()).contains(options.path)) { throw new Exception("two instances of WebSocketServer cannot listen on the same http server path"); } /*if (typeof this._server._webSocketPaths !== 'object') { this._server._webSocketPaths = {}; } this._server._webSocketPaths[options.value.path] = 1;*/ if (!_webSocketPaths.containsKey(this._server.toString())) { _webSocketPaths.put(this._server.toString(), new LinkedList<String>()); } _webSocketPaths.get(this._server.toString()).add(options.path); } } if (this._server!=null) { ///this._server.once('listening', function() { self.emit('listening'); }); this._server.onceListening(new AbstractServer.ListeningCallback() { @Override public void onListening() throws Exception { self.emit("listening"); } }); } /* if (typeof this._server != 'undefined') { this._server.on('error', function(error) { self.emit('error', error) }); this._server.on('upgrade', function(req, socket, upgradeHead) { //copy upgradeHead to avoid retention of large slab buffers used in node core var head = new Buffer(upgradeHead.length); upgradeHead.copy(head); self.handleUpgrade(req, socket, head, function(client) { self.emit('connection'+req.url, client); self.emit('connection', client); }); }); } */ if (this._server != null) { if (options.httpp) { HttppServer srv = (HttppServer)this._server; srv.onError(new AbstractServer.ErrorListener() { @Override public void onError(String error) throws Exception { self.emit("error", error); } }); srv.onUpgrade(new HttpServer.upgradeListener() { @Override public void onUpgrade(final IncomingMessage req, AbstractSocket socket, ByteBuffer upgradeHead) throws Exception { //copy upgradeHead to avoid retention of large slab buffers used in node core ///var head = new Buffer(upgradeHead.length); ///upgradeHead.copy(head); ByteBuffer head = ByteBuffer.allocate(upgradeHead.capacity()); head.put(upgradeHead); head.flip(); upgradeHead.flip(); debug(TAG, "onUpgrade, upgradeHead:"+upgradeHead+",head:"+head); /*self.handleUpgrade(req, socket, head, function(client) { self.emit("connection"+req.url, client); self.emit("connection", client); });*/ self.handleUpgrade(req, socket, head, new UpgradeCallback(){ @Override public void onUpgrade(WebSocket client) throws Exception { self.emit("connection"+req.url(), client); self.emit("connection", client); } }); } }); } else { HttpServer srv = (HttpServer)this._server; srv.onError(new AbstractServer.ErrorListener() { @Override public void onError(String error) throws Exception { self.emit("error", error); } }); srv.onUpgrade(new HttpServer.upgradeListener() { @Override public void onUpgrade(final IncomingMessage req, AbstractSocket socket, ByteBuffer upgradeHead) throws Exception { //copy upgradeHead to avoid retention of large slab buffers used in node core ///var head = new Buffer(upgradeHead.length); ///upgradeHead.copy(head); ByteBuffer head = ByteBuffer.allocate(upgradeHead.capacity()); head.put(upgradeHead); head.flip(); upgradeHead.flip(); debug(TAG, "onUpgrade, upgradeHead:"+upgradeHead+",head:"+head); /*self.handleUpgrade(req, socket, head, function(client) { self.emit("connection"+req.url, client); self.emit("connection", client); });*/ self.handleUpgrade(req, socket, head, new UpgradeCallback(){ @Override public void onUpgrade(WebSocket client) throws Exception { self.emit("connection"+req.url(), client); self.emit("connection", client); } }); } }); } } this.options = options;///.value; this.path = options.path;///value.path; this.clients = new LinkedList<WebSocket>();///[]; } @SuppressWarnings("unused") private WebSocketServer() {} private void _closeServer() throws Exception { if (this._server!=null) this._server.close(null); } /** * Immediately shuts down the connection. * @throws Exception * * @api public */ public void close() throws Exception { // terminate all associated clients String error = null; try { for (int i = 0, l = this.clients.size(); i < l; ++i) { this.clients.get(i).terminate(); } } catch (Exception e) { error = e.toString(); } // remove path descriptor, if any /* if (this.path && this._server._webSocketPaths) { delete this._server._webSocketPaths[this.path]; if (Object.keys(this._server._webSocketPaths).length == 0) { delete this._server._webSocketPaths; } }*/ if (this.path!=null && _webSocketPaths.containsKey(this._server.toString())) { if (_webSocketPaths.get(this._server.toString()).contains(this.path)) _webSocketPaths.get(this._server.toString()).remove(this.path); if (_webSocketPaths.get(this._server.toString()).isEmpty()) { _webSocketPaths.remove(this._server.toString()); } } // close the http server if it was internally created /*try { if (typeof this._closeServer !== 'undefined') { this._closeServer(); } } finally { delete this._server; } */ if (this.options.port > 0) { try { this._closeServer(); } finally { this._server = null; } } if (error != null) throw new Exception(error); } /** * Handle a HTTP Upgrade request. * @throws Exception * * @api public */ public void handleUpgrade(IncomingMessage req, AbstractSocket socket, ByteBuffer upgradeHead, UpgradeCallback cb) throws Exception { // check for wrong path if (this.options.path != null) { UrlObj u = Url.parse(req.url()); debug(TAG, "req.url:"+req.url()+",options.path:"+this.options.path+",u.pathname:"+u.pathname); if (u!=null && !this.options.path.equalsIgnoreCase(u.pathname)) return; } ///if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') { if (!req.headers().containsKey("upgrade") || req.headers().get("upgrade").isEmpty() || !req.headers().get("upgrade").get(0).equalsIgnoreCase("websocket")) { abortConnection(socket, 400, "Bad Request"); return; } if (req.headers().containsKey("sec-websocket-key1")) new handleHixieUpgrade(req, socket, upgradeHead, cb); else new handleHybiUpgrade(req, socket, upgradeHead, cb); } /** * Entirely private apis, * which may or may not be bound to a specific WebSocket instance. * @throws Exception */ private class handleHybiUpgrade { private final IncomingMessage req; private final AbstractSocket socket; private final ByteBuffer upgradeHead; private final UpgradeCallback cb; private final Listener errorHandler; private int version; private String protocols; protected handleHybiUpgrade( final IncomingMessage req, final AbstractSocket socket, final ByteBuffer upgradeHead, final UpgradeCallback cb ) throws Exception { this.req = req; this.socket = socket; this.upgradeHead = upgradeHead; this.cb = cb; // handle premature socket errors /*var errorHandler = function() { try { socket.destroy(); } catch (e) {} }*/ errorHandler = new Listener(){ @Override public void onEvent(Object data) throws Exception { try { socket.destroy(null); } catch (Exception e) {} } }; socket.on("error", errorHandler); // verify key presence ///if (!req.headers['sec-websocket-key']) { if (!req.headers().containsKey("sec-websocket-key") || req.headers().get("sec-websocket-key").isEmpty()) { abortConnection(socket, 400, "Bad Request"); return; } // verify version ///var version = parseInt(req.headers['sec-websocket-version']); version = req.headers().containsKey("sec-websocket-version") ? Integer.parseInt(req.headers().get("sec-websocket-version").get(0), 10) : -1; ///if ([8, 13].indexOf(version) === -1) { if (version!=13 && version!=8) { abortConnection(socket, 400, "Bad Request"); return; } // verify protocol ///var protocols = req.headers['sec-websocket-protocol']; protocols = req.headers().containsKey("sec-websocket-protocol") ? req.headers().get("sec-websocket-protocol").get(0) : null; // verify client /*var origin = version < 13 ? req.headers['sec-websocket-origin'] : req.headers['origin']; */ String origin = version < 13 ? (req.headers().containsKey("sec-websocket-origin") ? req.headers().get("sec-websocket-origin").get(0) : null) : (req.headers().containsKey("origin") ? req.headers().get("origin").get(0) : null); // optionally call external client verification handler // TBD... /* if (typeof this.options.verifyClient == 'function') { var info = { origin: origin, secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; if (this.options.verifyClient.length == 2) { this.options.verifyClient(info, function(result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; if (!result) abortConnection(socket, code, name); else completeHybiUpgrade1(); }); return; } else if (!this.options.verifyClient(info)) { abortConnection(socket, 401, 'Unauthorized'); return; } }*/ if (options.verifyClient != null) { VerifyInfo info = new VerifyInfo(); info.origin = origin; info.secure = false; // TBD... info.req = req; if (!options.verifyClient.onClient(info)) { abortConnection(socket, 401, "Unauthorized"); return; } } completeHybiUpgrade1(); } // optionally call external protocol selection handler before // calling completeHybiUpgrade2 private void completeHybiUpgrade1() throws Exception { // choose from the sub-protocols ///if (typeof self.options.handleProtocols == 'function') { if (options.handleProtocols != null) { ///var protList = (protocols || "").split(/, */); // TBD... String[] protList = (protocols!=null ? protocols : "").split(", *"); ///boolean callbackCalled = false; final BasicBean<Boolean> callbackCalled = new BasicBean<Boolean>(false); /*var res = self.options.handleProtocols(protList, function(result, protocol) { callbackCalled = true; if (!result) abortConnection(socket, 404, "Unauthorized"); else completeHybiUpgrade2( protocol, version, errorHandler, req, socket, upgradeHead, cb); });*/ options.handleProtocols.onProtocol(protList, new HandleProtocol.HandleProtocolCallback(){ @Override public void onHandle(boolean result, String protocol) throws Exception { ///callbackCalled = true; callbackCalled.set(true); if (!result) abortConnection(socket, 404, "Unauthorized"); else completeHybiUpgrade2(protocol); } }); if (!callbackCalled.get()) { // the handleProtocols handler never called our callback abortConnection(socket, 501, "Could not process protocols"); } return; } else { ///if (typeof protocols !== 'undefined') { if (protocols != null) { ///completeHybiUpgrade2(protocols.split(/, */)[0]); completeHybiUpgrade2(protocols.split(", *")[0]); } else { completeHybiUpgrade2(null); } } } private void completeHybiUpgrade2(String protocol) throws Exception { // calc key ///var key = req.headers['sec-websocket-key']; String keystr = req.headers().containsKey("sec-websocket-key") ? req.headers().get("sec-websocket-key").get(0) : ""; /*var shasum = crypto.createHash('sha1'); shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); key = shasum.digest('base64'); */ MessageDigest shasum = MessageDigest.getInstance("SHA1"); byte[] sharet = shasum.digest((keystr.trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes("utf-8")); byte[] retbuf = Base64.encode(sharet, Base64.DEFAULT); String key = new String(retbuf, "utf-8").trim(); debug(TAG, "keystr:"+keystr+",key:"+key); /*var headers = [ 'HTTP/1.1 101 Switching Protocols' , 'Upgrade: websocket' , 'Connection: Upgrade' , 'Sec-WebSocket-Accept: ' + key ]; if (typeof protocol != 'undefined') { headers.push('Sec-WebSocket-Protocol: ' + protocol); }*/ List<String> headers = new ArrayList<String>(); headers.add("HTTP/1.1 101 Switching Protocols"); headers.add("Upgrade: websocket"); headers.add("Connection: Upgrade"); headers.add("Sec-WebSocket-Accept: " + key); if (protocol != null && protocol != "") { headers.add("Sec-WebSocket-Protocol: " + protocol); } // allows external modification/inspection of handshake headers emit("headers", headers); ///socket.setTimeout(0); socket.setNoDelay(true); try { String headersStr = ""; headersStr += "HTTP/1.1 101 Switching Protocols\r\n"; headersStr += "Upgrade: websocket\r\n"; headersStr += "Connection: Upgrade\r\n"; headersStr += "Sec-WebSocket-Accept: " + key + "\r\n"; if (protocol != null && protocol != "") headersStr += "Sec-WebSocket-Protocol: " + protocol.trim() + "\r\n"; ///headersStr += "\r\n\r\n"; headersStr += "\r\n"; ///socket.write(headers.concat('', '').join('\r\n')); socket.write(headersStr, "utf-8", null); } catch (Exception e) { // if the upgrade write fails, shut the connection down hard try { socket.destroy(null); } catch (Exception ee) {} return; } WebSocket.Options wsopt = new WebSocket.Options(); wsopt.protocolVersion = version; wsopt.protocol = protocol; final WebSocket client = new WebSocket(context, new http.request_socket_head_b(req, socket, upgradeHead), wsopt); if (options.clientTracking) { ///self.clients.push(client); clients.add(client); /* client.on("close", function() { var index = self.clients.indexOf(client); if (index != -1) { self.clients.splice(index, 1); } });*/ client.on("close", new Listener(){ @Override public void onEvent(Object data) throws Exception { if (clients.contains(client)) clients.remove(client); } }); } // signal upgrade complete socket.removeListener("error", errorHandler); cb.onUpgrade(client); } } private class handleHixieUpgrade { private final IncomingMessage req; private final AbstractSocket socket; private final ByteBuffer upgradeHead; private final UpgradeCallback cb; private final Listener errorHandler; private String location; private String protocol; private String origin; protected handleHixieUpgrade( final IncomingMessage req, final AbstractSocket socket, final ByteBuffer upgradeHead, final UpgradeCallback cb) throws Exception { this.req = req; this.socket = socket; this.upgradeHead = upgradeHead; this.cb = cb; // handle premature socket errors /*var errorHandler = function() { try { socket.destroy(); } catch (e) {} } socket.on('error', errorHandler); */ errorHandler = new Listener(){ @Override public void onEvent(Object data) throws Exception { try { socket.destroy(null); } catch (Exception e) {} } }; socket.on("error", errorHandler); // bail if options prevent hixie if (options.disableHixie) { abortConnection(socket, 401, "Hixie support disabled"); return; } // verify key presence ///if (!req.headers['sec-websocket-key2']) { if (!req.headers().containsKey("sec-websocket-key2")) { abortConnection(socket, 400, "Bad Request"); return; } ///var origin = req.headers['origin'] /// , self = this; this.origin = req.headers().containsKey("origin") ? req.headers().get("origin").get(0) : null; // verify client /*if (typeof this.options.verifyClient == 'function') { var info = { origin: origin, secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined', req: req }; if (this.options.verifyClient.length == 2) { var self = this; this.options.verifyClient(info, function(result, code, name) { if (typeof code === 'undefined') code = 401; if (typeof name === 'undefined') name = http.STATUS_CODES[code]; if (!result) abortConnection(socket, code, name); else onClientVerified.apply(self); }); return; } else if (!this.options.verifyClient(info)) { abortConnection(socket, 401, 'Unauthorized'); return; } } */ if (options.verifyClient != null) { VerifyInfo info = new VerifyInfo(); info.origin = origin; info.secure = false; // TBD... info.req = req; if (!options.verifyClient.onClient(info)) { abortConnection(socket, 401, "Unauthorized"); return; } } // no client verification required onClientVerified(); } // setup handshake completion to run after client has been verified private void onClientVerified() throws Exception { /*var wshost; if (!req.headers['x-forwarded-host']) wshost = req.headers.host; else wshost = req.headers['x-forwarded-host']; */ String wshost; if (!req.headers().containsKey("x-forwarded-host")) wshost = req.headers().get("host").get(0); else wshost = req.headers().get("x-forwarded-host").get(0); ///var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url /// , protocol = req.headers['sec-websocket-protocol']; // TBD... secure this.location = "ws://" + wshost + req.url(); this.protocol = req.headers().containsKey("sec-websocket-protocol") ? req.headers().get("sec-websocket-protocol").get(0) : null; // retrieve nonce final int nonceLength = 8; if (upgradeHead!=null && upgradeHead.capacity() >= nonceLength) { /* var nonce = upgradeHead.slice(0, nonceLength); var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null; completeHandshake.call(self, nonce, rest);*/ ByteBuffer nonce = (ByteBuffer) Util.chunkSlice(upgradeHead, 0, nonceLength); ByteBuffer rest = (ByteBuffer) (upgradeHead.capacity() > nonceLength ? Util.chunkSlice(upgradeHead, nonceLength, upgradeHead.capacity()) : null); completeHandshake(nonce, rest); } else { // nonce not present in upgradeHead, so we must wait for enough data // data to arrive before continuing /*var nonce = new Buffer(nonceLength); upgradeHead.copy(nonce, 0); var received = upgradeHead.length; var rest = null;*/ final ByteBuffer nonce = ByteBuffer.allocate(nonceLength); BufferUtil.fastCopy(upgradeHead.capacity(), upgradeHead, nonce, 0); ///int received = upgradeHead.capacity(); final BasicBean<Integer> received = new BasicBean<Integer>(upgradeHead.capacity()); /*var handler = function (data) { var toRead = Math.min(data.length, nonceLength - received); if (toRead === 0) return; data.copy(nonce, received, 0, toRead); received += toRead; if (received == nonceLength) { socket.removeListener("data", handler); if (toRead < data.length) rest = data.slice(toRead); completeHandshake.call(self, nonce, rest); } } socket.on("data", handler);*/ socket.on("data", new Listener(){ public void onEvent(final Object raw) throws Exception { ByteBuffer data = (ByteBuffer)raw; ByteBuffer rest = null; int toRead = Math.min(data.capacity(), nonceLength - received.get()); if (toRead == 0) return; ///data.copy(nonce, received, 0, toRead); BufferUtil.fastCopy(toRead, data, nonce, received.get()); received.set(received.get() + toRead); if (received.get() == nonceLength) { socket.removeListener("data", this); ///if (toRead < data.length) rest = data.slice(toRead); if (toRead < data.capacity()) rest = (ByteBuffer) Util.chunkSlice(data, toRead, data.capacity()); completeHandshake(nonce, rest); } } }); } } // handshake completion code to run once nonce has been successfully retrieved private void completeHandshake(ByteBuffer nonce, final ByteBuffer rest) throws Exception { // calculate key /*var k1 = req.headers['sec-websocket-key1'] , k2 = req.headers['sec-websocket-key2'] , md5 = crypto.createHash('md5'); [k1, k2].forEach(function (k) { var n = parseInt(k.replace(/[^\d]/g, '')) , spaces = k.replace(/[^ ]/g, '').length; if (spaces === 0 || n % spaces !== 0){ abortConnection(socket, 400, 'Bad Request'); return; } n /= spaces; md5.update(String.fromCharCode( n >> 24 & 0xFF, n >> 16 & 0xFF, n >> 8 & 0xFF, n & 0xFF)); }); md5.update(nonce.toString('binary')); */ String k1 = req.headers().containsKey("sec-websocket-key1") ? req.headers().get("sec-websocket-key1").get(0): null; String k2 = req.headers().containsKey("sec-websocket-key2") ? req.headers().get("sec-websocket-key2").get(0): null; MessageDigest md5 = MessageDigest.getInstance("MD5"); if (k1 != null) { int n = Integer.parseInt(k1.replaceAll("[^[0-9]]", "")); int spaces = k1.replaceAll("[^ ]", "").length(); if (spaces == 0 || n % spaces != 0){ abortConnection(socket, 400, "Bad Request"); return; } n /= spaces; String kstr = new String(new int[]{ n >> 24 & 0xFF, n >> 16 & 0xFF, n >> 8 & 0xFF, n & 0xFF }, 0, 4); /*md5.update(String.fromCharCode( n >> 24 & 0xFF, n >> 16 & 0xFF, n >> 8 & 0xFF, n & 0xFF));*/ md5.update(kstr.getBytes("utf-8")); } if (k2 != null) { int n = Integer.parseInt(k2.replaceAll("[^[0-9]]", "")); int spaces = k2.replaceAll("[^ ]", "").length(); if (spaces == 0 || n % spaces != 0){ abortConnection(socket, 400, "Bad Request"); return; } n /= spaces; String kstr = new String(new int[]{ n >> 24 & 0xFF, n >> 16 & 0xFF, n >> 8 & 0xFF, n & 0xFF }, 0, 4); /*md5.update(String.fromCharCode( n >> 24 & 0xFF, n >> 16 & 0xFF, n >> 8 & 0xFF, n & 0xFF));*/ md5.update(kstr.getBytes("utf-8")); } /* var headers = [ 'HTTP/1.1 101 Switching Protocols' , 'Upgrade: WebSocket' , 'Connection: Upgrade' , 'Sec-WebSocket-Location: ' + location ]; if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol); if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin); */ String headers = ""; headers += "HTTP/1.1 101 Switching Protocols\r\n"; headers += "Upgrade: WebSocket\r\n"; headers += "Connection: Upgrade\r\n"; headers += "Sec-WebSocket-Location: " + location + "\r\n"; if (protocol != null) headers += "Sec-WebSocket-Protocol: " + protocol + "\r\n"; if (origin != null) headers += "Sec-WebSocket-Origin: " + origin + "\r\n"; headers += "\r\n"; ///socket.setTimeout(0); socket.setNoDelay(true); try { // merge header and hash buffer ///var headerBuffer = new Buffer(headers.concat('', '').join('\r\n')); ///var hashBuffer = new Buffer(md5.digest('binary'), 'binary'); ///var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length); ByteBuffer headerBuffer = ByteBuffer.wrap(headers.getBytes("utf-8")); ByteBuffer hashBuffer = ByteBuffer.wrap(md5.digest()); ByteBuffer handshakeBuffer = ByteBuffer.allocate(headerBuffer.capacity() + hashBuffer.capacity()); ///headerBuffer.copy(handshakeBuffer, 0); ///hashBuffer.copy(handshakeBuffer, headerBuffer.length); BufferUtil.fastCopy(headerBuffer.capacity(), headerBuffer, handshakeBuffer, 0); BufferUtil.fastCopy(hashBuffer.capacity(), hashBuffer, handshakeBuffer, headerBuffer.capacity()); // do a single write, which - upon success - causes a new client websocket to be setup /*socket.write(handshakeBuffer, 'binary', function(err) { if (err) return; // do not create client if an error happens var client = new WebSocket([req, socket, rest], { protocolVersion: 'hixie-76', protocol: protocol }); if (self.options.clientTracking) { self.clients.push(client); client.on('close', function() { var index = self.clients.indexOf(client); if (index != -1) { self.clients.splice(index, 1); } }); } // signal upgrade complete socket.removeListener("error", errorHandler); cb(client); });*/ socket.write(handshakeBuffer, null, new WriteCB(){ public void writeDone(final String err) throws Exception { if (err!=null) return; // do not create client if an error happens WebSocket.Options wsopt = new WebSocket.Options(); wsopt.protocolVersionHixie = "hixie-76"; wsopt.protocol = protocol; final WebSocket client = new WebSocket(context, new http.request_socket_head_b(req, socket, rest), wsopt); if (options.clientTracking) { clients.add(client); client.on("close", new Listener(){ @Override public void onEvent(Object data) throws Exception { if (clients.contains(client)) clients.remove(client); } }); } // signal upgrade complete socket.removeListener("error", errorHandler); cb.onUpgrade(client); } }); } catch (Exception e) { try { socket.destroy(null); } catch (Exception ee) {} return; } } } public static class VerifyInfo { public String origin = null; public boolean secure = false; public IncomingMessage req = null; } public interface VerifyClient { public boolean onClient(VerifyInfo info) throws Exception; } public interface HandleProtocol { public interface HandleProtocolCallback { public void onHandle(boolean result, String protocol) throws Exception; } public void onProtocol(String[] protList, HandleProtocolCallback cb) throws Exception; } private interface UpgradeCallback { void onUpgrade(WebSocket client) throws Exception; } private static void abortConnection(AbstractSocket socket, int code, String name) { try { /* var response = [ 'HTTP/1.1 ' + code + ' ' + name, 'Content-type: text/html' ]; socket.write(response.concat('', '').join('\r\n'));*/ String response = ""; response += "HTTP/1.1 " + code + " " + name + "\r\n"; response += "Content-type: text/html\r\n"; ///response += "\r\n\r\n"; response += "\r\n"; socket.write(response, "utf-8", null); } catch (Exception e) { /* ignore errors - we've aborted this connection */ } finally { // ensure that an early aborted connection is shut down completely try { socket.destroy(null); } catch (Exception e) {} } } }