123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- /**
- * Module dependencies.
- */
- var url = require('url');
- var http = require('http');
- var extend = require('extend');
- var NotFoundError = require('./notfound');
- var NotModifiedError = require('./notmodified');
- var debug = require('debug')('get-uri:http');
- /**
- * Module exports.
- */
- module.exports = get;
- /**
- * Returns a Readable stream from an "http:" URI.
- *
- * @api protected
- */
- function get (parsed, opts, fn) {
- debug('GET %o', parsed.href);
- var cache = getCache(parsed, opts.cache);
- // 5 redirects allowed by default
- var maxRedirects = opts.hasOwnProperty('maxRedirects') ? opts.maxRedirects : 5;
- debug('allowing %o max redirects', maxRedirects);
- // first check the previous Expires and/or Cache-Control headers
- // of a previous response if a `cache` was provided
- if (cache && isFresh(cache)) {
- // check for a 3xx "redirect" status code on the previous cache
- var location = cache.headers.location;
- var type = (cache.statusCode / 100 | 0);
- if (3 == type && location) {
- debug('cached redirect');
- fn(new Error('TODO: implement cached redirects!'));
- } else {
- // otherwise we assume that it's the destination endpoint,
- // since there's nowhere else to redirect to
- fn(new NotModifiedError());
- }
- return;
- }
- var 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;
- debug('using `http` core module');
- }
- var options = extend({}, opts, parsed);
- // add "cache validation" headers if a `cache` was provided
- if (cache) {
- if (!options.headers) options.headers = {};
- var lastModified = cache.headers['last-modified'];
- if (lastModified != null) {
- options.headers['If-Modified-Since'] = lastModified;
- debug('added "If-Modified-Since" request header: %o', lastModified);
- }
- var etag = cache.headers.etag;
- if (etag != null) {
- options.headers['If-None-Match'] = etag;
- debug('added "If-None-Match" request header: %o', etag);
- }
- }
- var req = mod.get(options);
- req.once('error', onerror);
- req.once('response', onresponse);
- // http.ClientRequest "error" event handler
- function onerror (err) {
- debug('http.ClientRequest "error" event: %o', err.stack || err);
- fn(err);
- }
- // http.ClientRequest "response" event handler
- function onresponse (res) {
- var code = res.statusCode;
- // assign a Date to this response for the "Cache-Control" delta calculation
- res.date = new Date();
- res.parsed = parsed;
- debug('got %o response status code', code);
- // any 2xx response is a "success" code
- var type = (code / 100 | 0);
- // check for a 3xx "redirect" status code
- var location = res.headers.location;
- if (3 == type && location) {
- if (!opts.redirects) opts.redirects = [];
- var 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);
- var newUri = url.resolve(parsed, location);
- debug('resolved redirect URL: %o', newUri);
- var left = maxRedirects - redirects.length;
- debug('%o more redirects allowed after this one', left);
- return get(url.parse(newUri), opts, fn);
- }
- }
- // if we didn't get a 2xx "success" status code, then create an Error object
- if (2 != type) {
- var err;
- if (304 == code) {
- err = new NotModifiedError();
- } else if (404 == code) {
- err = new NotFoundError();
- } else {
- // other HTTP-level error
- var message = http.STATUS_CODES[code];
- err = new Error(message);
- err.statusCode = code;
- err.code = code;
- }
- res.resume();
- return fn(err);
- }
- 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;
- }
- fn(null, res);
- }
- }
- /**
- * 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) {
- var cacheControl = cache.headers['cache-control'];
- var expires = cache.headers.expires;
- var fresh;
- if (cacheControl) {
- // for Cache-Control rules, see: http://www.mnot.net/cache_docs/#CACHE-CONTROL
- debug('Cache-Control: %o', cacheControl);
- var parts = cacheControl.split(/,\s*?\b/);
- for (var i = 0; i < parts.length; i++) {
- var part = parts[i];
- var subparts = part.split('=');
- var name = subparts[0];
- switch (name) {
- case 'max-age':
- var val = +subparts[1];
- expires = new Date(+cache.date + (val * 1000));
- fresh = new Date() < 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;
- }
- }
- } else if (expires) {
- // for Expires rules, see: http://www.mnot.net/cache_docs/#EXPIRES
- debug('Expires: %o', expires);
- fresh = new Date() < new Date(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) return;
- var href = parsed.href;
- if (cache.parsed.href == href) {
- return cache;
- }
- var redirects = cache.redirects;
- if (redirects) {
- for (var i = 0; i < redirects.length; i++) {
- var c = getCache(parsed, redirects[i]);
- if (c) return c;
- }
- }
- }
|