createPartialResponse.js 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. /*
  2. Copyright 2018 Google LLC
  3. Use of this source code is governed by an MIT-style
  4. license that can be found in the LICENSE file or at
  5. https://opensource.org/licenses/MIT.
  6. */
  7. import { WorkboxError } from 'workbox-core/_private/WorkboxError.js';
  8. import { assert } from 'workbox-core/_private/assert.js';
  9. import { logger } from 'workbox-core/_private/logger.js';
  10. import { calculateEffectiveBoundaries } from './utils/calculateEffectiveBoundaries.js';
  11. import { parseRangeHeader } from './utils/parseRangeHeader.js';
  12. import './_version.js';
  13. /**
  14. * Given a `Request` and `Response` objects as input, this will return a
  15. * promise for a new `Response`.
  16. *
  17. * If the original `Response` already contains partial content (i.e. it has
  18. * a status of 206), then this assumes it already fulfills the `Range:`
  19. * requirements, and will return it as-is.
  20. *
  21. * @param {Request} request A request, which should contain a Range:
  22. * header.
  23. * @param {Response} originalResponse A response.
  24. * @return {Promise<Response>} Either a `206 Partial Content` response, with
  25. * the response body set to the slice of content specified by the request's
  26. * `Range:` header, or a `416 Range Not Satisfiable` response if the
  27. * conditions of the `Range:` header can't be met.
  28. *
  29. * @memberof module:workbox-range-requests
  30. */
  31. async function createPartialResponse(request, originalResponse) {
  32. try {
  33. if (process.env.NODE_ENV !== 'production') {
  34. assert.isInstance(request, Request, {
  35. moduleName: 'workbox-range-requests',
  36. funcName: 'createPartialResponse',
  37. paramName: 'request',
  38. });
  39. assert.isInstance(originalResponse, Response, {
  40. moduleName: 'workbox-range-requests',
  41. funcName: 'createPartialResponse',
  42. paramName: 'originalResponse',
  43. });
  44. }
  45. if (originalResponse.status === 206) {
  46. // If we already have a 206, then just pass it through as-is;
  47. // see https://github.com/GoogleChrome/workbox/issues/1720
  48. return originalResponse;
  49. }
  50. const rangeHeader = request.headers.get('range');
  51. if (!rangeHeader) {
  52. throw new WorkboxError('no-range-header');
  53. }
  54. const boundaries = parseRangeHeader(rangeHeader);
  55. const originalBlob = await originalResponse.blob();
  56. const effectiveBoundaries = calculateEffectiveBoundaries(originalBlob, boundaries.start, boundaries.end);
  57. const slicedBlob = originalBlob.slice(effectiveBoundaries.start, effectiveBoundaries.end);
  58. const slicedBlobSize = slicedBlob.size;
  59. const slicedResponse = new Response(slicedBlob, {
  60. // Status code 206 is for a Partial Content response.
  61. // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206
  62. status: 206,
  63. statusText: 'Partial Content',
  64. headers: originalResponse.headers,
  65. });
  66. slicedResponse.headers.set('Content-Length', String(slicedBlobSize));
  67. slicedResponse.headers.set('Content-Range', `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` +
  68. originalBlob.size);
  69. return slicedResponse;
  70. }
  71. catch (error) {
  72. if (process.env.NODE_ENV !== 'production') {
  73. logger.warn(`Unable to construct a partial response; returning a ` +
  74. `416 Range Not Satisfiable response instead.`);
  75. logger.groupCollapsed(`View details here.`);
  76. logger.log(error);
  77. logger.log(request);
  78. logger.log(originalResponse);
  79. logger.groupEnd();
  80. }
  81. return new Response('', {
  82. status: 416,
  83. statusText: 'Range Not Satisfiable',
  84. });
  85. }
  86. }
  87. export { createPartialResponse };