222
schangxiang@126.com
2025-06-13 6a8393408d8cefcea02b7a598967de8dc1e565c2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
 
/**
 * Module dependencies.
 */
 
var url = require('url');
var http = require('http');
var https = require('https');
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);
 
        // check if redirecting to a different protocol
        var parsedUrl = url.parse(newUri);
        if (parsedUrl.protocol !== parsed.protocol) {
          opts.http = parsedUrl.protocol === 'https:' ? https : undefined;
        }
 
        return get(parsedUrl, 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;
    }
  }
}