seanmonstar / reqwest

An easy and powerful Rust HTTP Client
https://docs.rs/reqwest
Apache License 2.0
9.8k stars 1.1k forks source link

How to capture status code with allowed redirects? #878

Closed simeg closed 4 years ago

simeg commented 4 years ago

I'm writing a tool that checks a set of links for their HTTP statuses. I have a link that returns 301 Moved Permanently if I set the redirect policy to Policy::none(), but if I leave it to default (allowing 10 redirects) I get an error where I cannot get the status code. See the code below.

How can I capture the status code of a link that follows redirects? It feels like I'm missing something obvious here.

let client = reqwest::Client::builder()
//    .redirect(Policy::none()) // If I set this it correctly returns 301, but if I use default of allowing 10 redirects it gives me the error below
    .build()
    .unwrap();

let url = "http://www.jisaacks.com/gitgutter";
match client.head(url).send() {
    Ok(res) => {}
    Err(err) => {
        println!("{:?}", err.status()); // None
        println!("{:?}", err);
        /*
        * reqwest::Error {
        *   kind: Request,
        *   url: "https://www.jisaacks.com/gitgutter",
        *   source: hyper::Error(
        *     Connect,
        *     Error {
        *       code: -67843,
        *       message: "The certificate was not trusted."
        *     }
        *   )
        * }
        */
    }
}
seanmonstar commented 4 years ago

If you follow redirects automatically, the intermediate statuses are discarded. err.status() has nothing to do with redirects, it comes from Response::error_for_status(), which converts 4xx and 5xx status codes into errors.

As the error you got describes, after following the redirect to HTTPS, the certificate verification fails.

simeg commented 4 years ago

That makes sense. However, wouldn't the certificate verification failure result in a 4xx? I'm confused to why err.status() doesn't provide any status code.

seanmonstar commented 4 years ago

No, notice how the error says it's a Connect error, it happened during the TLS handshake, before the server could send a response. You don't have any code calling response.error_for_status(), so the error will not have been converted from a response's status code.

simeg commented 4 years ago

Is there a way to extract this type of error? I'm not sure I understand. I can't call response.error_for_status() because client.head(url).send() returns Err for this URL.

Appreciate the help!

seanmonstar commented 4 years ago

Let me see if we can back up some:

simeg commented 4 years ago

My code is interested in why the request failed, so I can let the user of my tool know what type of error occurred. In this case the TLS handshake fails but I can't find a way to know that in the code. I only know that by printing the error.

The intermediate status codes are not interesting, only the final one. Hope this makes more sense.

simeg commented 4 years ago

Is this error granularity not in scope for reqwest? Should I use hyper for this?

seanmonstar commented 4 years ago

You can check the Error::source() chain for more info. Looks like reqwest is missing an is_connect() method as well.

Besides printing the error for the user, what would you hope to inspect?

simeg commented 4 years ago

Nice, using the source chain solves my problem. However it would be useful to get the status code, not only knowing it's a redirect error. Would you accept a PR for that change?

I'm thinking that error.status() would provide this for all error types. WDYT? I could also implement the is_connect().

seanmonstar commented 4 years ago

There is no status code, it was a connect error.

simeg commented 4 years ago

I mean for redirect and timeout errors.

seanmonstar commented 4 years ago

Neither of those have status codes either. The only time an error contains a status code is when you convert an response into an error via Response::error_for_status().

simeg commented 4 years ago

Ah yes I see now, I misunderstood the Error type. Closing this now - thanks for the help!