1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-28 09:42:40 +01:00

add Date header to response

This commit is contained in:
Nikolay Kim 2018-10-08 10:14:29 -07:00
parent 30db78c19c
commit 431e33acb2
4 changed files with 91 additions and 75 deletions

View File

@ -48,7 +48,7 @@ struct Inner {
client_timeout: u64, client_timeout: u64,
client_disconnect: u64, client_disconnect: u64,
ka_enabled: bool, ka_enabled: bool,
date: UnsafeCell<(bool, Date)>, timer: DateService,
} }
impl Clone for ServiceConfig { impl Clone for ServiceConfig {
@ -78,7 +78,7 @@ impl ServiceConfig {
ka_enabled, ka_enabled,
client_timeout, client_timeout,
client_disconnect, client_disconnect,
date: UnsafeCell::new((false, Date::new())), timer: DateService::with(Duration::from_millis(500)),
})) }))
} }
@ -99,17 +99,14 @@ impl ServiceConfig {
self.0.ka_enabled self.0.ka_enabled
} }
fn update_date(&self) {
// Unsafe: WorkerSetting is !Sync and !Send
unsafe { (*self.0.date.get()).0 = false };
}
#[inline] #[inline]
/// Client timeout for first request. /// Client timeout for first request.
pub fn client_timer(&self) -> Option<Delay> { pub fn client_timer(&self) -> Option<Delay> {
let delay = self.0.client_timeout; let delay = self.0.client_timeout;
if delay != 0 { if delay != 0 {
Some(Delay::new(self.now() + Duration::from_millis(delay))) Some(Delay::new(
self.0.timer.now() + Duration::from_millis(delay),
))
} else { } else {
None None
} }
@ -119,7 +116,7 @@ impl ServiceConfig {
pub fn client_timer_expire(&self) -> Option<Instant> { pub fn client_timer_expire(&self) -> Option<Instant> {
let delay = self.0.client_timeout; let delay = self.0.client_timeout;
if delay != 0 { if delay != 0 {
Some(self.now() + Duration::from_millis(delay)) Some(self.0.timer.now() + Duration::from_millis(delay))
} else { } else {
None None
} }
@ -129,7 +126,7 @@ impl ServiceConfig {
pub fn client_disconnect_timer(&self) -> Option<Instant> { pub fn client_disconnect_timer(&self) -> Option<Instant> {
let delay = self.0.client_disconnect; let delay = self.0.client_disconnect;
if delay != 0 { if delay != 0 {
Some(self.now() + Duration::from_millis(delay)) Some(self.0.timer.now() + Duration::from_millis(delay))
} else { } else {
None None
} }
@ -139,7 +136,7 @@ impl ServiceConfig {
/// Return keep-alive timer delay is configured. /// Return keep-alive timer delay is configured.
pub fn keep_alive_timer(&self) -> Option<Delay> { pub fn keep_alive_timer(&self) -> Option<Delay> {
if let Some(ka) = self.0.keep_alive { if let Some(ka) = self.0.keep_alive {
Some(Delay::new(self.now() + ka)) Some(Delay::new(self.0.timer.now() + ka))
} else { } else {
None None
} }
@ -148,57 +145,23 @@ impl ServiceConfig {
/// Keep-alive expire time /// Keep-alive expire time
pub fn keep_alive_expire(&self) -> Option<Instant> { pub fn keep_alive_expire(&self) -> Option<Instant> {
if let Some(ka) = self.0.keep_alive { if let Some(ka) = self.0.keep_alive {
Some(self.now() + ka) Some(self.0.timer.now() + ka)
} else { } else {
None None
} }
} }
pub(crate) fn set_date(&self, dst: &mut BytesMut, full: bool) {
// Unsafe: WorkerSetting is !Sync and !Send
let date_bytes = unsafe {
let date = &mut (*self.0.date.get());
if !date.0 {
date.1.update();
date.0 = true;
// periodic date update
let s = self.clone();
spawn(sleep(Duration::from_millis(500)).then(move |_| {
s.update_date();
future::ok(())
}));
}
&date.1.bytes
};
if full {
let mut buf: [u8; 39] = [0; 39];
buf[..6].copy_from_slice(b"date: ");
buf[6..35].copy_from_slice(date_bytes);
buf[35..].copy_from_slice(b"\r\n\r\n");
dst.extend_from_slice(&buf);
} else {
dst.extend_from_slice(date_bytes);
}
}
#[inline] #[inline]
pub(crate) fn now(&self) -> Instant { pub(crate) fn now(&self) -> Instant {
unsafe { self.0.timer.now()
let date = &mut (*self.0.date.get()); }
if !date.0 {
date.1.update();
date.0 = true;
// periodic date update pub(crate) fn set_date(&self, dst: &mut BytesMut) {
let s = self.clone(); let mut buf: [u8; 39] = [0; 39];
spawn(sleep(Duration::from_millis(500)).then(move |_| { buf[..6].copy_from_slice(b"date: ");
s.update_date(); buf[6..35].copy_from_slice(&self.0.timer.date().bytes);
future::ok(()) buf[35..].copy_from_slice(b"\r\n\r\n");
})); dst.extend_from_slice(&buf);
}
date.1.current
}
} }
} }
@ -311,7 +274,6 @@ impl ServiceConfigBuilder {
} }
struct Date { struct Date {
current: Instant,
bytes: [u8; DATE_VALUE_LENGTH], bytes: [u8; DATE_VALUE_LENGTH],
pos: usize, pos: usize,
} }
@ -319,7 +281,6 @@ struct Date {
impl Date { impl Date {
fn new() -> Date { fn new() -> Date {
let mut date = Date { let mut date = Date {
current: Instant::now(),
bytes: [0; DATE_VALUE_LENGTH], bytes: [0; DATE_VALUE_LENGTH],
pos: 0, pos: 0,
}; };
@ -328,7 +289,6 @@ impl Date {
} }
fn update(&mut self) { fn update(&mut self) {
self.pos = 0; self.pos = 0;
self.current = Instant::now();
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap(); write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap();
} }
} }
@ -342,6 +302,68 @@ impl fmt::Write for Date {
} }
} }
#[derive(Clone)]
struct DateService(Rc<DateServiceInner>);
struct DateServiceInner {
interval: Duration,
current: UnsafeCell<Option<(Date, Instant)>>,
}
impl DateServiceInner {
fn new(interval: Duration) -> Self {
DateServiceInner {
interval,
current: UnsafeCell::new(None),
}
}
fn get_ref(&self) -> &Option<(Date, Instant)> {
unsafe { &*self.current.get() }
}
fn reset(&self) {
unsafe { (&mut *self.current.get()).take() };
}
fn update(&self) {
let now = Instant::now();
let date = Date::new();
*(unsafe { &mut *self.current.get() }) = Some((date, now));
}
}
impl DateService {
fn with(resolution: Duration) -> Self {
DateService(Rc::new(DateServiceInner::new(resolution)))
}
fn check_date(&self) {
if self.0.get_ref().is_none() {
self.0.update();
// periodic date update
let s = self.clone();
spawn(sleep(Duration::from_millis(500)).then(move |_| {
s.0.reset();
future::ok(())
}));
}
}
fn now(&self) -> Instant {
self.check_date();
self.0.get_ref().as_ref().unwrap().1
}
fn date(&self) -> &Date {
self.check_date();
let item = self.0.get_ref().as_ref().unwrap();
&item.0
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -360,9 +382,9 @@ mod tests {
let _ = rt.block_on(future::lazy(|| { let _ = rt.block_on(future::lazy(|| {
let settings = ServiceConfig::new(KeepAlive::Os, 0, 0); let settings = ServiceConfig::new(KeepAlive::Os, 0, 0);
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, true); settings.set_date(&mut buf1);
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, true); settings.set_date(&mut buf2);
assert_eq!(buf1, buf2); assert_eq!(buf1, buf2);
future::ok::<_, ()>(()) future::ok::<_, ()>(())
})); }));

View File

@ -7,6 +7,7 @@ use tokio_codec::{Decoder, Encoder};
use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder}; use super::decoder::{PayloadDecoder, PayloadItem, RequestDecoder};
use super::encoder::{ResponseEncoder, ResponseLength}; use super::encoder::{ResponseEncoder, ResponseLength};
use body::Body; use body::Body;
use config::ServiceConfig;
use error::ParseError; use error::ParseError;
use helpers; use helpers;
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
@ -48,6 +49,7 @@ pub enum InMessage {
/// HTTP/1 Codec /// HTTP/1 Codec
pub struct Codec { pub struct Codec {
config: ServiceConfig,
decoder: RequestDecoder, decoder: RequestDecoder,
payload: Option<PayloadDecoder>, payload: Option<PayloadDecoder>,
version: Version, version: Version,
@ -62,20 +64,19 @@ impl Codec {
/// Create HTTP/1 codec. /// Create HTTP/1 codec.
/// ///
/// `keepalive_enabled` how response `connection` header get generated. /// `keepalive_enabled` how response `connection` header get generated.
pub fn new(keepalive_enabled: bool) -> Self { pub fn new(config: ServiceConfig) -> Self {
Codec::with_pool(RequestPool::pool(), keepalive_enabled) Codec::with_pool(RequestPool::pool(), config)
} }
/// Create HTTP/1 codec with request's pool /// Create HTTP/1 codec with request's pool
pub(crate) fn with_pool( pub(crate) fn with_pool(pool: &'static RequestPool, config: ServiceConfig) -> Self {
pool: &'static RequestPool, keepalive_enabled: bool, let flags = if config.keep_alive_enabled() {
) -> Self {
let flags = if keepalive_enabled {
Flags::KEEPALIVE_ENABLED Flags::KEEPALIVE_ENABLED
} else { } else {
Flags::empty() Flags::empty()
}; };
Codec { Codec {
config,
decoder: RequestDecoder::with_pool(pool), decoder: RequestDecoder::with_pool(pool),
payload: None, payload: None,
version: Version::HTTP_11, version: Version::HTTP_11,
@ -217,7 +218,7 @@ impl Codec {
// optimized date header, set_date writes \r\n // optimized date header, set_date writes \r\n
if !has_date { if !has_date {
// self.settings.set_date(&mut buffer, true); self.config.set_date(buffer);
buffer.extend_from_slice(b"\r\n"); buffer.extend_from_slice(b"\r\n");
} else { } else {
// msg eof // msg eof

View File

@ -97,7 +97,7 @@ where
} else { } else {
Flags::FLUSHED Flags::FLUSHED
}; };
let framed = Framed::new(stream, Codec::new(keepalive)); let framed = Framed::new(stream, Codec::new(config.clone()));
let (ka_expire, ka_timer) = if let Some(delay) = timeout { let (ka_expire, ka_timer) = if let Some(delay) = timeout {
(delay.deadline(), Some(delay)) (delay.deadline(), Some(delay))

View File

@ -48,17 +48,10 @@
//! //!
//! ## Features //! ## Features
//! //!
//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols //! * Supported *HTTP/1.x* protocol
//! * Streaming and pipelining //! * Streaming and pipelining
//! * Keep-alive and slow requests handling //! * Keep-alive and slow requests handling
//! * `WebSockets` server/client //! * `WebSockets` server/client
//! * Transparent content compression/decompression (br, gzip, deflate)
//! * Configurable request routing
//! * Graceful server shutdown
//! * Multipart streams
//! * SSL support with OpenSSL or `native-tls`
//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`)
//! * Built on top of [Actix actor framework](https://github.com/actix/actix)
//! * Supported Rust version: 1.26 or later //! * Supported Rust version: 1.26 or later
//! //!
//! ## Package feature //! ## Package feature