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 once_1 = __importDefault(require("@tootallnate/once"));
|
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 |
|
17 |
|
18 | const http = async (url, opts = {}) => {
|
19 | debug('GET %o', url.href);
|
20 | const cache = getCache(url, opts.cache);
|
21 |
|
22 |
|
23 | if (cache && isFresh(cache) && typeof cache.statusCode === 'number') {
|
24 |
|
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 |
|
31 |
|
32 | throw new notmodified_1.default();
|
33 | }
|
34 |
|
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 |
|
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 |
|
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 |
|
68 | res.date = Date.now();
|
69 | res.parsed = url;
|
70 | debug('got %o response status code', code);
|
71 |
|
72 | let type = (code / 100) | 0;
|
73 |
|
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 |
|
82 | res.resume();
|
83 |
|
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 |
|
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 |
|
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 |
|
106 | throw new http_error_1.default(code);
|
107 | }
|
108 | if (opts.redirects) {
|
109 |
|
110 |
|
111 | res.redirects = opts.redirects;
|
112 | }
|
113 | return res;
|
114 | };
|
115 | exports.http = http;
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
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 |
|
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 |
|
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 |
|
154 | break;
|
155 | }
|
156 | }
|
157 | }
|
158 | else if (expires) {
|
159 |
|
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 |
|
171 |
|
172 |
|
173 |
|
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 |
|
\ | No newline at end of file |