basetestchannel.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  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. * @fileoverview Base TestChannel implementation.
  16. *
  17. */
  18. goog.provide('goog.labs.net.webChannel.BaseTestChannel');
  19. goog.require('goog.labs.net.webChannel.Channel');
  20. goog.require('goog.labs.net.webChannel.ChannelRequest');
  21. goog.require('goog.labs.net.webChannel.WebChannelDebug');
  22. goog.require('goog.labs.net.webChannel.requestStats');
  23. goog.require('goog.labs.net.webChannel.requestStats.Stat');
  24. goog.require('goog.net.WebChannel');
  25. /**
  26. * A TestChannel is used during the first part of channel negotiation
  27. * with the server to create the channel. It helps us determine whether we're
  28. * behind a buffering proxy.
  29. *
  30. * @constructor
  31. * @struct
  32. * @param {!goog.labs.net.webChannel.Channel} channel The channel
  33. * that owns this test channel.
  34. * @param {!goog.labs.net.webChannel.WebChannelDebug} channelDebug A
  35. * WebChannelDebug instance to use for logging.
  36. * @implements {goog.labs.net.webChannel.Channel}
  37. */
  38. goog.labs.net.webChannel.BaseTestChannel = function(channel, channelDebug) {
  39. /**
  40. * The channel that owns this test channel
  41. * @private {!goog.labs.net.webChannel.Channel}
  42. */
  43. this.channel_ = channel;
  44. /**
  45. * The channel debug to use for logging
  46. * @private {!goog.labs.net.webChannel.WebChannelDebug}
  47. */
  48. this.channelDebug_ = channelDebug;
  49. /**
  50. * Extra HTTP headers to add to all the requests sent to the server.
  51. * @private {Object}
  52. */
  53. this.extraHeaders_ = null;
  54. /**
  55. * The test request.
  56. * @private {goog.labs.net.webChannel.ChannelRequest}
  57. */
  58. this.request_ = null;
  59. /**
  60. * Whether we have received the first result as an intermediate result. This
  61. * helps us determine whether we're behind a buffering proxy.
  62. * @private {boolean}
  63. */
  64. this.receivedIntermediateResult_ = false;
  65. /**
  66. * The relative path for test requests.
  67. * @private {?string}
  68. */
  69. this.path_ = null;
  70. /**
  71. * The last status code received.
  72. * @private {number}
  73. */
  74. this.lastStatusCode_ = -1;
  75. /**
  76. * A subdomain prefix for using a subdomain in IE for the backchannel
  77. * requests.
  78. * @private {?string}
  79. */
  80. this.hostPrefix_ = null;
  81. /**
  82. * The effective client protocol as indicated by the initial handshake
  83. * response via the x-client-wire-protocol header.
  84. *
  85. * @private {?string}
  86. */
  87. this.clientProtocol_ = null;
  88. };
  89. goog.scope(function() {
  90. var WebChannel = goog.net.WebChannel;
  91. var BaseTestChannel = goog.labs.net.webChannel.BaseTestChannel;
  92. var WebChannelDebug = goog.labs.net.webChannel.WebChannelDebug;
  93. var ChannelRequest = goog.labs.net.webChannel.ChannelRequest;
  94. var requestStats = goog.labs.net.webChannel.requestStats;
  95. var Channel = goog.labs.net.webChannel.Channel;
  96. /**
  97. * Enum type for the test channel state machine
  98. * @enum {number}
  99. * @private
  100. */
  101. BaseTestChannel.State_ = {
  102. /**
  103. * The state for the TestChannel state machine where we making the
  104. * initial call to get the server configured parameters.
  105. */
  106. INIT: 0,
  107. /**
  108. * The state for the TestChannel state machine where we're checking to
  109. * se if we're behind a buffering proxy.
  110. */
  111. CONNECTION_TESTING: 1
  112. };
  113. /**
  114. * The state of the state machine for this object.
  115. *
  116. * @private {?BaseTestChannel.State_}
  117. */
  118. BaseTestChannel.prototype.state_ = null;
  119. /**
  120. * Sets extra HTTP headers to add to all the requests sent to the server.
  121. *
  122. * @param {Object} extraHeaders The HTTP headers.
  123. */
  124. BaseTestChannel.prototype.setExtraHeaders = function(extraHeaders) {
  125. this.extraHeaders_ = extraHeaders;
  126. };
  127. /**
  128. * Starts the test channel. This initiates connections to the server.
  129. *
  130. * @param {string} path The relative uri for the test connection.
  131. */
  132. BaseTestChannel.prototype.connect = function(path) {
  133. this.path_ = path;
  134. var sendDataUri = this.channel_.getForwardChannelUri(this.path_);
  135. requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_START);
  136. // If the channel already has the result of the handshake, then skip it.
  137. var handshakeResult = this.channel_.getConnectionState().handshakeResult;
  138. if (goog.isDefAndNotNull(handshakeResult)) {
  139. this.hostPrefix_ = this.channel_.correctHostPrefix(handshakeResult[0]);
  140. this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
  141. this.checkBufferingProxy_();
  142. return;
  143. }
  144. // the first request returns server specific parameters
  145. sendDataUri.setParameterValues('MODE', 'init');
  146. // http-session-id to be generated as the response
  147. if (!this.channel_.getBackgroundChannelTest() &&
  148. this.channel_.getHttpSessionIdParam()) {
  149. sendDataUri.setParameterValues(WebChannel.X_HTTP_SESSION_ID,
  150. this.channel_.getHttpSessionIdParam());
  151. }
  152. this.request_ = ChannelRequest.createChannelRequest(this, this.channelDebug_);
  153. this.request_.setExtraHeaders(this.extraHeaders_);
  154. this.request_.xmlHttpGet(
  155. sendDataUri, false /* decodeChunks */, null /* hostPrefix */,
  156. true /* opt_noClose */);
  157. this.state_ = BaseTestChannel.State_.INIT;
  158. };
  159. /**
  160. * Begins the second stage of the test channel where we test to see if we're
  161. * behind a buffering proxy. The server sends back a multi-chunked response
  162. * with the first chunk containing the content '1' and then two seconds later
  163. * sending the second chunk containing the content '2'. Depending on how we
  164. * receive the content, we can tell if we're behind a buffering proxy.
  165. * @private
  166. */
  167. BaseTestChannel.prototype.checkBufferingProxy_ = function() {
  168. this.channelDebug_.debug('TestConnection: starting stage 2');
  169. // If the test result is already available, skip its execution.
  170. var bufferingProxyResult =
  171. this.channel_.getConnectionState().bufferingProxyResult;
  172. if (goog.isDefAndNotNull(bufferingProxyResult)) {
  173. this.channelDebug_.debug(
  174. 'TestConnection: skipping stage 2, precomputed result is ' +
  175. bufferingProxyResult ?
  176. 'Buffered' :
  177. 'Unbuffered');
  178. requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);
  179. if (bufferingProxyResult) { // Buffered/Proxy connection
  180. requestStats.notifyStatEvent(requestStats.Stat.PROXY);
  181. this.channel_.testConnectionFinished(this, false);
  182. } else { // Unbuffered/NoProxy connection
  183. requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
  184. this.channel_.testConnectionFinished(this, true);
  185. }
  186. return; // Skip the test
  187. }
  188. this.request_ = ChannelRequest.createChannelRequest(this, this.channelDebug_);
  189. this.request_.setExtraHeaders(this.extraHeaders_);
  190. var recvDataUri = this.channel_.getBackChannelUri(
  191. this.hostPrefix_,
  192. /** @type {string} */ (this.path_));
  193. requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);
  194. recvDataUri.setParameterValues('TYPE', 'xmlhttp');
  195. var param = this.channel_.getHttpSessionIdParam();
  196. var value = this.channel_.getHttpSessionId();
  197. if (param && value) {
  198. recvDataUri.setParameterValue(param, value);
  199. }
  200. this.request_.xmlHttpGet(
  201. recvDataUri, false /** decodeChunks */, this.hostPrefix_,
  202. false /** opt_noClose */);
  203. };
  204. /**
  205. * @override
  206. */
  207. BaseTestChannel.prototype.createXhrIo = function(hostPrefix) {
  208. return this.channel_.createXhrIo(hostPrefix);
  209. };
  210. /**
  211. * Aborts the test channel.
  212. */
  213. BaseTestChannel.prototype.abort = function() {
  214. if (this.request_) {
  215. this.request_.cancel();
  216. this.request_ = null;
  217. }
  218. this.lastStatusCode_ = -1;
  219. };
  220. /**
  221. * Returns whether the test channel is closed. The ChannelRequest object expects
  222. * this method to be implemented on its handler.
  223. *
  224. * @return {boolean} Whether the channel is closed.
  225. * @override
  226. */
  227. BaseTestChannel.prototype.isClosed = function() {
  228. return false;
  229. };
  230. /**
  231. * Callback from ChannelRequest for when new data is received
  232. *
  233. * @param {ChannelRequest} req The request object.
  234. * @param {string} responseText The text of the response.
  235. * @override
  236. */
  237. BaseTestChannel.prototype.onRequestData = function(req, responseText) {
  238. this.lastStatusCode_ = req.getLastStatusCode();
  239. if (this.state_ == BaseTestChannel.State_.INIT) {
  240. this.channelDebug_.debug('TestConnection: Got data for stage 1');
  241. this.applyControlHeaders_(req);
  242. if (!responseText) {
  243. this.channelDebug_.debug('TestConnection: Null responseText');
  244. // The server should always send text; something is wrong here
  245. this.channel_.testConnectionFailure(this, ChannelRequest.Error.BAD_DATA);
  246. return;
  247. }
  248. try {
  249. var channel = /** @type {!goog.labs.net.webChannel.WebChannelBase} */ (
  250. this.channel_);
  251. var respArray = channel.getWireCodec().decodeMessage(responseText);
  252. } catch (e) {
  253. this.channelDebug_.dumpException(e);
  254. this.channel_.testConnectionFailure(this, ChannelRequest.Error.BAD_DATA);
  255. return;
  256. }
  257. this.hostPrefix_ = this.channel_.correctHostPrefix(respArray[0]);
  258. } else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
  259. if (this.receivedIntermediateResult_) {
  260. requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_TWO);
  261. } else {
  262. // '11111' is used instead of '1' to prevent a small amount of buffering
  263. // by Safari.
  264. if (responseText == '11111') {
  265. requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_ONE);
  266. this.receivedIntermediateResult_ = true;
  267. if (this.checkForEarlyNonBuffered_()) {
  268. // If early chunk detection is on, and we passed the tests,
  269. // assume HTTP_OK, cancel the test and turn on noproxy mode.
  270. this.lastStatusCode_ = 200;
  271. this.request_.cancel();
  272. this.channelDebug_.debug(
  273. 'Test connection succeeded; using streaming connection');
  274. requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
  275. this.channel_.testConnectionFinished(this, true);
  276. }
  277. } else {
  278. requestStats.notifyStatEvent(
  279. requestStats.Stat.TEST_STAGE_TWO_DATA_BOTH);
  280. this.receivedIntermediateResult_ = false;
  281. }
  282. }
  283. }
  284. };
  285. /**
  286. * Callback from ChannelRequest that indicates a request has completed.
  287. *
  288. * @param {!ChannelRequest} req The request object.
  289. * @override
  290. */
  291. BaseTestChannel.prototype.onRequestComplete = function(req) {
  292. this.lastStatusCode_ = this.request_.getLastStatusCode();
  293. if (!this.request_.getSuccess()) {
  294. this.channelDebug_.debug(
  295. 'TestConnection: request failed, in state ' + this.state_);
  296. if (this.state_ == BaseTestChannel.State_.INIT) {
  297. requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_FAILED);
  298. } else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
  299. requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_FAILED);
  300. }
  301. this.channel_.testConnectionFailure(
  302. this,
  303. /** @type {ChannelRequest.Error} */
  304. (this.request_.getLastError()));
  305. return;
  306. }
  307. if (this.state_ == BaseTestChannel.State_.INIT) {
  308. this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
  309. this.channelDebug_.debug(
  310. 'TestConnection: request complete for initial check');
  311. this.checkBufferingProxy_();
  312. } else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
  313. this.channelDebug_.debug('TestConnection: request complete for stage 2');
  314. var goodConn = this.receivedIntermediateResult_;
  315. if (goodConn) {
  316. this.channelDebug_.debug(
  317. 'Test connection succeeded; using streaming connection');
  318. requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
  319. this.channel_.testConnectionFinished(this, true);
  320. } else {
  321. this.channelDebug_.debug('Test connection failed; not using streaming');
  322. requestStats.notifyStatEvent(requestStats.Stat.PROXY);
  323. this.channel_.testConnectionFinished(this, false);
  324. }
  325. }
  326. };
  327. /**
  328. * Apply any control headers from the initial handshake response.
  329. *
  330. * @param {!ChannelRequest} req The request object.
  331. * @private
  332. */
  333. BaseTestChannel.prototype.applyControlHeaders_ = function(req) {
  334. if (this.channel_.getBackgroundChannelTest()) {
  335. return;
  336. }
  337. var xhr = req.getXhr();
  338. if (xhr) {
  339. var protocolHeader = xhr.getStreamingResponseHeader(
  340. WebChannel.X_CLIENT_WIRE_PROTOCOL);
  341. this.clientProtocol_ = protocolHeader ? protocolHeader : null;
  342. if (this.channel_.getHttpSessionIdParam()) {
  343. var httpSessionIdHeader = xhr.getStreamingResponseHeader(
  344. WebChannel.X_HTTP_SESSION_ID);
  345. if (httpSessionIdHeader) {
  346. this.channel_.setHttpSessionId(httpSessionIdHeader);
  347. } else {
  348. this.channelDebug_.warning(
  349. 'Missing X_HTTP_SESSION_ID in the handshake response');
  350. }
  351. }
  352. }
  353. };
  354. /**
  355. * @return {?string} The client protocol as recorded with the init handshake
  356. * request.
  357. */
  358. BaseTestChannel.prototype.getClientProtocol = function() {
  359. return this.clientProtocol_;
  360. };
  361. /**
  362. * Returns the last status code received for a request.
  363. * @return {number} The last status code received for a request.
  364. */
  365. BaseTestChannel.prototype.getLastStatusCode = function() {
  366. return this.lastStatusCode_;
  367. };
  368. /**
  369. * @return {boolean} Whether we should be using secondary domains when the
  370. * server instructs us to do so.
  371. * @override
  372. */
  373. BaseTestChannel.prototype.shouldUseSecondaryDomains = function() {
  374. return this.channel_.shouldUseSecondaryDomains();
  375. };
  376. /**
  377. * @override
  378. */
  379. BaseTestChannel.prototype.isActive = function() {
  380. return this.channel_.isActive();
  381. };
  382. /**
  383. * @return {boolean} True if test stage 2 detected a non-buffered
  384. * channel early and early no buffering detection is enabled.
  385. * @private
  386. */
  387. BaseTestChannel.prototype.checkForEarlyNonBuffered_ = function() {
  388. return ChannelRequest.supportsXhrStreaming();
  389. };
  390. /**
  391. * @override
  392. */
  393. BaseTestChannel.prototype.getForwardChannelUri = goog.abstractMethod;
  394. /**
  395. * @override
  396. */
  397. BaseTestChannel.prototype.getBackChannelUri = goog.abstractMethod;
  398. /**
  399. * @override
  400. */
  401. BaseTestChannel.prototype.correctHostPrefix = goog.abstractMethod;
  402. /**
  403. * @override
  404. */
  405. BaseTestChannel.prototype.createDataUri = goog.abstractMethod;
  406. /**
  407. * @override
  408. */
  409. BaseTestChannel.prototype.testConnectionFinished = goog.abstractMethod;
  410. /**
  411. * @override
  412. */
  413. BaseTestChannel.prototype.testConnectionFailure = goog.abstractMethod;
  414. /**
  415. * @override
  416. */
  417. BaseTestChannel.prototype.getConnectionState = goog.abstractMethod;
  418. /**
  419. * @override
  420. */
  421. BaseTestChannel.prototype.setHttpSessionIdParam = goog.abstractMethod;
  422. /**
  423. * @override
  424. */
  425. BaseTestChannel.prototype.getHttpSessionIdParam = goog.abstractMethod;
  426. /**
  427. * @override
  428. */
  429. BaseTestChannel.prototype.setHttpSessionId = goog.abstractMethod;
  430. /**
  431. * @override
  432. */
  433. BaseTestChannel.prototype.getHttpSessionId = goog.abstractMethod;
  434. /**
  435. * @override
  436. */
  437. BaseTestChannel.prototype.getBackgroundChannelTest = goog.abstractMethod;
  438. }); // goog.scope