split out config module

This commit is contained in:
Rob Ede 2022-01-31 14:47:04 +00:00
parent 312e415cd4
commit 28b27552e9
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
9 changed files with 181 additions and 171 deletions

View File

@ -3,24 +3,26 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Added ### Added
- Implement `Default` for `KeepAlive`. [#2611] - Implement `Default` for `KeepAlive`. [#2611]
- Implement `From<Option<Duration>> for KeepAlive`. [#2611] - Implement `From<Duration>` for `KeepAlive`. [#2611]
- Implement `From<Option<Duration>>` for `KeepAlive`. [#2611]
- Implement `Default` for `HttpServiceBuilder`. [#2611]
### Changed ### Changed
- Rename `ServiceConfig::{client_timer_expire => client_request_deadline}`. [#2611] - 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::{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] - 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 => keep_alive}`. [#2611]
- Rename `h1::Codec::{keepalive_enabled => keep_alive_enabled}`. [#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 ### Fixed
- HTTP/1.1 dispatcher correctly uses client request timeout. [#2611] - HTTP/1.1 dispatcher correctly uses client request timeout. [#2611]
### Removed ### Removed
- `ServiceConfig::{client_timer, keep_alive_timer}`. [#2611]
- `impl From<usize> for KeepAlive`; use `Duration`s instead. [#2611] - `impl From<usize> for KeepAlive`; use `Duration`s instead. [#2611]
- `impl From<Option<usize>> for KeepAlive`; use `Duration`s instead. [#2611] - `impl From<Option<usize>> for KeepAlive`; use `Duration`s instead. [#2611]
- `HttpServiceBuilder::new`; use `default` instead. [#2611]
[#2611]: https://github.com/actix/actix-web/pull/2611 [#2611]: https://github.com/actix/actix-web/pull/2611

View File

@ -1,18 +1,12 @@
use std::{ use std::{
cell::Cell,
fmt::{self, Write},
net, net,
rc::Rc, rc::Rc,
time::{Duration, Instant, SystemTime}, time::{Duration, Instant},
}; };
use actix_rt::{task::JoinHandle, time::interval};
use bytes::BytesMut; use bytes::BytesMut;
use crate::KeepAlive; use crate::{date::DateService, KeepAlive};
/// "Thu, 01 Jan 1970 00:00:00 GMT".len()
pub(crate) const DATE_VALUE_LENGTH: usize = 29;
/// HTTP service configuration. /// HTTP service configuration.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -49,14 +43,8 @@ impl ServiceConfig {
secure: bool, secure: bool,
local_addr: Option<net::SocketAddr>, local_addr: Option<net::SocketAddr>,
) -> ServiceConfig { ) -> 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 { ServiceConfig(Rc::new(Inner {
keep_alive, keep_alive: keep_alive.normalize(),
client_request_timeout, client_request_timeout,
client_disconnect_timeout, client_disconnect_timeout,
secure, secure,
@ -126,164 +114,30 @@ impl ServiceConfig {
self.0.date_service.now() 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]; let mut buf: [u8; 39] = [0; 39];
buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " }); buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " });
self.0 self.0
.date_service .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"); buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf); 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 self.0
.date_service .date_service
.set_date(|date| dst.extend_from_slice(&date.bytes)); .with_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<Cell<(Date, Instant)>>,
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(&current);
// 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<F: FnMut(&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<Option<bool>> = 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;
}
});
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{date::DATE_VALUE_LENGTH, notify_on_drop};
use actix_rt::{ use actix_rt::{
task::yield_now, task::yield_now,
@ -299,7 +153,7 @@ mod tests {
yield_now().await; yield_now().await;
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); 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(); let now1 = settings.now();
sleep_until((Instant::now() + Duration::from_secs(2)).into()).await; sleep_until((Instant::now() + Duration::from_secs(2)).into()).await;
@ -307,7 +161,7 @@ mod tests {
let now2 = settings.now(); let now2 = settings.now();
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); 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); assert_ne!(now1, now2);
@ -363,10 +217,10 @@ mod tests {
let settings = ServiceConfig::default(); let settings = ServiceConfig::default();
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); 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); 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); assert_eq!(buf1, buf2);
} }
@ -376,11 +230,11 @@ mod tests {
let settings = ServiceConfig::default(); let settings = ServiceConfig::default();
let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); 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()); assert!(memmem::find(&buf, b"date:").is_some());
let mut buf = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); 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()); assert!(memmem::find(&buf, b"Date:").is_some());
} }
} }

