follow-redirects 0.3.0 → 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.
- package/README.md +44 -22
- package/index.js +147 -131
- package/package.json +3 -3
package/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
## Follow Redirects
|
2
2
|
|
3
|
-
Drop
|
3
|
+
Drop-in replacement for Nodes `http` and `https` that automatically follows redirects.
|
4
4
|
|
5
5
|
[](https://www.npmjs.com/package/follow-redirects)
|
6
6
|
[](https://travis-ci.org/olalonde/follow-redirects)
|
@@ -19,8 +19,8 @@ Drop in replacement for Nodes `http` and `https` that automatically follows redi
|
|
19
19
|
var http = require('follow-redirects').http;
|
20
20
|
var https = require('follow-redirects').https;
|
21
21
|
|
22
|
-
http.get('http://bit.ly/900913', function (
|
23
|
-
|
22
|
+
http.get('http://bit.ly/900913', function (response) {
|
23
|
+
response.on('data', function (chunk) {
|
24
24
|
console.log(chunk);
|
25
25
|
});
|
26
26
|
}).on('error', function (err) {
|
@@ -28,36 +28,58 @@ http.get('http://bit.ly/900913', function (res) {
|
|
28
28
|
});
|
29
29
|
```
|
30
30
|
|
31
|
-
|
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.
|
32
33
|
|
33
34
|
```javascript
|
34
|
-
require('follow-redirects').maxRedirects = 10; // Has global affect (be careful!)
|
35
|
-
|
36
35
|
https.request({
|
37
36
|
host: 'bitly.com',
|
38
37
|
path: '/UHfDGO',
|
39
|
-
|
40
|
-
|
38
|
+
}, function (response) {
|
39
|
+
console.log(response.responseUrl);
|
40
|
+
// 'http://duckduckgo.com/robots.txt'
|
41
|
+
});
|
41
42
|
```
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
44
|
+
## Options
|
45
|
+
### Global options
|
46
|
+
Global options are set directly on the `follow-redirects` module:
|
46
47
|
|
47
48
|
```javascript
|
48
|
-
|
49
|
-
|
50
|
-
path: '/UHfDGO',
|
51
|
-
}, function (res) {
|
52
|
-
console.log(res.fetchedUrls);
|
53
|
-
// [ 'http://duckduckgo.com/robots.txt', 'http://bitly.com/UHfDGO' ]
|
54
|
-
});
|
49
|
+
var followRedirects = require('follow-redirects');
|
50
|
+
followRedirects.maxRedirects = 10;
|
55
51
|
```
|
56
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
|
+
|
57
79
|
## Browserify Usage
|
58
80
|
|
59
81
|
Due to the way `XMLHttpRequest` works, the `browserify` versions of `http` and `https` already follow redirects.
|
60
|
-
If you are *only*
|
82
|
+
If you are *only* targeting the browser, then this library has little value for you. If you want to write cross
|
61
83
|
platform code for node and the browser, `follow-redirects` provides a great solution for making the native node
|
62
84
|
modules behave the same as they do in browserified builds in the browser. To avoid bundling unnecessary code
|
63
85
|
you should tell browserify to swap out `follow-redirects` with the standard modules when bundling.
|
@@ -104,9 +126,9 @@ Pull Requests are always welcome. Please [file an issue](https://github.com/olal
|
|
104
126
|
|
105
127
|
## Authors
|
106
128
|
|
107
|
-
Olivier Lalonde (olalonde@gmail.com)
|
108
|
-
|
109
|
-
|
129
|
+
- Olivier Lalonde (olalonde@gmail.com)
|
130
|
+
- James Talmage (james@talmage.io)
|
131
|
+
- [Ruben Verborgh](https://ruben.verborgh.org/)
|
110
132
|
|
111
133
|
## License
|
112
134
|
|
package/index.js
CHANGED
@@ -7,163 +7,179 @@ var Writable = require('stream').Writable;
|
|
7
7
|
var debug = require('debug')('follow-redirects');
|
8
8
|
|
9
9
|
var nativeProtocols = {'http:': http, 'https:': https};
|
10
|
-
|
11
|
-
var
|
10
|
+
var schemes = {};
|
11
|
+
var exports = module.exports = {
|
12
12
|
maxRedirects: 21
|
13
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
|
+
});
|
14
25
|
|
15
|
-
//
|
16
|
-
function
|
26
|
+
// An HTTP(S) request that can be redirected
|
27
|
+
function RedirectableRequest(options, responseCallback) {
|
28
|
+
// Initialize the request
|
17
29
|
Writable.call(this);
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
RequestProxy.prototype.abort = function () {
|
22
|
-
this._request.abort();
|
23
|
-
};
|
24
|
-
|
25
|
-
RequestProxy.prototype.end = function (data, encoding, callback) {
|
26
|
-
this._request.end(data, encoding, callback);
|
27
|
-
};
|
28
|
-
|
29
|
-
RequestProxy.prototype.flushHeaders = function () {
|
30
|
-
this._request.flushHeaders();
|
31
|
-
};
|
32
|
-
|
33
|
-
RequestProxy.prototype.setNoDelay = function (noDelay) {
|
34
|
-
this._request.setNoDelay(noDelay);
|
35
|
-
};
|
30
|
+
this._options = options;
|
31
|
+
this._redirectCount = 0;
|
36
32
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
RequestProxy.prototype.setTimeout = function (timeout, callback) {
|
42
|
-
this._request.setSocketKeepAlive(timeout, callback);
|
43
|
-
};
|
33
|
+
// Attach a callback if passed
|
34
|
+
if (responseCallback) {
|
35
|
+
this.on('response', responseCallback);
|
36
|
+
}
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
38
|
+
// React to responses of native requests
|
39
|
+
var self = this;
|
40
|
+
this._onNativeResponse = function (response) {
|
41
|
+
self._processResponse(response);
|
42
|
+
};
|
48
43
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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]];
|
54
56
|
}
|
55
|
-
cb();
|
56
|
-
return requestProxy;
|
57
|
-
|
58
|
-
function cb(res) {
|
59
|
-
// skip the redirection logic on the first call.
|
60
|
-
if (res) {
|
61
|
-
var fetchedUrl = url.format(options);
|
62
|
-
fetchedUrls.unshift(fetchedUrl);
|
63
|
-
|
64
|
-
if (!isRedirect(res)) {
|
65
|
-
res.fetchedUrls = fetchedUrls;
|
66
|
-
requestProxy.emit('response', res);
|
67
|
-
return;
|
68
|
-
}
|
69
|
-
|
70
|
-
// need to use url.resolve() in case location is a relative URL
|
71
|
-
var redirectUrl = url.resolve(fetchedUrl, res.headers.location);
|
72
|
-
debug('redirecting to', redirectUrl);
|
73
|
-
extend(options, url.parse(redirectUrl));
|
74
|
-
}
|
75
57
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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]);
|
80
69
|
}
|
70
|
+
}
|
81
71
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
var req = (options.makeRequest || defaultMakeRequest)(options, cb, res);
|
86
|
-
requestProxy._request = req;
|
87
|
-
mirrorEvent(req, 'abort');
|
88
|
-
mirrorEvent(req, 'aborted');
|
89
|
-
mirrorEvent(req, 'error');
|
90
|
-
return req;
|
72
|
+
// The first request is explicitly ended in RedirectableRequest#end
|
73
|
+
if (this._currentResponse) {
|
74
|
+
request.end();
|
91
75
|
}
|
76
|
+
};
|
92
77
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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.'));
|
98
93
|
}
|
99
94
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
+
}
|
105
106
|
}
|
106
107
|
|
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);
|
108
118
|
}
|
119
|
+
};
|
109
120
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
});
|
115
|
-
}
|
116
|
-
}
|
121
|
+
// Aborts the current native request
|
122
|
+
RedirectableRequest.prototype.abort = function () {
|
123
|
+
this._currentRequest.abort();
|
124
|
+
};
|
117
125
|
|
118
|
-
//
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
options = url.parse(options);
|
123
|
-
options.maxRedirects = publicApi.maxRedirects;
|
124
|
-
} else {
|
125
|
-
options = extend({
|
126
|
-
maxRedirects: publicApi.maxRedirects,
|
127
|
-
protocol: wrappedProtocol
|
128
|
-
}, options);
|
129
|
-
}
|
130
|
-
assert.equal(options.protocol, wrappedProtocol, 'protocol mismatch');
|
126
|
+
// Ends the current native request
|
127
|
+
RedirectableRequest.prototype.end = function (data, encoding, callback) {
|
128
|
+
this._currentRequest.end(data, encoding, callback);
|
129
|
+
};
|
131
130
|
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
// Flushes the headers of the current native request
|
132
|
+
RedirectableRequest.prototype.flushHeaders = function () {
|
133
|
+
this._currentRequest.flushHeaders();
|
134
|
+
};
|
135
135
|
|
136
|
-
//
|
137
|
-
function
|
138
|
-
|
139
|
-
|
140
|
-
var key = keys[i];
|
141
|
-
destination[key] = source[key];
|
142
|
-
}
|
143
|
-
return destination;
|
144
|
-
}
|
136
|
+
// Sets the noDelay option of the current native request
|
137
|
+
RedirectableRequest.prototype.setNoDelay = function (noDelay) {
|
138
|
+
this._currentRequest.setNoDelay(noDelay);
|
139
|
+
};
|
145
140
|
|
146
|
-
//
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
return (res.statusCode >= 300 && res.statusCode <= 399 &&
|
151
|
-
'location' in res.headers);
|
152
|
-
}
|
141
|
+
// Sets the socketKeepAlive option of the current native request
|
142
|
+
RedirectableRequest.prototype.setSocketKeepAlive = function (enable, initialDelay) {
|
143
|
+
this._currentRequest.setSocketKeepAlive(enable, initialDelay);
|
144
|
+
};
|
153
145
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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);
|
158
175
|
|
159
|
-
|
160
|
-
return execute(parseOptions(options, wrappedProtocol), callback);
|
176
|
+
return new RedirectableRequest(options, callback);
|
161
177
|
};
|
162
178
|
|
163
|
-
//
|
164
|
-
|
165
|
-
var
|
166
|
-
|
167
|
-
return
|
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;
|
168
184
|
};
|
169
185
|
});
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "follow-redirects",
|
3
|
-
"version": "0.
|
3
|
+
"version": "1.0.0",
|
4
4
|
"description": "HTTP and HTTPS modules that follow redirects.",
|
5
5
|
"main": "index.js",
|
6
6
|
"scripts": {
|
@@ -29,7 +29,8 @@
|
|
29
29
|
"url": "http://www.syskall.com"
|
30
30
|
},
|
31
31
|
"contributors": [
|
32
|
-
"James Talmage <james@talmage.io>"
|
32
|
+
"James Talmage <james@talmage.io>",
|
33
|
+
"Ruben Verborgh <ruben@verborgh.org> (https://ruben.verborgh.org/)"
|
33
34
|
],
|
34
35
|
"files": [
|
35
36
|
"index.js",
|
@@ -47,7 +48,6 @@
|
|
47
48
|
"express": "^4.13.0",
|
48
49
|
"mocha": "^3.1.2",
|
49
50
|
"nyc": "^8.3.1",
|
50
|
-
"semver": "^5.3.0",
|
51
51
|
"xo": "^0.17.0"
|
52
52
|
},
|
53
53
|
"license": "MIT",
|