-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A better Client Error experience #1130
Comments
Every function/method can have its own error type. More general abstractions may seem reasonable now, but have such a large API surface that revising error handling will break code all over the place. The abstraction of |
@sanmai-NL I don't quite understand which way you lean. You say each method can have its own type, but that it's not needed in hyper. |
One thing I'd like is to differentiate IO errors based on where they came from. I care about connect errors in particular, since you can retry the request on a different host if you can't connect to one of them, even if the request isn't idempotent or you have a body you can't reset. |
I made a comment in #1131 about having an internal Connect variant (and related ways to inspect for that). |
@seanmonstar, regarding #1130 (comment): #[derive(Copy, Clone, Debug)]
pub enum MyFnError {
FailurePathOne { cause: OtherFnError },
}
impl Display for MyFnError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
fmt::Display::fmt(match *self {
FailurePathOne::OtherFnError { .. } => "Failed, ... . ",
}, f)
}
}
impl Error for MyFnError {
fn description(&self) -> &'static str { "" }
fn cause(&self) -> Option<&Error> {
match *self {
MyFnError::FailurePathOne { ref cause, .. } => Some(cause),
}
}
}
pub type MyFnResult = Result<(), MyFnError>; |
Edit: What follows was ill-informed on my part, but gives an example of how a new and foolish user might make the mistake of trying to instantiate a hyper::Error. See comment below. Hi, I'm not sure this is still open for feedback or where its progressing, but given my evolving use case, I'm surprised by this opening comment:
As of hyper 0.11 this is of course a requirement, but I'm finding that awkward. I'm implementing a client that needs to be resilient to retrieving large responses up to ~1GiB in size and processing these with more than one pass. After retrieving a threshold size the client will switch from in-memory to buffering the response on disk. In addition the client may wish to terminate response streaming early if the initial Content-Length header, or the actually received chunked response, is too large (for example, given available disk space.) I don't want to panic in these cases, but I do want the body stream to terminate and hyper to close the connection (which won't be completely read). These kinds of errors are entirely in the domain of my application, but as it stands, my best route is along the lines of: if length_to_read > self.max_payload() {
Err(IoError::new(
ErrorKind::Other,
format!("[APP] response body too large: {}+", length_to_read)
).into()) // -> hyper::Error
} //... Also my disk based implementation can io::Error, but as it stands its going to be difficult without hacks like the above to reliably distinguish between I/O errors originating from the HTTP sockets and my disk impl. I/O errors. But distinguishing these is important to determine which should be retried vs. resulting in a fatal application error. My personal ideal would be the ability to use the failure crate's Error type inside of |
@dekellum Just because the Client's future returns a hyper::Error doesn't mean your future that works off of that has to return a hyper::Error. |
Yes, thanks, but like with my above code example originating in |
Like this? response.body().map_err(|hyper_error| MyErrorType::from(hyper_error)).for_each(Fn) |
No, unfortunately the logic that decides upon and produces the error needs to be in the |
I don't understand. |
I'll try and get to where I can show more of my code, and my apologies if I'm explaining myself badly, but the |
You can use |
@sfackler: thanks for your patient suggestions. I now finally get it, and its much better! I was missing the wrapping nature of
So now the issues opening comment makes perfect sense and I'll edit down my prior ill-informed input. Since the issue title includes experience I would just suggest that somewhere between futures' or hyper's rustdoc and/or a hyper client demo with error handling including Thanks again and sorry for the noise. |
As I've been working on HTTP/2 support in hyper, I realized that there is a similar situation in h2, which is that one could at anytime send a In HTTP/1, the only action would be to close the connection, but that's fine. The point here is that this concept exists in HTTP/2, and could perhaps be used for both client and server errors. It could be a Or perhaps even better, Also, this type would never be given to the user, only created by the user to signal a stream errored. I wrote up more of the design in #1431. |
**The `Error` is now an opaque struct**, which allows for more variants to be added freely, and the internal representation to change without being breaking changes. For inspecting an `Error`, there are several `is_*` methods to check for certain classes of errors, such as `Error::is_parse()`. The `cause` can also be inspected, like before. This likely seems like a downgrade, but more inspection can be added as needed! The `Error` now knows about more states, which gives much more context around when a certain error occurs. This is also expressed in the description and `fmt` messages. **Most places where a user would provide an error to hyper can now pass any error type** (`E: Into<Box<std::error::Error>>`). This error is passed back in relevant places, and can be useful for logging. This should make it much clearer about what error a user should provide to hyper: any it feels is relevant! Closes #1128 Closes #1130 Closes #1431 Closes #1338 BREAKING CHANGE: `Error` is no longer an enum to pattern match over, or to construct. Code will need to be updated accordingly. For body streams or `Service`s, inference might be unable to determine what error type you mean to return. Starting in Rust 1.26, you could just label that as `!` if you never return an error.
**The `Error` is now an opaque struct**, which allows for more variants to be added freely, and the internal representation to change without being breaking changes. For inspecting an `Error`, there are several `is_*` methods to check for certain classes of errors, such as `Error::is_parse()`. The `cause` can also be inspected, like before. This likely seems like a downgrade, but more inspection can be added as needed! The `Error` now knows about more states, which gives much more context around when a certain error occurs. This is also expressed in the description and `fmt` messages. **Most places where a user would provide an error to hyper can now pass any error type** (`E: Into<Box<std::error::Error>>`). This error is passed back in relevant places, and can be useful for logging. This should make it much clearer about what error a user should provide to hyper: any it feels is relevant! Closes #1128 Closes #1130 Closes #1431 Closes #1338 BREAKING CHANGE: `Error` is no longer an enum to pattern match over, or to construct. Code will need to be updated accordingly. For body streams or `Service`s, inference might be unable to determine what error type you mean to return. Starting in Rust 1.26, you could just label that as `!` if you never return an error.
**The `Error` is now an opaque struct**, which allows for more variants to be added freely, and the internal representation to change without being breaking changes. For inspecting an `Error`, there are several `is_*` methods to check for certain classes of errors, such as `Error::is_parse()`. The `cause` can also be inspected, like before. This likely seems like a downgrade, but more inspection can be added as needed! The `Error` now knows about more states, which gives much more context around when a certain error occurs. This is also expressed in the description and `fmt` messages. **Most places where a user would provide an error to hyper can now pass any error type** (`E: Into<Box<std::error::Error>>`). This error is passed back in relevant places, and can be useful for logging. This should make it much clearer about what error a user should provide to hyper: any it feels is relevant! Closes #1128 Closes #1130 Closes #1431 Closes #1338 BREAKING CHANGE: `Error` is no longer an enum to pattern match over, or to construct. Code will need to be updated accordingly. For body streams or `Service`s, inference might be unable to determine what error type you mean to return. Starting in Rust 1.26, you could just label that as `!` if you never return an error.
**The `Error` is now an opaque struct**, which allows for more variants to be added freely, and the internal representation to change without being breaking changes. For inspecting an `Error`, there are several `is_*` methods to check for certain classes of errors, such as `Error::is_parse()`. The `cause` can also be inspected, like before. This likely seems like a downgrade, but more inspection can be added as needed! The `Error` now knows about more states, which gives much more context around when a certain error occurs. This is also expressed in the description and `fmt` messages. **Most places where a user would provide an error to hyper can now pass any error type** (`E: Into<Box<std::error::Error>>`). This error is passed back in relevant places, and can be useful for logging. This should make it much clearer about what error a user should provide to hyper: any it feels is relevant! Closes #1128 Closes #1130 Closes #1431 Closes #1338 BREAKING CHANGE: `Error` is no longer an enum to pattern match over, or to construct. Code will need to be updated accordingly. For body streams or `Service`s, inference might be unable to determine what error type you mean to return. Starting in Rust 1.26, you could just label that as `!` if you never return an error.
**The `Error` is now an opaque struct**, which allows for more variants to be added freely, and the internal representation to change without being breaking changes. For inspecting an `Error`, there are several `is_*` methods to check for certain classes of errors, such as `Error::is_parse()`. The `cause` can also be inspected, like before. This likely seems like a downgrade, but more inspection can be added as needed! The `Error` now knows about more states, which gives much more context around when a certain error occurs. This is also expressed in the description and `fmt` messages. **Most places where a user would provide an error to hyper can now pass any error type** (`E: Into<Box<std::error::Error>>`). This error is passed back in relevant places, and can be useful for logging. This should make it much clearer about what error a user should provide to hyper: any it feels is relevant! Closes #1128 Closes #1130 Closes #1431 Closes #1338 BREAKING CHANGE: `Error` is no longer an enum to pattern match over, or to construct. Code will need to be updated accordingly. For body streams or `Service`s, inference might be unable to determine what error type you mean to return. Starting in Rust 1.26, you could just label that as `!` if you never return an error.
As discussed in #1128, there is good reason to consider having 2 different error types, and they would be useful for the
Client
as well. Thehyper::Error
is the correct error type for theClient
to return from itsFuture
, as well as from the response's body stream.However, It does not makes sense for the request body stream to require sending a
hyper::Error
if generating the body failed. In that case, nothing in hyper failed, and the only relevant thing to hyper is that the stream cannot be completed. So the error type sent should be some sort ofDisconnect
orAbort
or whatever, since an error on the body stream means the only thing hyper can reasonably do is to give up on the connection.As in the server issue, the
From<std::io::Error>
andFrom<hyper::Error>
implementations would be useful to allow easily sending streams from other sources, but it would be clear that the actual error type means "kill it dead."New proposal: #1431
The text was updated successfully, but these errors were encountered: