http.js 8.3 KB

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