Repository: sagebind/isahc (rust)

Issue:
Breaking change from 0.9.10 to 0.9.11 due to #240 (presumably)
#240 rewrote the redirect implementation away from curl. I noticed that this broke redirects in my use case.

Test code:

```rust
use isahc::{
    config::{Configurable, RedirectPolicy},
    http::request::Request,
    HttpClient,
};
use std::error::Error;

#[async_std::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
    let client = HttpClient::builder()
        .redirect_policy(RedirectPolicy::Limit(8))
        .build()
        .expect("FATAL: failed to create http client");

    let req = Request::post("http://localhost:8265/command/help").body(())?;
    let res = client.send_async(req).await?;
    dbg!(res);
    Ok(())
}
```

The URL issues a 302 redirect.

### Output with isahc 0.9.10

```
[src/bin/isahc-test.rs:21] res = Response {
    status: 200,
    version: HTTP/1.1,
    headers: {
        "server": "nginx/1.18.0",
        "date": "Tue, 10 Nov 2020 08:09:31 GMT",
        "content-type": "text/plain",
        "content-length": "614",
        "last-modified": "Thu, 05 Nov 2020 10:51:25 GMT",
        "connection": "keep-alive",
        "etag": "\"5fa3d92d-266\"",
        "accept-ranges": "bytes",
    },
    body: Body(614),
}
```

### Output with isahc 0.9.11

```
[src/bin/isahc-test.rs:21] res = Response {
    status: 302,
    version: HTTP/1.1,
    headers: {
        "server": "nginx/1.18.0",
        "date": "Tue, 10 Nov 2020 08:04:33 GMT",
        "content-type": "text/html",
        "content-length": "145",
        "location": "http://localhost:8265/static/help.txt",
        "connection": "keep-alive",
    },
    body: Body(145),
}
```

Pull request description:
Fix client-wide redirect policy not respected
Fix a regression from #240 that caused redirect policies to be ignored if being set client-wide instead of per-request.

Fixes #250.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0057302..67479b4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -52,6 +52,7 @@ jobs:
       - name: Generate code coverage report
         uses: actions-rs/tarpaulin@v0.1
         with:
+          version: '0.16.0'
           args: "-p isahc --run-types Doctests Tests --features cookies,psl"
 
       - name: Upload coverage to Codecov
diff --git a/src/client.rs b/src/client.rs
index 94ba0ef..7c14eae 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -892,13 +892,20 @@ impl HttpClient {
     }
 
     /// Actually send the request. All the public methods go through here.
-    async fn send_async_inner(&self, request: Request<Body>) -> Result<Response<Body>, Error> {
+    async fn send_async_inner(&self, mut request: Request<Body>) -> Result<Response<Body>, Error> {
         let span = tracing::debug_span!(
             "send_async",
             method = ?request.method(),
             uri = ?request.uri(),
         );
 
+        // Set redirect policy if not specified.
+        if request.extensions().get::<RedirectPolicy>().is_none() {
+            if let Some(policy) = self.inner.defaults.get::<RedirectPolicy>().cloned() {
+                request.extensions_mut().insert(policy);
+            }
+        }
+
         let ctx = interceptor::Context {
             invoker: Arc::new(self),
             interceptors: &self.inner.interceptors,


Test patch:
diff --git a/tests/redirects.rs b/tests/redirects.rs
index da7c6ab..1af83aa 100644
--- a/tests/redirects.rs
+++ b/tests/redirects.rs
@@ -144,6 +144,33 @@ fn redirect_also_sends_post(status: u16) {
     assert_eq!(m2.request().method, "POST");
 }
 
+// Issue #250
+#[test]
+fn redirect_policy_from_client() {
+    let m2 = mock!();
+    let location = m2.url();
+
+    let m1 = mock! {
+        status: 302,
+        headers {
+            "Location": location,
+        }
+    };
+
+    let client = HttpClient::builder()
+        .redirect_policy(RedirectPolicy::Limit(8))
+        .build()
+        .unwrap();
+
+    let response = client.post(m1.url(), ()).unwrap();
+
+    assert_eq!(response.status(), 200);
+    assert_eq!(response.effective_uri().unwrap().to_string(), m2.url());
+
+    assert_eq!(m1.request().method, "POST");
+    assert_eq!(m2.request().method, "GET");
+}
+
 #[test]
 fn redirect_non_rewindable_body_returns_error() {
     let m2 = mock!();