Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/labs/net/webchannel/webchannelbase.js
1865 views
1
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Base WebChannel implementation.
17
*
18
*/
19
20
21
goog.provide('goog.labs.net.webChannel.WebChannelBase');
22
23
goog.require('goog.Uri');
24
goog.require('goog.array');
25
goog.require('goog.asserts');
26
goog.require('goog.debug.TextFormatter');
27
goog.require('goog.json');
28
goog.require('goog.labs.net.webChannel.BaseTestChannel');
29
goog.require('goog.labs.net.webChannel.Channel');
30
goog.require('goog.labs.net.webChannel.ChannelRequest');
31
goog.require('goog.labs.net.webChannel.ConnectionState');
32
goog.require('goog.labs.net.webChannel.ForwardChannelRequestPool');
33
goog.require('goog.labs.net.webChannel.WebChannelDebug');
34
goog.require('goog.labs.net.webChannel.Wire');
35
goog.require('goog.labs.net.webChannel.WireV8');
36
goog.require('goog.labs.net.webChannel.netUtils');
37
goog.require('goog.labs.net.webChannel.requestStats');
38
goog.require('goog.labs.net.webChannel.requestStats.Stat');
39
goog.require('goog.log');
40
goog.require('goog.net.WebChannel');
41
goog.require('goog.net.XhrIo');
42
goog.require('goog.net.rpc.HttpCors');
43
goog.require('goog.object');
44
goog.require('goog.string');
45
goog.require('goog.structs');
46
goog.require('goog.structs.CircularBuffer');
47
48
goog.scope(function() {
49
var WebChannel = goog.net.WebChannel;
50
var BaseTestChannel = goog.labs.net.webChannel.BaseTestChannel;
51
var ChannelRequest = goog.labs.net.webChannel.ChannelRequest;
52
var ConnectionState = goog.labs.net.webChannel.ConnectionState;
53
var ForwardChannelRequestPool =
54
goog.labs.net.webChannel.ForwardChannelRequestPool;
55
var WebChannelDebug = goog.labs.net.webChannel.WebChannelDebug;
56
var Wire = goog.labs.net.webChannel.Wire;
57
var WireV8 = goog.labs.net.webChannel.WireV8;
58
var netUtils = goog.labs.net.webChannel.netUtils;
59
var requestStats = goog.labs.net.webChannel.requestStats;
60
61
var httpCors = goog.module.get('goog.net.rpc.HttpCors');
62
63
64
/**
65
* This WebChannel implementation is branched off goog.net.BrowserChannel
66
* for now. Ongoing changes to goog.net.BrowserChannel will be back
67
* ported to this implementation as needed.
68
*
69
* @param {!goog.net.WebChannel.Options=} opt_options Configuration for the
70
* WebChannel instance.
71
* @param {number=} opt_clientVersion An application-specific version number
72
* that is sent to the server when connected.
73
* @param {!ConnectionState=} opt_conn Previously determined connection
74
* conditions.
75
* @constructor
76
* @struct
77
* @implements {goog.labs.net.webChannel.Channel}
78
*/
79
goog.labs.net.webChannel.WebChannelBase = function(
80
opt_options, opt_clientVersion, opt_conn) {
81
/**
82
* The client library version (capabilities).
83
* @private {number}
84
*/
85
this.clientVersion_ = opt_clientVersion || 0;
86
87
/**
88
* The server library version (capabilities).
89
* @private {number}
90
*/
91
this.serverVersion_ = 0;
92
93
94
/**
95
* An array of queued maps that need to be sent to the server.
96
* @private {!Array<Wire.QueuedMap>}
97
*/
98
this.outgoingMaps_ = [];
99
100
/**
101
* An array of dequeued maps that we have either received a non-successful
102
* response for, or no response at all, and which therefore may or may not
103
* have been received by the server.
104
* @private {!Array<Wire.QueuedMap>}
105
*/
106
this.pendingMaps_ = [];
107
108
/**
109
* The channel debug used for logging
110
* @private {!WebChannelDebug}
111
*/
112
this.channelDebug_ = new WebChannelDebug();
113
114
/**
115
* Previous connectivity test results.
116
* @private {!ConnectionState}
117
*/
118
this.connState_ = opt_conn || new ConnectionState();
119
120
/**
121
* Extra HTTP headers to add to all the requests sent to the server.
122
* @private {Object}
123
*/
124
this.extraHeaders_ = null;
125
126
/**
127
* Extra HTTP headers to add to the init request(s) sent to the server.
128
* @private {Object}
129
*/
130
this.initHeaders_ = null;
131
132
/**
133
* @private {?string} The URL param name to overwrite custom HTTP headers
134
* to bypass CORS preflight.
135
*/
136
this.httpHeadersOverwriteParam_ = null;
137
138
/**
139
* Extra parameters to add to all the requests sent to the server.
140
* @private {Object}
141
*/
142
this.extraParams_ = null;
143
144
/**
145
* Parameter name for the http session id.
146
* @private {?string}
147
*/
148
this.httpSessionIdParam_ = null;
149
150
/**
151
* The http session id, to be sent with httpSessionIdParam_ with each
152
* request after the initial handshake.
153
* @private {?string}
154
*/
155
this.httpSessionId_ = null;
156
157
/**
158
* The ChannelRequest object for the backchannel.
159
* @private {ChannelRequest}
160
*/
161
this.backChannelRequest_ = null;
162
163
/**
164
* The relative path (in the context of the the page hosting the browser
165
* channel) for making requests to the server.
166
* @private {?string}
167
*/
168
this.path_ = null;
169
170
/**
171
* The absolute URI for the forwardchannel request.
172
* @private {goog.Uri}
173
*/
174
this.forwardChannelUri_ = null;
175
176
/**
177
* The absolute URI for the backchannel request.
178
* @private {goog.Uri}
179
*/
180
this.backChannelUri_ = null;
181
182
/**
183
* A subdomain prefix for using a subdomain in IE for the backchannel
184
* requests.
185
* @private {?string}
186
*/
187
this.hostPrefix_ = null;
188
189
/**
190
* Whether we allow the use of a subdomain in IE for the backchannel requests.
191
* @private {boolean}
192
*/
193
this.allowHostPrefix_ = true;
194
195
/**
196
* The next id to use for the RID (request identifier) parameter. This
197
* identifier uniquely identifies the forward channel request.
198
* @private {number}
199
*/
200
this.nextRid_ = 0;
201
202
/**
203
* The id to use for the next outgoing map. This identifier uniquely
204
* identifies a sent map.
205
* @private {number}
206
*/
207
this.nextMapId_ = 0;
208
209
/**
210
* Whether to fail forward-channel requests after one try or a few tries.
211
* @private {boolean}
212
*/
213
this.failFast_ = false;
214
215
/**
216
* The handler that receive callbacks for state changes and data.
217
* @private {goog.labs.net.webChannel.WebChannelBase.Handler}
218
*/
219
this.handler_ = null;
220
221
/**
222
* Timer identifier for asynchronously making a forward channel request.
223
* @private {?number}
224
*/
225
this.forwardChannelTimerId_ = null;
226
227
/**
228
* Timer identifier for asynchronously making a back channel request.
229
* @private {?number}
230
*/
231
this.backChannelTimerId_ = null;
232
233
/**
234
* Timer identifier for the timer that waits for us to retry the backchannel
235
* in the case where it is dead and no longer receiving data.
236
* @private {?number}
237
*/
238
this.deadBackChannelTimerId_ = null;
239
240
/**
241
* The TestChannel object which encapsulates the logic for determining
242
* interesting network conditions about the client.
243
* @private {BaseTestChannel}
244
*/
245
this.connectionTest_ = null;
246
247
/**
248
* Whether the client's network conditions can support chunked responses.
249
* @private {?boolean}
250
*/
251
this.useChunked_ = null;
252
253
/**
254
* Whether chunked mode is allowed. In certain debugging situations, it's
255
* useful to disable this.
256
* @private {boolean}
257
*/
258
this.allowChunkedMode_ = true;
259
260
/**
261
* The array identifier of the last array received from the server for the
262
* backchannel request.
263
* @private {number}
264
*/
265
this.lastArrayId_ = -1;
266
267
/**
268
* The array id of the last array sent by the server that we know about.
269
* @private {number}
270
*/
271
this.lastPostResponseArrayId_ = -1;
272
273
/**
274
* The last status code received.
275
* @private {number}
276
*/
277
this.lastStatusCode_ = -1;
278
279
/**
280
* Number of times we have retried the current forward channel request.
281
* @private {number}
282
*/
283
this.forwardChannelRetryCount_ = 0;
284
285
/**
286
* Number of times in a row that we have retried the current back channel
287
* request and received no data.
288
* @private {number}
289
*/
290
this.backChannelRetryCount_ = 0;
291
292
/**
293
* The attempt id for the current back channel request. Starts at 1 and
294
* increments for each reconnect. The server uses this to log if our
295
* connection is flaky or not.
296
* @private {number}
297
*/
298
this.backChannelAttemptId_ = 0;
299
300
/**
301
* The base part of the time before firing next retry request. Default is 5
302
* seconds. Note that a random delay is added (see {@link retryDelaySeedMs_})
303
* for all retries, and linear backoff is applied to the sum for subsequent
304
* retries.
305
* @private {number}
306
*/
307
this.baseRetryDelayMs_ = 5 * 1000;
308
309
/**
310
* A random time between 0 and this number of MS is added to the
311
* {@link baseRetryDelayMs_}. Default is 10 seconds.
312
* @private {number}
313
*/
314
this.retryDelaySeedMs_ = 10 * 1000;
315
316
/**
317
* Maximum number of attempts to connect to the server for forward channel
318
* requests. Defaults to 2.
319
* @private {number}
320
*/
321
this.forwardChannelMaxRetries_ = 2;
322
323
/**
324
* The timeout in milliseconds for a forward channel request. Defaults to 20
325
* seconds. Note that part of this timeout can be randomized.
326
* @private {number}
327
*/
328
this.forwardChannelRequestTimeoutMs_ = 20 * 1000;
329
330
/**
331
* A throttle time in ms for readystatechange events for the backchannel.
332
* Useful for throttling when ready state is INTERACTIVE (partial data).
333
*
334
* This throttle is useful if the server sends large data chunks down the
335
* backchannel. It prevents examining XHR partial data on every readystate
336
* change event. This is useful because large chunks can trigger hundreds
337
* of readystatechange events, each of which takes ~5ms or so to handle,
338
* in turn making the UI unresponsive for a significant period.
339
*
340
* If set to zero no throttle is used.
341
* @private {number}
342
*/
343
this.readyStateChangeThrottleMs_ = 0;
344
345
/**
346
* Whether cross origin requests are supported for the channel.
347
*
348
* See {@link goog.net.XhrIo#setWithCredentials}.
349
* @private {boolean}
350
*/
351
this.supportsCrossDomainXhrs_ =
352
(opt_options && opt_options.supportsCrossDomainXhr) || false;
353
354
/**
355
* The current session id.
356
* @private {string}
357
*/
358
this.sid_ = '';
359
360
/**
361
* The current ChannelRequest pool for the forward channel.
362
* @private {!ForwardChannelRequestPool}
363
*/
364
this.forwardChannelRequestPool_ = new ForwardChannelRequestPool(
365
opt_options && opt_options.concurrentRequestLimit);
366
367
/**
368
* The V8 codec.
369
* @private {!WireV8}
370
*/
371
this.wireCodec_ = new WireV8();
372
373
/**
374
* Whether to run the channel test as a background process to not block
375
* the OPEN event.
376
*
377
* @private {boolean}
378
*/
379
this.backgroundChannelTest_ =
380
(opt_options && opt_options.backgroundChannelTest) || false;
381
};
382
383
var WebChannelBase = goog.labs.net.webChannel.WebChannelBase;
384
385
386
/**
387
* The channel version that we negotiated with the server for this session.
388
* Starts out as the version we request, and then is changed to the negotiated
389
* version after the initial open.
390
* @private {number}
391
*/
392
WebChannelBase.prototype.channelVersion_ = Wire.LATEST_CHANNEL_VERSION;
393
394
395
/**
396
* Enum type for the channel state machine.
397
* @enum {number}
398
*/
399
WebChannelBase.State = {
400
/** The channel is closed. */
401
CLOSED: 0,
402
403
/** The channel has been initialized but hasn't yet initiated a connection. */
404
INIT: 1,
405
406
/** The channel is in the process of opening a connection to the server. */
407
OPENING: 2,
408
409
/** The channel is open. */
410
OPENED: 3
411
};
412
413
414
/**
415
* The current state of the WebChannel.
416
* @private {!WebChannelBase.State}
417
*/
418
WebChannelBase.prototype.state_ = WebChannelBase.State.INIT;
419
420
421
/**
422
* The timeout in milliseconds for a forward channel request.
423
* @type {number}
424
*/
425
WebChannelBase.FORWARD_CHANNEL_RETRY_TIMEOUT = 20 * 1000;
426
427
428
/**
429
* Maximum number of attempts to connect to the server for back channel
430
* requests.
431
* @type {number}
432
*/
433
WebChannelBase.BACK_CHANNEL_MAX_RETRIES = 3;
434
435
436
/**
437
* A number in MS of how long we guess the maxmium amount of time a round trip
438
* to the server should take. In the future this could be substituted with a
439
* real measurement of the RTT.
440
* @type {number}
441
*/
442
WebChannelBase.RTT_ESTIMATE = 3 * 1000;
443
444
445
/**
446
* When retrying for an inactive channel, we will multiply the total delay by
447
* this number.
448
* @type {number}
449
*/
450
WebChannelBase.INACTIVE_CHANNEL_RETRY_FACTOR = 2;
451
452
453
/**
454
* Enum type for identifying an error.
455
* @enum {number}
456
*/
457
WebChannelBase.Error = {
458
/** Value that indicates no error has occurred. */
459
OK: 0,
460
461
/** An error due to a request failing. */
462
REQUEST_FAILED: 2,
463
464
/** An error due to the user being logged out. */
465
LOGGED_OUT: 4,
466
467
/** An error due to server response which contains no data. */
468
NO_DATA: 5,
469
470
/** An error due to a server response indicating an unknown session id */
471
UNKNOWN_SESSION_ID: 6,
472
473
/** An error due to a server response requesting to stop the channel. */
474
STOP: 7,
475
476
/** A general network error. */
477
NETWORK: 8,
478
479
/** An error due to bad data being returned from the server. */
480
BAD_DATA: 10,
481
482
/** An error due to a response that is not parsable. */
483
BAD_RESPONSE: 11
484
};
485
486
487
/**
488
* Internal enum type for the two channel types.
489
* @enum {number}
490
* @private
491
*/
492
WebChannelBase.ChannelType_ = {
493
FORWARD_CHANNEL: 1,
494
495
BACK_CHANNEL: 2
496
};
497
498
499
/**
500
* The maximum number of maps that can be sent in one POST. Should match
501
* MAX_MAPS_PER_REQUEST on the server code.
502
* @type {number}
503
* @private
504
*/
505
WebChannelBase.MAX_MAPS_PER_REQUEST_ = 1000;
506
507
508
/**
509
* A guess at a cutoff at which to no longer assume the backchannel is dead
510
* when we are slow to receive data. Number in bytes.
511
*
512
* Assumption: The worst bandwidth we work on is 50 kilobits/sec
513
* 50kbits/sec * (1 byte / 8 bits) * 6 sec dead backchannel timeout
514
* @type {number}
515
*/
516
WebChannelBase.OUTSTANDING_DATA_BACKCHANNEL_RETRY_CUTOFF = 37500;
517
518
519
/**
520
* @return {!ForwardChannelRequestPool} The forward channel request pool.
521
*/
522
WebChannelBase.prototype.getForwardChannelRequestPool = function() {
523
return this.forwardChannelRequestPool_;
524
};
525
526
527
/**
528
* @return {!Object} The codec object, to be used for the test channel.
529
*/
530
WebChannelBase.prototype.getWireCodec = function() {
531
return this.wireCodec_;
532
};
533
534
535
/**
536
* Returns the logger.
537
*
538
* @return {!WebChannelDebug} The channel debug object.
539
*/
540
WebChannelBase.prototype.getChannelDebug = function() {
541
return this.channelDebug_;
542
};
543
544
545
/**
546
* Sets the logger.
547
*
548
* @param {!WebChannelDebug} channelDebug The channel debug object.
549
*/
550
WebChannelBase.prototype.setChannelDebug = function(channelDebug) {
551
this.channelDebug_ = channelDebug;
552
};
553
554
555
/**
556
* Starts the channel. This initiates connections to the server.
557
*
558
* @param {string} testPath The path for the test connection.
559
* @param {string} channelPath The path for the channel connection.
560
* @param {!Object=} opt_extraParams Extra parameter keys and values to add to
561
* the requests.
562
* @param {string=} opt_oldSessionId Session ID from a previous session.
563
* @param {number=} opt_oldArrayId The last array ID from a previous session.
564
*/
565
WebChannelBase.prototype.connect = function(
566
testPath, channelPath, opt_extraParams, opt_oldSessionId, opt_oldArrayId) {
567
this.channelDebug_.debug('connect()');
568
569
requestStats.notifyStatEvent(requestStats.Stat.CONNECT_ATTEMPT);
570
571
this.path_ = channelPath;
572
this.extraParams_ = opt_extraParams || {};
573
574
// Attach parameters about the previous session if reconnecting.
575
if (opt_oldSessionId && goog.isDef(opt_oldArrayId)) {
576
this.extraParams_['OSID'] = opt_oldSessionId;
577
this.extraParams_['OAID'] = opt_oldArrayId;
578
}
579
580
if (this.backgroundChannelTest_) {
581
this.channelDebug_.debug('connect() bypassed channel-test.');
582
this.connState_.handshakeResult = [];
583
this.connState_.bufferingProxyResult = false;
584
585
// TODO(user): merge states with background channel test
586
// requestStats.setTimeout(goog.bind(this.connectTest_, this, testPath), 0);
587
// this.connectChannel_();
588
}
589
590
this.connectTest_(testPath);
591
};
592
593
594
/**
595
* Disconnects and closes the channel.
596
*/
597
WebChannelBase.prototype.disconnect = function() {
598
this.channelDebug_.debug('disconnect()');
599
600
this.cancelRequests_();
601
602
if (this.state_ == WebChannelBase.State.OPENED) {
603
var rid = this.nextRid_++;
604
var uri = this.forwardChannelUri_.clone();
605
uri.setParameterValue('SID', this.sid_);
606
uri.setParameterValue('RID', rid);
607
uri.setParameterValue('TYPE', 'terminate');
608
609
// Add the reconnect parameters.
610
this.addAdditionalParams_(uri);
611
612
var request = ChannelRequest.createChannelRequest(
613
this, this.channelDebug_, this.sid_, rid);
614
request.sendCloseRequest(uri);
615
}
616
617
this.onClose_();
618
};
619
620
621
/**
622
* Returns the session id of the channel. Only available after the
623
* channel has been opened.
624
* @return {string} Session ID.
625
*/
626
WebChannelBase.prototype.getSessionId = function() {
627
return this.sid_;
628
};
629
630
631
/**
632
* Starts the test channel to determine network conditions.
633
*
634
* @param {string} testPath The relative PATH for the test connection.
635
* @private
636
*/
637
WebChannelBase.prototype.connectTest_ = function(testPath) {
638
this.channelDebug_.debug('connectTest_()');
639
if (!this.okToMakeRequest_()) {
640
return; // channel is cancelled
641
}
642
this.connectionTest_ = new BaseTestChannel(this, this.channelDebug_);
643
644
if (this.httpHeadersOverwriteParam_ === null) {
645
this.connectionTest_.setExtraHeaders(this.extraHeaders_);
646
}
647
648
var urlPath = testPath;
649
if (this.httpHeadersOverwriteParam_ && this.extraHeaders_) {
650
urlPath = httpCors.setHttpHeadersWithOverwriteParam(
651
testPath, this.httpHeadersOverwriteParam_, this.extraHeaders_);
652
}
653
654
this.connectionTest_.connect(/** @type {string} */ (urlPath));
655
};
656
657
658
/**
659
* Starts the regular channel which is run after the test channel is complete.
660
* @private
661
*/
662
WebChannelBase.prototype.connectChannel_ = function() {
663
this.channelDebug_.debug('connectChannel_()');
664
this.ensureInState_(WebChannelBase.State.INIT, WebChannelBase.State.CLOSED);
665
this.forwardChannelUri_ =
666
this.getForwardChannelUri(/** @type {string} */ (this.path_));
667
this.ensureForwardChannel_();
668
};
669
670
671
/**
672
* Cancels all outstanding requests.
673
* @private
674
*/
675
WebChannelBase.prototype.cancelRequests_ = function() {
676
if (this.connectionTest_) {
677
this.connectionTest_.abort();
678
this.connectionTest_ = null;
679
}
680
681
if (this.backChannelRequest_) {
682
this.backChannelRequest_.cancel();
683
this.backChannelRequest_ = null;
684
}
685
686
if (this.backChannelTimerId_) {
687
goog.global.clearTimeout(this.backChannelTimerId_);
688
this.backChannelTimerId_ = null;
689
}
690
691
this.clearDeadBackchannelTimer_();
692
693
this.forwardChannelRequestPool_.cancel();
694
695
if (this.forwardChannelTimerId_) {
696
goog.global.clearTimeout(this.forwardChannelTimerId_);
697
this.forwardChannelTimerId_ = null;
698
}
699
};
700
701
702
/**
703
* Returns the extra HTTP headers to add to all the requests sent to the server.
704
*
705
* @return {Object} The HTTP headers, or null.
706
*/
707
WebChannelBase.prototype.getExtraHeaders = function() {
708
return this.extraHeaders_;
709
};
710
711
712
/**
713
* Sets extra HTTP headers to add to all the requests sent to the server.
714
*
715
* @param {Object} extraHeaders The HTTP headers, or null.
716
*/
717
WebChannelBase.prototype.setExtraHeaders = function(extraHeaders) {
718
this.extraHeaders_ = extraHeaders;
719
};
720
721
722
/**
723
* Returns the extra HTTP headers to add to the init requests
724
* sent to the server.
725
*
726
* @return {Object} The HTTP headers, or null.
727
*/
728
WebChannelBase.prototype.getInitHeaders = function() {
729
return this.initHeaders_;
730
};
731
732
733
/**
734
* Sets extra HTTP headers to add to the init requests sent to the server.
735
*
736
* @param {Object} initHeaders The HTTP headers, or null.
737
*/
738
WebChannelBase.prototype.setInitHeaders = function(initHeaders) {
739
this.initHeaders_ = initHeaders;
740
};
741
742
743
/**
744
* Sets the URL param name to overwrite custom HTTP headers.
745
*
746
* @param {string} httpHeadersOverwriteParam The URL param name.
747
*/
748
WebChannelBase.prototype.setHttpHeadersOverwriteParam = function(
749
httpHeadersOverwriteParam) {
750
this.httpHeadersOverwriteParam_ = httpHeadersOverwriteParam;
751
};
752
753
754
/**
755
* @override
756
*/
757
WebChannelBase.prototype.setHttpSessionIdParam = function(httpSessionIdParam) {
758
this.httpSessionIdParam_ = httpSessionIdParam;
759
};
760
761
762
/**
763
* @override
764
*/
765
WebChannelBase.prototype.getHttpSessionIdParam = function() {
766
return this.httpSessionIdParam_;
767
};
768
769
770
/**
771
* @override
772
*/
773
WebChannelBase.prototype.setHttpSessionId = function(httpSessionId) {
774
this.httpSessionId_ = httpSessionId;
775
};
776
777
778
/**
779
* @override
780
*/
781
WebChannelBase.prototype.getHttpSessionId = function() {
782
return this.httpSessionId_;
783
};
784
785
786
/**
787
* @override
788
*/
789
WebChannelBase.prototype.getBackgroundChannelTest = function() {
790
return this.backgroundChannelTest_;
791
};
792
793
794
/**
795
* Sets the throttle for handling onreadystatechange events for the request.
796
*
797
* @param {number} throttle The throttle in ms. A value of zero indicates
798
* no throttle.
799
*/
800
WebChannelBase.prototype.setReadyStateChangeThrottle = function(throttle) {
801
this.readyStateChangeThrottleMs_ = throttle;
802
};
803
804
805
/**
806
* Sets whether cross origin requests are supported for the channel.
807
*
808
* Setting this allows the creation of requests to secondary domains and
809
* sends XHRs with the CORS withCredentials bit set to true.
810
*
811
* In order for cross-origin requests to work, the server will also need to set
812
* CORS response headers as per:
813
* https://developer.mozilla.org/en-US/docs/HTTP_access_control
814
*
815
* See {@link goog.net.XhrIo#setWithCredentials}.
816
* @param {boolean} supportCrossDomain Whether cross domain XHRs are supported.
817
*/
818
WebChannelBase.prototype.setSupportsCrossDomainXhrs = function(
819
supportCrossDomain) {
820
this.supportsCrossDomainXhrs_ = supportCrossDomain;
821
};
822
823
824
/**
825
* Returns the handler used for channel callback events.
826
*
827
* @return {WebChannelBase.Handler} The handler.
828
*/
829
WebChannelBase.prototype.getHandler = function() {
830
return this.handler_;
831
};
832
833
834
/**
835
* Sets the handler used for channel callback events.
836
* @param {WebChannelBase.Handler} handler The handler to set.
837
*/
838
WebChannelBase.prototype.setHandler = function(handler) {
839
this.handler_ = handler;
840
};
841
842
843
/**
844
* Returns whether the channel allows the use of a subdomain. There may be
845
* cases where this isn't allowed.
846
* @return {boolean} Whether a host prefix is allowed.
847
*/
848
WebChannelBase.prototype.getAllowHostPrefix = function() {
849
return this.allowHostPrefix_;
850
};
851
852
853
/**
854
* Sets whether the channel allows the use of a subdomain. There may be cases
855
* where this isn't allowed, for example, logging in with troutboard where
856
* using a subdomain causes Apache to force the user to authenticate twice.
857
* @param {boolean} allowHostPrefix Whether a host prefix is allowed.
858
*/
859
WebChannelBase.prototype.setAllowHostPrefix = function(allowHostPrefix) {
860
this.allowHostPrefix_ = allowHostPrefix;
861
};
862
863
864
/**
865
* Returns whether the channel is buffered or not. This state is valid for
866
* querying only after the test connection has completed. This may be
867
* queried in the WebChannelBase.okToMakeRequest() callback.
868
* A channel may be buffered if the test connection determines that
869
* a chunked response could not be sent down within a suitable time.
870
* @return {boolean} Whether the channel is buffered.
871
*/
872
WebChannelBase.prototype.isBuffered = function() {
873
return !this.useChunked_;
874
};
875
876
877
/**
878
* Returns whether chunked mode is allowed. In certain debugging situations,
879
* it's useful for the application to have a way to disable chunked mode for a
880
* user.
881
882
* @return {boolean} Whether chunked mode is allowed.
883
*/
884
WebChannelBase.prototype.getAllowChunkedMode = function() {
885
return this.allowChunkedMode_;
886
};
887
888
889
/**
890
* Sets whether chunked mode is allowed. In certain debugging situations, it's
891
* useful for the application to have a way to disable chunked mode for a user.
892
* @param {boolean} allowChunkedMode Whether chunked mode is allowed.
893
*/
894
WebChannelBase.prototype.setAllowChunkedMode = function(allowChunkedMode) {
895
this.allowChunkedMode_ = allowChunkedMode;
896
};
897
898
899
/**
900
* Sends a request to the server. The format of the request is a Map data
901
* structure of key/value pairs. These maps are then encoded in a format
902
* suitable for the wire and then reconstituted as a Map data structure that
903
* the server can process.
904
* @param {!Object|!goog.structs.Map} map The map to send.
905
* @param {!Object=} opt_context The context associated with the map.
906
*/
907
WebChannelBase.prototype.sendMap = function(map, opt_context) {
908
goog.asserts.assert(
909
this.state_ != WebChannelBase.State.CLOSED,
910
'Invalid operation: sending map when state is closed');
911
912
// We can only send 1000 maps per POST, but typically we should never have
913
// that much to send, so warn if we exceed that (we still send all the maps).
914
if (this.outgoingMaps_.length == WebChannelBase.MAX_MAPS_PER_REQUEST_) {
915
// severe() is temporary so that we get these uploaded and can figure out
916
// what's causing them. Afterwards can change to warning().
917
this.channelDebug_.severe(
918
'Already have ' + WebChannelBase.MAX_MAPS_PER_REQUEST_ +
919
' queued maps upon queueing ' + goog.json.serialize(map));
920
}
921
922
this.outgoingMaps_.push(
923
new Wire.QueuedMap(this.nextMapId_++, map, opt_context));
924
if (this.state_ == WebChannelBase.State.OPENING ||
925
this.state_ == WebChannelBase.State.OPENED) {
926
this.ensureForwardChannel_();
927
}
928
};
929
930
931
/**
932
* When set to true, this changes the behavior of the forward channel so it
933
* will not retry requests; it will fail after one network failure, and if
934
* there was already one network failure, the request will fail immediately.
935
* @param {boolean} failFast Whether or not to fail fast.
936
*/
937
WebChannelBase.prototype.setFailFast = function(failFast) {
938
this.failFast_ = failFast;
939
this.channelDebug_.info('setFailFast: ' + failFast);
940
if ((this.forwardChannelRequestPool_.hasPendingRequest() ||
941
this.forwardChannelTimerId_) &&
942
this.forwardChannelRetryCount_ > this.getForwardChannelMaxRetries()) {
943
this.channelDebug_.info(
944
'Retry count ' + this.forwardChannelRetryCount_ + ' > new maxRetries ' +
945
this.getForwardChannelMaxRetries() + '. Fail immediately!');
946
947
if (!this.forwardChannelRequestPool_.forceComplete(
948
goog.bind(this.onRequestComplete, this))) {
949
// i.e., this.forwardChannelTimerId_
950
goog.global.clearTimeout(this.forwardChannelTimerId_);
951
this.forwardChannelTimerId_ = null;
952
// The error code from the last failed request is gone, so just use a
953
// generic one.
954
this.signalError_(WebChannelBase.Error.REQUEST_FAILED);
955
}
956
}
957
};
958
959
960
/**
961
* @return {number} The max number of forward-channel retries, which will be 0
962
* in fail-fast mode.
963
*/
964
WebChannelBase.prototype.getForwardChannelMaxRetries = function() {
965
return this.failFast_ ? 0 : this.forwardChannelMaxRetries_;
966
};
967
968
969
/**
970
* Sets the maximum number of attempts to connect to the server for forward
971
* channel requests.
972
* @param {number} retries The maximum number of attempts.
973
*/
974
WebChannelBase.prototype.setForwardChannelMaxRetries = function(retries) {
975
this.forwardChannelMaxRetries_ = retries;
976
};
977
978
979
/**
980
* Sets the timeout for a forward channel request.
981
* @param {number} timeoutMs The timeout in milliseconds.
982
*/
983
WebChannelBase.prototype.setForwardChannelRequestTimeout = function(timeoutMs) {
984
this.forwardChannelRequestTimeoutMs_ = timeoutMs;
985
};
986
987
988
/**
989
* @return {number} The max number of back-channel retries, which is a constant.
990
*/
991
WebChannelBase.prototype.getBackChannelMaxRetries = function() {
992
// Back-channel retries is a constant.
993
return WebChannelBase.BACK_CHANNEL_MAX_RETRIES;
994
};
995
996
997
/**
998
* @override
999
*/
1000
WebChannelBase.prototype.isClosed = function() {
1001
return this.state_ == WebChannelBase.State.CLOSED;
1002
};
1003
1004
1005
/**
1006
* Returns the channel state.
1007
* @return {WebChannelBase.State} The current state of the channel.
1008
*/
1009
WebChannelBase.prototype.getState = function() {
1010
return this.state_;
1011
};
1012
1013
1014
/**
1015
* Return the last status code received for a request.
1016
* @return {number} The last status code received for a request.
1017
*/
1018
WebChannelBase.prototype.getLastStatusCode = function() {
1019
return this.lastStatusCode_;
1020
};
1021
1022
1023
/**
1024
* @return {number} The last array id received.
1025
*/
1026
WebChannelBase.prototype.getLastArrayId = function() {
1027
return this.lastArrayId_;
1028
};
1029
1030
1031
/**
1032
* Returns whether there are outstanding requests servicing the channel.
1033
* @return {boolean} true if there are outstanding requests.
1034
*/
1035
WebChannelBase.prototype.hasOutstandingRequests = function() {
1036
return this.getOutstandingRequests_() != 0;
1037
};
1038
1039
1040
/**
1041
* Returns the number of outstanding requests.
1042
* @return {number} The number of outstanding requests to the server.
1043
* @private
1044
*/
1045
WebChannelBase.prototype.getOutstandingRequests_ = function() {
1046
var count = 0;
1047
if (this.backChannelRequest_) {
1048
count++;
1049
}
1050
count += this.forwardChannelRequestPool_.getRequestCount();
1051
return count;
1052
};
1053
1054
1055
/**
1056
* Ensures that a forward channel request is scheduled.
1057
* @private
1058
*/
1059
WebChannelBase.prototype.ensureForwardChannel_ = function() {
1060
if (this.forwardChannelRequestPool_.isFull()) {
1061
// enough connection in process - no need to start a new request
1062
return;
1063
}
1064
1065
if (this.forwardChannelTimerId_) {
1066
// no need to start a new request - one is already scheduled
1067
return;
1068
}
1069
1070
this.forwardChannelTimerId_ = requestStats.setTimeout(
1071
goog.bind(this.onStartForwardChannelTimer_, this), 0);
1072
this.forwardChannelRetryCount_ = 0;
1073
};
1074
1075
1076
/**
1077
* Schedules a forward-channel retry for the specified request, unless the max
1078
* retries has been reached.
1079
* @param {ChannelRequest} request The failed request to retry.
1080
* @return {boolean} true iff a retry was scheduled.
1081
* @private
1082
*/
1083
WebChannelBase.prototype.maybeRetryForwardChannel_ = function(request) {
1084
if (this.forwardChannelRequestPool_.isFull() || this.forwardChannelTimerId_) {
1085
// Should be impossible to be called in this state.
1086
this.channelDebug_.severe('Request already in progress');
1087
return false;
1088
}
1089
1090
if (this.state_ == WebChannelBase.State.INIT || // no retry open_()
1091
(this.forwardChannelRetryCount_ >= this.getForwardChannelMaxRetries())) {
1092
return false;
1093
}
1094
1095
this.channelDebug_.debug('Going to retry POST');
1096
1097
this.forwardChannelTimerId_ = requestStats.setTimeout(
1098
goog.bind(this.onStartForwardChannelTimer_, this, request),
1099
this.getRetryTime_(this.forwardChannelRetryCount_));
1100
this.forwardChannelRetryCount_++;
1101
return true;
1102
};
1103
1104
1105
/**
1106
* Timer callback for ensureForwardChannel
1107
* @param {ChannelRequest=} opt_retryRequest A failed request
1108
* to retry.
1109
* @private
1110
*/
1111
WebChannelBase.prototype.onStartForwardChannelTimer_ = function(
1112
opt_retryRequest) {
1113
this.forwardChannelTimerId_ = null;
1114
this.startForwardChannel_(opt_retryRequest);
1115
};
1116
1117
1118
/**
1119
* Begins a new forward channel operation to the server.
1120
* @param {ChannelRequest=} opt_retryRequest A failed request to retry.
1121
* @private
1122
*/
1123
WebChannelBase.prototype.startForwardChannel_ = function(opt_retryRequest) {
1124
this.channelDebug_.debug('startForwardChannel_');
1125
if (!this.okToMakeRequest_()) {
1126
return; // channel is cancelled
1127
} else if (this.state_ == WebChannelBase.State.INIT) {
1128
if (opt_retryRequest) {
1129
this.channelDebug_.severe('Not supposed to retry the open');
1130
return;
1131
}
1132
this.open_();
1133
this.state_ = WebChannelBase.State.OPENING;
1134
} else if (this.state_ == WebChannelBase.State.OPENED) {
1135
if (opt_retryRequest) {
1136
this.makeForwardChannelRequest_(opt_retryRequest);
1137
return;
1138
}
1139
1140
if (this.outgoingMaps_.length == 0) {
1141
this.channelDebug_.debug(
1142
'startForwardChannel_ returned: ' +
1143
'nothing to send');
1144
// no need to start a new forward channel request
1145
return;
1146
}
1147
1148
if (this.forwardChannelRequestPool_.isFull()) {
1149
// Should be impossible to be called in this state.
1150
this.channelDebug_.severe(
1151
'startForwardChannel_ returned: ' +
1152
'connection already in progress');
1153
return;
1154
}
1155
1156
this.makeForwardChannelRequest_();
1157
this.channelDebug_.debug('startForwardChannel_ finished, sent request');
1158
}
1159
};
1160
1161
1162
/**
1163
* Establishes a new channel session with the the server.
1164
* @private
1165
*/
1166
WebChannelBase.prototype.open_ = function() {
1167
this.channelDebug_.debug('open_()');
1168
this.nextRid_ = Math.floor(Math.random() * 100000);
1169
1170
var rid = this.nextRid_++;
1171
var request =
1172
ChannelRequest.createChannelRequest(this, this.channelDebug_, '', rid);
1173
1174
// mix the init headers
1175
var extraHeaders = this.extraHeaders_;
1176
if (this.initHeaders_) {
1177
if (extraHeaders) {
1178
extraHeaders = goog.object.clone(extraHeaders);
1179
goog.object.extend(extraHeaders, this.initHeaders_);
1180
} else {
1181
extraHeaders = this.initHeaders_;
1182
}
1183
}
1184
1185
if (this.httpHeadersOverwriteParam_ === null) {
1186
request.setExtraHeaders(extraHeaders);
1187
}
1188
1189
var requestText = this.dequeueOutgoingMaps_();
1190
var uri = this.forwardChannelUri_.clone();
1191
uri.setParameterValue('RID', rid);
1192
1193
if (this.clientVersion_ > 0) {
1194
uri.setParameterValue('CVER', this.clientVersion_);
1195
}
1196
1197
// http-session-id to be generated as the response
1198
if (this.getBackgroundChannelTest() && this.getHttpSessionIdParam()) {
1199
uri.setParameterValues(
1200
WebChannel.X_HTTP_SESSION_ID, this.getHttpSessionIdParam());
1201
}
1202
1203
// Add the reconnect parameters.
1204
this.addAdditionalParams_(uri);
1205
1206
if (this.httpHeadersOverwriteParam_ && extraHeaders) {
1207
httpCors.setHttpHeadersWithOverwriteParam(
1208
uri, this.httpHeadersOverwriteParam_, extraHeaders);
1209
}
1210
1211
this.forwardChannelRequestPool_.addRequest(request);
1212
request.xmlHttpPost(uri, requestText, true);
1213
};
1214
1215
1216
/**
1217
* Makes a forward channel request using XMLHTTP.
1218
* @param {!ChannelRequest=} opt_retryRequest A failed request to retry.
1219
* @private
1220
*/
1221
WebChannelBase.prototype.makeForwardChannelRequest_ = function(
1222
opt_retryRequest) {
1223
var rid;
1224
var requestText;
1225
if (opt_retryRequest) {
1226
this.requeuePendingMaps_();
1227
rid = this.nextRid_ - 1; // Must use last RID
1228
requestText = this.dequeueOutgoingMaps_();
1229
} else {
1230
rid = this.nextRid_++;
1231
requestText = this.dequeueOutgoingMaps_();
1232
}
1233
1234
var uri = this.forwardChannelUri_.clone();
1235
uri.setParameterValue('SID', this.sid_);
1236
uri.setParameterValue('RID', rid);
1237
uri.setParameterValue('AID', this.lastArrayId_);
1238
// Add the additional reconnect parameters.
1239
this.addAdditionalParams_(uri);
1240
1241
if (this.httpHeadersOverwriteParam_ && this.extraHeaders_) {
1242
httpCors.setHttpHeadersWithOverwriteParam(
1243
uri, this.httpHeadersOverwriteParam_, this.extraHeaders_);
1244
}
1245
1246
var request = ChannelRequest.createChannelRequest(
1247
this, this.channelDebug_, this.sid_, rid,
1248
this.forwardChannelRetryCount_ + 1);
1249
1250
if (this.httpHeadersOverwriteParam_ === null) {
1251
request.setExtraHeaders(this.extraHeaders_);
1252
}
1253
1254
// Randomize from 50%-100% of the forward channel timeout to avoid
1255
// a big hit if servers happen to die at once.
1256
request.setTimeout(
1257
Math.round(this.forwardChannelRequestTimeoutMs_ * 0.50) +
1258
Math.round(this.forwardChannelRequestTimeoutMs_ * 0.50 * Math.random()));
1259
this.forwardChannelRequestPool_.addRequest(request);
1260
request.xmlHttpPost(uri, requestText, true);
1261
};
1262
1263
1264
/**
1265
* Adds the additional parameters from the handler to the given URI.
1266
* @param {!goog.Uri} uri The URI to add the parameters to.
1267
* @private
1268
*/
1269
WebChannelBase.prototype.addAdditionalParams_ = function(uri) {
1270
// Add the additional reconnect parameters as needed.
1271
if (this.handler_) {
1272
var params = this.handler_.getAdditionalParams(this);
1273
if (params) {
1274
goog.structs.forEach(params, function(value, key, coll) {
1275
uri.setParameterValue(key, value);
1276
});
1277
}
1278
}
1279
};
1280
1281
1282
/**
1283
* Returns the request text from the outgoing maps and resets it.
1284
* @return {string} The encoded request text created from all the currently
1285
* queued outgoing maps.
1286
* @private
1287
*/
1288
WebChannelBase.prototype.dequeueOutgoingMaps_ = function() {
1289
var count =
1290
Math.min(this.outgoingMaps_.length, WebChannelBase.MAX_MAPS_PER_REQUEST_);
1291
var badMapHandler = this.handler_ ?
1292
goog.bind(this.handler_.badMapError, this.handler_, this) :
1293
null;
1294
var result = this.wireCodec_.encodeMessageQueue(
1295
this.outgoingMaps_, count, badMapHandler);
1296
this.pendingMaps_ =
1297
this.pendingMaps_.concat(this.outgoingMaps_.splice(0, count));
1298
return result;
1299
};
1300
1301
1302
/**
1303
* Requeues unacknowledged sent arrays for retransmission in the next forward
1304
* channel request.
1305
* @private
1306
*/
1307
WebChannelBase.prototype.requeuePendingMaps_ = function() {
1308
this.outgoingMaps_ = this.pendingMaps_.concat(this.outgoingMaps_);
1309
this.pendingMaps_.length = 0;
1310
};
1311
1312
1313
/**
1314
* Ensures there is a backchannel request for receiving data from the server.
1315
* @private
1316
*/
1317
WebChannelBase.prototype.ensureBackChannel_ = function() {
1318
if (this.backChannelRequest_) {
1319
// already have one
1320
return;
1321
}
1322
1323
if (this.backChannelTimerId_) {
1324
// no need to start a new request - one is already scheduled
1325
return;
1326
}
1327
1328
this.backChannelAttemptId_ = 1;
1329
this.backChannelTimerId_ = requestStats.setTimeout(
1330
goog.bind(this.onStartBackChannelTimer_, this), 0);
1331
this.backChannelRetryCount_ = 0;
1332
};
1333
1334
1335
/**
1336
* Schedules a back-channel retry, unless the max retries has been reached.
1337
* @return {boolean} true iff a retry was scheduled.
1338
* @private
1339
*/
1340
WebChannelBase.prototype.maybeRetryBackChannel_ = function() {
1341
if (this.backChannelRequest_ || this.backChannelTimerId_) {
1342
// Should be impossible to be called in this state.
1343
this.channelDebug_.severe('Request already in progress');
1344
return false;
1345
}
1346
1347
if (this.backChannelRetryCount_ >= this.getBackChannelMaxRetries()) {
1348
return false;
1349
}
1350
1351
this.channelDebug_.debug('Going to retry GET');
1352
1353
this.backChannelAttemptId_++;
1354
this.backChannelTimerId_ = requestStats.setTimeout(
1355
goog.bind(this.onStartBackChannelTimer_, this),
1356
this.getRetryTime_(this.backChannelRetryCount_));
1357
this.backChannelRetryCount_++;
1358
return true;
1359
};
1360
1361
1362
/**
1363
* Timer callback for ensureBackChannel_.
1364
* @private
1365
*/
1366
WebChannelBase.prototype.onStartBackChannelTimer_ = function() {
1367
this.backChannelTimerId_ = null;
1368
this.startBackChannel_();
1369
};
1370
1371
1372
/**
1373
* Begins a new back channel operation to the server.
1374
* @private
1375
*/
1376
WebChannelBase.prototype.startBackChannel_ = function() {
1377
if (!this.okToMakeRequest_()) {
1378
// channel is cancelled
1379
return;
1380
}
1381
1382
this.channelDebug_.debug('Creating new HttpRequest');
1383
this.backChannelRequest_ = ChannelRequest.createChannelRequest(
1384
this, this.channelDebug_, this.sid_, 'rpc', this.backChannelAttemptId_);
1385
1386
if (this.httpHeadersOverwriteParam_ === null) {
1387
this.backChannelRequest_.setExtraHeaders(this.extraHeaders_);
1388
}
1389
1390
this.backChannelRequest_.setReadyStateChangeThrottle(
1391
this.readyStateChangeThrottleMs_);
1392
var uri = this.backChannelUri_.clone();
1393
uri.setParameterValue('RID', 'rpc');
1394
uri.setParameterValue('SID', this.sid_);
1395
uri.setParameterValue('CI', this.useChunked_ ? '0' : '1');
1396
uri.setParameterValue('AID', this.lastArrayId_);
1397
1398
// Add the reconnect parameters.
1399
this.addAdditionalParams_(uri);
1400
1401
uri.setParameterValue('TYPE', 'xmlhttp');
1402
1403
if (this.httpHeadersOverwriteParam_ && this.extraHeaders_) {
1404
httpCors.setHttpHeadersWithOverwriteParam(
1405
uri, this.httpHeadersOverwriteParam_, this.extraHeaders_);
1406
}
1407
1408
this.backChannelRequest_.xmlHttpGet(
1409
uri, true /* decodeChunks */, this.hostPrefix_, false /* opt_noClose */);
1410
1411
this.channelDebug_.debug('New Request created');
1412
};
1413
1414
1415
/**
1416
* Gives the handler a chance to return an error code and stop channel
1417
* execution. A handler might want to do this to check that the user is still
1418
* logged in, for example.
1419
* @private
1420
* @return {boolean} If it's OK to make a request.
1421
*/
1422
WebChannelBase.prototype.okToMakeRequest_ = function() {
1423
if (this.handler_) {
1424
var result = this.handler_.okToMakeRequest(this);
1425
if (result != WebChannelBase.Error.OK) {
1426
this.channelDebug_.debug(
1427
'Handler returned error code from okToMakeRequest');
1428
this.signalError_(result);
1429
return false;
1430
}
1431
}
1432
return true;
1433
};
1434
1435
1436
/**
1437
* @override
1438
*/
1439
WebChannelBase.prototype.testConnectionFinished = function(
1440
testChannel, useChunked) {
1441
this.channelDebug_.debug('Test Connection Finished');
1442
1443
// Forward channel will not be used prior to this method is called
1444
var clientProtocol = testChannel.getClientProtocol();
1445
if (clientProtocol) {
1446
this.forwardChannelRequestPool_.applyClientProtocol(clientProtocol);
1447
}
1448
1449
this.useChunked_ = this.allowChunkedMode_ && useChunked;
1450
this.lastStatusCode_ = testChannel.getLastStatusCode();
1451
1452
this.connectChannel_();
1453
};
1454
1455
1456
/**
1457
* @override
1458
*/
1459
WebChannelBase.prototype.testConnectionFailure = function(
1460
testChannel, errorCode) {
1461
this.channelDebug_.debug('Test Connection Failed');
1462
this.lastStatusCode_ = testChannel.getLastStatusCode();
1463
this.signalError_(WebChannelBase.Error.REQUEST_FAILED);
1464
};
1465
1466
1467
/**
1468
* @override
1469
*/
1470
WebChannelBase.prototype.onRequestData = function(request, responseText) {
1471
if (this.state_ == WebChannelBase.State.CLOSED ||
1472
(this.backChannelRequest_ != request &&
1473
!this.forwardChannelRequestPool_.hasRequest(request))) {
1474
// either CLOSED or a request we don't know about (perhaps an old request)
1475
return;
1476
}
1477
this.lastStatusCode_ = request.getLastStatusCode();
1478
1479
if (this.forwardChannelRequestPool_.hasRequest(request) &&
1480
this.state_ == WebChannelBase.State.OPENED) {
1481
var response;
1482
try {
1483
response = this.wireCodec_.decodeMessage(responseText);
1484
} catch (ex) {
1485
response = null;
1486
}
1487
if (goog.isArray(response) && response.length == 3) {
1488
this.handlePostResponse_(/** @type {!Array<?>} */ (response), request);
1489
} else {
1490
this.channelDebug_.debug('Bad POST response data returned');
1491
this.signalError_(WebChannelBase.Error.BAD_RESPONSE);
1492
}
1493
} else {
1494
if (this.backChannelRequest_ == request) {
1495
this.clearDeadBackchannelTimer_();
1496
}
1497
if (!goog.string.isEmptyOrWhitespace(responseText)) {
1498
var response = this.wireCodec_.decodeMessage(responseText);
1499
this.onInput_(/** @type {!Array<?>} */ (response), request);
1500
}
1501
}
1502
};
1503
1504
1505
/**
1506
* Handles a POST response from the server.
1507
* @param {Array<number>} responseValues The key value pairs in
1508
* the POST response.
1509
* @param {!ChannelRequest} forwardReq The forward channel request that
1510
* triggers this function call.
1511
* @private
1512
*/
1513
WebChannelBase.prototype.handlePostResponse_ = function(
1514
responseValues, forwardReq) {
1515
// The first response value is set to 0 if server is missing backchannel.
1516
if (responseValues[0] == 0) {
1517
this.handleBackchannelMissing_(forwardReq);
1518
return;
1519
}
1520
this.lastPostResponseArrayId_ = responseValues[1];
1521
var outstandingArrays = this.lastPostResponseArrayId_ - this.lastArrayId_;
1522
if (0 < outstandingArrays) {
1523
var numOutstandingBackchannelBytes = responseValues[2];
1524
this.channelDebug_.debug(
1525
numOutstandingBackchannelBytes + ' bytes (in ' + outstandingArrays +
1526
' arrays) are outstanding on the BackChannel');
1527
if (!this.shouldRetryBackChannel_(numOutstandingBackchannelBytes)) {
1528
return;
1529
}
1530
if (!this.deadBackChannelTimerId_) {
1531
// We expect to receive data within 2 RTTs or we retry the backchannel.
1532
this.deadBackChannelTimerId_ = requestStats.setTimeout(
1533
goog.bind(this.onBackChannelDead_, this),
1534
2 * WebChannelBase.RTT_ESTIMATE);
1535
}
1536
}
1537
};
1538
1539
1540
/**
1541
* Handles a POST response from the server telling us that it has detected that
1542
* we have no hanging GET connection.
1543
* @param {!ChannelRequest} forwardReq The forward channel request that
1544
* triggers this function call.
1545
* @private
1546
*/
1547
WebChannelBase.prototype.handleBackchannelMissing_ = function(forwardReq) {
1548
// As long as the back channel was started before the POST was sent,
1549
// we should retry the backchannel. We give a slight buffer of RTT_ESTIMATE
1550
// so as not to excessively retry the backchannel
1551
this.channelDebug_.debug('Server claims our backchannel is missing.');
1552
if (this.backChannelTimerId_) {
1553
this.channelDebug_.debug('But we are currently starting the request.');
1554
return;
1555
} else if (!this.backChannelRequest_) {
1556
this.channelDebug_.warning('We do not have a BackChannel established');
1557
} else if (
1558
this.backChannelRequest_.getRequestStartTime() +
1559
WebChannelBase.RTT_ESTIMATE <
1560
forwardReq.getRequestStartTime()) {
1561
this.clearDeadBackchannelTimer_();
1562
this.backChannelRequest_.cancel();
1563
this.backChannelRequest_ = null;
1564
} else {
1565
return;
1566
}
1567
this.maybeRetryBackChannel_();
1568
requestStats.notifyStatEvent(requestStats.Stat.BACKCHANNEL_MISSING);
1569
};
1570
1571
1572
/**
1573
* Determines whether we should start the process of retrying a possibly
1574
* dead backchannel.
1575
* @param {number} outstandingBytes The number of bytes for which the server has
1576
* not yet received acknowledgement.
1577
* @return {boolean} Whether to start the backchannel retry timer.
1578
* @private
1579
*/
1580
WebChannelBase.prototype.shouldRetryBackChannel_ = function(outstandingBytes) {
1581
// Not too many outstanding bytes, not buffered and not after a retry.
1582
return outstandingBytes <
1583
WebChannelBase.OUTSTANDING_DATA_BACKCHANNEL_RETRY_CUTOFF &&
1584
!this.isBuffered() && this.backChannelRetryCount_ == 0;
1585
};
1586
1587
1588
/**
1589
* Decides which host prefix should be used, if any. If there is a handler,
1590
* allows the handler to validate a host prefix provided by the server, and
1591
* optionally override it.
1592
* @param {?string} serverHostPrefix The host prefix provided by the server.
1593
* @return {?string} The host prefix to actually use, if any. Will return null
1594
* if the use of host prefixes was disabled via setAllowHostPrefix().
1595
* @override
1596
*/
1597
WebChannelBase.prototype.correctHostPrefix = function(serverHostPrefix) {
1598
if (this.allowHostPrefix_) {
1599
if (this.handler_) {
1600
return this.handler_.correctHostPrefix(serverHostPrefix);
1601
}
1602
return serverHostPrefix;
1603
}
1604
return null;
1605
};
1606
1607
1608
/**
1609
* Handles the timer that indicates that our backchannel is no longer able to
1610
* successfully receive data from the server.
1611
* @private
1612
*/
1613
WebChannelBase.prototype.onBackChannelDead_ = function() {
1614
if (goog.isDefAndNotNull(this.deadBackChannelTimerId_)) {
1615
this.deadBackChannelTimerId_ = null;
1616
this.backChannelRequest_.cancel();
1617
this.backChannelRequest_ = null;
1618
this.maybeRetryBackChannel_();
1619
requestStats.notifyStatEvent(requestStats.Stat.BACKCHANNEL_DEAD);
1620
}
1621
};
1622
1623
1624
/**
1625
* Clears the timer that indicates that our backchannel is no longer able to
1626
* successfully receive data from the server.
1627
* @private
1628
*/
1629
WebChannelBase.prototype.clearDeadBackchannelTimer_ = function() {
1630
if (goog.isDefAndNotNull(this.deadBackChannelTimerId_)) {
1631
goog.global.clearTimeout(this.deadBackChannelTimerId_);
1632
this.deadBackChannelTimerId_ = null;
1633
}
1634
};
1635
1636
1637
/**
1638
* Returns whether or not the given error/status combination is fatal or not.
1639
* On fatal errors we immediately close the session rather than retrying the
1640
* failed request.
1641
* @param {?ChannelRequest.Error} error The error code for the
1642
* failed request.
1643
* @param {number} statusCode The last HTTP status code.
1644
* @return {boolean} Whether or not the error is fatal.
1645
* @private
1646
*/
1647
WebChannelBase.isFatalError_ = function(error, statusCode) {
1648
return error == ChannelRequest.Error.UNKNOWN_SESSION_ID ||
1649
(error == ChannelRequest.Error.STATUS && statusCode > 0);
1650
};
1651
1652
1653
/**
1654
* @override
1655
*/
1656
WebChannelBase.prototype.onRequestComplete = function(request) {
1657
this.channelDebug_.debug('Request complete');
1658
var type;
1659
if (this.backChannelRequest_ == request) {
1660
this.clearDeadBackchannelTimer_();
1661
this.backChannelRequest_ = null;
1662
type = WebChannelBase.ChannelType_.BACK_CHANNEL;
1663
} else if (this.forwardChannelRequestPool_.hasRequest(request)) {
1664
this.forwardChannelRequestPool_.removeRequest(request);
1665
type = WebChannelBase.ChannelType_.FORWARD_CHANNEL;
1666
} else {
1667
// return if it was an old request from a previous session
1668
return;
1669
}
1670
1671
this.lastStatusCode_ = request.getLastStatusCode();
1672
1673
if (this.state_ == WebChannelBase.State.CLOSED) {
1674
return;
1675
}
1676
1677
if (request.getSuccess()) {
1678
// Yay!
1679
if (type == WebChannelBase.ChannelType_.FORWARD_CHANNEL) {
1680
var size = request.getPostData() ? request.getPostData().length : 0;
1681
requestStats.notifyTimingEvent(
1682
size, goog.now() - request.getRequestStartTime(),
1683
this.forwardChannelRetryCount_);
1684
this.ensureForwardChannel_();
1685
this.onSuccess_();
1686
this.pendingMaps_.length = 0;
1687
} else { // i.e., back-channel
1688
this.ensureBackChannel_();
1689
}
1690
return;
1691
}
1692
// Else unsuccessful. Fall through.
1693
1694
var lastError = request.getLastError();
1695
if (!WebChannelBase.isFatalError_(lastError, this.lastStatusCode_)) {
1696
// Maybe retry.
1697
this.channelDebug_.debug(
1698
'Maybe retrying, last error: ' +
1699
ChannelRequest.errorStringFromCode(lastError, this.lastStatusCode_));
1700
if (type == WebChannelBase.ChannelType_.FORWARD_CHANNEL) {
1701
if (this.maybeRetryForwardChannel_(request)) {
1702
return;
1703
}
1704
}
1705
if (type == WebChannelBase.ChannelType_.BACK_CHANNEL) {
1706
if (this.maybeRetryBackChannel_()) {
1707
return;
1708
}
1709
}
1710
// Else exceeded max retries. Fall through.
1711
this.channelDebug_.debug('Exceeded max number of retries');
1712
} else {
1713
// Else fatal error. Fall through and mark the pending maps as failed.
1714
this.channelDebug_.debug('Not retrying due to error type');
1715
}
1716
1717
1718
// Can't save this session. :(
1719
this.channelDebug_.debug('Error: HTTP request failed');
1720
switch (lastError) {
1721
case ChannelRequest.Error.NO_DATA:
1722
this.signalError_(WebChannelBase.Error.NO_DATA);
1723
break;
1724
case ChannelRequest.Error.BAD_DATA:
1725
this.signalError_(WebChannelBase.Error.BAD_DATA);
1726
break;
1727
case ChannelRequest.Error.UNKNOWN_SESSION_ID:
1728
this.signalError_(WebChannelBase.Error.UNKNOWN_SESSION_ID);
1729
break;
1730
default:
1731
this.signalError_(WebChannelBase.Error.REQUEST_FAILED);
1732
break;
1733
}
1734
};
1735
1736
1737
/**
1738
* @param {number} retryCount Number of retries so far.
1739
* @return {number} Time in ms before firing next retry request.
1740
* @private
1741
*/
1742
WebChannelBase.prototype.getRetryTime_ = function(retryCount) {
1743
var retryTime = this.baseRetryDelayMs_ +
1744
Math.floor(Math.random() * this.retryDelaySeedMs_);
1745
if (!this.isActive()) {
1746
this.channelDebug_.debug('Inactive channel');
1747
retryTime = retryTime * WebChannelBase.INACTIVE_CHANNEL_RETRY_FACTOR;
1748
}
1749
// Backoff for subsequent retries
1750
retryTime *= retryCount;
1751
return retryTime;
1752
};
1753
1754
1755
/**
1756
* @param {number} baseDelayMs The base part of the retry delay, in ms.
1757
* @param {number} delaySeedMs A random delay between 0 and this is added to
1758
* the base part.
1759
*/
1760
WebChannelBase.prototype.setRetryDelay = function(baseDelayMs, delaySeedMs) {
1761
this.baseRetryDelayMs_ = baseDelayMs;
1762
this.retryDelaySeedMs_ = delaySeedMs;
1763
};
1764
1765
1766
/**
1767
* Apply any handshake control headers.
1768
* @param {!ChannelRequest} request The underlying request object
1769
* @private
1770
*/
1771
WebChannelBase.prototype.applyControlHeaders_ = function(request) {
1772
if (!this.backgroundChannelTest_) {
1773
return;
1774
}
1775
1776
var xhr = request.getXhr();
1777
if (xhr) {
1778
var clientProtocol =
1779
xhr.getStreamingResponseHeader(WebChannel.X_CLIENT_WIRE_PROTOCOL);
1780
if (clientProtocol) {
1781
this.forwardChannelRequestPool_.applyClientProtocol(clientProtocol);
1782
}
1783
1784
if (this.getHttpSessionIdParam()) {
1785
var httpSessionIdHeader =
1786
xhr.getStreamingResponseHeader(WebChannel.X_HTTP_SESSION_ID);
1787
if (httpSessionIdHeader) {
1788
this.setHttpSessionId(httpSessionIdHeader);
1789
// update the cached uri
1790
var httpSessionIdParam = this.getHttpSessionIdParam();
1791
1792
this.forwardChannelUri_.setParameterValue(
1793
/** @type {string} */ (httpSessionIdParam), // never null
1794
httpSessionIdHeader);
1795
} else {
1796
this.channelDebug_.warning(
1797
'Missing X_HTTP_SESSION_ID in the handshake response');
1798
}
1799
}
1800
}
1801
};
1802
1803
1804
/**
1805
* Processes the data returned by the server.
1806
* @param {!Array<!Array<?>>} respArray The response array returned
1807
* by the server.
1808
* @param {!ChannelRequest} request The underlying request object
1809
* @private
1810
*/
1811
WebChannelBase.prototype.onInput_ = function(respArray, request) {
1812
var batch =
1813
this.handler_ && this.handler_.channelHandleMultipleArrays ? [] : null;
1814
for (var i = 0; i < respArray.length; i++) {
1815
var nextArray = respArray[i];
1816
this.lastArrayId_ = nextArray[0];
1817
nextArray = nextArray[1];
1818
if (this.state_ == WebChannelBase.State.OPENING) {
1819
if (nextArray[0] == 'c') {
1820
this.sid_ = nextArray[1];
1821
this.hostPrefix_ = this.correctHostPrefix(nextArray[2]);
1822
1823
var negotiatedVersion = nextArray[3];
1824
if (goog.isDefAndNotNull(negotiatedVersion)) {
1825
this.channelVersion_ = negotiatedVersion;
1826
this.channelDebug_.info('VER=' + this.channelVersion_);
1827
}
1828
1829
var negotiatedServerVersion = nextArray[4];
1830
if (goog.isDefAndNotNull(negotiatedServerVersion)) {
1831
this.serverVersion_ = negotiatedServerVersion;
1832
this.channelDebug_.info('SVER=' + this.serverVersion_);
1833
}
1834
1835
this.applyControlHeaders_(request);
1836
1837
this.state_ = WebChannelBase.State.OPENED;
1838
if (this.handler_) {
1839
this.handler_.channelOpened(this);
1840
}
1841
this.backChannelUri_ = this.getBackChannelUri(
1842
this.hostPrefix_, /** @type {string} */ (this.path_));
1843
// Open connection to receive data
1844
this.ensureBackChannel_();
1845
} else if (nextArray[0] == 'stop' || nextArray[0] == 'close') {
1846
// treat close also as an abort
1847
this.signalError_(WebChannelBase.Error.STOP);
1848
}
1849
} else if (this.state_ == WebChannelBase.State.OPENED) {
1850
if (nextArray[0] == 'stop' || nextArray[0] == 'close') {
1851
if (batch && !goog.array.isEmpty(batch)) {
1852
this.handler_.channelHandleMultipleArrays(this, batch);
1853
batch.length = 0;
1854
}
1855
if (nextArray[0] == 'stop') {
1856
this.signalError_(WebChannelBase.Error.STOP);
1857
} else {
1858
this.disconnect();
1859
}
1860
} else if (nextArray[0] == 'noop') {
1861
// ignore - noop to keep connection happy
1862
} else {
1863
if (batch) {
1864
batch.push(nextArray);
1865
} else if (this.handler_) {
1866
this.handler_.channelHandleArray(this, nextArray);
1867
}
1868
}
1869
// We have received useful data on the back-channel, so clear its retry
1870
// count. We do this because back-channels by design do not complete
1871
// quickly, so on a flaky connection we could have many fail to complete
1872
// fully but still deliver a lot of data before they fail. We don't want
1873
// to count such failures towards the retry limit, because we don't want
1874
// to give up on a session if we can still receive data.
1875
this.backChannelRetryCount_ = 0;
1876
}
1877
}
1878
if (batch && !goog.array.isEmpty(batch)) {
1879
this.handler_.channelHandleMultipleArrays(this, batch);
1880
}
1881
};
1882
1883
1884
/**
1885
* Helper to ensure the channel is in the expected state.
1886
* @param {...number} var_args The channel must be in one of the indicated
1887
* states.
1888
* @private
1889
*/
1890
WebChannelBase.prototype.ensureInState_ = function(var_args) {
1891
goog.asserts.assert(
1892
goog.array.contains(arguments, this.state_),
1893
'Unexpected channel state: %s', this.state_);
1894
};
1895
1896
1897
/**
1898
* Signals an error has occurred.
1899
* @param {WebChannelBase.Error} error The error code for the failure.
1900
* @private
1901
*/
1902
WebChannelBase.prototype.signalError_ = function(error) {
1903
this.channelDebug_.info('Error code ' + error);
1904
if (error == WebChannelBase.Error.REQUEST_FAILED) {
1905
// Create a separate Internet connection to check
1906
// if it's a server error or user's network error.
1907
var imageUri = null;
1908
if (this.handler_) {
1909
imageUri = this.handler_.getNetworkTestImageUri(this);
1910
}
1911
netUtils.testNetwork(goog.bind(this.testNetworkCallback_, this), imageUri);
1912
} else {
1913
requestStats.notifyStatEvent(requestStats.Stat.ERROR_OTHER);
1914
}
1915
this.onError_(error);
1916
};
1917
1918
1919
/**
1920
* Callback for netUtils.testNetwork during error handling.
1921
* @param {boolean} networkUp Whether the network is up.
1922
* @private
1923
*/
1924
WebChannelBase.prototype.testNetworkCallback_ = function(networkUp) {
1925
if (networkUp) {
1926
this.channelDebug_.info('Successfully pinged google.com');
1927
requestStats.notifyStatEvent(requestStats.Stat.ERROR_OTHER);
1928
} else {
1929
this.channelDebug_.info('Failed to ping google.com');
1930
requestStats.notifyStatEvent(requestStats.Stat.ERROR_NETWORK);
1931
// Do not call onError_ again to eliminate duplicated Error events.
1932
}
1933
};
1934
1935
1936
/**
1937
* Called when messages have been successfully sent from the queue.
1938
* @private
1939
*/
1940
WebChannelBase.prototype.onSuccess_ = function() {
1941
// TODO(user): optimize for request pool (>1)
1942
if (this.handler_) {
1943
this.handler_.channelSuccess(this, this.pendingMaps_);
1944
}
1945
};
1946
1947
1948
/**
1949
* Called when we've determined the final error for a channel. It closes the
1950
* notifiers the handler of the error and closes the channel.
1951
* @param {WebChannelBase.Error} error The error code for the failure.
1952
* @private
1953
*/
1954
WebChannelBase.prototype.onError_ = function(error) {
1955
this.channelDebug_.debug('HttpChannel: error - ' + error);
1956
this.state_ = WebChannelBase.State.CLOSED;
1957
if (this.handler_) {
1958
this.handler_.channelError(this, error);
1959
}
1960
this.onClose_();
1961
this.cancelRequests_();
1962
};
1963
1964
1965
/**
1966
* Called when the channel has been closed. It notifiers the handler of the
1967
* event, and reports any pending or undelivered maps.
1968
* @private
1969
*/
1970
WebChannelBase.prototype.onClose_ = function() {
1971
this.state_ = WebChannelBase.State.CLOSED;
1972
this.lastStatusCode_ = -1;
1973
if (this.handler_) {
1974
if (this.pendingMaps_.length == 0 && this.outgoingMaps_.length == 0) {
1975
this.handler_.channelClosed(this);
1976
} else {
1977
this.channelDebug_.debug(
1978
'Number of undelivered maps' +
1979
', pending: ' + this.pendingMaps_.length + ', outgoing: ' +
1980
this.outgoingMaps_.length);
1981
1982
var copyOfPendingMaps = goog.array.clone(this.pendingMaps_);
1983
var copyOfUndeliveredMaps = goog.array.clone(this.outgoingMaps_);
1984
this.pendingMaps_.length = 0;
1985
this.outgoingMaps_.length = 0;
1986
1987
this.handler_.channelClosed(
1988
this, copyOfPendingMaps, copyOfUndeliveredMaps);
1989
}
1990
}
1991
};
1992
1993
1994
/**
1995
* @override
1996
*/
1997
WebChannelBase.prototype.getForwardChannelUri = function(path) {
1998
var uri = this.createDataUri(null, path);
1999
this.channelDebug_.debug('GetForwardChannelUri: ' + uri);
2000
return uri;
2001
};
2002
2003
2004
/**
2005
* @override
2006
*/
2007
WebChannelBase.prototype.getConnectionState = function() {
2008
return this.connState_;
2009
};
2010
2011
2012
/**
2013
* @override
2014
*/
2015
WebChannelBase.prototype.getBackChannelUri = function(hostPrefix, path) {
2016
var uri = this.createDataUri(
2017
this.shouldUseSecondaryDomains() ? hostPrefix : null, path);
2018
this.channelDebug_.debug('GetBackChannelUri: ' + uri);
2019
return uri;
2020
};
2021
2022
2023
/**
2024
* @override
2025
*/
2026
WebChannelBase.prototype.createDataUri = function(
2027
hostPrefix, path, opt_overridePort) {
2028
var uri = goog.Uri.parse(path);
2029
var uriAbsolute = (uri.getDomain() != '');
2030
if (uriAbsolute) {
2031
if (hostPrefix) {
2032
uri.setDomain(hostPrefix + '.' + uri.getDomain());
2033
}
2034
2035
uri.setPort(opt_overridePort || uri.getPort());
2036
} else {
2037
var locationPage = goog.global.location;
2038
var hostName;
2039
if (hostPrefix) {
2040
hostName = hostPrefix + '.' + locationPage.hostname;
2041
} else {
2042
hostName = locationPage.hostname;
2043
}
2044
2045
var port = opt_overridePort || locationPage.port;
2046
2047
uri = goog.Uri.create(locationPage.protocol, null, hostName, port, path);
2048
}
2049
2050
if (this.extraParams_) {
2051
goog.object.forEach(this.extraParams_, function(value, key) {
2052
uri.setParameterValue(key, value);
2053
});
2054
}
2055
2056
var param = this.getHttpSessionIdParam();
2057
var value = this.getHttpSessionId();
2058
if (param && value) {
2059
uri.setParameterValue(param, value);
2060
}
2061
2062
// Add the protocol version to the URI.
2063
uri.setParameterValue('VER', this.channelVersion_);
2064
2065
// Add the reconnect parameters.
2066
this.addAdditionalParams_(uri);
2067
2068
return uri;
2069
};
2070
2071
2072
/**
2073
* @override
2074
*/
2075
WebChannelBase.prototype.createXhrIo = function(hostPrefix) {
2076
if (hostPrefix && !this.supportsCrossDomainXhrs_) {
2077
throw Error('Can\'t create secondary domain capable XhrIo object.');
2078
}
2079
var xhr = new goog.net.XhrIo();
2080
xhr.setWithCredentials(this.supportsCrossDomainXhrs_);
2081
return xhr;
2082
};
2083
2084
2085
/**
2086
* @override
2087
*/
2088
WebChannelBase.prototype.isActive = function() {
2089
return !!this.handler_ && this.handler_.isActive(this);
2090
};
2091
2092
2093
/**
2094
* @override
2095
*/
2096
WebChannelBase.prototype.shouldUseSecondaryDomains = function() {
2097
return this.supportsCrossDomainXhrs_;
2098
};
2099
2100
2101
/**
2102
* A LogSaver that can be used to accumulate all the debug logs so they
2103
* can be sent to the server when a problem is detected.
2104
*/
2105
WebChannelBase.LogSaver = {};
2106
2107
2108
/**
2109
* Buffer for accumulating the debug log
2110
* @type {goog.structs.CircularBuffer}
2111
* @private
2112
*/
2113
WebChannelBase.LogSaver.buffer_ = new goog.structs.CircularBuffer(1000);
2114
2115
2116
/**
2117
* Whether we're currently accumulating the debug log.
2118
* @type {boolean}
2119
* @private
2120
*/
2121
WebChannelBase.LogSaver.enabled_ = false;
2122
2123
2124
/**
2125
* Formatter for saving logs.
2126
* @type {goog.debug.Formatter}
2127
* @private
2128
*/
2129
WebChannelBase.LogSaver.formatter_ = new goog.debug.TextFormatter();
2130
2131
2132
/**
2133
* Returns whether the LogSaver is enabled.
2134
* @return {boolean} Whether saving is enabled or disabled.
2135
*/
2136
WebChannelBase.LogSaver.isEnabled = function() {
2137
return WebChannelBase.LogSaver.enabled_;
2138
};
2139
2140
2141
/**
2142
* Enables of disables the LogSaver.
2143
* @param {boolean} enable Whether to enable or disable saving.
2144
*/
2145
WebChannelBase.LogSaver.setEnabled = function(enable) {
2146
if (enable == WebChannelBase.LogSaver.enabled_) {
2147
return;
2148
}
2149
2150
var fn = WebChannelBase.LogSaver.addLogRecord;
2151
var logger = goog.log.getLogger('goog.net');
2152
if (enable) {
2153
goog.log.addHandler(logger, fn);
2154
} else {
2155
goog.log.removeHandler(logger, fn);
2156
}
2157
};
2158
2159
2160
/**
2161
* Adds a log record.
2162
* @param {goog.log.LogRecord} logRecord the LogRecord.
2163
*/
2164
WebChannelBase.LogSaver.addLogRecord = function(logRecord) {
2165
WebChannelBase.LogSaver.buffer_.add(
2166
WebChannelBase.LogSaver.formatter_.formatRecord(logRecord));
2167
};
2168
2169
2170
/**
2171
* Returns the log as a single string.
2172
* @return {string} The log as a single string.
2173
*/
2174
WebChannelBase.LogSaver.getBuffer = function() {
2175
return WebChannelBase.LogSaver.buffer_.getValues().join('');
2176
};
2177
2178
2179
/**
2180
* Clears the buffer
2181
*/
2182
WebChannelBase.LogSaver.clearBuffer = function() {
2183
WebChannelBase.LogSaver.buffer_.clear();
2184
};
2185
2186
2187
2188
/**
2189
* Abstract base class for the channel handler
2190
* @constructor
2191
* @struct
2192
*/
2193
WebChannelBase.Handler = function() {};
2194
2195
2196
/**
2197
* Callback handler for when a batch of response arrays is received from the
2198
* server. When null, batched dispatching is disabled.
2199
* @type {?function(!WebChannelBase, !Array<!Array<?>>)}
2200
*/
2201
WebChannelBase.Handler.prototype.channelHandleMultipleArrays = null;
2202
2203
2204
/**
2205
* Whether it's okay to make a request to the server. A handler can return
2206
* false if the channel should fail. For example, if the user has logged out,
2207
* the handler may want all requests to fail immediately.
2208
* @param {WebChannelBase} channel The channel.
2209
* @return {WebChannelBase.Error} An error code. The code should
2210
* return WebChannelBase.Error.OK to indicate it's okay. Any other
2211
* error code will cause a failure.
2212
*/
2213
WebChannelBase.Handler.prototype.okToMakeRequest = function(channel) {
2214
return WebChannelBase.Error.OK;
2215
};
2216
2217
2218
/**
2219
* Indicates the WebChannel has successfully negotiated with the server
2220
* and can now send and receive data.
2221
* @param {WebChannelBase} channel The channel.
2222
*/
2223
WebChannelBase.Handler.prototype.channelOpened = function(channel) {};
2224
2225
2226
/**
2227
* New input is available for the application to process.
2228
*
2229
* @param {WebChannelBase} channel The channel.
2230
* @param {Array<?>} array The data array.
2231
*/
2232
WebChannelBase.Handler.prototype.channelHandleArray = function(channel, array) {
2233
};
2234
2235
2236
/**
2237
* Indicates maps were successfully sent on the channel.
2238
*
2239
* @param {WebChannelBase} channel The channel.
2240
* @param {Array<Wire.QueuedMap>} deliveredMaps The
2241
* array of maps that have been delivered to the server. This is a direct
2242
* reference to the internal array, so a copy should be made
2243
* if the caller desires a reference to the data.
2244
*/
2245
WebChannelBase.Handler.prototype.channelSuccess = function(
2246
channel, deliveredMaps) {};
2247
2248
2249
/**
2250
* Indicates an error occurred on the WebChannel.
2251
*
2252
* @param {WebChannelBase} channel The channel.
2253
* @param {WebChannelBase.Error} error The error code.
2254
*/
2255
WebChannelBase.Handler.prototype.channelError = function(channel, error) {};
2256
2257
2258
/**
2259
* Indicates the WebChannel is closed. Also notifies about which maps,
2260
* if any, that may not have been delivered to the server.
2261
* @param {WebChannelBase} channel The channel.
2262
* @param {Array<Wire.QueuedMap>=} opt_pendingMaps The
2263
* array of pending maps, which may or may not have been delivered to the
2264
* server.
2265
* @param {Array<Wire.QueuedMap>=} opt_undeliveredMaps
2266
* The array of undelivered maps, which have definitely not been delivered
2267
* to the server.
2268
*/
2269
WebChannelBase.Handler.prototype.channelClosed = function(
2270
channel, opt_pendingMaps, opt_undeliveredMaps) {};
2271
2272
2273
/**
2274
* Gets any parameters that should be added at the time another connection is
2275
* made to the server.
2276
* @param {WebChannelBase} channel The channel.
2277
* @return {!Object} Extra parameter keys and values to add to the requests.
2278
*/
2279
WebChannelBase.Handler.prototype.getAdditionalParams = function(channel) {
2280
return {};
2281
};
2282
2283
2284
/**
2285
* Gets the URI of an image that can be used to test network connectivity.
2286
* @param {WebChannelBase} channel The channel.
2287
* @return {goog.Uri?} A custom URI to load for the network test.
2288
*/
2289
WebChannelBase.Handler.prototype.getNetworkTestImageUri = function(channel) {
2290
return null;
2291
};
2292
2293
2294
/**
2295
* Gets whether this channel is currently active. This is used to determine the
2296
* length of time to wait before retrying.
2297
* @param {WebChannelBase} channel The channel.
2298
* @return {boolean} Whether the channel is currently active.
2299
*/
2300
WebChannelBase.Handler.prototype.isActive = function(channel) {
2301
return true;
2302
};
2303
2304
2305
/**
2306
* Called by the channel if enumeration of the map throws an exception.
2307
* @param {WebChannelBase} channel The channel.
2308
* @param {Object} map The map that can't be enumerated.
2309
*/
2310
WebChannelBase.Handler.prototype.badMapError = function(channel, map) {};
2311
2312
2313
/**
2314
* Allows the handler to override a host prefix provided by the server. Will
2315
* be called whenever the channel has received such a prefix and is considering
2316
* its use.
2317
* @param {?string} serverHostPrefix The host prefix provided by the server.
2318
* @return {?string} The host prefix the client should use.
2319
*/
2320
WebChannelBase.Handler.prototype.correctHostPrefix = function(
2321
serverHostPrefix) {
2322
return serverHostPrefix;
2323
};
2324
}); // goog.scope
2325
2326