diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d6d2555cb..a2d26d66e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,24 +3,26 @@ ## Unreleased - 2021-xx-xx ### Added - Implement `Default` for `KeepAlive`. [#2611] -- Implement `From> for KeepAlive`. [#2611] +- Implement `From` for `KeepAlive`. [#2611] +- Implement `From>` for `KeepAlive`. [#2611] +- Implement `Default` for `HttpServiceBuilder`. [#2611] ### Changed - Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611] -- Rename `ServiceConfig::{client_timer => client_request_timer}`. [#2611] - Rename `ServiceConfig::{client_disconnect_timer => client_disconnect_deadline}`. [#2611] -- Rename `ServiceConfig::{keep_alive_timer => keep_alive_deadline}`. [#2611] - Deadline methods in `ServiceConfig` now return `std::time::Instant`s instead of Tokio's wrapper type. [#2611] - Rename `h1::Codec::{keepalive => keep_alive}`. [#2611] - Rename `h1::Codec::{keepalive_enabled => keep_alive_enabled}`. [#2611] -- `HttpServiceBuilder::keep_alive` now receives a `Duration` instead of an integer number of seconds. [#2611] +- `ServiceConfig::keep_alive` now returns a `KeepAlive`. [#2611] ### Fixed - HTTP/1.1 dispatcher correctly uses client request timeout. [#2611] ### Removed +- `ServiceConfig::{client_timer, keep_alive_timer}`. [#2611] - `impl From for KeepAlive`; use `Duration`s instead. [#2611] - `impl From> for KeepAlive`; use `Duration`s instead. [#2611] +- `HttpServiceBuilder::new`; use `default` instead. [#2611] [#2611]: https://github.com/actix/actix-web/pull/2611 diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index efe75ca02..0b9383a84 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -1,18 +1,12 @@ use std::{ - cell::Cell, - fmt::{self, Write}, net, rc::Rc, - time::{Duration, Instant, SystemTime}, + time::{Duration, Instant}, }; -use actix_rt::{task::JoinHandle, time::interval}; use bytes::BytesMut; -use crate::KeepAlive; - -/// "Thu, 01 Jan 1970 00:00:00 GMT".len() -pub(crate) const DATE_VALUE_LENGTH: usize = 29; +use crate::{date::DateService, KeepAlive}; /// HTTP service configuration. #[derive(Debug, Clone)] @@ -49,14 +43,8 @@ impl ServiceConfig { secure: bool, local_addr: Option, ) -> ServiceConfig { - // zero timeout keep-alive maps to disabled - let keep_alive = match keep_alive { - KeepAlive::Timeout(Duration::ZERO) => KeepAlive::Disabled, - ka => ka, - }; - ServiceConfig(Rc::new(Inner { - keep_alive, + keep_alive: keep_alive.normalize(), client_request_timeout, client_disconnect_timeout, secure, @@ -126,164 +114,30 @@ impl ServiceConfig { self.0.date_service.now() } - pub(crate) fn set_date(&self, dst: &mut BytesMut, camel_case: bool) { + pub(crate) fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " }); self.0 .date_service - .set_date(|date| buf[6..35].copy_from_slice(&date.bytes)); + .with_date(|date| buf[6..35].copy_from_slice(&date.bytes)); buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } - pub(crate) fn write_date_header(&self, dst: &mut BytesMut) { + pub(crate) fn write_date_header_value(&self, dst: &mut BytesMut) { self.0 .date_service - .set_date(|date| dst.extend_from_slice(&date.bytes)); - } -} - -#[derive(Copy, Clone)] -struct Date { - bytes: [u8; DATE_VALUE_LENGTH], - pos: usize, -} - -impl Date { - fn new() -> Date { - let mut date = Date { - bytes: [0; DATE_VALUE_LENGTH], - pos: 0, - }; - date.update(); - date - } - - fn update(&mut self) { - self.pos = 0; - write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); - } -} - -impl fmt::Write for Date { - fn write_str(&mut self, s: &str) -> fmt::Result { - let len = s.len(); - self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); - self.pos += len; - Ok(()) - } -} - -/// Service for update Date and Instant periodically at 500 millis interval. -struct DateService { - current: Rc>, - handle: JoinHandle<()>, -} - -impl DateService { - fn new() -> Self { - // shared date and timer for DateService and update async task. - let current = Rc::new(Cell::new((Date::new(), Instant::now()))); - let current_clone = Rc::clone(¤t); - // spawn an async task sleep for 500 millis and update current date/timer in a loop. - // handle is used to stop the task on DateService drop. - let handle = actix_rt::spawn(async move { - #[cfg(test)] - let _notify = notify_on_drop::NotifyOnDrop::new(); - - let mut interval = interval(Duration::from_millis(500)); - loop { - let now = interval.tick().await; - let date = Date::new(); - current_clone.set((date, now.into_std())); - } - }); - - DateService { current, handle } - } - - fn now(&self) -> Instant { - self.current.get().1 - } - - fn set_date(&self, mut f: F) { - f(&self.current.get().0); - } -} - -impl fmt::Debug for DateService { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DateService").finish_non_exhaustive() - } -} - -impl Drop for DateService { - fn drop(&mut self) { - // stop the timer update async task on drop. - self.handle.abort(); - } -} - -// TODO: move to a util module for testing all spawn handle drop style tasks. -/// Test Module for checking the drop state of certain async tasks that are spawned -/// with `actix_rt::spawn` -/// -/// The target task must explicitly generate `NotifyOnDrop` when spawn the task -#[cfg(test)] -mod notify_on_drop { - use std::cell::RefCell; - - thread_local! { - static NOTIFY_DROPPED: RefCell> = RefCell::new(None); - } - - /// Check if the spawned task is dropped. - /// - /// # Panics - /// Panics when there was no `NotifyOnDrop` instance on current thread. - pub(crate) fn is_dropped() -> bool { - NOTIFY_DROPPED.with(|bool| { - bool.borrow() - .expect("No NotifyOnDrop existed on current thread") - }) - } - - pub(crate) struct NotifyOnDrop; - - impl NotifyOnDrop { - /// # Panics - /// Panics hen construct multiple instances on any given thread. - pub(crate) fn new() -> Self { - NOTIFY_DROPPED.with(|bool| { - let mut bool = bool.borrow_mut(); - if bool.is_some() { - panic!("NotifyOnDrop existed on current thread"); - } else { - *bool = Some(false); - } - }); - - NotifyOnDrop - } - } - - impl Drop for NotifyOnDrop { - fn drop(&mut self) { - NOTIFY_DROPPED.with(|bool| { - if let Some(b) = bool.borrow_mut().as_mut() { - *b = true; - } - }); - } + .with_date(|date| dst.extend_from_slice(&date.bytes)); } } #[cfg(test)] mod tests { use super::*; + use crate::{date::DATE_VALUE_LENGTH, notify_on_drop}; use actix_rt::{ task::yield_now, @@ -299,7 +153,7 @@ mod tests { yield_now().await; let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, false); + settings.write_date_header(&mut buf1, false); let now1 = settings.now(); sleep_until((Instant::now() + Duration::from_secs(2)).into()).await; @@ -307,7 +161,7 @@ mod tests { let now2 = settings.now(); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, false); + settings.write_date_header(&mut buf2, false); assert_ne!(now1, now2); @@ -363,10 +217,10 @@ mod tests { let settings = ServiceConfig::default(); let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf1, false); + settings.write_date_header(&mut buf1, false); let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf2, false); + settings.write_date_header(&mut buf2, false); assert_eq!(buf1, buf2); } @@ -376,11 +230,11 @@ mod tests { let settings = ServiceConfig::default(); let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf, false); + settings.write_date_header(&mut buf, false); assert!(memmem::find(&buf, b"date:").is_some()); let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); - settings.set_date(&mut buf, true); + settings.write_date_header(&mut buf, true); assert!(memmem::find(&buf, b"Date:").is_some()); } } diff --git a/actix-http/src/date.rs b/actix-http/src/date.rs new file mode 100644 index 000000000..1358bbd8c --- /dev/null +++ b/actix-http/src/date.rs @@ -0,0 +1,92 @@ +use std::{ + cell::Cell, + fmt::{self, Write}, + rc::Rc, + time::{Duration, Instant, SystemTime}, +}; + +use actix_rt::{task::JoinHandle, time::interval}; + +/// "Thu, 01 Jan 1970 00:00:00 GMT".len() +pub(crate) const DATE_VALUE_LENGTH: usize = 29; + +#[derive(Clone, Copy)] +pub(crate) struct Date { + pub(crate) bytes: [u8; DATE_VALUE_LENGTH], + pos: usize, +} + +impl Date { + fn new() -> Date { + let mut date = Date { + bytes: [0; DATE_VALUE_LENGTH], + pos: 0, + }; + date.update(); + date + } + + fn update(&mut self) { + self.pos = 0; + write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap(); + } +} + +impl fmt::Write for Date { + fn write_str(&mut self, s: &str) -> fmt::Result { + let len = s.len(); + self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes()); + self.pos += len; + Ok(()) + } +} + +/// Service for update Date and Instant periodically at 500 millis interval. +pub(crate) struct DateService { + current: Rc>, + handle: JoinHandle<()>, +} + +impl DateService { + pub(crate) fn new() -> Self { + // shared date and timer for DateService and update async task. + let current = Rc::new(Cell::new((Date::new(), Instant::now()))); + let current_clone = Rc::clone(¤t); + // spawn an async task sleep for 500 millis and update current date/timer in a loop. + // handle is used to stop the task on DateService drop. + let handle = actix_rt::spawn(async move { + #[cfg(test)] + let _notify = crate::notify_on_drop::NotifyOnDrop::new(); + + let mut interval = interval(Duration::from_millis(500)); + loop { + let now = interval.tick().await; + let date = Date::new(); + current_clone.set((date, now.into_std())); + } + }); + + DateService { current, handle } + } + + pub(crate) fn now(&self) -> Instant { + self.current.get().1 + } + + pub(crate) fn with_date(&self, mut f: F) { + f(&self.current.get().0); + } +} + +impl fmt::Debug for DateService { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DateService").finish_non_exhaustive() + } +} + +impl Drop for DateService { + fn drop(&mut self) { + // stop the timer update async task on drop. + self.handle.abort(); + } +} diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 091b71b24..a24ba5911 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -212,7 +212,7 @@ pub(crate) trait MessageType: Sized { // optimized date header, set_date writes \r\n if !has_date { - config.set_date(dst, camel_case); + config.write_date_header(dst, camel_case); } else { // msg eof dst.extend_from_slice(b"\r\n"); diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index b41a95600..7e222d61b 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -322,7 +322,7 @@ fn prepare_response( // set date header if !has_date { let mut bytes = BytesMut::with_capacity(29); - config.write_date_header(&mut bytes); + config.write_date_header_value(&mut bytes); res.headers_mut().insert( DATE, // SAFETY: serialized date-times are known ASCII strings diff --git a/actix-http/src/header/shared/http_date.rs b/actix-http/src/header/shared/http_date.rs index 473d6cad0..21ed49f0c 100644 --- a/actix-http/src/header/shared/http_date.rs +++ b/actix-http/src/header/shared/http_date.rs @@ -4,8 +4,7 @@ use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; use crate::{ - config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, - helpers::MutWriter, + date::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, helpers::MutWriter, }; /// A timestamp with HTTP-style formatting and parsing. diff --git a/actix-http/src/keep_alive.rs b/actix-http/src/keep_alive.rs index 2f6ba2c6e..136bab04c 100644 --- a/actix-http/src/keep_alive.rs +++ b/actix-http/src/keep_alive.rs @@ -30,6 +30,14 @@ impl KeepAlive { _ => None, } } + + /// Map zero duration to disabled. + pub(crate) fn normalize(self) -> KeepAlive { + match self { + KeepAlive::Timeout(Duration::ZERO) => KeepAlive::Disabled, + ka => ka, + } + } } impl Default for KeepAlive { @@ -40,17 +48,17 @@ impl Default for KeepAlive { impl From for KeepAlive { fn from(dur: Duration) -> Self { - KeepAlive::Timeout(dur) + KeepAlive::Timeout(dur).normalize() } } impl From> for KeepAlive { fn from(ka_dur: Option) -> Self { match ka_dur { - Some(Duration::ZERO) => KeepAlive::Disabled, - Some(dur) => KeepAlive::Timeout(dur), + Some(dur) => KeepAlive::from(dur), None => KeepAlive::Disabled, } + .normalize() } } @@ -66,6 +74,9 @@ mod tests { let test: KeepAlive = Duration::from_secs(0).into(); assert_eq!(test, KeepAlive::Disabled); + let test: KeepAlive = Some(Duration::from_secs(0)).into(); + assert_eq!(test, KeepAlive::Disabled); + let test: KeepAlive = None.into(); assert_eq!(test, KeepAlive::Disabled); } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 2f469fe84..c8c7d55c9 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -33,6 +33,7 @@ pub use ::http::{Method, StatusCode, Version}; pub mod body; mod builder; mod config; +mod date; #[cfg(feature = "__compress")] pub mod encoding; pub mod error; @@ -44,6 +45,8 @@ mod helpers; mod http_message; mod keep_alive; mod message; +#[cfg(test)] +mod notify_on_drop; mod payload; mod requests; mod responses; diff --git a/actix-http/src/notify_on_drop.rs b/actix-http/src/notify_on_drop.rs new file mode 100644 index 000000000..98544bb5d --- /dev/null +++ b/actix-http/src/notify_on_drop.rs @@ -0,0 +1,49 @@ +/// Test Module for checking the drop state of certain async tasks that are spawned +/// with `actix_rt::spawn` +/// +/// The target task must explicitly generate `NotifyOnDrop` when spawn the task +use std::cell::RefCell; + +thread_local! { + static NOTIFY_DROPPED: RefCell> = RefCell::new(None); +} + +/// Check if the spawned task is dropped. +/// +/// # Panics +/// Panics when there was no `NotifyOnDrop` instance on current thread. +pub(crate) fn is_dropped() -> bool { + NOTIFY_DROPPED.with(|bool| { + bool.borrow() + .expect("No NotifyOnDrop existed on current thread") + }) +} + +pub(crate) struct NotifyOnDrop; + +impl NotifyOnDrop { + /// # Panics + /// Panics hen construct multiple instances on any given thread. + pub(crate) fn new() -> Self { + NOTIFY_DROPPED.with(|bool| { + let mut bool = bool.borrow_mut(); + if bool.is_some() { + panic!("NotifyOnDrop existed on current thread"); + } else { + *bool = Some(false); + } + }); + + NotifyOnDrop + } +} + +impl Drop for NotifyOnDrop { + fn drop(&mut self) { + NOTIFY_DROPPED.with(|bool| { + if let Some(b) = bool.borrow_mut().as_mut() { + *b = true; + } + }); + } +}