UNPKG

7.26 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.http = void 0;
7const http_1 = __importDefault(require("http"));
8const https_1 = __importDefault(require("https"));
9const once_1 = __importDefault(require("@tootallnate/once"));
10const debug_1 = __importDefault(require("debug"));
11const http_error_1 = __importDefault(require("./http-error"));
12const notfound_1 = __importDefault(require("./notfound"));
13const notmodified_1 = __importDefault(require("./notmodified"));
14const debug = (0, debug_1.default)('get-uri:http');
15/**
16 * Returns a Readable stream from an "http:" URI.
17 */
18const 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, once_1.default)(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 let type = (code / 100) | 0;
73 // check for a 3xx "redirect" status code
74 let location = res.headers.location;
75 if (type === 3 && location) {
76 if (!opts.redirects)
77 opts.redirects = [];
78 let 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 let newUri = new URL(location, url.href);
86 debug('resolved redirect URL: %o', newUri.href);
87 let 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};
115exports.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 */
124function 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 */
175function 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
\No newline at end of file