Source: lib/net/http_xhr_plugin.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.net.HttpXHRPlugin');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.net.HttpPluginUtils');
  9. goog.require('shaka.net.NetworkingEngine');
  10. goog.require('shaka.util.AbortableOperation');
  11. goog.require('shaka.util.Error');
  12. /**
  13. * @summary A networking plugin to handle http and https URIs via XHR.
  14. * @export
  15. */
  16. shaka.net.HttpXHRPlugin = class {
  17. /**
  18. * @param {string} uri
  19. * @param {shaka.extern.Request} request
  20. * @param {shaka.net.NetworkingEngine.RequestType} requestType
  21. * @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
  22. * progress event happened.
  23. * @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
  24. * @export
  25. */
  26. static parse(uri, request, requestType, progressUpdated) {
  27. const xhr = new shaka.net.HttpXHRPlugin.Xhr_();
  28. // Last time stamp when we got a progress event.
  29. let lastTime = Date.now();
  30. // Last number of bytes loaded, from progress event.
  31. let lastLoaded = 0;
  32. const promise = new Promise(((resolve, reject) => {
  33. xhr.open(request.method, uri, true);
  34. xhr.responseType = 'arraybuffer';
  35. xhr.timeout = request.retryParameters.timeout;
  36. xhr.withCredentials = request.allowCrossSiteCredentials;
  37. xhr.onabort = () => {
  38. reject(new shaka.util.Error(
  39. shaka.util.Error.Severity.RECOVERABLE,
  40. shaka.util.Error.Category.NETWORK,
  41. shaka.util.Error.Code.OPERATION_ABORTED,
  42. uri, requestType));
  43. };
  44. xhr.onload = (event) => {
  45. const target = event.target;
  46. goog.asserts.assert(target, 'XHR onload has no target!');
  47. // Since IE and Edge incorrectly return the header with a leading new
  48. // line character ('\n'), we trim the header here.
  49. const headerLines = target.getAllResponseHeaders().trim().split('\r\n');
  50. const headers = {};
  51. for (const header of headerLines) {
  52. /** @type {!Array.<string>} */
  53. const parts = header.split(': ');
  54. headers[parts[0].toLowerCase()] = parts.slice(1).join(': ');
  55. }
  56. try {
  57. const response = shaka.net.HttpPluginUtils.makeResponse(headers,
  58. target.response, target.status, uri, target.responseURL,
  59. requestType);
  60. resolve(response);
  61. } catch (error) {
  62. goog.asserts.assert(error instanceof shaka.util.Error,
  63. 'Wrong error type!');
  64. reject(error);
  65. }
  66. };
  67. xhr.onerror = (event) => {
  68. reject(new shaka.util.Error(
  69. shaka.util.Error.Severity.RECOVERABLE,
  70. shaka.util.Error.Category.NETWORK,
  71. shaka.util.Error.Code.HTTP_ERROR,
  72. uri, event, requestType));
  73. };
  74. xhr.ontimeout = (event) => {
  75. reject(new shaka.util.Error(
  76. shaka.util.Error.Severity.RECOVERABLE,
  77. shaka.util.Error.Category.NETWORK,
  78. shaka.util.Error.Code.TIMEOUT,
  79. uri, requestType));
  80. };
  81. xhr.onprogress = (event) => {
  82. const currentTime = Date.now();
  83. // If the time between last time and this time we got progress event
  84. // is long enough, or if a whole segment is downloaded, call
  85. // progressUpdated().
  86. if (currentTime - lastTime > 100 ||
  87. (event.lengthComputable && event.loaded == event.total)) {
  88. progressUpdated(currentTime - lastTime, event.loaded - lastLoaded,
  89. event.total - event.loaded);
  90. lastLoaded = event.loaded;
  91. lastTime = currentTime;
  92. }
  93. };
  94. for (const key in request.headers) {
  95. // The Fetch API automatically normalizes outgoing header keys to
  96. // lowercase. For consistency's sake, do it here too.
  97. const lowercasedKey = key.toLowerCase();
  98. xhr.setRequestHeader(lowercasedKey, request.headers[key]);
  99. }
  100. xhr.send(request.body);
  101. }));
  102. return new shaka.util.AbortableOperation(
  103. promise,
  104. () => {
  105. xhr.abort();
  106. return Promise.resolve();
  107. });
  108. }
  109. };
  110. /**
  111. * Overridden in unit tests, but compiled out in production.
  112. *
  113. * @const {function(new: XMLHttpRequest)}
  114. * @private
  115. */
  116. shaka.net.HttpXHRPlugin.Xhr_ = window.XMLHttpRequest;
  117. shaka.net.NetworkingEngine.registerScheme(
  118. 'http', shaka.net.HttpXHRPlugin.parse,
  119. shaka.net.NetworkingEngine.PluginPriority.FALLBACK);
  120. shaka.net.NetworkingEngine.registerScheme(
  121. 'https', shaka.net.HttpXHRPlugin.parse,
  122. shaka.net.NetworkingEngine.PluginPriority.FALLBACK);