From: Bill Erickson Date: Thu, 8 Nov 2012 17:36:16 +0000 (-0500) Subject: LP#1268619: websocket JS additions X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=27707398e955b8a8a2df1a5311aebc19b8eb1708;p=working%2FOpenSRF.git LP#1268619: websocket JS additions Signed-off-by: Bill Erickson Signed-off-by: Galen Charlton --- diff --git a/src/javascript/opensrf.js b/src/javascript/opensrf.js index 408f0af..813bef8 100644 --- a/src/javascript/opensrf.js +++ b/src/javascript/opensrf.js @@ -206,9 +206,8 @@ OpenSRF.Session = function() { this.state = OSRF_APP_SESSION_DISCONNECTED; }; -OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS; -if (true || typeof WebSocket == 'undefined') - OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_XHR; +//OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS; +OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_XHR; OpenSRF.Session.cache = {}; OpenSRF.Session.find_session = function(thread_trace) { @@ -222,7 +221,7 @@ OpenSRF.Session.prototype.send = function(osrf_msg, args) { args = (args) ? args : {}; switch(OpenSRF.Session.transport) { case OSRF_TRANSPORT_TYPE_WS: - return this.send_ws(osrf_msg, args); + return this.send_ws(osrf_msg); case OSRF_TRANSPORT_TYPE_XHR: return this.send_xhr(osrf_msg, args); case OSRF_TRANSPORT_TYPE_XMPP: @@ -237,18 +236,11 @@ OpenSRF.Session.prototype.send_xhr = function(osrf_msg, args) { new OpenSRF.XHRequest(osrf_msg, args).send(); }; -OpenSRF.Session.prototype.send_ws = function(osrf_msg, args) { - args.session = this; - if (this.websocket) { - this.websocket.args = args; // callbacks - this.websocket.send(osrf_msg); - } else { - this.websocket = new OpenSRF.WSRequest( - this, args, function(wsreq) { - wsreq.send(osrf_msg); - } - ); - } +OpenSRF.Session.prototype.send_ws = function(osrf_msg) { + new OpenSRF.WebSocketRequest( + this, + function(wsreq) {wsreq.send(osrf_msg)} // onopen + ); }; OpenSRF.Session.prototype.send_xmpp = function(osrf_msg, args) { @@ -262,9 +254,9 @@ OpenSRF.ClientSession = function(service) { this.remote_id = null; this.locale = OpenSRF.locale || 'en-US'; this.last_id = 0; - this.thread = Math.random() + '' + new Date().getTime(); this.requests = []; this.onconnect = null; + this.thread = Math.random() + '' + new Date().getTime(); OpenSRF.Session.cache[this.thread] = this; }; OpenSRF.set_subclass('OpenSRF.ClientSession', 'OpenSRF.Session'); @@ -412,11 +404,12 @@ OpenSRF.Request.prototype.send = function() { }); }; -OpenSRF.NetMessage = function(to, from, thread, body) { +OpenSRF.NetMessage = function(to, from, thread, body, osrf_msg) { this.to = to; this.from = from; this.thread = thread; this.body = body; + this.osrf_msg = osrf_msg; }; OpenSRF.Stack = function() { @@ -435,49 +428,59 @@ function log(msg) { } // ses may be passed to us by the network handler -OpenSRF.Stack.push = function(net_msg, callbacks, ses) { - if (!ses) ses = OpenSRF.Session.find_session(net_msg.thread); +OpenSRF.Stack.push = function(net_msg, callbacks) { + var ses = OpenSRF.Session.find_session(net_msg.thread); if (!ses) return; ses.remote_id = net_msg.from; - osrf_msgs = []; - try { - osrf_msgs = JSON2js(net_msg.body); + // NetMessage's from websocket connections are parsed before they get here + osrf_msgs = net_msg.osrf_msg; - } catch(E) { - log('Error parsing OpenSRF message body as JSON: ' + net_msg.body + '\n' + E); - - /** UGH - * For unknown reasons, the Content-Type header will occasionally - * be included in the XHR.responseText for multipart/mixed messages. - * When this happens, strip the header and newlines from the message - * body and re-parse. - */ - net_msg.body = net_msg.body.replace(/^.*\n\n/, ''); - log('Cleaning up and retrying...'); + if (!osrf_msgs) { try { osrf_msgs = JSON2js(net_msg.body); - } catch(E2) { - log('Unable to clean up message, giving up: ' + net_msg.body); - return; + + if (OpenSRF.Session.transport == OSRF_TRANSPORT_TYPE_WS) { + // WebSocketRequests wrap the content + osrf_msgs = osrf_msgs.osrf_msg; + } + + } catch(E) { + log('Error parsing OpenSRF message body as JSON: ' + net_msg.body + '\n' + E); + + /** UGH + * For unknown reasons, the Content-Type header will occasionally + * be included in the XHR.responseText for multipart/mixed messages. + * When this happens, strip the header and newlines from the message + * body and re-parse. + */ + net_msg.body = net_msg.body.replace(/^.*\n\n/, ''); + log('Cleaning up and retrying...'); + + try { + osrf_msgs = JSON2js(net_msg.body); + } catch(E2) { + log('Unable to clean up message, giving up: ' + net_msg.body); + return; + } } } // push the latest responses onto the end of the inbound message queue for(var i = 0; i < osrf_msgs.length; i++) - OpenSRF.Stack.queue.push({msg : osrf_msgs[i], callbacks : callbacks, ses : ses}); + OpenSRF.Stack.queue.push({msg : osrf_msgs[i], ses : ses}); // continue processing responses, oldest to newest while(OpenSRF.Stack.queue.length) { var data = OpenSRF.Stack.queue.shift(); - OpenSRF.Stack.handle_message(data.ses, data.msg, data.callbacks); + OpenSRF.Stack.handle_message(data.ses, data.msg); } }; -OpenSRF.Stack.handle_message = function(ses, osrf_msg, callbacks) { +OpenSRF.Stack.handle_message = function(ses, osrf_msg) { - var req = null; + var req = ses.find_request(osrf_msg.threadTrace()); if(osrf_msg.type() == OSRF_MESSAGE_TYPE_STATUS) { @@ -486,12 +489,11 @@ OpenSRF.Stack.handle_message = function(ses, osrf_msg, callbacks) { var status_text = payload.status(); if(status == OSRF_STATUS_COMPLETE) { - req = ses.find_request(osrf_msg.threadTrace()); if(req) { req.complete = true; - if(callbacks.oncomplete && !req.oncomplete_called) { + if(req.oncomplete && !req.oncomplete_called) { req.oncomplete_called = true; - return callbacks.oncomplete(req); + return req.oncomplete(req); } } } @@ -507,18 +509,17 @@ OpenSRF.Stack.handle_message = function(ses, osrf_msg, callbacks) { } if(status == OSRF_STATUS_NOTFOUND || status == OSRF_STATUS_INTERNALSERVERERROR) { - req = ses.find_request(osrf_msg.threadTrace()); - if(callbacks.onmethoderror) - return callbacks.onmethoderror(req, status, status_text); + if(req && req.onmethoderror) + return req.onmethoderror(req, status, status_text); } } if(osrf_msg.type() == OSRF_MESSAGE_TYPE_RESULT) { - req = ses.find_request(osrf_msg.threadTrace()); if(req) { req.response_queue.push(osrf_msg.payload()); - if(callbacks.onresponse) - return callbacks.onresponse(req); + if(req.onresponse) { + return req.onresponse(req); + } } } }; diff --git a/src/javascript/opensrf_ws.js b/src/javascript/opensrf_ws.js index d522834..62302be 100644 --- a/src/javascript/opensrf_ws.js +++ b/src/javascript/opensrf_ws.js @@ -13,92 +13,153 @@ * GNU General Public License for more details. * ----------------------------------------------------------------------- */ -var WS_PATH = '/osrf-websocket'; +// opensrf defaults +var WEBSOCKET_URL_PATH = '/osrf-websocket-translator'; +var WEBSOCKET_PORT = 7680; +var WEBSOCKET_PORT_SSL = 7682; + + +// Create the websocket and connect to the server +// args.onopen is required +// if args.default is true, use the default connection +OpenSRF.WebSocketConnection = function(args, handlers) { + args = args || {}; + this.handlers = handlers; + + var secure = (args.ssl || location.protocol == 'https'); + var path = args.path || WEBSOCKET_URL_PATH; + var port = args.port || (secure ? WEBSOCKET_PORT_SSL : WEBSOCKET_PORT); + var host = args.host || location.host; + var proto = (secure) ? 'wss' : 'ws'; + this.path = proto + '://' + host + ':' + port + path; + + this.setupSocket(); + OpenSRF.WebSocketConnection.pool[args.name] = this; +}; -/** - * onopen is required. no data can be sent - * until the async connection dance completes. - */ -OpenSRF.WSRequest = function(session, args, onopen) { - this.session = session; - this.args = args; +// global pool of connection objects; name => connection map +OpenSRF.WebSocketConnection.pool = {}; - var proto = location.protocol == 'https' ? 'wss' : 'ws'; +OpenSRF.WebSocketConnection.defaultConnection = function() { + return OpenSRF.WebSocketConnection.pool['default']; +} - var path = proto + '://' + location.host + - WS_PATH + '?service=' + this.session.service; +/** + * create a new WebSocket. useful for new connections or + * applying a new socket to an existing connection (whose + * socket was disconnected) + */ +OpenSRF.WebSocketConnection.prototype.setupSocket = function() { try { - this.ws = new WebSocket(path); + this.socket = new WebSocket(this.path); } catch(e) { - throw new Error("WebSocket() not supported in this browser " + e); - } - - var self = this; - - this.ws.onopen = function(evt) { - onopen(self); + throw new Error("WebSocket() not supported in this browser: " + e); } - this.ws.onmessage = function(evt) { - self.core_handler(evt.data); - } + this.socket.onopen = this.handlers.onopen; + this.socket.onmessage = this.handlers.onmessage; + this.socket.onerror = this.handlers.onerror; + this.socket.onclose = this.handlers.onclose; +}; - this.ws.onerror = function(evt) { - self.transport_error_handler(evt.data); - } +/** default onmessage handler: push the message up the opensrf stack */ +OpenSRF.WebSocketConnection.default_onmessage = function(evt) { + console.log('receiving: ' + evt.data); + var msg = JSON2js(evt.data); + OpenSRF.Stack.push( + new OpenSRF.NetMessage( + null, null, msg.thread, null, msg.osrf_msg) + ); +}; - this.ws.onclose = function(evt) { - } +/** default error handler */ +OpenSRF.WebSocketConnection.default_onerror = function(evt) { + throw new Error("WebSocket Error " + evt + ' : ' + evt.data); }; -OpenSRF.WSRequest.prototype.send = function(message) { - //console.log('sending: ' + js2JSON([message.serialize()])); - this.last_message = message; - this.ws.send(js2JSON([message.serialize()])); - return this; + +/** shut it down */ +OpenSRF.WebSocketConnection.prototype.destroy = function() { + this.socket.close(); + delete OpenSRF.WebSocketConnection.pool[this.name]; }; -OpenSRF.WSRequest.prototype.close = function() { - try { this.ws.close(); } catch(e) {} +/** + * Creates the request object, but does not connect or send anything + * until the first call to send(). + */ +OpenSRF.WebSocketRequest = function(session, onopen, connectionArgs) { + this.session = session; + this.onopen = onopen; + this.setupConnection(connectionArgs || {}); } -OpenSRF.WSRequest.prototype.core_handler = function(json) { - //console.log('received: ' + json); +OpenSRF.WebSocketRequest.prototype.setupConnection = function(args) { + var self = this; - OpenSRF.Stack.push( - new OpenSRF.NetMessage(null, null, '', json), - { - onresponse : this.args.onresponse, - oncomplete : this.args.oncomplete, - onerror : this.args.onerror, - onmethoderror : this.method_error_handler() - }, - this.args.session - ); -}; + var cname = args.name || 'default'; + this.wsc = OpenSRF.WebSocketConnection.pool[cname]; + if (this.wsc) { // we have a WebSocketConnection. -OpenSRF.WSRequest.prototype.method_error_handler = function() { - var self = this; - return function(req, status, status_text) { - if(self.args.onmethoderror) - self.args.onmethoderror(req, status, status_text); + switch (this.wsc.socket.readyState) { + + case this.wsc.socket.CONNECTING: + // replace the original onopen handler with a new combined handler + var orig_open = this.wsc.socket.onopen; + this.wsc.socket.onopen = function() { + orig_open(); + self.onopen(self); + }; + break; - if(self.args.onerror) { - self.args.onerror( - self.last_message, self.session.service, ''); + case this.wsc.socket.OPEN: + // user is expecting an onopen event. socket is + // already open, so we have to manufacture one. + this.onopen(this); + break; + + default: + console.log('WebSocket is no longer connecting; reconnecting'); + this.wsc.setupSocket(); } - }; -}; -OpenSRF.WSRequest.prototype.transport_error_handler = function(msg) { - if(this.args.ontransporterror) { - this.args.ontransporterror(msg); - } - if(this.args.onerror) { - this.args.onerror(msg, this.session.service, ''); + } else { // no connection found + + if (cname == 'default' || args.useDefaultHandlers) { // create the default handle + + this.wsc = new OpenSRF.WebSocketConnection( + {name : cname}, { + onopen : function(evt) {if (self.onopen) self.onopen(self)}, + onmessage : OpenSRF.WebSocketConnection.default_onmessage, + onerror : OpenSRF.WebSocketRequest.default_onerror, + onclose : OpenSRF.WebSocketRequest.default_onclose + } + ); + + } else { + throw new Error("No such WebSocketConnection '" + cname + "'"); + } } +} + + +OpenSRF.WebSocketRequest.prototype.send = function(message) { + var wrapper = { + service : this.session.service, + thread : this.session.thread, + osrf_msg : [message.serialize()] + }; + + var json = js2JSON(wrapper); + console.log('sending: ' + json); + + // drop it on the wire + this.wsc.socket.send(json); + return this; }; + +