Skip to content

Commit

Permalink
Pass status & request details to beforeRedirect (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
davecardwell committed May 3, 2022
1 parent 7abae9b commit 24dcb20
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 4 deletions.
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -63,10 +63,16 @@ const { http, https } = require('follow-redirects');

const options = url.parse('http://bit.ly/900913');
options.maxRedirects = 10;
options.beforeRedirect = (options, { headers }) => {
options.beforeRedirect = (options, { headers, statusCode }, { method, url }) => {
// Use this to adjust the request options upon redirecting,
// to inspect the latest response headers,
// or to cancel the request by throwing an error

// headers = the redirect response headers
// statusCode = the redirect response code (eg. 301, 307, etc.)

// method = the request method that resulted in a redirect
// url = the requested URL that resulted in a redirect
if (options.hostname === "example.com") {
options.auth = "user:password";
}
Expand Down
15 changes: 12 additions & 3 deletions index.js
Expand Up @@ -366,6 +366,7 @@ RedirectableRequest.prototype._processResponse = function (response) {
// care for methods not known to be safe, […]
// RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
// the request method from POST to GET for the subsequent request.
var method = this._options.method;
if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
// RFC7231§6.4.4: The 303 (See Other) status code indicates that
// the server is redirecting the user agent to a different resource […]
Expand Down Expand Up @@ -413,10 +414,18 @@ RedirectableRequest.prototype._processResponse = function (response) {
}

// Evaluate the beforeRedirect callback
if (typeof this._options.beforeRedirect === "function") {
var responseDetails = { headers: response.headers };
var beforeRedirect = this._options.beforeRedirect;
if (typeof beforeRedirect === "function") {
var responseDetails = {
headers: response.headers,
statusCode: statusCode,
};
var requestDetails = {
method: method,
url: currentUrl,
};
try {
this._options.beforeRedirect.call(null, this._options, responseDetails);
beforeRedirect(this._options, responseDetails, requestDetails);
}
catch (err) {
this.emit("error", err);
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 89 additions & 0 deletions test/test.js
Expand Up @@ -1581,6 +1581,95 @@ describe("follow-redirects", function () {
assert.equal(body[header.toLowerCase()], undefined);
});
});

it("passes the redirect status code to beforeRedirect", function () {
app.get("/a", redirectsTo("/b"));
app.get("/b", redirectsTo("/c", 301));
app.get("/c", redirectsTo("/d", 302));
app.get("/d", redirectsTo("/e", 303));
app.get("/e", redirectsTo("/f", 307));
app.get("/f", redirectsTo("/g", 308));
app.get("/g", sendsJson({ a: "b" }));

const statusCodes = [];

return server.start(app)
.then(asPromise(function (resolve, reject) {
var options = {
host: "localhost",
port: 3600,
path: "/a",
method: "GET",
beforeRedirect: function (_, response) {
statusCodes.push(response.statusCode);
},
};
http.get(options, concatJson(resolve, reject)).on("error", reject);
}))
.then(function (res) {
assert.deepEqual(res.responseUrl, "http://localhost:3600/g");
assert.deepEqual(res.parsedJson, { a: "b" });
assert.deepEqual(statusCodes, [302, 301, 302, 303, 307, 308]);
});
});

it("passes the request method to beforeRedirect", function () {
app.post("/a", redirectsTo("/b", 308));
app.post("/b", redirectsTo("/c", 301));
app.get("/c", redirectsTo("/d", 301));
app.get("/d", sendsJson({ a: "b" }));

const requestMethods = [];

return server.start(app)
.then(asPromise(function (resolve, reject) {
var options = {
host: "localhost",
port: 3600,
path: "/a",
method: "POST",
beforeRedirect: function (_, __, request) {
requestMethods.push(request.method);
},
};
http.get(options, concatJson(resolve, reject)).on("error", reject);
}))
.then(function (res) {
assert.deepEqual(res.responseUrl, "http://localhost:3600/d");
assert.deepEqual(res.parsedJson, { a: "b" });
assert.deepEqual(requestMethods, ["POST", "POST", "GET"]);
});
});

it("passes the original request URL to beforeRedirect", function () {
app.get("/a", redirectsTo("/b"));
app.get("/b", redirectsTo("/c"));
app.get("/c", sendsJson({ a: "b" }));

const urlChain = [];

return server.start(app)
.then(asPromise(function (resolve, reject) {
var options = {
host: "localhost",
port: 3600,
path: "/a",
method: "GET",
beforeRedirect: function (_, __, request) {
urlChain.push(request.url);
},
};
http.get(options, concatJson(resolve, reject)).on("error", reject);
}))
.then(function (res) {
assert.deepEqual(res.responseUrl, "http://localhost:3600/c");
assert.deepEqual(res.parsedJson, { a: "b" });
assert.deepEqual(urlChain, [
"http://localhost:3600/a",
"http://localhost:3600/b",
]);
});
});
});

describe("when the followRedirects option is set to false", function () {
Expand Down

0 comments on commit 24dcb20

Please sign in to comment.