Skip to content

Commit

Permalink
feat: allow enabling http1/http2 individually
Browse files Browse the repository at this point in the history
  • Loading branch information
SergioBenitez committed Dec 19, 2023
1 parent 61724d1 commit a4c9186
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 6 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ authors = ["Sean McArthur <sean@seanmonstar.com>"]
keywords = ["http", "hyper", "hyperium"]
categories = ["network-programming", "web-programming::http-client", "web-programming::http-server"]
edition = "2018"
resolver = "2"

[package.metadata.docs.rs]
features = ["full"]
Expand Down Expand Up @@ -48,7 +49,6 @@ full = [
"client",
"client-legacy",
"server",
"server-auto",
"service",
"http1",
"http2",
Expand All @@ -59,7 +59,6 @@ client = ["hyper/client", "dep:tower", "dep:tower-service"]
client-legacy = ["client"]

server = ["hyper/server"]
server-auto = ["server", "http1", "http2"]

service = ["dep:tower", "dep:tower-service"]

Expand Down
2 changes: 2 additions & 0 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(missing_docs)]

#[allow(unused)]
macro_rules! ready {
($e:expr) => {
match $e {
Expand All @@ -9,6 +10,7 @@ macro_rules! ready {
};
}

#[allow(unused)]
pub(crate) use ready;
pub(crate) mod exec;
#[cfg(feature = "client")]
Expand Down
115 changes: 112 additions & 3 deletions src/server/conn/auto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,35 @@ 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};

use pin_project_lite::pin_project;

use crate::common::rewind::Rewind;

type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
type Error = Box<dyn std::error::Error + Send + Sync>;

type Result<T> = std::result::Result<T, Error>;

const H2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";

/// Http1 or Http2 connection builder.
#[derive(Clone, Debug)]
pub struct Builder<E> {
#[cfg(feature = "http1")]
http1: http1::Builder,
#[cfg(feature = "http2")]
http2: http2::Builder<E>,
#[cfg(not(feature = "http2"))]
_executor: E,
}

impl<E> Builder<E> {
Expand All @@ -50,22 +62,29 @@ impl<E> Builder<E> {
/// ```
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 }
}

/// Bind a connection together with a [`Service`].
#[cfg(feature = "http2")]
pub async fn serve_connection<I, S, B>(&self, io: I, service: S) -> Result<()>
where
S: Service<Request<Incoming>, Response = Response<B>>,
Expand All @@ -78,8 +97,32 @@ impl<E> Builder<E> {
{
let (version, io) = read_version(io).await?;
match version {
#[cfg(feature = "http1")]
Version::H1 => self.http1.serve_connection(io, service).await?,
Version::H2 => self.http2.serve_connection(io, service).await?,
#[cfg(any(not(feature = "http2"), not(feature = "http1")))]
version => return Err(version.unsupported()),
}

Ok(())
}

/// Bind a connection together with a [`Service`].
#[cfg(not(feature = "http2"))]
pub async fn serve_connection<I, S, B>(&self, io: I, service: S) -> Result<()>
where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + 'static,
{
let (version, io) = read_version(io).await?;
match version {
#[cfg(feature = "http1")]
Version::H1 => self.http1.serve_connection(io, service).await?,
version => return Err(version.unsupported()),
}

Ok(())
Expand All @@ -88,6 +131,7 @@ impl<E> Builder<E> {
/// Bind a connection together with a [`Service`], with the ability to
/// handle HTTP upgrades. This requires that the IO object implements
/// `Send`.
#[cfg(feature = "http2")]
pub async fn serve_connection_with_upgrades<I, S, B>(&self, io: I, service: S) -> Result<()>
where
S: Service<Request<Incoming>, Response = Response<B>>,
Expand All @@ -100,13 +144,45 @@ impl<E> Builder<E> {
{
let (version, io) = read_version(io).await?;
match version {
#[cfg(feature = "http1")]
Version::H1 => {
self.http1
.serve_connection(io, service)
.with_upgrades()
.await?
}
Version::H2 => self.http2.serve_connection(io, service).await?,
#[cfg(any(not(feature = "http2"), not(feature = "http1")))]
version => return Err(version.unsupported()),
}

Ok(())
}

/// Bind a connection together with a [`Service`], with the ability to
/// handle HTTP upgrades. This requires that the IO object implements
/// `Send`.
#[cfg(not(feature = "http2"))]
pub async fn serve_connection_with_upgrades<I, S, B>(&self, io: I, service: S) -> Result<()>
where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + Send + 'static,
{
let (version, io) = read_version(io).await?;
match version {
#[cfg(feature = "http1")]
Version::H1 => {
self.http1
.serve_connection(io, service)
.with_upgrades()
.await?
}
#[cfg(any(not(feature = "http2"), not(feature = "http1")))]
version => return Err(version.unsupported()),
}

Ok(())
Expand All @@ -117,6 +193,18 @@ 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"),
}
}
}

async fn read_version<'a, A>(mut reader: A) -> IoResult<(Version, Rewind<A>)>
where
A: Read + Unpin,
Expand Down Expand Up @@ -172,12 +260,15 @@ where
}

/// Http1 part of builder.
#[cfg(feature = "http1")]
pub struct Http1Builder<'a, E> {
inner: &'a mut Builder<E>,
}

#[cfg(feature = "http1")]
impl<E> Http1Builder<'_, E> {
/// Http2 configuration.
#[cfg(feature = "http2")]
pub fn http2(&mut self) -> Http2Builder<'_, E> {
Http2Builder {
inner: &mut self.inner,
Expand Down Expand Up @@ -292,6 +383,7 @@ impl<E> Http1Builder<'_, E> {
}

/// Bind a connection together with a [`Service`].
#[cfg(feature = "http2")]
pub async fn serve_connection<I, S, B>(&self, io: I, service: S) -> Result<()>
where
S: Service<Request<Incoming>, Response = Response<B>>,
Expand All @@ -304,14 +396,31 @@ impl<E> Http1Builder<'_, E> {
{
self.inner.serve_connection(io, service).await
}

/// Bind a connection together with a [`Service`].
#[cfg(not(feature = "http2"))]
pub async fn serve_connection<I, S, B>(&self, io: I, service: S) -> Result<()>
where
S: Service<Request<Incoming>, Response = Response<B>>,
S::Future: 'static,
S::Error: Into<Box<dyn StdError + Send + Sync>>,
B: Body + 'static,
B::Error: Into<Box<dyn StdError + Send + Sync>>,
I: Read + Write + Unpin + 'static,
{
self.inner.serve_connection(io, service).await
}
}

/// Http2 part of builder.
#[cfg(feature = "http2")]
pub struct Http2Builder<'a, E> {
inner: &'a mut Builder<E>,
}

#[cfg(feature = "http2")]
impl<E> Http2Builder<'_, E> {
#[cfg(feature = "http1")]
/// Http1 configuration.
pub fn http1(&mut self) -> Http1Builder<'_, E> {
Http1Builder {
Expand Down
2 changes: 1 addition & 1 deletion src/server/conn/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Connection utilities.

#[cfg(feature = "server-auto")]
#[cfg(any(feature = "http1", feature = "http2"))]
pub mod auto;

0 comments on commit a4c9186

Please sign in to comment.