diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2c537defc..4ea60bd6d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -29,6 +29,9 @@ path = "src/lib.rs" [features] default = [] +http2 = [ + "h2", +] ws = [ "local-channel", @@ -65,7 +68,6 @@ bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } -h2 = "0.3.9" http = "0.2.5" httparse = "1.5.1" httpdate = "1.0.1" @@ -77,6 +79,9 @@ percent-encoding = "2.1" pin-project-lite = "0.2" smallvec = "1.6.1" +# http2 +h2 = { version = "0.3.9", optional = true } + # websockets local-channel = { version = "0.1", optional = true } base64 = { version = "0.13", optional = true } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 9dd145ce1..526a23d53 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -6,7 +6,6 @@ use actix_service::{IntoServiceFactory, Service, ServiceFactory}; use crate::{ body::{BoxBody, MessageBody}, h1::{self, ExpectHandler, H1Service, UpgradeHandler}, - h2::H2Service, service::HttpService, ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfig, }; @@ -211,7 +210,8 @@ where } /// Finish service configuration and create a HTTP service for HTTP/2 protocol. - pub fn h2(self, service: F) -> H2Service + #[cfg(feature = "http2")] + pub fn h2(self, service: F) -> crate::h2::H2Service where F: IntoServiceFactory, S::Error: Into> + 'static, @@ -228,7 +228,8 @@ where self.local_addr, ); - H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext) + crate::h2::H2Service::with_config(cfg, service.into_factory()) + .on_connect_ext(self.on_connect_ext) } /// Finish service configuration and create `HttpService` instance. diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index aa05d6aba..8045910be 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -117,6 +117,7 @@ impl ServiceConfig { dst.extend_from_slice(&buf); } + #[allow(unused)] // used with `http2` feature flag pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) { self.0 .date_service diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index d1aab0b92..841322861 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -280,8 +280,9 @@ pub enum PayloadError { UnknownLength, /// HTTP/2 payload error. + #[cfg(feature = "http2")] #[display(fmt = "{}", _0)] - Http2Payload(h2::Error), + Http2Payload(::h2::Error), /// Generic I/O error. #[display(fmt = "{}", _0)] @@ -296,14 +297,16 @@ impl std::error::Error for PayloadError { PayloadError::EncodingCorrupted => None, PayloadError::Overflow => None, PayloadError::UnknownLength => None, + #[cfg(feature = "http2")] PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error), PayloadError::Io(err) => Some(err as &dyn std::error::Error), } } } -impl From for PayloadError { - fn from(err: h2::Error) -> Self { +#[cfg(feature = "http2")] +impl From<::h2::Error> for PayloadError { + fn from(err: ::h2::Error) -> Self { PayloadError::Http2Payload(err) } } @@ -359,6 +362,7 @@ pub enum DispatchError { /// HTTP/2 error. #[display(fmt = "{}", _0)] + #[cfg(feature = "http2")] H2(h2::Error), /// The first request did not complete within the specified timeout. @@ -382,7 +386,10 @@ impl StdError for DispatchError { DispatchError::Body(err) => Some(&**err), DispatchError::Io(err) => Some(err), DispatchError::Parse(err) => Some(err), + + #[cfg(feature = "http2")] DispatchError::H2(err) => Some(err), + _ => None, } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 7a11f9b33..d528bec96 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -141,7 +141,7 @@ where DispatchError::SendResponse(err) => { trace!("Error sending HTTP/2 response: {:?}", err) } - DispatchError::SendData(err) => warn!("{:?}", err), + DispatchError::SendData(err) => log::warn!("{:?}", err), DispatchError::ResponseBody(err) => { error!("Response payload stream error: {:?}", err) } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 469648054..653982d37 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -355,7 +355,7 @@ where } Err(err) => { - trace!("H2 handshake error: {}", err); + log::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } }, diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index 068e23b96..198254e02 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -55,7 +55,7 @@ pub trait HttpMessage: Sized { "" } - /// Get content type encoding + /// Get content type encoding. /// /// UTF-8 is used by default, If request charset is not set. fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> { diff --git a/actix-http/src/keep_alive.rs b/actix-http/src/keep_alive.rs index 27161614d..feb7ff5df 100644 --- a/actix-http/src/keep_alive.rs +++ b/actix-http/src/keep_alive.rs @@ -24,6 +24,7 @@ impl KeepAlive { !matches!(self, Self::Disabled) } + #[allow(unused)] // used with `http2` feature flag pub(crate) fn duration(&self) -> Option { match self { KeepAlive::Timeout(dur) => Some(*dur), diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index cdf61b6a6..dbff89612 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -24,9 +24,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#[macro_use] -extern crate log; - pub use ::http::{uri, uri::Uri}; pub use ::http::{Method, StatusCode, Version}; @@ -39,6 +36,7 @@ pub mod encoding; pub mod error; mod extensions; pub mod h1; +#[cfg(feature = "http2")] pub mod h2; pub mod header; mod helpers; diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index aed24e963..33d9ec6f5 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -16,6 +16,18 @@ pub type BoxedPayloadStream = Pin { + None, + H1 { payload: crate::h1::Payload }, + Stream { #[pin] payload: S }, + } +} + +#[cfg(feature = "http2")] pin_project! { /// A streaming payload. #[project = PayloadProj] @@ -33,14 +45,16 @@ impl From for Payload { } } +#[cfg(feature = "http2")] impl From for Payload { fn from(payload: crate::h2::Payload) -> Self { Payload::H2 { payload } } } -impl From for Payload { - fn from(stream: h2::RecvStream) -> Self { +#[cfg(feature = "http2")] +impl From<::h2::RecvStream> for Payload { + fn from(stream: ::h2::RecvStream) -> Self { Payload::H2 { payload: crate::h2::Payload::new(stream), } @@ -71,7 +85,10 @@ where match self.project() { PayloadProj::None => Poll::Ready(None), PayloadProj::H1 { payload } => Pin::new(payload).poll_next(cx), + + #[cfg(feature = "http2")] PayloadProj::H2 { payload } => Pin::new(payload).poll_next(cx), + PayloadProj::Stream { payload } => payload.poll_next(cx), } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 4fe573aa5..b220e55a4 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -20,7 +20,7 @@ use crate::{ body::{BoxBody, MessageBody}, builder::HttpServiceBuilder, error::DispatchError, - h1, h2, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, + h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, }; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. @@ -502,10 +502,11 @@ where let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); match proto { + #[cfg(feature = "http2")] Protocol::Http2 => HttpServiceHandlerResponse { state: State::H2Handshake { handshake: Some(( - h2::handshake_with_timeout(io, &self.cfg), + crate::h2::handshake_with_timeout(io, &self.cfg), self.cfg.clone(), self.flow.clone(), conn_data, @@ -514,6 +515,11 @@ where }, }, + #[cfg(not(feature = "http2"))] + Protocol::Http2 => { + panic!("HTTP/2 support is disabled (enable with the `http2` feature flag)") + } + Protocol::Http1 => HttpServiceHandlerResponse { state: State::H1 { dispatcher: h1::Dispatcher::new( @@ -531,6 +537,7 @@ where } } +#[cfg(not(feature = "http2"))] pin_project! { #[project = StateProj] enum State @@ -552,10 +559,37 @@ pin_project! { U::Error: fmt::Display, { H1 { #[pin] dispatcher: h1::Dispatcher }, - H2 { #[pin] dispatcher: h2::Dispatcher }, + } +} + +#[cfg(feature = "http2")] +pin_project! { + #[project = StateProj] + enum State + where + T: AsyncRead, + T: AsyncWrite, + T: Unpin, + + S: Service, + S::Future: 'static, + S::Error: Into>, + + B: MessageBody, + + X: Service, + X::Error: Into>, + + U: Service<(Request, Framed), Response = ()>, + U::Error: fmt::Display, + { + H1 { #[pin] dispatcher: h1::Dispatcher }, + + H2 { #[pin] dispatcher: crate::h2::Dispatcher }, + H2Handshake { handshake: Option<( - h2::HandshakeWithTimeout, + crate::h2::HandshakeWithTimeout, ServiceConfig, Rc>, OnConnectData, @@ -614,21 +648,25 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.as_mut().project().state.project() { StateProj::H1 { dispatcher } => dispatcher.poll(cx), + + #[cfg(feature = "http2")] StateProj::H2 { dispatcher } => dispatcher.poll(cx), + + #[cfg(feature = "http2")] StateProj::H2Handshake { handshake: data } => { match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { Ok((conn, timer)) => { let (_, config, flow, conn_data, peer_addr) = data.take().unwrap(); self.as_mut().project().state.set(State::H2 { - dispatcher: h2::Dispatcher::new( + dispatcher: crate::h2::Dispatcher::new( conn, flow, config, peer_addr, conn_data, timer, ), }); self.poll(cx) } Err(err) => { - trace!("H2 handshake error: {}", err); + log::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } }