http.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.http = void 0;
  7. const http_1 = __importDefault(require("http"));
  8. const https_1 = __importDefault(require("https"));
  9. const events_1 = require("events");
  10. const debug_1 = __importDefault(require("debug"));
  11. const http_error_1 = __importDefault(require("./http-error"));
  12. const notfound_1 = __importDefault(require("./notfound"));
  13. const notmodified_1 = __importDefault(require("./notmodified"));
  14. const debug = (0, debug_1.default)('get-uri:http');
  15. /**
  16. * Returns a Readable stream from an "http:" URI.
  17. */
  18. const http = async (url, opts = {}) => {
  19. debug('GET %o', url.href);
  20. const cache = getCache(url, opts.cache);
  21. // first check the previous Expires and/or Cache-Control headers
  22. // of a previous response if a `cache` was provided
  23. if (cache && isFresh(cache) && typeof cache.statusCode === 'number') {
  24. // check for a 3xx "redirect" status code on the previous cache
  25. const type = (cache.statusCode / 100) | 0;
  26. if (type === 3 && cache.headers.location) {
  27. debug('cached redirect');
  28. throw new Error('TODO: implement cached redirects!');
  29. }
  30. // otherwise we assume that it's the destination endpoint,
  31. // since there's nowhere else to redirect to
  32. throw new notmodified_1.default();
  33. }
  34. // 5 redirects allowed by default
  35. const maxRedirects = typeof opts.maxRedirects === 'number' ? opts.maxRedirects : 5;
  36. debug('allowing %o max redirects', maxRedirects);
  37. let mod;
  38. if (opts.http) {
  39. // the `https` module passed in from the "http.js" file
  40. mod = opts.http;
  41. debug('using secure `https` core module');
  42. }
  43. else {
  44. mod = http_1.default;
  45. debug('using `http` core module');
  46. }
  47. const options = { ...opts };
  48. // add "cache validation" headers if a `cache` was provided
  49. if (cache) {
  50. if (!options.headers) {
  51. options.headers = {};
  52. }
  53. const lastModified = cache.headers['last-modified'];
  54. if (lastModified) {
  55. options.headers['If-Modified-Since'] = lastModified;
  56. debug('added "If-Modified-Since" request header: %o', lastModified);
  57. }
  58. const etag = cache.headers.etag;
  59. if (etag) {
  60. options.headers['If-None-Match'] = etag;
  61. debug('added "If-None-Match" request header: %o', etag);
  62. }
  63. }
  64. const req = mod.get(url, options);
  65. const [res] = await (0, events_1.once)(req, 'response');
  66. const code = res.statusCode || 0;
  67. // assign a Date to this response for the "Cache-Control" delta calculation
  68. res.date = Date.now();
  69. res.parsed = url;
  70. debug('got %o response status code', code);
  71. // any 2xx response is a "success" code
  72. const type = (code / 100) | 0;
  73. // check for a 3xx "redirect" status code
  74. const location = res.headers.location;
  75. if (type === 3 && location) {
  76. if (!opts.redirects)
  77. opts.redirects = [];
  78. const redirects = opts.redirects;
  79. if (redirects.length < maxRedirects) {
  80. debug('got a "redirect" status code with Location: %o', location);
  81. // flush this response - we're not going to use it
  82. res.resume();
  83. // hang on to this Response object for the "redirects" Array
  84. redirects.push(res);
  85. const newUri = new URL(location, url.href);
  86. debug('resolved redirect URL: %o', newUri.href);
  87. const left = maxRedirects - redirects.length;
  88. debug('%o more redirects allowed after this one', left);
  89. // check if redirecting to a different protocol
  90. if (newUri.protocol !== url.protocol) {
  91. opts.http = newUri.protocol === 'https:' ? https_1.default : undefined;
  92. }
  93. return (0, exports.http)(newUri, opts);
  94. }
  95. }
  96. // if we didn't get a 2xx "success" status code, then create an Error object
  97. if (type !== 2) {
  98. res.resume();
  99. if (code === 304) {
  100. throw new notmodified_1.default();
  101. }
  102. else if (code === 404) {
  103. throw new notfound_1.default();
  104. }
  105. // other HTTP-level error
  106. throw new http_error_1.default(code);
  107. }
  108. if (opts.redirects) {
  109. // store a reference to the "redirects" Array on the Response object so that
  110. // they can be inspected during a subsequent call to GET the same URI
  111. res.redirects = opts.redirects;
  112. }
  113. return res;
  114. };
  115. exports.http = http;
  116. /**
  117. * Returns `true` if the provided cache's "freshness" is valid. That is, either
  118. * the Cache-Control header or Expires header values are still within the allowed
  119. * time period.
  120. *
  121. * @return {Boolean}
  122. * @api private
  123. */
  124. function isFresh(cache) {
  125. let fresh = false;
  126. let expires = parseInt(cache.headers.expires || '', 10);
  127. const cacheControl = cache.headers['cache-control'];
  128. if (cacheControl) {
  129. // for Cache-Control rules, see: http://www.mnot.net/cache_docs/#CACHE-CONTROL
  130. debug('Cache-Control: %o', cacheControl);
  131. const parts = cacheControl.split(/,\s*?\b/);
  132. for (let i = 0; i < parts.length; i++) {
  133. const part = parts[i];
  134. const subparts = part.split('=');
  135. const name = subparts[0];
  136. switch (name) {
  137. case 'max-age':
  138. expires =
  139. (cache.date || 0) + parseInt(subparts[1], 10) * 1000;
  140. fresh = Date.now() < expires;
  141. if (fresh) {
  142. debug('cache is "fresh" due to previous %o Cache-Control param', part);
  143. }
  144. return fresh;
  145. case 'must-revalidate':
  146. // XXX: what we supposed to do here?
  147. break;
  148. case 'no-cache':
  149. case 'no-store':
  150. debug('cache is "stale" due to explicit %o Cache-Control param', name);
  151. return false;
  152. default:
  153. // ignore unknown cache value
  154. break;
  155. }
  156. }
  157. }
  158. else if (expires) {
  159. // for Expires rules, see: http://www.mnot.net/cache_docs/#EXPIRES
  160. debug('Expires: %o', expires);
  161. fresh = Date.now() < expires;
  162. if (fresh) {
  163. debug('cache is "fresh" due to previous Expires response header');
  164. }
  165. return fresh;
  166. }
  167. return false;
  168. }
  169. /**
  170. * Attempts to return a previous Response object from a previous GET call to the
  171. * same URI.
  172. *
  173. * @api private
  174. */
  175. function getCache(url, cache) {
  176. if (cache) {
  177. if (cache.parsed && cache.parsed.href === url.href) {
  178. return cache;
  179. }
  180. if (cache.redirects) {
  181. for (let i = 0; i < cache.redirects.length; i++) {
  182. const c = getCache(url, cache.redirects[i]);
  183. if (c) {
  184. return c;
  185. }
  186. }
  187. }
  188. }
  189. return null;
  190. }
  191. //# sourceMappingURL=http.js.map