92
actix-http/src/date.rs Normal file
View File

@ -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<Cell<(Date, Instant)>>,
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(&current);
// 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<F: FnMut(&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();
}
}

View File

@ -212,7 +212,7 @@ pub(crate) trait MessageType: Sized {
// optimized date header, set_date writes \r\n // optimized date header, set_date writes \r\n
if !has_date { if !has_date {
config.set_date(dst, camel_case); config.write_date_header(dst, camel_case);
} else { } else {
// msg eof // msg eof
dst.extend_from_slice(b"\r\n"); dst.extend_from_slice(b"\r\n");

View File

@ -322,7 +322,7 @@ fn prepare_response(
// set date header // set date header
if !has_date { if !has_date {
let mut bytes = BytesMut::with_capacity(29); let mut bytes = BytesMut::with_capacity(29);
config.write_date_header(&mut bytes); config.write_date_header_value(&mut bytes);
res.headers_mut().insert( res.headers_mut().insert(
DATE, DATE,
// SAFETY: serialized date-times are known ASCII strings // SAFETY: serialized date-times are known ASCII strings

View File

@ -4,8 +4,7 @@ use bytes::BytesMut;
use http::header::{HeaderValue, InvalidHeaderValue}; use http::header::{HeaderValue, InvalidHeaderValue};
use crate::{ use crate::{
config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, date::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, helpers::MutWriter,
helpers::MutWriter,
}; };
/// A timestamp with HTTP-style formatting and parsing. /// A timestamp with HTTP-style formatting and parsing.

View File

@ -30,6 +30,14 @@ impl KeepAlive {
_ => None, _ => 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 { impl Default for KeepAlive {
@ -40,17 +48,17 @@ impl Default for KeepAlive {
impl From<Duration> for KeepAlive { impl From<Duration> for KeepAlive {
fn from(dur: Duration) -> Self { fn from(dur: Duration) -> Self {
KeepAlive::Timeout(dur) KeepAlive::Timeout(dur).normalize()
} }
} }
impl From<Option<Duration>> for KeepAlive { impl From<Option<Duration>> for KeepAlive {
fn from(ka_dur: Option<Duration>) -> Self { fn from(ka_dur: Option<Duration>) -> Self {
match ka_dur { match ka_dur {
Some(Duration::ZERO) => KeepAlive::Disabled, Some(dur) => KeepAlive::from(dur),
Some(dur) => KeepAlive::Timeout(dur),
None => KeepAlive::Disabled, None => KeepAlive::Disabled,
} }
.normalize()
} }
} }
@ -66,6 +74,9 @@ mod tests {
let test: KeepAlive = Duration::from_secs(0).into(); let test: KeepAlive = Duration::from_secs(0).into();
assert_eq!(test, KeepAlive::Disabled); assert_eq!(test, KeepAlive::Disabled);
let test: KeepAlive = Some(Duration::from_secs(0)).into();
assert_eq!(test, KeepAlive::Disabled);
let test: KeepAlive = None.into(); let test: KeepAlive = None.into();
assert_eq!(test, KeepAlive::Disabled); assert_eq!(test, KeepAlive::Disabled);
} }

View File

@ -33,6 +33,7 @@ pub use ::http::{Method, StatusCode, Version};
pub mod body; pub mod body;
mod builder; mod builder;
mod config; mod config;
mod date;
#[cfg(feature = "__compress")] #[cfg(feature = "__compress")]
pub mod encoding; pub mod encoding;
pub mod error; pub mod error;
@ -44,6 +45,8 @@ mod helpers;
mod http_message; mod http_message;
mod keep_alive; mod keep_alive;
mod message; mod message;
#[cfg(test)]
mod notify_on_drop;
mod payload; mod payload;
mod requests; mod requests;
mod responses; mod responses;

View File

@ -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<Option<bool>> = 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;
}
});
}
}