diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index f052c6d22..b52aa5c58 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -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 diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 09b379e87..c01b63ccd 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -17,6 +17,7 @@ pub struct HttpServiceBuilder { keep_alive: KeepAlive, client_request_timeout: Duration, client_disconnect_timeout: Duration, + tcp_nodelay: Option, secure: bool, local_addr: Option, 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) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 96e2aef07..5dc18eada 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -60,6 +60,12 @@ impl ServiceConfigBuilder { self } + /// Sets `TCP_NODELAY` preference for accepted TCP connections. + pub fn tcp_nodelay(mut self, nodelay: Option) -> 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, + tcp_nodelay: Option, 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 { + self.0.tcp_nodelay + } + pub(crate) fn now(&self) -> Instant { self.0.date_service.now() } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index debc73e59..2ac9368fd 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -25,6 +25,16 @@ use crate::{ ConnectCallback, OnConnectData, Request, Response, }; +#[inline] +fn desired_nodelay(tcp_nodelay: Option) -> Option { + tcp_nodelay +} + +#[inline] +fn set_nodelay(stream: &TcpStream, nodelay: bool) { + let _ = stream.set_nodelay(nodelay); +} + /// `ServiceFactory` implementation for HTTP/2 transport pub struct H2Service { 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, 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| { + .map(move |io: TlsStream| { + 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, 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| { + .map(move |io: TlsStream| { + 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, 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| { + .map(move |io: TlsStream| { + 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, 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| { + .map(move |io: TlsStream| { + 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, 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| { + .map(move |io: TlsStream| { + 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) }) diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 3be099d9f..1b6391740 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -24,6 +24,16 @@ use crate::{ h1, ConnectCallback, OnConnectData, Protocol, Request, Response, ServiceConfig, }; +#[inline] +fn desired_nodelay(tcp_nodelay: Option) -> Option { + 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 { - 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 { + 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, 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| { + .map(move |io: TlsStream| { 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, 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| async { + .and_then(move |io: TlsStream| 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, 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| async { + .and_then(move |io: TlsStream| 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, 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| async { + .and_then(move |io: TlsStream| 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, 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| async { + .and_then(move |io: TlsStream| 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)) }) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 966ab3967..7be595db6 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -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, data| { + data.insert(io.get_ref().0.nodelay().unwrap()); + }) + .h2(|req: Request| { + assert_eq!(req.conn_data::(), 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, data| { + data.insert(io.get_ref().0.nodelay().unwrap()); + }) + .h2(|req: Request| { + assert_eq!(req.conn_data::(), 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); diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 53fa18ce6..96f0126d9 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -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 diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs index 54d6fcf2d..60bb116bf 100644 --- a/actix-web/src/guard/mod.rs +++ b/actix-web/src/guard/mod.rs @@ -636,7 +636,7 @@ mod tests { #[test] fn app_data() { const TEST_VALUE: u32 = 42; - let guard = fn_guard(|ctx| dbg!(ctx.app_data::()) == Some(&TEST_VALUE)); + let guard = fn_guard(|ctx| ctx.app_data::() == Some(&TEST_VALUE)); let req = TestRequest::default().app_data(TEST_VALUE).to_srv_request(); assert!(guard.check(&req.guard_ctx())); diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 2bd7c4463..39f1300bc 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -29,6 +29,7 @@ struct Socket { struct Config { host: Option, keep_alive: KeepAlive, + tcp_nodelay: Option, 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() diff --git a/actix-web/tests/test_httpserver.rs b/actix-web/tests/test_httpserver.rs index 5fd7d7190..c4f87fabb 100644 --- a/actix-web/tests/test_httpserver.rs +++ b/actix-web/tests/test_httpserver.rs @@ -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::().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::() { + 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; +}