forked from extern/nushell
Allow http
commands' automatic redirect-following to be disabled (#11329)
Intends to close #8920 This PR suggests a new flag for the `http` commands, `--redirect-mode`, which enables users to choose between different redirect handling modes. The current behaviour of letting ureq silently follow redirects remains the default, but two new options are introduced here, following the lead of [JavaScript's `fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch#redirect): "manual", where any 3xx response to a request is simply returned as the command's result, and "error", where any 3xx response causes a network error like those caused by 4xx and 5xx responses. This PR is a draft. Tests have not been added or run, the flag is currently only implemented for the `http get` command, and design tweaks are likely to be appropriate. Most notably, it's not obvious to me whether a single flag which can take one of three values is the nicest solution here. We might instead consider two binary flags (like `--no-following-redirects` and `--disallow-redirects`, although I'm bad at naming things so I need help with that anyway), or completely drop the "error" option if it's not deemed useful enough. (I personally think it has some merit, especially since 4xx and 5xx responses are already treated as errors by default; So this would allow users to treat only immediate 2xx responses as success) # User-facing changes New options for the `http [method]` commands. Behaviour remains unchanged when the command line flag introduced here is not used. 
This commit is contained in:
@ -38,3 +38,68 @@ fn http_delete_failed_due_to_server_error() {
|
||||
|
||||
assert!(actual.err.contains("Bad request (400)"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_delete_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("DELETE", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http delete {url}/foo", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_delete_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("DELETE", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http delete --redirect-mode manual {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_delete_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("DELETE", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http delete --redirect-mode error {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -176,6 +176,71 @@ fn http_get_full_response() {
|
||||
assert_eq!(header["value"], "close");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("GET", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http get {url}/foo", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("GET", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http get --redirect-mode manual {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_get_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("GET", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http get --redirect-mode error {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
||||
// These tests require network access; they use badssl.com which is a Google-affiliated site for testing various SSL errors.
|
||||
// Revisit this if these tests prove to be flaky or unstable.
|
||||
|
||||
|
@ -39,3 +39,75 @@ fn http_head_failed_due_to_server_error() {
|
||||
|
||||
assert!(actual.err.contains("Bad request (400)"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_head_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("HEAD", "/bar")
|
||||
.with_header("bar", "bar")
|
||||
.create();
|
||||
let _mock = server
|
||||
.mock("HEAD", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http head {url}/foo | (where name == bar).0.value",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_head_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("HEAD", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http head --redirect-mode manual {url}/foo | (where name == location).0.value",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "/bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_head_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("HEAD", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http head --redirect-mode error {url}/foo",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -76,3 +76,68 @@ fn http_patch_failed_due_to_unexpected_body() {
|
||||
|
||||
assert!(actual.err.contains("Cannot make request"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_patch_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("PATCH", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http patch {url}/foo patchbody", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_patch_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("PATCH", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http patch --redirect-mode manual {url}/foo patchbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_patch_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("PATCH", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http patch --redirect-mode error {url}/foo patchbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -112,3 +112,68 @@ fn http_post_json_list_is_success() {
|
||||
mock.assert();
|
||||
assert!(actual.out.is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_post_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("POST", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http post {url}/foo postbody", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_post_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("POST", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http post --redirect-mode manual {url}/foo postbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_post_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("POST", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http post --redirect-mode error {url}/foo postbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
@ -76,3 +76,68 @@ fn http_put_failed_due_to_unexpected_body() {
|
||||
|
||||
assert!(actual.err.contains("Cannot make request"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_put_follows_redirect() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server.mock("GET", "/bar").with_body("bar").create();
|
||||
let _mock = server
|
||||
.mock("PUT", "/foo")
|
||||
.with_status(301)
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!("http put {url}/foo putbody", url = server.url()).as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_put_redirect_mode_manual() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("PUT", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http put --redirect-mode manual {url}/foo putbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert_eq!(&actual.out, "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_put_redirect_mode_error() {
|
||||
let mut server = Server::new();
|
||||
|
||||
let _mock = server
|
||||
.mock("PUT", "/foo")
|
||||
.with_status(301)
|
||||
.with_body("foo")
|
||||
.with_header("Location", "/bar")
|
||||
.create();
|
||||
|
||||
let actual = nu!(pipeline(
|
||||
format!(
|
||||
"http put --redirect-mode error {url}/foo putbody",
|
||||
url = server.url()
|
||||
)
|
||||
.as_str()
|
||||
));
|
||||
|
||||
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||
assert!(&actual.err.contains(
|
||||
"Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)"
|
||||
));
|
||||
}
|
||||
|
Reference in New Issue
Block a user