You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					202 lines
				
				8.3 KiB
			
		
		
			
		
	
	
					202 lines
				
				8.3 KiB
			| 
											3 years ago
										 | "use strict"; | ||
|  | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
|  |     function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
|  |     return new (P || (P = Promise))(function (resolve, reject) { | ||
|  |         function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
|  |         function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
|  |         function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
|  |         step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
|  |     }); | ||
|  | }; | ||
|  | var __importDefault = (this && this.__importDefault) || function (mod) { | ||
|  |     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
|  | }; | ||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||
|  | const http_1 = __importDefault(require("http")); | ||
|  | const https_1 = __importDefault(require("https")); | ||
|  | const once_1 = __importDefault(require("@tootallnate/once")); | ||
|  | const debug_1 = __importDefault(require("debug")); | ||
|  | const url_1 = require("url"); | ||
|  | const http_error_1 = __importDefault(require("./http-error")); | ||
|  | const notfound_1 = __importDefault(require("./notfound")); | ||
|  | const notmodified_1 = __importDefault(require("./notmodified")); | ||
|  | const debug = debug_1.default('get-uri:http'); | ||
|  | /** | ||
|  |  * Returns a Readable stream from an "http:" URI. | ||
|  |  */ | ||
|  | function get(parsed, opts) { | ||
|  |     return __awaiter(this, void 0, void 0, function* () { | ||
|  |         debug('GET %o', parsed.href); | ||
|  |         const cache = getCache(parsed, opts.cache); | ||
|  |         // first check the previous Expires and/or Cache-Control headers
 | ||
|  |         // of a previous response if a `cache` was provided
 | ||
|  |         if (cache && isFresh(cache) && typeof cache.statusCode === 'number') { | ||
|  |             // check for a 3xx "redirect" status code on the previous cache
 | ||
|  |             const type = (cache.statusCode / 100) | 0; | ||
|  |             if (type === 3 && cache.headers.location) { | ||
|  |                 debug('cached redirect'); | ||
|  |                 throw new Error('TODO: implement cached redirects!'); | ||
|  |             } | ||
|  |             // otherwise we assume that it's the destination endpoint,
 | ||
|  |             // since there's nowhere else to redirect to
 | ||
|  |             throw new notmodified_1.default(); | ||
|  |         } | ||
|  |         // 5 redirects allowed by default
 | ||
|  |         const maxRedirects = typeof opts.maxRedirects === 'number' ? opts.maxRedirects : 5; | ||
|  |         debug('allowing %o max redirects', maxRedirects); | ||
|  |         let mod; | ||
|  |         if (opts.http) { | ||
|  |             // the `https` module passed in from the "http.js" file
 | ||
|  |             mod = opts.http; | ||
|  |             debug('using secure `https` core module'); | ||
|  |         } | ||
|  |         else { | ||
|  |             mod = http_1.default; | ||
|  |             debug('using `http` core module'); | ||
|  |         } | ||
|  |         const options = Object.assign(Object.assign({}, opts), parsed); | ||
|  |         // add "cache validation" headers if a `cache` was provided
 | ||
|  |         if (cache) { | ||
|  |             if (!options.headers) { | ||
|  |                 options.headers = {}; | ||
|  |             } | ||
|  |             const lastModified = cache.headers['last-modified']; | ||
|  |             if (lastModified) { | ||
|  |                 options.headers['If-Modified-Since'] = lastModified; | ||
|  |                 debug('added "If-Modified-Since" request header: %o', lastModified); | ||
|  |             } | ||
|  |             const etag = cache.headers.etag; | ||
|  |             if (etag) { | ||
|  |                 options.headers['If-None-Match'] = etag; | ||
|  |                 debug('added "If-None-Match" request header: %o', etag); | ||
|  |             } | ||
|  |         } | ||
|  |         const req = mod.get(options); | ||
|  |         const res = yield once_1.default(req, 'response'); | ||
|  |         const code = res.statusCode || 0; | ||
|  |         // assign a Date to this response for the "Cache-Control" delta calculation
 | ||
|  |         res.date = Date.now(); | ||
|  |         res.parsed = parsed; | ||
|  |         debug('got %o response status code', code); | ||
|  |         // any 2xx response is a "success" code
 | ||
|  |         let type = (code / 100) | 0; | ||
|  |         // check for a 3xx "redirect" status code
 | ||
|  |         let location = res.headers.location; | ||
|  |         if (type === 3 && location) { | ||
|  |             if (!opts.redirects) | ||
|  |                 opts.redirects = []; | ||
|  |             let redirects = opts.redirects; | ||
|  |             if (redirects.length < maxRedirects) { | ||
|  |                 debug('got a "redirect" status code with Location: %o', location); | ||
|  |                 // flush this response - we're not going to use it
 | ||
|  |                 res.resume(); | ||
|  |                 // hang on to this Response object for the "redirects" Array
 | ||
|  |                 redirects.push(res); | ||
|  |                 let newUri = url_1.resolve(parsed.href, location); | ||
|  |                 debug('resolved redirect URL: %o', newUri); | ||
|  |                 let left = maxRedirects - redirects.length; | ||
|  |                 debug('%o more redirects allowed after this one', left); | ||
|  |                 // check if redirecting to a different protocol
 | ||
|  |                 let parsedUrl = url_1.parse(newUri); | ||
|  |                 if (parsedUrl.protocol !== parsed.protocol) { | ||
|  |                     opts.http = parsedUrl.protocol === 'https:' ? https_1.default : undefined; | ||
|  |                 } | ||
|  |                 return get(parsedUrl, opts); | ||
|  |             } | ||
|  |         } | ||
|  |         // if we didn't get a 2xx "success" status code, then create an Error object
 | ||
|  |         if (type !== 2) { | ||
|  |             res.resume(); | ||
|  |             if (code === 304) { | ||
|  |                 throw new notmodified_1.default(); | ||
|  |             } | ||
|  |             else if (code === 404) { | ||
|  |                 throw new notfound_1.default(); | ||
|  |             } | ||
|  |             // other HTTP-level error
 | ||
|  |             throw new http_error_1.default(code); | ||
|  |         } | ||
|  |         if (opts.redirects) { | ||
|  |             // store a reference to the "redirects" Array on the Response object so that
 | ||
|  |             // they can be inspected during a subsequent call to GET the same URI
 | ||
|  |             res.redirects = opts.redirects; | ||
|  |         } | ||
|  |         return res; | ||
|  |     }); | ||
|  | } | ||
|  | exports.default = get; | ||
|  | /** | ||
|  |  * Returns `true` if the provided cache's "freshness" is valid. That is, either | ||
|  |  * the Cache-Control header or Expires header values are still within the allowed | ||
|  |  * time period. | ||
|  |  * | ||
|  |  * @return {Boolean} | ||
|  |  * @api private | ||
|  |  */ | ||
|  | function isFresh(cache) { | ||
|  |     let fresh = false; | ||
|  |     let expires = parseInt(cache.headers.expires || '', 10); | ||
|  |     const cacheControl = cache.headers['cache-control']; | ||
|  |     if (cacheControl) { | ||
|  |         // for Cache-Control rules, see: http://www.mnot.net/cache_docs/#CACHE-CONTROL
 | ||
|  |         debug('Cache-Control: %o', cacheControl); | ||
|  |         const parts = cacheControl.split(/,\s*?\b/); | ||
|  |         for (let i = 0; i < parts.length; i++) { | ||
|  |             const part = parts[i]; | ||
|  |             const subparts = part.split('='); | ||
|  |             const name = subparts[0]; | ||
|  |             switch (name) { | ||
|  |                 case 'max-age': | ||
|  |                     expires = (cache.date || 0) + parseInt(subparts[1], 10) * 1000; | ||
|  |                     fresh = Date.now() < expires; | ||
|  |                     if (fresh) { | ||
|  |                         debug('cache is "fresh" due to previous %o Cache-Control param', part); | ||
|  |                     } | ||
|  |                     return fresh; | ||
|  |                 case 'must-revalidate': | ||
|  |                     // XXX: what we supposed to do here?
 | ||
|  |                     break; | ||
|  |                 case 'no-cache': | ||
|  |                 case 'no-store': | ||
|  |                     debug('cache is "stale" due to explicit %o Cache-Control param', name); | ||
|  |                     return false; | ||
|  |                 default: | ||
|  |                     // ignore unknown cache value
 | ||
|  |                     break; | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     else if (expires) { | ||
|  |         // for Expires rules, see: http://www.mnot.net/cache_docs/#EXPIRES
 | ||
|  |         debug('Expires: %o', expires); | ||
|  |         fresh = Date.now() < expires; | ||
|  |         if (fresh) { | ||
|  |             debug('cache is "fresh" due to previous Expires response header'); | ||
|  |         } | ||
|  |         return fresh; | ||
|  |     } | ||
|  |     return false; | ||
|  | } | ||
|  | /** | ||
|  |  * Attempts to return a previous Response object from a previous GET call to the | ||
|  |  * same URI. | ||
|  |  * | ||
|  |  * @api private | ||
|  |  */ | ||
|  | function getCache(parsed, cache) { | ||
|  |     if (cache) { | ||
|  |         if (cache.parsed && cache.parsed.href === parsed.href) { | ||
|  |             return cache; | ||
|  |         } | ||
|  |         if (cache.redirects) { | ||
|  |             for (let i = 0; i < cache.redirects.length; i++) { | ||
|  |                 const c = getCache(parsed, cache.redirects[i]); | ||
|  |                 if (c) { | ||
|  |                     return c; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     return null; | ||
|  | } | ||
|  | //# sourceMappingURL=http.js.map
 |