follow-redirects 0.0.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of follow-redirects might be problematic. Click here for more details.

Files changed (4) hide show
  1. package/README.md +45 -22
  2. package/index.js +184 -3
  3. package/package.json +21 -16
  4. package/create.js +0 -162
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  ## Follow Redirects
2
2
 
3
- Drop in replacement for Nodes `http` and `https` that automatically follows redirects.
3
+ Drop-in replacement for Nodes `http` and `https` that automatically follows redirects.
4
4
 
5
+ [![npm version](https://badge.fury.io/js/follow-redirects.svg)](https://www.npmjs.com/package/follow-redirects)
5
6
  [![Build Status](https://travis-ci.org/olalonde/follow-redirects.svg?branch=master)](https://travis-ci.org/olalonde/follow-redirects)
6
7
  [![Coverage Status](https://coveralls.io/repos/olalonde/follow-redirects/badge.svg?branch=master)](https://coveralls.io/r/olalonde/follow-redirects?branch=master)
7
8
  [![Code Climate](https://codeclimate.com/github/olalonde/follow-redirects/badges/gpa.svg)](https://codeclimate.com/github/olalonde/follow-redirects)
@@ -18,8 +19,8 @@ Drop in replacement for Nodes `http` and `https` that automatically follows redi
18
19
  var http = require('follow-redirects').http;
19
20
  var https = require('follow-redirects').https;
20
21
 
21
- http.get('http://bit.ly/900913', function (res) {
22
- res.on('data', function (chunk) {
22
+ http.get('http://bit.ly/900913', function (response) {
23
+ response.on('data', function (chunk) {
23
24
  console.log(chunk);
24
25
  });
25
26
  }).on('error', function (err) {
@@ -27,36 +28,58 @@ http.get('http://bit.ly/900913', function (res) {
27
28
  });
28
29
  ```
29
30
 
30
- By default the number of redirects is limited to 5, but you can modify that globally or per request.
31
+ You can inspect the final redirected URL through the `responseUrl` property on the `response`.
32
+ If no redirection happened, `responseUrl` is the original request URL.
31
33
 
32
34
  ```javascript
33
- require('follow-redirects').maxRedirects = 10; // Has global affect (be careful!)
34
-
35
35
  https.request({
36
36
  host: 'bitly.com',
37
37
  path: '/UHfDGO',
38
- maxRedirects: 3 // per request setting
39
- }, function (res) {/* ... */});
38
+ }, function (response) {
39
+ console.log(response.responseUrl);
40
+ // 'http://duckduckgo.com/robots.txt'
41
+ });
40
42
  ```
41
43
 
42
- You can inspect the redirection chain from the `fetchedUrls` array on the `response`.
43
- The array is populated in reverse order, so the original url you requested will be the
44
- last element, while the final redirection point will be at index 0.
44
+ ## Options
45
+ ### Global options
46
+ Global options are set directly on the `follow-redirects` module:
45
47
 
46
48
  ```javascript
47
- https.request({
48
- host: 'bitly.com',
49
- path: '/UHfDGO',
50
- }, function (res) {
51
- console.log(res.fetchedUrls);
52
- // [ 'http://duckduckgo.com/robots.txt', 'http://bitly.com/UHfDGO' ]
53
- });
49
+ var followRedirects = require('follow-redirects');
50
+ followRedirects.maxRedirects = 10;
54
51
  ```
55
52
 
53
+ The following global options are supported:
54
+
55
+ - `maxRedirects` (default: `21`) – sets the maximum number of allowed redirects; if exceeded, an error will be emitted.
56
+
57
+
58
+ ### Per-request options
59
+ Per-request options are set by passing an `options` object:
60
+
61
+ ```javascript
62
+ var url = require('url');
63
+ var followRedirects = require('follow-redirects');
64
+
65
+ var options = url.parse('http://bit.ly/900913');
66
+ options.maxRedirects = 10;
67
+ http.request(options);
68
+ ```
69
+
70
+ In addition to the [standard HTTP](https://nodejs.org/api/http.html#http_http_request_options_callback) and [HTTPS options](https://nodejs.org/api/https.html#https_https_request_options_callback),
71
+ the following per-request options are supported:
72
+ - `followRedirects` (default: `true`) – whether redirects should be followed.
73
+
74
+ - `maxRedirects` (default: `21`) – sets the maximum number of allowed redirects; if exceeded, an error will be emitted.
75
+
76
+ - `agents` (default: `undefined`) – sets the `agent` option per protocol, since HTTP and HTTPS use different agents. Example value: `{ http: new http.Agent(), https: new https.Agent() }`
77
+
78
+
56
79
  ## Browserify Usage
57
80
 
58
81
  Due to the way `XMLHttpRequest` works, the `browserify` versions of `http` and `https` already follow redirects.
59
- If you are *only* targetting the browser, then this library has little value for you. If you want to write cross
82
+ If you are *only* targeting the browser, then this library has little value for you. If you want to write cross
60
83
  platform code for node and the browser, `follow-redirects` provides a great solution for making the native node
61
84
  modules behave the same as they do in browserified builds in the browser. To avoid bundling unnecessary code
62
85
  you should tell browserify to swap out `follow-redirects` with the standard modules when bundling.
@@ -103,9 +126,9 @@ Pull Requests are always welcome. Please [file an issue](https://github.com/olal
103
126
 
104
127
  ## Authors
105
128
 
106
- Olivier Lalonde (olalonde@gmail.com)
107
-
108
- James Talmage (james@talmage.io)
129
+ - Olivier Lalonde (olalonde@gmail.com)
130
+ - James Talmage (james@talmage.io)
131
+ - [Ruben Verborgh](https://ruben.verborgh.org/)
109
132
 
110
133
  ## License
111
134
 
package/index.js CHANGED
@@ -1,4 +1,185 @@
1
- module.exports = require('./create')({
2
- 'http': require('http'),
3
- 'https': require('https')
1
+ 'use strict';
2
+ var url = require('url');
3
+ var assert = require('assert');
4
+ var http = require('http');
5
+ var https = require('https');
6
+ var Writable = require('stream').Writable;
7
+ var debug = require('debug')('follow-redirects');
8
+
9
+ var nativeProtocols = {'http:': http, 'https:': https};
10
+ var schemes = {};
11
+ var exports = module.exports = {
12
+ maxRedirects: 21
13
+ };
14
+ // RFC7231§4.2.1: Of the request methods defined by this specification,
15
+ // the GET, HEAD, OPTIONS, and TRACE methods are defined to be safe.
16
+ var safeMethods = {GET: true, HEAD: true, OPTIONS: true, TRACE: true};
17
+
18
+ // Create handlers that pass events from native requests
19
+ var eventHandlers = Object.create(null);
20
+ ['abort', 'aborted', 'error'].forEach(function (event) {
21
+ eventHandlers[event] = function (arg) {
22
+ this._redirectable.emit(event, arg);
23
+ };
24
+ });
25
+
26
+ // An HTTP(S) request that can be redirected
27
+ function RedirectableRequest(options, responseCallback) {
28
+ // Initialize the request
29
+ Writable.call(this);
30
+ this._options = options;
31
+ this._redirectCount = 0;
32
+
33
+ // Attach a callback if passed
34
+ if (responseCallback) {
35
+ this.on('response', responseCallback);
36
+ }
37
+
38
+ // React to responses of native requests
39
+ var self = this;
40
+ this._onNativeResponse = function (response) {
41
+ self._processResponse(response);
42
+ };
43
+
44
+ // Perform the first request
45
+ this._performRequest();
46
+ }
47
+ RedirectableRequest.prototype = Object.create(Writable.prototype);
48
+
49
+ // Executes the next native request (initial or redirect)
50
+ RedirectableRequest.prototype._performRequest = function () {
51
+ // If specified, use the agent corresponding to the protocol
52
+ // (HTTP and HTTPS use different types of agents)
53
+ var protocol = this._options.protocol;
54
+ if (this._options.agents) {
55
+ this._options.agent = this._options.agents[schemes[protocol]];
56
+ }
57
+
58
+ // Create the native request
59
+ var nativeProtocol = nativeProtocols[this._options.protocol];
60
+ var request = this._currentRequest =
61
+ nativeProtocol.request(this._options, this._onNativeResponse);
62
+ this._currentUrl = url.format(this._options);
63
+
64
+ // Set up event handlers
65
+ request._redirectable = this;
66
+ for (var event in eventHandlers) {
67
+ if (event) {
68
+ request.on(event, eventHandlers[event]);
69
+ }
70
+ }
71
+
72
+ // The first request is explicitly ended in RedirectableRequest#end
73
+ if (this._currentResponse) {
74
+ request.end();
75
+ }
76
+ };
77
+
78
+ // Processes a response from the current native request
79
+ RedirectableRequest.prototype._processResponse = function (response) {
80
+ // RFC7231§6.4: The 3xx (Redirection) class of status code indicates
81
+ // that further action needs to be taken by the user agent in order to
82
+ // fulfill the request. If a Location header field is provided,
83
+ // the user agent MAY automatically redirect its request to the URI
84
+ // referenced by the Location field value,
85
+ // even if the specific status code is not understood.
86
+ var location = response.headers.location;
87
+ if (location && this._options.followRedirects !== false &&
88
+ response.statusCode >= 300 && response.statusCode < 400) {
89
+ // RFC7231§6.4: A client SHOULD detect and intervene
90
+ // in cyclical redirections (i.e., "infinite" redirection loops).
91
+ if (++this._redirectCount > this._options.maxRedirects) {
92
+ return this.emit('error', new Error('Max redirects exceeded.'));
93
+ }
94
+
95
+ // RFC7231§6.4.7: The 307 (Temporary Redirect) status code indicates
96
+ // that the target resource resides temporarily under a different URI
97
+ // and the user agent MUST NOT change the request method
98
+ // if it performs an automatic redirection to that URI.
99
+ if (response.statusCode !== 307) {
100
+ // RFC7231§6.4: Automatic redirection needs to done with
101
+ // care for methods not known to be safe […],
102
+ // since the user might not wish to redirect an unsafe request.
103
+ if (!(this._options.method in safeMethods)) {
104
+ this._options.method = 'GET';
105
+ }
106
+ }
107
+
108
+ // Perform the redirected request
109
+ var redirectUrl = url.resolve(this._currentUrl, location);
110
+ debug('redirecting to', redirectUrl);
111
+ Object.assign(this._options, url.parse(redirectUrl));
112
+ this._currentResponse = response;
113
+ this._performRequest();
114
+ } else {
115
+ // The response is not a redirect; return it as-is
116
+ response.responseUrl = this._currentUrl;
117
+ return this.emit('response', response);
118
+ }
119
+ };
120
+
121
+ // Aborts the current native request
122
+ RedirectableRequest.prototype.abort = function () {
123
+ this._currentRequest.abort();
124
+ };
125
+
126
+ // Ends the current native request
127
+ RedirectableRequest.prototype.end = function (data, encoding, callback) {
128
+ this._currentRequest.end(data, encoding, callback);
129
+ };
130
+
131
+ // Flushes the headers of the current native request
132
+ RedirectableRequest.prototype.flushHeaders = function () {
133
+ this._currentRequest.flushHeaders();
134
+ };
135
+
136
+ // Sets the noDelay option of the current native request
137
+ RedirectableRequest.prototype.setNoDelay = function (noDelay) {
138
+ this._currentRequest.setNoDelay(noDelay);
139
+ };
140
+
141
+ // Sets the socketKeepAlive option of the current native request
142
+ RedirectableRequest.prototype.setSocketKeepAlive = function (enable, initialDelay) {
143
+ this._currentRequest.setSocketKeepAlive(enable, initialDelay);
144
+ };
145
+
146
+ // Sets the timeout option of the current native request
147
+ RedirectableRequest.prototype.setTimeout = function (timeout, callback) {
148
+ this._currentRequest.setTimeout(timeout, callback);
149
+ };
150
+
151
+ // Writes buffered data to the current native request
152
+ RedirectableRequest.prototype._write = function (chunk, encoding, callback) {
153
+ this._currentRequest.write(chunk, encoding, callback);
154
+ };
155
+
156
+ // Export a redirecting wrapper for each native protocol
157
+ Object.keys(nativeProtocols).forEach(function (protocol) {
158
+ var scheme = schemes[protocol] = protocol.substr(0, protocol.length - 1);
159
+ var nativeProtocol = nativeProtocols[protocol];
160
+ var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol);
161
+
162
+ // Executes an HTTP request, following redirects
163
+ wrappedProtocol.request = function (options, callback) {
164
+ if (typeof options === 'string') {
165
+ options = url.parse(options);
166
+ options.maxRedirects = exports.maxRedirects;
167
+ } else {
168
+ options = Object.assign({
169
+ maxRedirects: exports.maxRedirects,
170
+ protocol: protocol
171
+ }, options);
172
+ }
173
+ assert.equal(options.protocol, protocol, 'protocol mismatch');
174
+ debug('options', options);
175
+
176
+ return new RedirectableRequest(options, callback);
177
+ };
178
+
179
+ // Executes a GET request, following redirects
180
+ wrappedProtocol.get = function (options, callback) {
181
+ var request = wrappedProtocol.request(options, callback);
182
+ request.end();
183
+ return request;
184
+ };
4
185
  });
package/package.json CHANGED
@@ -1,14 +1,10 @@
1
1
  {
2
2
  "name": "follow-redirects",
3
- "version": "0.0.7",
3
+ "version": "1.0.0",
4
4
  "description": "HTTP and HTTPS modules that follow redirects.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "npm run cover && npm run lint && npm run style",
8
- "lint": "jshint *.js test/*.js test/**/*.js",
9
- "style": "jscs *.js && jscs test/*.js test/**/*.js --config=test/.jscsrc",
10
- "cover": "BLUEBIRD_DEBUG=1 istanbul cover ./node_modules/.bin/_mocha",
11
- "debug": "BLUEBIRD_DEBUG=1 mocha"
7
+ "test": "xo && BLUEBIRD_DEBUG=1 nyc mocha"
12
8
  },
13
9
  "repository": {
14
10
  "type": "git",
@@ -33,7 +29,8 @@
33
29
  "url": "http://www.syskall.com"
34
30
  },
35
31
  "contributors": [
36
- "James Talmage <james@talmage.io>"
32
+ "James Talmage <james@talmage.io>",
33
+ "Ruben Verborgh <ruben@verborgh.org> (https://ruben.verborgh.org/)"
37
34
  ],
38
35
  "files": [
39
36
  "index.js",
@@ -42,19 +39,27 @@
42
39
  "https.js"
43
40
  ],
44
41
  "dependencies": {
45
- "debug": "^2.2.0",
46
- "stream-consume": "^0.1.0"
42
+ "debug": "^2.2.0"
47
43
  },
48
44
  "devDependencies": {
49
- "bluebird": "^2.9.30",
45
+ "bluebird": "^3.4.0",
50
46
  "concat-stream": "^1.5.0",
51
47
  "coveralls": "^2.11.2",
52
48
  "express": "^4.13.0",
53
- "istanbul": "^0.3.17",
54
- "jscs": "^1.13.1",
55
- "jshint": "^2.8.0",
56
- "mocha": "^2.2.5",
57
- "semver": "~4.3.6"
49
+ "mocha": "^3.1.2",
50
+ "nyc": "^8.3.1",
51
+ "xo": "^0.17.0"
58
52
  },
59
- "license": "MIT"
53
+ "license": "MIT",
54
+ "nyc": {
55
+ "reporter": [
56
+ "lcov",
57
+ "text"
58
+ ]
59
+ },
60
+ "xo": {
61
+ "envs": [
62
+ "mocha"
63
+ ]
64
+ }
60
65
  }
package/create.js DELETED
@@ -1,162 +0,0 @@
1
- 'use strict';
2
- var url = require('url');
3
- var debug = require('debug')('follow-redirects');
4
- var assert = require('assert');
5
- var consume = require('stream-consume');
6
-
7
- module.exports = function(_nativeProtocols) {
8
- var nativeProtocols = {};
9
-
10
- var publicApi = {
11
- maxRedirects: 5
12
- };
13
-
14
- for (var p in _nativeProtocols) {
15
- /* istanbul ignore else */
16
- if (_nativeProtocols.hasOwnProperty(p)) {
17
- // http://www.ietf.org/rfc/rfc2396.txt - Section 3.1
18
- assert(/^[A-Z][A-Z\+\-\.]*$/i.test(p), JSON.stringify(p) + ' is not a valid scheme name');
19
- generateWrapper(p, _nativeProtocols[p]);
20
- }
21
- }
22
-
23
- return publicApi;
24
-
25
- function execute(options) {
26
- var clientRequest;
27
- var fetchedUrls = [];
28
-
29
- return (clientRequest = cb());
30
-
31
- function cb(res) {
32
- // skip the redirection logic on the first call.
33
- if (res) {
34
- var fetchedUrl = url.format(options);
35
- fetchedUrls.unshift(fetchedUrl);
36
-
37
- if (!isRedirect(res)) {
38
- res.fetchedUrls = fetchedUrls;
39
- return options.userCallback(res);
40
- }
41
-
42
- // we are going to follow the redirect, but in node 0.10 we must first attach a data listener
43
- // to consume the stream and send the 'end' event
44
- consume(res);
45
-
46
- // need to use url.resolve() in case location is a relative URL
47
- var redirectUrl = url.resolve(fetchedUrl, res.headers.location);
48
- debug('redirecting to', redirectUrl);
49
-
50
- // clean all the properties related to the old url away, and copy from the redirect url
51
- wipeUrlProps(options);
52
- extend(options, url.parse(redirectUrl));
53
- }
54
-
55
- if (fetchedUrls.length > options.maxRedirects) {
56
- var err = new Error('Max redirects exceeded.');
57
- return forwardError(err);
58
- }
59
-
60
- options.nativeProtocol = nativeProtocols[options.protocol];
61
- options.defaultRequest = defaultMakeRequest;
62
-
63
- var req = (options.makeRequest || defaultMakeRequest)(options, cb, res);
64
-
65
- if (res) {
66
- req.on('error', forwardError);
67
- }
68
- return req;
69
- }
70
-
71
- function defaultMakeRequest(options, cb, res) {
72
- if (res) {
73
- // This is a redirect, so use only GET methods
74
- options.method = 'GET';
75
- }
76
-
77
- var req = options.nativeProtocol.request(options, cb);
78
-
79
- if (res) {
80
- // We leave the user to call `end` on the first request
81
- req.end();
82
- }
83
-
84
- return req;
85
- }
86
-
87
- // bubble errors that occur on the redirect back up to the initiating client request
88
- // object, otherwise they wind up killing the process.
89
- function forwardError (err) {
90
- clientRequest.emit('error', err);
91
- }
92
- }
93
-
94
- function generateWrapper (scheme, nativeProtocol) {
95
- var wrappedProtocol = scheme + ':';
96
- var H = function() {};
97
- H.prototype = nativeProtocols[wrappedProtocol] = nativeProtocol;
98
- H = new H();
99
- publicApi[scheme] = H;
100
-
101
- H.request = function(options, callback) {
102
- return execute(parseOptions(options, callback, wrappedProtocol));
103
- };
104
-
105
- // see https://github.com/joyent/node/blob/master/lib/http.js#L1623
106
- H.get = function(options, callback) {
107
- options = parseOptions(options, callback, wrappedProtocol);
108
- var req = execute(options);
109
- req.end();
110
- return req;
111
- };
112
- }
113
-
114
- // returns a safe copy of options (or a parsed url object if options was a string).
115
- // validates that the supplied callback is a function
116
- function parseOptions (options, callback, wrappedProtocol) {
117
- assert.equal(typeof callback, 'function', 'callback must be a function');
118
- if ('string' === typeof options) {
119
- options = url.parse(options);
120
- options.maxRedirects = publicApi.maxRedirects;
121
- } else {
122
- options = extend({
123
- maxRedirects: publicApi.maxRedirects,
124
- protocol: wrappedProtocol
125
- }, options);
126
- }
127
- assert.equal(options.protocol, wrappedProtocol, 'protocol mismatch');
128
- options.protocol = wrappedProtocol;
129
- options.userCallback = callback;
130
-
131
- debug('options', options);
132
- return options;
133
- }
134
- };
135
-
136
- // copies source's own properties onto destination and returns destination
137
- function extend(destination, source) {
138
- for (var i in source) {
139
- if (source.hasOwnProperty(i)) {
140
- destination[i] = source[i];
141
- }
142
- }
143
- return destination;
144
- }
145
-
146
- // to redirect the result must have
147
- // a statusCode between 300-399
148
- // and a `Location` header
149
- function isRedirect (res) {
150
- return (res.statusCode >= 300 && res.statusCode <= 399 &&
151
- 'location' in res.headers);
152
- }
153
-
154
- // nulls all url related properties on the object.
155
- // required on node <10
156
- function wipeUrlProps(options) {
157
- for (var i = 0, l = urlProps.length; i < l; ++i) {
158
- options[urlProps[i]] = null;
159
- }
160
- }
161
- var urlProps = ['protocol', 'slashes', 'auth', 'host', 'port', 'hostname',
162
- 'hash', 'search', 'query', 'pathname', 'path', 'href'];