Path: blob/trunk/third_party/closure/goog/net/websocket.js
2868 views
// Copyright 2011 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Definition of the WebSocket class. A WebSocket provides a16* bi-directional, full-duplex communications channel, over a single TCP socket.17*18* See http://dev.w3.org/html5/websockets/19* for the full HTML5 WebSocket API.20*21* Typical usage will look like this:22*23* var ws = new goog.net.WebSocket();24*25* var handler = new goog.events.EventHandler();26* handler.listen(ws, goog.net.WebSocket.EventType.OPENED, onOpen);27* handler.listen(ws, goog.net.WebSocket.EventType.MESSAGE, onMessage);28*29* try {30* ws.open('ws://127.0.0.1:4200');31* } catch (e) {32* ...33* }34*35*/3637goog.provide('goog.net.WebSocket');38goog.provide('goog.net.WebSocket.ErrorEvent');39goog.provide('goog.net.WebSocket.EventType');40goog.provide('goog.net.WebSocket.MessageEvent');4142goog.require('goog.Timer');43goog.require('goog.asserts');44goog.require('goog.debug.entryPointRegistry');45goog.require('goog.events');46goog.require('goog.events.Event');47goog.require('goog.events.EventTarget');48goog.require('goog.log');49505152/**53* Class encapsulating the logic for using a WebSocket.54*55* @param {boolean=} opt_autoReconnect True if the web socket should56* automatically reconnect or not. This is true by default.57* @param {function(number):number=} opt_getNextReconnect A function for58* obtaining the time until the next reconnect attempt. Given the reconnect59* attempt count (which is a positive integer), the function should return a60* positive integer representing the milliseconds to the next reconnect61* attempt. The default function used is an exponential back-off. Note that62* this function is never called if auto reconnect is disabled.63* @constructor64* @extends {goog.events.EventTarget}65*/66goog.net.WebSocket = function(opt_autoReconnect, opt_getNextReconnect) {67goog.net.WebSocket.base(this, 'constructor');6869/**70* True if the web socket should automatically reconnect or not.71* @type {boolean}72* @private73*/74this.autoReconnect_ =75goog.isDef(opt_autoReconnect) ? opt_autoReconnect : true;7677/**78* A function for obtaining the time until the next reconnect attempt.79* Given the reconnect attempt count (which is a positive integer), the80* function should return a positive integer representing the milliseconds to81* the next reconnect attempt.82* @type {function(number):number}83* @private84*/85this.getNextReconnect_ =86opt_getNextReconnect || goog.net.WebSocket.EXPONENTIAL_BACKOFF_;8788/**89* The time, in milliseconds, that must elapse before the next attempt to90* reconnect.91* @type {number}92* @private93*/94this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);95};96goog.inherits(goog.net.WebSocket, goog.events.EventTarget);979899/**100* The actual web socket that will be used to send/receive messages.101* @type {WebSocket}102* @private103*/104goog.net.WebSocket.prototype.webSocket_ = null;105106107/**108* The URL to which the web socket will connect.109* @type {?string}110* @private111*/112goog.net.WebSocket.prototype.url_ = null;113114115/**116* The subprotocol name used when establishing the web socket connection.117* @type {string|undefined}118* @private119*/120goog.net.WebSocket.prototype.protocol_ = undefined;121122123/**124* True if a call to the close callback is expected or not.125* @type {boolean}126* @private127*/128goog.net.WebSocket.prototype.closeExpected_ = false;129130131/**132* Keeps track of the number of reconnect attempts made since the last133* successful connection.134* @type {number}135* @private136*/137goog.net.WebSocket.prototype.reconnectAttempt_ = 0;138139140/** @private {?number} */141goog.net.WebSocket.prototype.reconnectTimer_ = null;142143144/**145* The logger for this class.146* @type {goog.log.Logger}147* @private148*/149goog.net.WebSocket.prototype.logger_ = goog.log.getLogger('goog.net.WebSocket');150151152/**153* The events fired by the web socket.154* @enum {string} The event types for the web socket.155*/156goog.net.WebSocket.EventType = {157158/**159* Fired when an attempt to open the WebSocket fails or there is a connection160* failure after a successful connection has been established.161*/162CLOSED: goog.events.getUniqueId('closed'),163164/**165* Fired when the WebSocket encounters an error.166*/167ERROR: goog.events.getUniqueId('error'),168169/**170* Fired when a new message arrives from the WebSocket.171*/172MESSAGE: goog.events.getUniqueId('message'),173174/**175* Fired when the WebSocket connection has been established.176*/177OPENED: goog.events.getUniqueId('opened')178};179180181/**182* The various states of the web socket.183* @enum {number} The states of the web socket.184* @private185*/186goog.net.WebSocket.ReadyState_ = {187// This is the initial state during construction.188CONNECTING: 0,189// This is when the socket is actually open and ready for data.190OPEN: 1,191// This is when the socket is in the middle of a close handshake.192// Note that this is a valid state even if the OPEN state was never achieved.193CLOSING: 2,194// This is when the socket is actually closed.195CLOSED: 3196};197198199/**200* The maximum amount of time between reconnect attempts for the exponential201* back-off in milliseconds.202* @type {number}203* @private204*/205goog.net.WebSocket.EXPONENTIAL_BACKOFF_CEILING_ = 60 * 1000;206207208/**209* Computes the next reconnect time given the number of reconnect attempts since210* the last successful connection.211*212* @param {number} attempt The number of reconnect attempts since the last213* connection.214* @return {number} The time, in milliseconds, until the next reconnect attempt.215* @const216* @private217*/218goog.net.WebSocket.EXPONENTIAL_BACKOFF_ = function(attempt) {219var time = Math.pow(2, attempt) * 1000;220return Math.min(time, goog.net.WebSocket.EXPONENTIAL_BACKOFF_CEILING_);221};222223224/**225* Installs exception protection for all entry points introduced by226* goog.net.WebSocket instances which are not protected by227* {@link goog.debug.ErrorHandler#protectWindowSetTimeout},228* {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or229* {@link goog.events.protectBrowserEventEntryPoint}.230*231* @param {!goog.debug.ErrorHandler} errorHandler Error handler with which to232* protect the entry points.233*/234goog.net.WebSocket.protectEntryPoints = function(errorHandler) {235goog.net.WebSocket.prototype.onOpen_ =236errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onOpen_);237goog.net.WebSocket.prototype.onClose_ =238errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onClose_);239goog.net.WebSocket.prototype.onMessage_ =240errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onMessage_);241goog.net.WebSocket.prototype.onError_ =242errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onError_);243};244245246/**247* Creates and opens the actual WebSocket. Only call this after attaching the248* appropriate listeners to this object. If listeners aren't registered, then249* the {@code goog.net.WebSocket.EventType.OPENED} event might be missed.250*251* @param {string} url The URL to which to connect.252* @param {string=} opt_protocol The subprotocol to use. The connection will253* only be established if the server reports that it has selected this254* subprotocol. The subprotocol name must all be a non-empty ASCII string255* with no control characters and no spaces in them (i.e. only characters256* in the range U+0021 to U+007E).257*/258goog.net.WebSocket.prototype.open = function(url, opt_protocol) {259// Sanity check. This works only in modern browsers.260goog.asserts.assert(261goog.global['WebSocket'], 'This browser does not support WebSocket');262263// Don't do anything if the web socket is already open.264goog.asserts.assert(!this.isOpen(), 'The WebSocket is already open');265266// Clear any pending attempts to reconnect.267this.clearReconnectTimer_();268269// Construct the web socket.270this.url_ = url;271this.protocol_ = opt_protocol;272273// This check has to be made otherwise you get protocol mismatch exceptions274// for passing undefined, null, '', or [].275if (this.protocol_) {276goog.log.info(277this.logger_, 'Opening the WebSocket on ' + this.url_ +278' with protocol ' + this.protocol_);279this.webSocket_ = new WebSocket(this.url_, this.protocol_);280} else {281goog.log.info(this.logger_, 'Opening the WebSocket on ' + this.url_);282this.webSocket_ = new WebSocket(this.url_);283}284285// Register the event handlers. Note that it is not possible for these286// callbacks to be missed because it is registered after the web socket is287// instantiated. Because of the synchronous nature of JavaScript, this code288// will execute before the browser creates the resource and makes any calls289// to these callbacks.290this.webSocket_.onopen = goog.bind(this.onOpen_, this);291this.webSocket_.onclose = goog.bind(this.onClose_, this);292this.webSocket_.onmessage = goog.bind(this.onMessage_, this);293this.webSocket_.onerror = goog.bind(this.onError_, this);294};295296297/**298* Closes the web socket connection.299*/300goog.net.WebSocket.prototype.close = function() {301302// Clear any pending attempts to reconnect.303this.clearReconnectTimer_();304305// Attempt to close only if the web socket was created.306if (this.webSocket_) {307goog.log.info(this.logger_, 'Closing the WebSocket.');308309// Close is expected here since it was a direct call. Close is considered310// unexpected when opening the connection fails or there is some other form311// of connection loss after being connected.312this.closeExpected_ = true;313this.webSocket_.close();314this.webSocket_ = null;315}316};317318319/**320* Sends the message over the web socket.321*322* @param {string|!ArrayBuffer|!ArrayBufferView} message The message to send.323*/324goog.net.WebSocket.prototype.send = function(message) {325// Make sure the socket is ready to go before sending a message.326goog.asserts.assert(this.isOpen(), 'Cannot send without an open socket');327328// Send the message and let onError_ be called if it fails thereafter.329this.webSocket_.send(message);330};331332333/**334* Checks to see if the web socket is open or not.335*336* @return {boolean} True if the web socket is open, false otherwise.337*/338goog.net.WebSocket.prototype.isOpen = function() {339return !!this.webSocket_ &&340this.webSocket_.readyState == goog.net.WebSocket.ReadyState_.OPEN;341};342343344/**345* Gets the number of bytes of data that have been queued using calls to send()346* but not yet transmitted to the network.347*348* @return {number} Number of bytes of data that have been queued.349*/350goog.net.WebSocket.prototype.getBufferedAmount = function() {351return this.webSocket_.bufferedAmount;352};353354355/**356* Called when the web socket has connected.357*358* @private359*/360goog.net.WebSocket.prototype.onOpen_ = function() {361goog.log.info(this.logger_, 'WebSocket opened on ' + this.url_);362this.dispatchEvent(goog.net.WebSocket.EventType.OPENED);363364// Set the next reconnect interval.365this.reconnectAttempt_ = 0;366this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);367};368369370/**371* Called when the web socket has closed.372*373* @param {!Event} event The close event.374* @private375*/376goog.net.WebSocket.prototype.onClose_ = function(event) {377goog.log.info(this.logger_, 'The WebSocket on ' + this.url_ + ' closed.');378379// Firing this event allows handlers to query the URL.380this.dispatchEvent(goog.net.WebSocket.EventType.CLOSED);381382// Always clear out the web socket on a close event.383this.webSocket_ = null;384385// See if this is an expected call to onClose_.386if (this.closeExpected_) {387goog.log.info(this.logger_, 'The WebSocket closed normally.');388// Only clear out the URL if this is a normal close.389this.url_ = null;390this.protocol_ = undefined;391} else {392// Unexpected, so try to reconnect.393goog.log.error(394this.logger_, 'The WebSocket disconnected unexpectedly: ' + event.data);395396// Only try to reconnect if it is enabled.397if (this.autoReconnect_) {398// Log the reconnect attempt.399var seconds = Math.floor(this.nextReconnect_ / 1000);400goog.log.info(401this.logger_, 'Seconds until next reconnect attempt: ' + seconds);402403// Actually schedule the timer.404this.reconnectTimer_ = goog.Timer.callOnce(405goog.bind(this.open, this, this.url_, this.protocol_),406this.nextReconnect_, this);407408// Set the next reconnect interval.409this.reconnectAttempt_++;410this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);411}412}413this.closeExpected_ = false;414};415416417/**418* Called when a new message arrives from the server.419*420* @param {MessageEvent<string>} event The web socket message event.421* @private422*/423goog.net.WebSocket.prototype.onMessage_ = function(event) {424var message = event.data;425this.dispatchEvent(new goog.net.WebSocket.MessageEvent(message));426};427428429/**430* Called when there is any error in communication.431*432* @param {Event} event The error event containing the error data.433* @private434*/435goog.net.WebSocket.prototype.onError_ = function(event) {436var data = /** @type {string} */ (event.data);437goog.log.error(this.logger_, 'An error occurred: ' + data);438this.dispatchEvent(new goog.net.WebSocket.ErrorEvent(data));439};440441442/**443* Clears the reconnect timer.444*445* @private446*/447goog.net.WebSocket.prototype.clearReconnectTimer_ = function() {448if (goog.isDefAndNotNull(this.reconnectTimer_)) {449goog.Timer.clear(this.reconnectTimer_);450}451this.reconnectTimer_ = null;452};453454455/** @override */456goog.net.WebSocket.prototype.disposeInternal = function() {457goog.net.WebSocket.base(this, 'disposeInternal');458this.close();459};460461462463/**464* Object representing a new incoming message event.465*466* @param {string} message The raw message coming from the web socket.467* @extends {goog.events.Event}468* @constructor469* @final470*/471goog.net.WebSocket.MessageEvent = function(message) {472goog.net.WebSocket.MessageEvent.base(473this, 'constructor', goog.net.WebSocket.EventType.MESSAGE);474475/**476* The new message from the web socket.477* @type {string}478*/479this.message = message;480};481goog.inherits(goog.net.WebSocket.MessageEvent, goog.events.Event);482483484485/**486* Object representing an error event. This is fired whenever an error occurs487* on the web socket.488*489* @param {string} data The error data.490* @extends {goog.events.Event}491* @constructor492* @final493*/494goog.net.WebSocket.ErrorEvent = function(data) {495goog.net.WebSocket.ErrorEvent.base(496this, 'constructor', goog.net.WebSocket.EventType.ERROR);497498/**499* The error data coming from the web socket.500* @type {string}501*/502this.data = data;503};504goog.inherits(goog.net.WebSocket.ErrorEvent, goog.events.Event);505506507// Register the WebSocket as an entry point, so that it can be monitored for508// exception handling, etc.509goog.debug.entryPointRegistry.register(510/**511* @param {function(!Function): !Function} transformer The transforming512* function.513*/514function(transformer) {515goog.net.WebSocket.prototype.onOpen_ =516transformer(goog.net.WebSocket.prototype.onOpen_);517goog.net.WebSocket.prototype.onClose_ =518transformer(goog.net.WebSocket.prototype.onClose_);519goog.net.WebSocket.prototype.onMessage_ =520transformer(goog.net.WebSocket.prototype.onMessage_);521goog.net.WebSocket.prototype.onError_ =522transformer(goog.net.WebSocket.prototype.onError_);523});524525526