From 703d8da3901a5c53db3199e9b16088704ec111f0 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Tue, 30 Jan 2024 12:59:47 -0800 Subject: [PATCH] feat: allow enabling http1/http2 individually for server::auto (#80) --- Cargo.toml | 7 +- src/server/conn/auto.rs | 140 +++++++++++++++++++++++++++++++++++----- src/server/conn/mod.rs | 2 +- 3 files changed, 130 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5d1e333..b2de1e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ authors = ["Sean McArthur "] keywords = ["http", "hyper", "hyperium"] categories = ["network-programming", "web-programming::http-client", "web-programming::http-server"] edition = "2021" +resolver = "2" [package.metadata.docs.rs] features = ["full"] @@ -18,14 +19,14 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] hyper = "1.1.0" -futures-channel = "0.3" futures-util = { version = "0.3.16", default-features = false } http = "1.0" http-body = "1.0.0" bytes = "1" pin-project-lite = "0.2.4" +futures-channel = { version = "0.3", optional = true } socket2 = { version = "0.5", optional = true, features = ["all"] } -tracing = { version = "0.1", default-features = false, features = ["std"] } +tracing = { version = "0.1", default-features = false, features = ["std"], optional = true } tokio = { version = "1", optional = true, features = ["net", "rt", "time"] } tower-service ={ version = "0.3", optional = true } tower = { version = "0.4.1", optional = true, features = ["make", "util"] } @@ -56,7 +57,7 @@ full = [ "tokio", ] -client = ["hyper/client", "dep:tower", "dep:tower-service"] +client = ["hyper/client", "dep:tracing", "dep:futures-channel", "dep:tower", "dep:tower-service"] client-legacy = ["client"] server = ["hyper/server"] diff --git a/src/server/conn/auto.rs b/src/server/conn/auto.rs index fa5337a..5e5ed42 100644 --- a/src/server/conn/auto.rs +++ b/src/server/conn/auto.rs @@ -15,23 +15,52 @@ use http::{Request, Response}; use http_body::Body; use hyper::{ body::Incoming, - rt::{bounds::Http2ServerConnExec, Read, ReadBuf, Timer, Write}, - server::conn::{http1, http2}, + rt::{Read, ReadBuf, Timer, Write}, service::Service, }; + +#[cfg(feature = "http1")] +use hyper::server::conn::http1; + +#[cfg(feature = "http2")] +use hyper::{rt::bounds::Http2ServerConnExec, server::conn::http2}; + +#[cfg(any(not(feature = "http2"), not(feature = "http1")))] +use std::marker::PhantomData; + use pin_project_lite::pin_project; use crate::common::rewind::Rewind; -type Result = std::result::Result>; +type Error = Box; + +type Result = std::result::Result; const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; +/// Exactly equivalent to [`Http2ServerConnExec`]. +#[cfg(feature = "http2")] +pub trait HttpServerConnExec: Http2ServerConnExec {} + +#[cfg(feature = "http2")] +impl> HttpServerConnExec for T {} + +/// Exactly equivalent to [`Http2ServerConnExec`]. +#[cfg(not(feature = "http2"))] +pub trait HttpServerConnExec {} + +#[cfg(not(feature = "http2"))] +impl HttpServerConnExec for T {} + /// Http1 or Http2 connection builder. #[derive(Clone, Debug)] pub struct Builder { + #[cfg(feature = "http1")] http1: http1::Builder, + #[cfg(feature = "http2")] http2: http2::Builder, + #[cfg(not(feature = "http2"))] + _executor: E, } impl Builder { @@ -52,17 +81,23 @@ impl Builder { /// ``` pub fn new(executor: E) -> Self { Self { + #[cfg(feature = "http1")] http1: http1::Builder::new(), + #[cfg(feature = "http2")] http2: http2::Builder::new(executor), + #[cfg(not(feature = "http2"))] + _executor: executor, } } /// Http1 configuration. + #[cfg(feature = "http1")] pub fn http1(&mut self) -> Http1Builder<'_, E> { Http1Builder { inner: self } } /// Http2 configuration. + #[cfg(feature = "http2")] pub fn http2(&mut self) -> Http2Builder<'_, E> { Http2Builder { inner: self } } @@ -76,7 +111,7 @@ impl Builder { B: Body + 'static, B::Error: Into>, I: Read + Write + Unpin + 'static, - E: Http2ServerConnExec, + E: HttpServerConnExec, { Connection { state: ConnState::ReadVersion { @@ -102,7 +137,7 @@ impl Builder { B: Body + 'static, B::Error: Into>, I: Read + Write + Unpin + Send + 'static, - E: Http2ServerConnExec, + E: HttpServerConnExec, { UpgradeableConnection { state: UpgradeableConnState::ReadVersion { @@ -113,12 +148,24 @@ impl Builder { } } } + #[derive(Copy, Clone)] enum Version { H1, H2, } +impl Version { + #[must_use] + #[cfg(any(not(feature = "http2"), not(feature = "http1")))] + pub fn unsupported(self) -> Error { + match self { + Version::H1 => Error::from("HTTP/1 is not supported"), + Version::H2 => Error::from("HTTP/2 is not supported"), + } + } +} + fn read_version(io: I) -> ReadVersion where I: Read + Unpin, @@ -202,6 +249,18 @@ pin_project! { } } +#[cfg(feature = "http1")] +type Http1Connection = hyper::server::conn::http1::Connection, S>; + +#[cfg(not(feature = "http1"))] +type Http1Connection = (PhantomData, PhantomData); + +#[cfg(feature = "http2")] +type Http2Connection = hyper::server::conn::http2::Connection, S, E>; + +#[cfg(not(feature = "http2"))] +type Http2Connection = (PhantomData, PhantomData, PhantomData); + pin_project! { #[project = ConnStateProj] enum ConnState<'a, I, S, E> @@ -216,11 +275,11 @@ pin_project! { }, H1 { #[pin] - conn: hyper::server::conn::http1::Connection, S>, + conn: Http1Connection, }, H2 { #[pin] - conn: hyper::server::conn::http2::Connection, S, E>, + conn: Http2Connection, }, } } @@ -232,7 +291,7 @@ where I: Read + Write + Unpin, B: Body + 'static, B::Error: Into>, - E: Http2ServerConnExec, + E: HttpServerConnExec, { /// Start a graceful shutdown process for this connection. /// @@ -245,8 +304,12 @@ where pub fn graceful_shutdown(self: Pin<&mut Self>) { match self.project().state.project() { ConnStateProj::ReadVersion { .. } => {} + #[cfg(feature = "http1")] ConnStateProj::H1 { conn } => conn.graceful_shutdown(), + #[cfg(feature = "http2")] ConnStateProj::H2 { conn } => conn.graceful_shutdown(), + #[cfg(any(not(feature = "http1"), not(feature = "http2")))] + _ => unreachable!(), } } } @@ -259,7 +322,7 @@ where B: Body + 'static, B::Error: Into>, I: Read + Write + Unpin + 'static, - E: Http2ServerConnExec, + E: HttpServerConnExec, { type Output = Result<()>; @@ -276,22 +339,30 @@ where let (version, io) = ready!(read_version.poll(cx))?; let service = service.take().unwrap(); match version { + #[cfg(feature = "http1")] Version::H1 => { let conn = builder.http1.serve_connection(io, service); this.state.set(ConnState::H1 { conn }); } + #[cfg(feature = "http2")] Version::H2 => { let conn = builder.http2.serve_connection(io, service); this.state.set(ConnState::H2 { conn }); } + #[cfg(any(not(feature = "http1"), not(feature = "http2")))] + _ => return Poll::Ready(Err(version.unsupported())), } } + #[cfg(feature = "http1")] ConnStateProj::H1 { conn } => { return conn.poll(cx).map_err(Into::into); } + #[cfg(feature = "http2")] ConnStateProj::H2 { conn } => { return conn.poll(cx).map_err(Into::into); } + #[cfg(any(not(feature = "http1"), not(feature = "http2")))] + _ => unreachable!(), } } } @@ -308,6 +379,12 @@ pin_project! { } } +#[cfg(feature = "http1")] +type Http1UpgradeableConnection = hyper::server::conn::http1::UpgradeableConnection; + +#[cfg(not(feature = "http1"))] +type Http1UpgradeableConnection = (PhantomData, PhantomData); + pin_project! { #[project = UpgradeableConnStateProj] enum UpgradeableConnState<'a, I, S, E> @@ -322,11 +399,11 @@ pin_project! { }, H1 { #[pin] - conn: hyper::server::conn::http1::UpgradeableConnection, S>, + conn: Http1UpgradeableConnection, S>, }, H2 { #[pin] - conn: hyper::server::conn::http2::Connection, S, E>, + conn: Http2Connection, }, } } @@ -338,7 +415,7 @@ where I: Read + Write + Unpin, B: Body + 'static, B::Error: Into>, - E: Http2ServerConnExec, + E: HttpServerConnExec, { /// Start a graceful shutdown process for this connection. /// @@ -351,8 +428,12 @@ where pub fn graceful_shutdown(self: Pin<&mut Self>) { match self.project().state.project() { UpgradeableConnStateProj::ReadVersion { .. } => {} + #[cfg(feature = "http1")] UpgradeableConnStateProj::H1 { conn } => conn.graceful_shutdown(), + #[cfg(feature = "http2")] UpgradeableConnStateProj::H2 { conn } => conn.graceful_shutdown(), + #[cfg(any(not(feature = "http1"), not(feature = "http2")))] + _ => unreachable!(), } } } @@ -365,7 +446,7 @@ where B: Body + 'static, B::Error: Into>, I: Read + Write + Unpin + Send + 'static, - E: Http2ServerConnExec, + E: HttpServerConnExec, { type Output = Result<()>; @@ -382,34 +463,45 @@ where let (version, io) = ready!(read_version.poll(cx))?; let service = service.take().unwrap(); match version { + #[cfg(feature = "http1")] Version::H1 => { let conn = builder.http1.serve_connection(io, service).with_upgrades(); this.state.set(UpgradeableConnState::H1 { conn }); } + #[cfg(feature = "http2")] Version::H2 => { let conn = builder.http2.serve_connection(io, service); this.state.set(UpgradeableConnState::H2 { conn }); } + #[cfg(any(not(feature = "http1"), not(feature = "http2")))] + _ => return Poll::Ready(Err(version.unsupported())), } } + #[cfg(feature = "http1")] UpgradeableConnStateProj::H1 { conn } => { return conn.poll(cx).map_err(Into::into); } + #[cfg(feature = "http2")] UpgradeableConnStateProj::H2 { conn } => { return conn.poll(cx).map_err(Into::into); } + #[cfg(any(not(feature = "http1"), not(feature = "http2")))] + _ => unreachable!(), } } } } /// Http1 part of builder. +#[cfg(feature = "http1")] pub struct Http1Builder<'a, E> { inner: &'a mut Builder, } +#[cfg(feature = "http1")] impl Http1Builder<'_, E> { /// Http2 configuration. + #[cfg(feature = "http2")] pub fn http2(&mut self) -> Http2Builder<'_, E> { Http2Builder { inner: self.inner } } @@ -522,6 +614,22 @@ impl Http1Builder<'_, E> { } /// Bind a connection together with a [`Service`]. + #[cfg(feature = "http2")] + pub async fn serve_connection(&self, io: I, service: S) -> Result<()> + where + S: Service, Response = Response>, + S::Future: 'static, + S::Error: Into>, + B: Body + 'static, + B::Error: Into>, + I: Read + Write + Unpin + 'static, + E: HttpServerConnExec, + { + self.inner.serve_connection(io, service).await + } + + /// Bind a connection together with a [`Service`]. + #[cfg(not(feature = "http2"))] pub async fn serve_connection(&self, io: I, service: S) -> Result<()> where S: Service, Response = Response>, @@ -530,18 +638,20 @@ impl Http1Builder<'_, E> { B: Body + 'static, B::Error: Into>, I: Read + Write + Unpin + 'static, - E: Http2ServerConnExec, { self.inner.serve_connection(io, service).await } } /// Http2 part of builder. +#[cfg(feature = "http2")] pub struct Http2Builder<'a, E> { inner: &'a mut Builder, } +#[cfg(feature = "http2")] impl Http2Builder<'_, E> { + #[cfg(feature = "http1")] /// Http1 configuration. pub fn http1(&mut self) -> Http1Builder<'_, E> { Http1Builder { inner: self.inner } @@ -675,7 +785,7 @@ impl Http2Builder<'_, E> { B: Body + 'static, B::Error: Into>, I: Read + Write + Unpin + 'static, - E: Http2ServerConnExec, + E: HttpServerConnExec, { self.inner.serve_connection(io, service).await } diff --git a/src/server/conn/mod.rs b/src/server/conn/mod.rs index 535aee2..b23503a 100644 --- a/src/server/conn/mod.rs +++ b/src/server/conn/mod.rs @@ -1,4 +1,4 @@ //! Connection utilities. -#[cfg(feature = "server-auto")] +#[cfg(any(feature = "http1", feature = "http2"))] pub mod auto;