mirror of https://github.com/fafhrd91/actix-web
http service finalizer for automatic h2c detection
This commit is contained in:
parent
d2364c80c4
commit
1dfcb5ee3f
|
@ -4,12 +4,14 @@
|
|||
### Added
|
||||
- Implement `MessageBody` for `&mut B` where `B: MessageBody + Unpin`. [#2868]
|
||||
- Implement `MessageBody` for `Pin<B>` where `B::Target: MessageBody`. [#2868]
|
||||
- Automatic h2c detection via new service finalizer `HttpService::tcp_auto_h2c()`. [#????]
|
||||
|
||||
### Performance
|
||||
- Improve overall performance of operations on `Extensions`. [#2890]
|
||||
|
||||
[#2868]: https://github.com/actix/actix-web/pull/2868
|
||||
[#2890]: https://github.com/actix/actix-web/pull/2890
|
||||
[#????]: https://github.com/actix/actix-web/pull/????
|
||||
|
||||
|
||||
## 3.2.2 - 2022-09-11
|
||||
|
|
|
@ -8,38 +8,20 @@
|
|||
|
||||
use std::{convert::Infallible, io};
|
||||
|
||||
use actix_http::{error::DispatchError, HttpService, Protocol, Request, Response, StatusCode};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_http::{HttpService, Request, Response, StatusCode};
|
||||
use actix_server::Server;
|
||||
use actix_service::{fn_service, ServiceFactoryExt};
|
||||
|
||||
const H2_PREFACE: &[u8] = b"PRI * HTTP/2";
|
||||
|
||||
#[actix_rt::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
Server::build()
|
||||
.bind("h2c-detect", ("127.0.0.1", 8080), || {
|
||||
fn_service(move |io: TcpStream| async move {
|
||||
let mut buf = [0; 12];
|
||||
|
||||
io.peek(&mut buf).await.map_err(DispatchError::Io)?;
|
||||
|
||||
let proto = if buf == H2_PREFACE {
|
||||
tracing::info!("selecting h2c");
|
||||
Protocol::Http2
|
||||
} else {
|
||||
tracing::info!("selecting h1");
|
||||
Protocol::Http1
|
||||
};
|
||||
|
||||
let peer_addr = io.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
.and_then(HttpService::build().finish(|_req: Request| async move {
|
||||
Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!"))
|
||||
}))
|
||||
HttpService::build()
|
||||
.finish(|_req: Request| async move {
|
||||
Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!"))
|
||||
})
|
||||
.tcp_auto_h2c()
|
||||
})?
|
||||
.workers(2)
|
||||
.run()
|
||||
|
|
|
@ -186,7 +186,7 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Finish service configuration and create a HTTP Service for HTTP/1 protocol.
|
||||
/// Finish service configuration and create a service for the HTTP/1 protocol.
|
||||
pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
|
||||
where
|
||||
B: MessageBody,
|
||||
|
@ -209,7 +209,7 @@ where
|
|||
.on_connect_ext(self.on_connect_ext)
|
||||
}
|
||||
|
||||
/// Finish service configuration and create a HTTP service for HTTP/2 protocol.
|
||||
/// Finish service configuration and create a service for the HTTP/2 protocol.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn h2<F, B>(self, service: F) -> crate::h2::H2Service<T, S, B>
|
||||
|
|
|
@ -24,7 +24,36 @@ use crate::{
|
|||
h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig,
|
||||
};
|
||||
|
||||
/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol.
|
||||
/// A [`ServiceFactory`] for HTTP/1.1 and HTTP/2 connections.
|
||||
///
|
||||
/// Use [`build`](Self::build) to begin constructing service. Also see [`HttpServiceBuilder`].
|
||||
///
|
||||
/// # Automatic HTTP Version Selection
|
||||
/// There are two ways to select the HTTP version of an incoming connection:
|
||||
/// - One is to rely on the ALPN information that is provided when using a TLS (HTTPS); both
|
||||
/// versions are supported automatically when using either of the `.rustls()` or `.openssl()`
|
||||
/// finalizing methods.
|
||||
/// - The other is to read the first few bytes of the TCP stream. This is the only viable approach
|
||||
/// for supporting H2C, which allows the HTTP/2 protocol to work over plaintext connections. Use
|
||||
/// the `.tcp_auto_h2c()` finalizing method to enable this behavior.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::HttpService;
|
||||
///
|
||||
/// HttpService::build()
|
||||
/// // the builder finalizing method, other finalizers would not return an `HttpService`
|
||||
/// .finish(|_req: Request| async move {
|
||||
/// Ok::<_, Infallible>(
|
||||
/// Response::build(StatusCode::OK).body("Hello!")
|
||||
/// )
|
||||
/// })
|
||||
/// // the service finalizing method method
|
||||
/// // you can use `.tcp_auto_h2c()`, `.rustls()`, or `.openssl()` instead of `.tcp()`
|
||||
/// .tcp();
|
||||
///
|
||||
/// // this service would then be used in an actix_server::Server
|
||||
/// ```
|
||||
pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
|
||||
srv: S,
|
||||
cfg: ServiceConfig,
|
||||
|
@ -163,7 +192,9 @@ where
|
|||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create simple tcp stream service
|
||||
/// Creates TCP stream service from HTTP service.
|
||||
///
|
||||
/// The resulting service only supports HTTP/1.x.
|
||||
pub fn tcp(
|
||||
self,
|
||||
) -> impl ServiceFactory<
|
||||
|
@ -179,6 +210,42 @@ where
|
|||
})
|
||||
.and_then(self)
|
||||
}
|
||||
|
||||
/// Creates TCP stream service from HTTP service that automatically selects HTTP/1.x or HTTP/2
|
||||
/// on plaintext connections.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn tcp_auto_h2c(
|
||||
self,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = DispatchError,
|
||||
InitError = (),
|
||||
> {
|
||||
fn_service(move |io: TcpStream| async move {
|
||||
// subset of HTTP/2 preface defined by RFC 9113 §3.4
|
||||
// this subset was chosen to maximize likelihood that peeking only once will allow us to
|
||||
// reliably determine version or else it should fallback to h1 and fail quickly if data
|
||||
// on the wire is junk
|
||||
const H2_PREFACE: &[u8] = b"PRI * HTTP/2";
|
||||
|
||||
let mut buf = [0; 12];
|
||||
|
||||
io.peek(&mut buf).await?;
|
||||
|
||||
let proto = if buf == H2_PREFACE {
|
||||
Protocol::Http2
|
||||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
|
||||
let peer_addr = io.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
.and_then(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration options used when accepting TLS connection.
|
||||
|
|
Loading…
Reference in New Issue