Source: lib/net/http_fetch_plugin.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.net.HttpFetchPlugin');
  18. goog.require('shaka.net.HttpPluginUtils');
  19. goog.require('shaka.net.NetworkingEngine');
  20. goog.require('shaka.util.AbortableOperation');
  21. goog.require('shaka.util.Error');
  22. goog.require('shaka.util.MapUtils');
  23. /**
  24. * @namespace
  25. * @summary A networking plugin to handle http and https URIs via the Fetch API.
  26. * @param {string} uri
  27. * @param {shakaExtern.Request} request
  28. * @param {shaka.net.NetworkingEngine.RequestType} requestType
  29. * @return {!shakaExtern.IAbortableOperation.<shakaExtern.Response>}
  30. * @export
  31. */
  32. shaka.net.HttpFetchPlugin = function(uri, request, requestType) {
  33. const headers = new shaka.net.HttpFetchPlugin.Headers_();
  34. shaka.util.MapUtils.forEach(request.headers, function(key, value) {
  35. headers.append(key, value);
  36. });
  37. const controller = new shaka.net.HttpFetchPlugin.AbortController_();
  38. /** @type {!RequestInit} */
  39. const init = {
  40. // Edge does not treat null as undefined for body; https://bit.ly/2luyE6x
  41. body: request.body || undefined,
  42. headers: headers,
  43. method: request.method,
  44. signal: controller.signal,
  45. credentials: request.allowCrossSiteCredentials ? 'include' : undefined
  46. };
  47. /** @type {shaka.net.HttpFetchPlugin.AbortStatus} */
  48. const abortStatus = {
  49. canceled: false,
  50. timedOut: false,
  51. };
  52. // The fetch API does not timeout natively, so do a timeout manually using the
  53. // AbortController.
  54. let timeout;
  55. if (request.retryParameters.timeout) {
  56. let onTimeout = function() {
  57. abortStatus.timedOut = true;
  58. controller.abort();
  59. };
  60. timeout = setTimeout(onTimeout, request.retryParameters.timeout);
  61. }
  62. const promise = shaka.net.HttpFetchPlugin.request_(uri, requestType, init,
  63. abortStatus, timeout);
  64. return new shaka.util.AbortableOperation(
  65. promise,
  66. () => {
  67. abortStatus.canceled = true;
  68. controller.abort();
  69. return Promise.resolve();
  70. });
  71. };
  72. /**
  73. * @param {string} uri
  74. * @param {shaka.net.NetworkingEngine.RequestType} requestType
  75. * @param {!RequestInit} init
  76. * @param {shaka.net.HttpFetchPlugin.AbortStatus} abortStatus
  77. * @param {number|undefined} timeoutId
  78. * @return {!Promise<!shakaExtern.Response>}
  79. * @private
  80. */
  81. shaka.net.HttpFetchPlugin.request_ = async function(uri, requestType, init,
  82. abortStatus, timeoutId) {
  83. const fetch = shaka.net.HttpFetchPlugin.fetch_;
  84. let response;
  85. let arrayBuffer;
  86. try {
  87. response = await fetch(uri, init);
  88. arrayBuffer = await response.arrayBuffer();
  89. } catch (error) {
  90. if (abortStatus.canceled) {
  91. throw new shaka.util.Error(
  92. shaka.util.Error.Severity.RECOVERABLE,
  93. shaka.util.Error.Category.NETWORK,
  94. shaka.util.Error.Code.OPERATION_ABORTED,
  95. uri, requestType);
  96. } else if (abortStatus.timedOut) {
  97. throw new shaka.util.Error(
  98. shaka.util.Error.Severity.RECOVERABLE,
  99. shaka.util.Error.Category.NETWORK,
  100. shaka.util.Error.Code.TIMEOUT,
  101. uri, requestType);
  102. } else {
  103. throw new shaka.util.Error(
  104. shaka.util.Error.Severity.RECOVERABLE,
  105. shaka.util.Error.Category.NETWORK,
  106. shaka.util.Error.Code.HTTP_ERROR,
  107. uri, error, requestType);
  108. }
  109. } finally {
  110. clearTimeout(timeoutId);
  111. }
  112. const headers = {};
  113. /** @type {Headers} */
  114. const responseHeaders = response.headers;
  115. responseHeaders.forEach(function(value, key) {
  116. // Since IE/Edge incorrectly return the header with a leading new line
  117. // character ('\n'), we trim the header here.
  118. headers[key.trim()] = value;
  119. });
  120. return shaka.net.HttpPluginUtils.makeResponse(headers,
  121. arrayBuffer, response.status, uri, response.url, requestType);
  122. };
  123. /**
  124. * @typedef {{
  125. * canceled: boolean,
  126. * timedOut: boolean
  127. * }}
  128. * @property {boolean} canceled
  129. * Indicates if the request was canceled.
  130. * @property {boolean} timedOut
  131. * Indicates if the request timed out.
  132. */
  133. shaka.net.HttpFetchPlugin.AbortStatus;
  134. /**
  135. * Determine if the Fetch API is supported in the browser. Note: this is
  136. * deliberately exposed as a method to allow the client app to use the same
  137. * logic as Shaka when determining support.
  138. * @return {boolean}
  139. * @export
  140. */
  141. shaka.net.HttpFetchPlugin.isSupported = function() {
  142. return !!(window.fetch && window.AbortController);
  143. };
  144. /**
  145. * Overridden in unit tests, but compiled out in production.
  146. *
  147. * @const {function(string, !RequestInit)}
  148. * @private
  149. */
  150. shaka.net.HttpFetchPlugin.fetch_ = window.fetch;
  151. /**
  152. * Overridden in unit tests, but compiled out in production.
  153. *
  154. * @const {function(new: AbortController)}
  155. * @private
  156. */
  157. shaka.net.HttpFetchPlugin.AbortController_ = window.AbortController;
  158. /**
  159. * Overridden in unit tests, but compiled out in production.
  160. *
  161. * @const {function(new: Headers)}
  162. * @private
  163. */
  164. shaka.net.HttpFetchPlugin.Headers_ = window.Headers;
  165. if (shaka.net.HttpFetchPlugin.isSupported()) {
  166. shaka.net.NetworkingEngine.registerScheme('http', shaka.net.HttpFetchPlugin,
  167. shaka.net.NetworkingEngine.PluginPriority.PREFERRED);
  168. shaka.net.NetworkingEngine.registerScheme('https', shaka.net.HttpFetchPlugin,
  169. shaka.net.NetworkingEngine.PluginPriority.PREFERRED);
  170. }