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:
Sean McArthur
2020-03-20 13:58:52 -07:00
parent d838d54fdf
commit 9a8413d910
13 changed files with 1166 additions and 255 deletions

View File

@@ -13,6 +13,8 @@ use std::fmt;
use std::mem;
#[cfg(feature = "tcp")]
use std::net::SocketAddr;
#[cfg(feature = "runtime")]
use std::time::Duration;
use bytes::Bytes;
use pin_project::{pin_project, project};
@@ -46,10 +48,10 @@ pub use super::tcp::{AddrIncoming, AddrStream};
pub struct Http<E = Exec> {
exec: E,
h1_half_close: bool,
h1_keep_alive: bool,
h1_writev: bool,
h2_builder: proto::h2::server::Config,
mode: ConnectionMode,
keep_alive: bool,
max_buf_size: Option<usize>,
pipeline_flush: bool,
}
@@ -182,10 +184,10 @@ impl Http {
Http {
exec: Exec::Default,
h1_half_close: false,
h1_keep_alive: true,
h1_writev: true,
h2_builder: Default::default(),
mode: ConnectionMode::Fallback,
keep_alive: true,
max_buf_size: None,
pipeline_flush: false,
}
@@ -218,6 +220,21 @@ impl<E> Http<E> {
self
}
/// Enables or disables HTTP/1 keep-alive.
///
/// Default is true.
pub fn http1_keep_alive(&mut self, val: bool) -> &mut Self {
self.h1_keep_alive = val;
self
}
// renamed due different semantics of http2 keep alive
#[doc(hidden)]
#[deprecated(note = "renamed to `http1_keep_alive`")]
pub fn keep_alive(&mut self, val: bool) -> &mut Self {
self.http1_keep_alive(val)
}
/// Set whether HTTP/1 connections should try to use vectored writes,
/// or always flatten into a single buffer.
///
@@ -303,11 +320,38 @@ impl<E> Http<E> {
self
}
/// Enables or disables HTTP keep-alive.
/// Sets an interval for HTTP2 Ping frames should be sent to keep a
/// connection alive.
///
/// Default is true.
pub fn keep_alive(&mut self, val: bool) -> &mut Self {
self.keep_alive = val;
/// Pass `None` to disable HTTP2 keep-alive.
///
/// Default is currently disabled.
///
/// # Cargo Feature
///
/// Requires the `runtime` cargo feature to be enabled.
#[cfg(feature = "runtime")]
pub fn http2_keep_alive_interval(
&mut self,
interval: impl Into<Option<Duration>>,
) -> &mut Self {
self.h2_builder.keep_alive_interval = interval.into();
self
}
/// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
///
/// If the ping is not acknowledged within the timeout, the connection will
/// be closed. Does nothing if `http2_keep_alive_interval` is disabled.
///
/// Default is 20 seconds.
///
/// # Cargo Feature
///
/// Requires the `runtime` cargo feature to be enabled.
#[cfg(feature = "runtime")]
pub fn http2_keep_alive_timeout(&mut self, timeout: Duration) -> &mut Self {
self.h2_builder.keep_alive_timeout = timeout;
self
}
@@ -344,10 +388,10 @@ impl<E> Http<E> {
Http {
exec,
h1_half_close: self.h1_half_close,
h1_keep_alive: self.h1_keep_alive,
h1_writev: self.h1_writev,
h2_builder: self.h2_builder,
mode: self.mode,
keep_alive: self.keep_alive,
max_buf_size: self.max_buf_size,
pipeline_flush: self.pipeline_flush,
}
@@ -392,7 +436,7 @@ impl<E> Http<E> {
let proto = match self.mode {
ConnectionMode::H1Only | ConnectionMode::Fallback => {
let mut conn = proto::Conn::new(io);
if !self.keep_alive {
if !self.h1_keep_alive {
conn.disable_keep_alive();
}
if self.h1_half_close {

View File

@@ -240,7 +240,7 @@ impl<I, E> Builder<I, E> {
///
/// Default is `true`.
pub fn http1_keepalive(mut self, val: bool) -> Self {
self.protocol.keep_alive(val);
self.protocol.http1_keep_alive(val);
self
}
@@ -257,11 +257,11 @@ impl<I, E> Builder<I, E> {
self
}
/// Sets whether HTTP/1 is required.
/// Set the maximum buffer size.
///
/// Default is `false`.
pub fn http1_only(mut self, val: bool) -> Self {
self.protocol.http1_only(val);
/// Default is ~ 400kb.
pub fn http1_max_buf_size(mut self, val: usize) -> Self {
self.protocol.max_buf_size(val);
self
}
@@ -290,6 +290,14 @@ impl<I, E> Builder<I, E> {
self
}
/// Sets whether HTTP/1 is required.
///
/// Default is `false`.
pub fn http1_only(mut self, val: bool) -> Self {
self.protocol.http1_only(val);
self
}
/// Sets whether HTTP/2 is required.
///
/// Default is `false`.
@@ -343,11 +351,35 @@ impl<I, E> Builder<I, E> {
self
}
/// Set the maximum buffer size.
/// Sets an interval for HTTP2 Ping frames should be sent to keep a
/// connection alive.
///
/// Default is ~ 400kb.
pub fn http1_max_buf_size(mut self, val: usize) -> Self {
self.protocol.max_buf_size(val);
/// Pass `None` to disable HTTP2 keep-alive.
///
/// Default is currently disabled.
///
/// # Cargo Feature
///
/// Requires the `runtime` cargo feature to be enabled.
#[cfg(feature = "runtime")]
pub fn http2_keep_alive_interval(mut self, interval: impl Into<Option<Duration>>) -> Self {
self.protocol.http2_keep_alive_interval(interval);
self
}
/// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
///
/// If the ping is not acknowledged within the timeout, the connection will
/// be closed. Does nothing if `http2_keep_alive_interval` is disabled.
///
/// Default is 20 seconds.
///
/// # Cargo Feature
///
/// Requires the `runtime` cargo feature to be enabled.
#[cfg(feature = "runtime")]
pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> Self {
self.protocol.http2_keep_alive_timeout(timeout);
self
}