mirror of https://github.com/fafhrd91/actix-web
feat(http,web): add config for `TCP_NODELAY` (#3918)
This commit is contained in:
parent
d98b35db92
commit
d66f89b7b6
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
- Minimum supported Rust version (MSRV) is now 1.88.
|
||||
- Fix truncated body ending without error when connection closed abnormally. [#3067]
|
||||
- Add config/method for `TCP_NODELAY`. [#3918]
|
||||
|
||||
[#3067]: https://github.com/actix/actix-web/pull/3067
|
||||
[#3918]: https://github.com/actix/actix-web/pull/3918
|
||||
|
||||
## 3.11.2
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
|
|||
keep_alive: KeepAlive,
|
||||
client_request_timeout: Duration,
|
||||
client_disconnect_timeout: Duration,
|
||||
tcp_nodelay: Option<bool>,
|
||||
secure: bool,
|
||||
local_addr: Option<net::SocketAddr>,
|
||||
h1_allow_half_closed: bool,
|
||||
|
|
@ -39,6 +40,7 @@ where
|
|||
keep_alive: KeepAlive::default(),
|
||||
client_request_timeout: Duration::from_secs(5),
|
||||
client_disconnect_timeout: Duration::ZERO,
|
||||
tcp_nodelay: None,
|
||||
secure: false,
|
||||
local_addr: None,
|
||||
h1_allow_half_closed: true,
|
||||
|
|
@ -120,6 +122,12 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets `TCP_NODELAY` value on accepted TCP connections.
|
||||
pub fn tcp_nodelay(mut self, nodelay: bool) -> Self {
|
||||
self.tcp_nodelay = Some(nodelay);
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "3.0.0", note = "Renamed to `client_disconnect_timeout`.")]
|
||||
pub fn client_disconnect(self, dur: Duration) -> Self {
|
||||
|
|
@ -154,6 +162,7 @@ where
|
|||
keep_alive: self.keep_alive,
|
||||
client_request_timeout: self.client_request_timeout,
|
||||
client_disconnect_timeout: self.client_disconnect_timeout,
|
||||
tcp_nodelay: self.tcp_nodelay,
|
||||
secure: self.secure,
|
||||
local_addr: self.local_addr,
|
||||
h1_allow_half_closed: self.h1_allow_half_closed,
|
||||
|
|
@ -179,6 +188,7 @@ where
|
|||
keep_alive: self.keep_alive,
|
||||
client_request_timeout: self.client_request_timeout,
|
||||
client_disconnect_timeout: self.client_disconnect_timeout,
|
||||
tcp_nodelay: self.tcp_nodelay,
|
||||
secure: self.secure,
|
||||
local_addr: self.local_addr,
|
||||
h1_allow_half_closed: self.h1_allow_half_closed,
|
||||
|
|
@ -215,6 +225,7 @@ where
|
|||
.keep_alive(self.keep_alive)
|
||||
.client_request_timeout(self.client_request_timeout)
|
||||
.client_disconnect_timeout(self.client_disconnect_timeout)
|
||||
.tcp_nodelay(self.tcp_nodelay)
|
||||
.secure(self.secure)
|
||||
.local_addr(self.local_addr)
|
||||
.h1_allow_half_closed(self.h1_allow_half_closed)
|
||||
|
|
@ -241,6 +252,7 @@ where
|
|||
.keep_alive(self.keep_alive)
|
||||
.client_request_timeout(self.client_request_timeout)
|
||||
.client_disconnect_timeout(self.client_disconnect_timeout)
|
||||
.tcp_nodelay(self.tcp_nodelay)
|
||||
.secure(self.secure)
|
||||
.local_addr(self.local_addr)
|
||||
.h1_allow_half_closed(self.h1_allow_half_closed)
|
||||
|
|
@ -264,6 +276,7 @@ where
|
|||
.keep_alive(self.keep_alive)
|
||||
.client_request_timeout(self.client_request_timeout)
|
||||
.client_disconnect_timeout(self.client_disconnect_timeout)
|
||||
.tcp_nodelay(self.tcp_nodelay)
|
||||
.secure(self.secure)
|
||||
.local_addr(self.local_addr)
|
||||
.h1_allow_half_closed(self.h1_allow_half_closed)
|
||||
|
|
|
|||
|
|
@ -60,6 +60,12 @@ impl ServiceConfigBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets `TCP_NODELAY` preference for accepted TCP connections.
|
||||
pub fn tcp_nodelay(mut self, nodelay: Option<bool>) -> Self {
|
||||
self.inner.tcp_nodelay = nodelay;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether HTTP/1 connections should support half-closures.
|
||||
///
|
||||
/// Clients can choose to shutdown their writer-side of the connection after completing their
|
||||
|
|
@ -87,6 +93,7 @@ struct Inner {
|
|||
client_disconnect_timeout: Duration,
|
||||
secure: bool,
|
||||
local_addr: Option<SocketAddr>,
|
||||
tcp_nodelay: Option<bool>,
|
||||
date_service: DateService,
|
||||
h1_allow_half_closed: bool,
|
||||
}
|
||||
|
|
@ -99,6 +106,7 @@ impl Default for Inner {
|
|||
client_disconnect_timeout: Duration::ZERO,
|
||||
secure: false,
|
||||
local_addr: None,
|
||||
tcp_nodelay: None,
|
||||
date_service: DateService::new(),
|
||||
h1_allow_half_closed: true,
|
||||
}
|
||||
|
|
@ -120,6 +128,7 @@ impl ServiceConfig {
|
|||
client_disconnect_timeout,
|
||||
secure,
|
||||
local_addr,
|
||||
tcp_nodelay: None,
|
||||
date_service: DateService::new(),
|
||||
h1_allow_half_closed: true,
|
||||
}))
|
||||
|
|
@ -181,6 +190,11 @@ impl ServiceConfig {
|
|||
self.0.h1_allow_half_closed
|
||||
}
|
||||
|
||||
/// Returns configured `TCP_NODELAY` setting for accepted TCP connections.
|
||||
pub fn tcp_nodelay(&self) -> Option<bool> {
|
||||
self.0.tcp_nodelay
|
||||
}
|
||||
|
||||
pub(crate) fn now(&self) -> Instant {
|
||||
self.0.date_service.now()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@ use crate::{
|
|||
ConnectCallback, OnConnectData, Request, Response,
|
||||
};
|
||||
|
||||
#[inline]
|
||||
fn desired_nodelay(tcp_nodelay: Option<bool>) -> Option<bool> {
|
||||
tcp_nodelay
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(stream: &TcpStream, nodelay: bool) {
|
||||
let _ = stream.set_nodelay(nodelay);
|
||||
}
|
||||
|
||||
/// `ServiceFactory` implementation for HTTP/2 transport
|
||||
pub struct H2Service<T, S, B> {
|
||||
srv: S,
|
||||
|
|
@ -82,8 +92,13 @@ where
|
|||
Error = DispatchError,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
fn_factory(|| {
|
||||
ready(Ok::<_, S::InitError>(fn_service(|io: TcpStream| {
|
||||
let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
|
||||
|
||||
fn_factory(move || {
|
||||
ready(Ok::<_, S::InitError>(fn_service(move |io: TcpStream| {
|
||||
if let Some(nodelay) = tcp_nodelay {
|
||||
set_nodelay(&io, nodelay);
|
||||
}
|
||||
let peer_addr = io.peer_addr().ok();
|
||||
ready(Ok::<_, DispatchError>((io, peer_addr)))
|
||||
})))
|
||||
|
|
@ -126,12 +141,17 @@ mod openssl {
|
|||
Error = TlsError<SslError, DispatchError>,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
|
||||
|
||||
Acceptor::new(acceptor)
|
||||
.map_init_err(|_| {
|
||||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
.map(move |io: TlsStream<TcpStream>| {
|
||||
if let Some(nodelay) = tcp_nodelay {
|
||||
set_nodelay(io.get_ref(), nodelay);
|
||||
}
|
||||
let peer_addr = io.get_ref().peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
|
|
@ -173,6 +193,7 @@ mod rustls_0_20 {
|
|||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
|
||||
let mut protos = vec![b"h2".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
|
@ -182,7 +203,10 @@ mod rustls_0_20 {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
.map(move |io: TlsStream<TcpStream>| {
|
||||
if let Some(nodelay) = tcp_nodelay {
|
||||
set_nodelay(io.get_ref().0, nodelay);
|
||||
}
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
|
|
@ -224,6 +248,7 @@ mod rustls_0_21 {
|
|||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
|
||||
let mut protos = vec![b"h2".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
|
@ -233,7 +258,10 @@ mod rustls_0_21 {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
.map(move |io: TlsStream<TcpStream>| {
|
||||
if let Some(nodelay) = tcp_nodelay {
|
||||
set_nodelay(io.get_ref().0, nodelay);
|
||||
}
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
|
|
@ -275,6 +303,7 @@ mod rustls_0_22 {
|
|||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
|
||||
let mut protos = vec![b"h2".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
|
@ -284,7 +313,10 @@ mod rustls_0_22 {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
.map(move |io: TlsStream<TcpStream>| {
|
||||
if let Some(nodelay) = tcp_nodelay {
|
||||
set_nodelay(io.get_ref().0, nodelay);
|
||||
}
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
|
|
@ -326,6 +358,7 @@ mod rustls_0_23 {
|
|||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = S::InitError,
|
||||
> {
|
||||
let tcp_nodelay = desired_nodelay(self.cfg.tcp_nodelay());
|
||||
let mut protos = vec![b"h2".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
|
@ -335,7 +368,10 @@ mod rustls_0_23 {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
.map(move |io: TlsStream<TcpStream>| {
|
||||
if let Some(nodelay) = tcp_nodelay {
|
||||
set_nodelay(io.get_ref().0, nodelay);
|
||||
}
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
(io, peer_addr)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -24,6 +24,16 @@ use crate::{
|
|||
h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig,
|
||||
};
|
||||
|
||||
#[inline]
|
||||
fn desired_nodelay(tcp_nodelay: Option<bool>) -> Option<bool> {
|
||||
tcp_nodelay
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_nodelay(stream: &TcpStream, nodelay: bool) {
|
||||
let _ = stream.set_nodelay(nodelay);
|
||||
}
|
||||
|
||||
/// A [`ServiceFactory`] for HTTP/1.1 and HTTP/2 connections.
|
||||
///
|
||||
/// Use [`build`](Self::build) to begin constructing service. Also see [`HttpServiceBuilder`].
|
||||
|
|
@ -202,7 +212,13 @@ where
|
|||
self,
|
||||
) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
|
||||
{
|
||||
fn_service(|io: TcpStream| async {
|
||||
let tcp_nodelay = self.cfg.tcp_nodelay();
|
||||
|
||||
fn_service(move |io: TcpStream| async move {
|
||||
if let Some(nodelay) = desired_nodelay(tcp_nodelay) {
|
||||
set_nodelay(&io, nodelay);
|
||||
}
|
||||
|
||||
let peer_addr = io.peer_addr().ok();
|
||||
Ok((io, Protocol::Http1, peer_addr))
|
||||
})
|
||||
|
|
@ -216,6 +232,8 @@ where
|
|||
self,
|
||||
) -> impl ServiceFactory<TcpStream, Config = (), Response = (), Error = DispatchError, InitError = ()>
|
||||
{
|
||||
let tcp_nodelay = self.cfg.tcp_nodelay();
|
||||
|
||||
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
|
||||
|
|
@ -233,6 +251,10 @@ where
|
|||
Protocol::Http1
|
||||
};
|
||||
|
||||
if let Some(nodelay) = desired_nodelay(tcp_nodelay) {
|
||||
set_nodelay(&io, nodelay);
|
||||
}
|
||||
|
||||
let peer_addr = io.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
|
|
@ -322,6 +344,7 @@ mod openssl {
|
|||
Error = TlsError<SslError, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
let tcp_nodelay = self.cfg.tcp_nodelay();
|
||||
let mut acceptor = Acceptor::new(acceptor);
|
||||
|
||||
if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
|
||||
|
|
@ -333,7 +356,7 @@ mod openssl {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.map(|io: TlsStream<TcpStream>| {
|
||||
.map(move |io: TlsStream<TcpStream>| {
|
||||
let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
|
|
@ -344,6 +367,10 @@ mod openssl {
|
|||
Protocol::Http1
|
||||
};
|
||||
|
||||
if let Some(nodelay) = desired_nodelay(tcp_nodelay) {
|
||||
set_nodelay(io.get_ref(), nodelay);
|
||||
}
|
||||
|
||||
let peer_addr = io.get_ref().peer_addr().ok();
|
||||
(io, proto, peer_addr)
|
||||
})
|
||||
|
|
@ -415,6 +442,7 @@ mod rustls_0_20 {
|
|||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
let tcp_nodelay = self.cfg.tcp_nodelay();
|
||||
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
|
@ -430,7 +458,7 @@ mod rustls_0_20 {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.and_then(|io: TlsStream<TcpStream>| async {
|
||||
.and_then(move |io: TlsStream<TcpStream>| async move {
|
||||
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
|
|
@ -440,6 +468,11 @@ mod rustls_0_20 {
|
|||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
|
||||
if let Some(nodelay) = desired_nodelay(tcp_nodelay) {
|
||||
set_nodelay(io.get_ref().0, nodelay);
|
||||
}
|
||||
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
|
|
@ -511,6 +544,7 @@ mod rustls_0_21 {
|
|||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
let tcp_nodelay = self.cfg.tcp_nodelay();
|
||||
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
|
@ -526,7 +560,7 @@ mod rustls_0_21 {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.and_then(|io: TlsStream<TcpStream>| async {
|
||||
.and_then(move |io: TlsStream<TcpStream>| async move {
|
||||
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
|
|
@ -536,6 +570,11 @@ mod rustls_0_21 {
|
|||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
|
||||
if let Some(nodelay) = desired_nodelay(tcp_nodelay) {
|
||||
set_nodelay(io.get_ref().0, nodelay);
|
||||
}
|
||||
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
|
|
@ -607,6 +646,7 @@ mod rustls_0_22 {
|
|||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
let tcp_nodelay = self.cfg.tcp_nodelay();
|
||||
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
|
@ -622,7 +662,7 @@ mod rustls_0_22 {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.and_then(|io: TlsStream<TcpStream>| async {
|
||||
.and_then(move |io: TlsStream<TcpStream>| async move {
|
||||
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
|
|
@ -632,6 +672,11 @@ mod rustls_0_22 {
|
|||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
|
||||
if let Some(nodelay) = desired_nodelay(tcp_nodelay) {
|
||||
set_nodelay(io.get_ref().0, nodelay);
|
||||
}
|
||||
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
|
|
@ -703,6 +748,7 @@ mod rustls_0_23 {
|
|||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
let tcp_nodelay = self.cfg.tcp_nodelay();
|
||||
let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
protos.extend_from_slice(&config.alpn_protocols);
|
||||
config.alpn_protocols = protos;
|
||||
|
|
@ -718,7 +764,7 @@ mod rustls_0_23 {
|
|||
unreachable!("TLS acceptor service factory does not error on init")
|
||||
})
|
||||
.map_err(TlsError::into_service_error)
|
||||
.and_then(|io: TlsStream<TcpStream>| async {
|
||||
.and_then(move |io: TlsStream<TcpStream>| async move {
|
||||
let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
|
||||
if protos.windows(2).any(|window| window == b"h2") {
|
||||
Protocol::Http2
|
||||
|
|
@ -728,6 +774,11 @@ mod rustls_0_23 {
|
|||
} else {
|
||||
Protocol::Http1
|
||||
};
|
||||
|
||||
if let Some(nodelay) = desired_nodelay(tcp_nodelay) {
|
||||
set_nodelay(io.get_ref().0, nodelay);
|
||||
}
|
||||
|
||||
let peer_addr = io.get_ref().0.peer_addr().ok();
|
||||
Ok((io, proto, peer_addr))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ use actix_http::{
|
|||
Error, HttpService, Method, Request, Response, StatusCode, TlsAcceptorConfig, Version,
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_rt::pin;
|
||||
use actix_rt::{net::TcpStream as RtTcpStream, pin};
|
||||
use actix_service::{fn_factory_with_config, fn_service};
|
||||
use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
|
||||
use actix_tls::{accept::rustls_0_23::TlsStream, connect::rustls_0_23::webpki_roots_cert_store};
|
||||
use actix_utils::future::{err, ok, poll_fn};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use derive_more::{Display, Error};
|
||||
|
|
@ -171,6 +171,48 @@ async fn h2_1() -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn h2_tcp_nodelay_override_true() -> io::Result<()> {
|
||||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.tcp_nodelay(true)
|
||||
.on_connect_ext(|io: &TlsStream<RtTcpStream>, data| {
|
||||
data.insert(io.get_ref().0.nodelay().unwrap());
|
||||
})
|
||||
.h2(|req: Request| {
|
||||
assert_eq!(req.conn_data::<bool>(), Some(&true));
|
||||
ok::<_, Error>(Response::ok())
|
||||
})
|
||||
.rustls_0_23(tls_config_h2())
|
||||
})
|
||||
.await;
|
||||
|
||||
let response = srv.sget("/").send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn h2_tcp_nodelay_override_false() -> io::Result<()> {
|
||||
let srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.tcp_nodelay(false)
|
||||
.on_connect_ext(|io: &TlsStream<RtTcpStream>, data| {
|
||||
data.insert(io.get_ref().0.nodelay().unwrap());
|
||||
})
|
||||
.h2(|req: Request| {
|
||||
assert_eq!(req.conn_data::<bool>(), Some(&false));
|
||||
ok::<_, Error>(Response::ok())
|
||||
})
|
||||
.rustls_0_23(tls_config_h2())
|
||||
})
|
||||
.await;
|
||||
|
||||
let response = srv.sget("/").send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn h2_body1() -> io::Result<()> {
|
||||
let data = "HELLOWORLD".to_owned().repeat(64 * 1024);
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@
|
|||
- Add `HttpRequest::url_for_map` and `HttpRequest::url_for_iter` methods for named URL parameters. [#3895]
|
||||
- Ignore unparsable cookies in `Cookie` request header.
|
||||
- Add `experimental-introspection` feature to report configured routes [#3594]
|
||||
- Add config/method for `TCP_NODELAY`. [#3918]
|
||||
|
||||
[#3895]: https://github.com/actix/actix-web/pull/3895
|
||||
[#3594]: https://github.com/actix/actix-web/pull/3594
|
||||
[#3918]: https://github.com/actix/actix-web/pull/3918
|
||||
|
||||
## 4.12.1
|
||||
|
||||
|
|
|
|||
|
|
@ -636,7 +636,7 @@ mod tests {
|
|||
#[test]
|
||||
fn app_data() {
|
||||
const TEST_VALUE: u32 = 42;
|
||||
let guard = fn_guard(|ctx| dbg!(ctx.app_data::<u32>()) == Some(&TEST_VALUE));
|
||||
let guard = fn_guard(|ctx| ctx.app_data::<u32>() == Some(&TEST_VALUE));
|
||||
|
||||
let req = TestRequest::default().app_data(TEST_VALUE).to_srv_request();
|
||||
assert!(guard.check(&req.guard_ctx()));
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ struct Socket {
|
|||
struct Config {
|
||||
host: Option<String>,
|
||||
keep_alive: KeepAlive,
|
||||
tcp_nodelay: Option<bool>,
|
||||
client_request_timeout: Duration,
|
||||
client_disconnect_timeout: Duration,
|
||||
h1_allow_half_closed: bool,
|
||||
|
|
@ -115,6 +116,7 @@ where
|
|||
config: Arc::new(Mutex::new(Config {
|
||||
host: None,
|
||||
keep_alive: KeepAlive::default(),
|
||||
tcp_nodelay: None,
|
||||
client_request_timeout: Duration::from_secs(5),
|
||||
client_disconnect_timeout: Duration::from_secs(1),
|
||||
h1_allow_half_closed: true,
|
||||
|
|
@ -155,6 +157,15 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets `TCP_NODELAY` value on accepted TCP connections.
|
||||
///
|
||||
/// By default, accepted TCP connections keep the OS default.
|
||||
/// This method overrides that behavior for all accepted TCP connections.
|
||||
pub fn tcp_nodelay(self, enabled: bool) -> Self {
|
||||
self.config.lock().unwrap().tcp_nodelay = Some(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum number of pending connections.
|
||||
///
|
||||
/// This refers to the number of clients that can be waiting to be served. Exceeding this number
|
||||
|
|
@ -575,6 +586,10 @@ where
|
|||
.h1_allow_half_closed(cfg.h1_allow_half_closed)
|
||||
.local_addr(addr);
|
||||
|
||||
if let Some(enabled) = cfg.tcp_nodelay {
|
||||
svc = svc.tcp_nodelay(enabled);
|
||||
}
|
||||
|
||||
if let Some(handler) = on_connect_fn.clone() {
|
||||
svc =
|
||||
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
|
||||
|
|
@ -620,6 +635,10 @@ where
|
|||
.h1_allow_half_closed(cfg.h1_allow_half_closed)
|
||||
.local_addr(addr);
|
||||
|
||||
if let Some(enabled) = cfg.tcp_nodelay {
|
||||
svc = svc.tcp_nodelay(enabled);
|
||||
}
|
||||
|
||||
if let Some(handler) = on_connect_fn.clone() {
|
||||
svc =
|
||||
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
|
||||
|
|
@ -690,16 +709,19 @@ where
|
|||
let c = cfg.lock().unwrap();
|
||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||
|
||||
let svc = HttpService::build()
|
||||
let mut svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_request_timeout(c.client_request_timeout)
|
||||
.h1_allow_half_closed(c.h1_allow_half_closed)
|
||||
.client_disconnect_timeout(c.client_disconnect_timeout);
|
||||
|
||||
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
|
||||
} else {
|
||||
svc
|
||||
if let Some(enabled) = c.tcp_nodelay {
|
||||
svc = svc.tcp_nodelay(enabled);
|
||||
}
|
||||
|
||||
if let Some(handler) = on_connect_fn.clone() {
|
||||
svc = svc
|
||||
.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext));
|
||||
};
|
||||
|
||||
let fac = factory()
|
||||
|
|
@ -742,16 +764,19 @@ where
|
|||
let c = cfg.lock().unwrap();
|
||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||
|
||||
let svc = HttpService::build()
|
||||
let mut svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_request_timeout(c.client_request_timeout)
|
||||
.h1_allow_half_closed(c.h1_allow_half_closed)
|
||||
.client_disconnect_timeout(c.client_disconnect_timeout);
|
||||
|
||||
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
|
||||
} else {
|
||||
svc
|
||||
if let Some(enabled) = c.tcp_nodelay {
|
||||
svc = svc.tcp_nodelay(enabled);
|
||||
}
|
||||
|
||||
if let Some(handler) = on_connect_fn.clone() {
|
||||
svc = svc
|
||||
.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext));
|
||||
};
|
||||
|
||||
let fac = factory()
|
||||
|
|
@ -809,16 +834,19 @@ where
|
|||
let c = cfg.lock().unwrap();
|
||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||
|
||||
let svc = HttpService::build()
|
||||
let mut svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_request_timeout(c.client_request_timeout)
|
||||
.h1_allow_half_closed(c.h1_allow_half_closed)
|
||||
.client_disconnect_timeout(c.client_disconnect_timeout);
|
||||
|
||||
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
|
||||
} else {
|
||||
svc
|
||||
if let Some(enabled) = c.tcp_nodelay {
|
||||
svc = svc.tcp_nodelay(enabled);
|
||||
}
|
||||
|
||||
if let Some(handler) = on_connect_fn.clone() {
|
||||
svc = svc
|
||||
.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext));
|
||||
};
|
||||
|
||||
let fac = factory()
|
||||
|
|
@ -876,16 +904,19 @@ where
|
|||
let c = cfg.lock().unwrap();
|
||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||
|
||||
let svc = HttpService::build()
|
||||
let mut svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_request_timeout(c.client_request_timeout)
|
||||
.h1_allow_half_closed(c.h1_allow_half_closed)
|
||||
.client_disconnect_timeout(c.client_disconnect_timeout);
|
||||
|
||||
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
|
||||
} else {
|
||||
svc
|
||||
if let Some(enabled) = c.tcp_nodelay {
|
||||
svc = svc.tcp_nodelay(enabled);
|
||||
}
|
||||
|
||||
if let Some(handler) = on_connect_fn.clone() {
|
||||
svc = svc
|
||||
.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext));
|
||||
};
|
||||
|
||||
let fac = factory()
|
||||
|
|
@ -943,17 +974,20 @@ where
|
|||
let c = cfg.lock().unwrap();
|
||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||
|
||||
let svc = HttpService::build()
|
||||
let mut svc = HttpService::build()
|
||||
.keep_alive(c.keep_alive)
|
||||
.client_request_timeout(c.client_request_timeout)
|
||||
.client_disconnect_timeout(c.client_disconnect_timeout)
|
||||
.h1_allow_half_closed(c.h1_allow_half_closed)
|
||||
.local_addr(addr);
|
||||
|
||||
let svc = if let Some(handler) = on_connect_fn.clone() {
|
||||
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
|
||||
} else {
|
||||
svc
|
||||
if let Some(enabled) = c.tcp_nodelay {
|
||||
svc = svc.tcp_nodelay(enabled);
|
||||
}
|
||||
|
||||
if let Some(handler) = on_connect_fn.clone() {
|
||||
svc = svc
|
||||
.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext));
|
||||
};
|
||||
|
||||
let fac = factory()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ extern crate tls_openssl as openssl;
|
|||
|
||||
use std::{sync::mpsc, thread, time::Duration};
|
||||
|
||||
use actix_web::{web, App, HttpResponse, HttpServer};
|
||||
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_start() {
|
||||
|
|
@ -153,3 +153,67 @@ async fn test_start_ssl() {
|
|||
|
||||
srv.stop(false).await;
|
||||
}
|
||||
|
||||
async fn assert_tcp_nodelay_config(nodelay: bool) {
|
||||
let addr = actix_test::unused_addr();
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
actix_rt::System::new()
|
||||
.block_on(async move {
|
||||
let srv = HttpServer::new(move || {
|
||||
let expected = nodelay;
|
||||
|
||||
App::new().service(web::resource("/").route(web::to(
|
||||
move |req: HttpRequest| {
|
||||
let expected = expected;
|
||||
|
||||
async move {
|
||||
let actual = req.conn_data::<bool>().copied().unwrap_or(!expected);
|
||||
if actual == expected {
|
||||
HttpResponse::Ok().finish()
|
||||
} else {
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
},
|
||||
)))
|
||||
})
|
||||
.workers(1)
|
||||
.tcp_nodelay(nodelay)
|
||||
.on_connect(move |io, ext| {
|
||||
if let Some(io) = io.downcast_ref::<actix_web::rt::net::TcpStream>() {
|
||||
ext.insert(io.nodelay().unwrap());
|
||||
}
|
||||
})
|
||||
.bind(format!("{}", addr))
|
||||
.unwrap()
|
||||
.run();
|
||||
|
||||
tx.send(srv.handle()).unwrap();
|
||||
srv.await
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let srv = rx.recv().unwrap();
|
||||
|
||||
let client = awc::Client::builder()
|
||||
.connector(awc::Connector::new().timeout(Duration::from_millis(100)))
|
||||
.finish();
|
||||
|
||||
let response = client.get(format!("http://{}", addr)).send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
srv.stop(false).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_tcp_nodelay_enabled() {
|
||||
assert_tcp_nodelay_config(true).await;
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_tcp_nodelay_disabled() {
|
||||
assert_tcp_nodelay_config(false).await;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue