feat(http2): add HTTP2 keep-alive support for client and server
This adds HTTP2 keep-alive support to client and server connections based losely on GRPC keep-alive. When enabled, after no data has been received for some configured interval, an HTTP2 PING frame is sent. If the PING is not acknowledged with a configured timeout, the connection is closed. Clients have an additional option to enable keep-alive while the connection is otherwise idle. When disabled, keep-alive PINGs are only used while there are open request/response streams. If enabled, PINGs are sent even when there are no active streams. For now, since these features use `tokio::time::Delay`, the `runtime` cargo feature is required to use them.
This commit is contained in:
37
src/error.rs
37
src/error.rs
@@ -91,6 +91,10 @@ pub(crate) enum User {
|
||||
ManualUpgrade,
|
||||
}
|
||||
|
||||
// Sentinel type to indicate the error was caused by a timeout.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TimedOut;
|
||||
|
||||
impl Error {
|
||||
/// Returns true if this was an HTTP parse error.
|
||||
pub fn is_parse(&self) -> bool {
|
||||
@@ -133,6 +137,11 @@ impl Error {
|
||||
self.inner.kind == Kind::BodyWriteAborted
|
||||
}
|
||||
|
||||
/// Returns true if the error was caused by a timeout.
|
||||
pub fn is_timeout(&self) -> bool {
|
||||
self.find_source::<TimedOut>().is_some()
|
||||
}
|
||||
|
||||
/// Consumes the error, returning its cause.
|
||||
pub fn into_cause(self) -> Option<Box<dyn StdError + Send + Sync>> {
|
||||
self.inner.cause
|
||||
@@ -153,19 +162,25 @@ impl Error {
|
||||
&self.inner.kind
|
||||
}
|
||||
|
||||
pub(crate) fn h2_reason(&self) -> h2::Reason {
|
||||
// Find an h2::Reason somewhere in the cause stack, if it exists,
|
||||
// otherwise assume an INTERNAL_ERROR.
|
||||
fn find_source<E: StdError + 'static>(&self) -> Option<&E> {
|
||||
let mut cause = self.source();
|
||||
while let Some(err) = cause {
|
||||
if let Some(h2_err) = err.downcast_ref::<h2::Error>() {
|
||||
return h2_err.reason().unwrap_or(h2::Reason::INTERNAL_ERROR);
|
||||
if let Some(ref typed) = err.downcast_ref() {
|
||||
return Some(typed);
|
||||
}
|
||||
cause = err.source();
|
||||
}
|
||||
|
||||
// else
|
||||
h2::Reason::INTERNAL_ERROR
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn h2_reason(&self) -> h2::Reason {
|
||||
// Find an h2::Reason somewhere in the cause stack, if it exists,
|
||||
// otherwise assume an INTERNAL_ERROR.
|
||||
self.find_source::<h2::Error>()
|
||||
.and_then(|h2_err| h2_err.reason())
|
||||
.unwrap_or(h2::Reason::INTERNAL_ERROR)
|
||||
}
|
||||
|
||||
pub(crate) fn new_canceled() -> Error {
|
||||
@@ -397,6 +412,16 @@ trait AssertSendSync: Send + Sync + 'static {}
|
||||
#[doc(hidden)]
|
||||
impl AssertSendSync for Error {}
|
||||
|
||||
// ===== impl TimedOut ====
|
||||
|
||||
impl fmt::Display for TimedOut {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("operation timed out")
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for TimedOut {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user