xitca_http/
date.rs

1//! low resolution async date time for reduced syscall for generating http date time.
2
3use std::{
4    cell::RefCell,
5    fmt::{self, Write},
6    ops::Deref,
7    rc::Rc,
8    time::{Duration, SystemTime},
9};
10
11use httpdate::HttpDate;
12use tokio::{
13    task::JoinHandle,
14    time::{Instant, interval},
15};
16
17/// Trait for getting current date/time.
18///
19/// This is usually used by a low resolution of timer to reduce frequent syscall to OS.
20pub trait DateTime {
21    /// The size hint of slice by Self::date method.
22    const DATE_VALUE_LENGTH: usize;
23
24    /// closure would receive byte slice representation of [HttpDate].
25    fn with_date<F, O>(&self, f: F) -> O
26    where
27        F: FnOnce(&[u8]) -> O;
28
29    fn now(&self) -> Instant;
30}
31
32/// Struct with Date update periodically at 500 milliseconds interval.
33pub struct DateTimeService {
34    state: Rc<RefCell<DateTimeState>>,
35    handle: JoinHandle<()>,
36}
37
38impl Drop for DateTimeService {
39    fn drop(&mut self) {
40        // stop the timer update async task on drop.
41        self.handle.abort();
42    }
43}
44
45impl Default for DateTimeService {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl DateTimeService {
52    pub fn new() -> Self {
53        // shared date and timer for Date and update async task.
54        let state = Rc::new(RefCell::new(DateTimeState::new()));
55        let state_clone = Rc::clone(&state);
56        // spawn an async task sleep for 1 sec and update date in a loop.
57        // handle is used to stop the task on Date drop.
58        let handle = tokio::task::spawn_local(async move {
59            let mut interval = interval(Duration::from_millis(500));
60            let state = &*state_clone;
61            loop {
62                let _ = interval.tick().await;
63                *state.borrow_mut() = DateTimeState::new();
64            }
65        });
66
67        Self { state, handle }
68    }
69
70    #[inline]
71    pub fn get(&self) -> &DateTimeHandle {
72        self.state.deref()
73    }
74}
75
76pub(crate) type DateTimeHandle = RefCell<DateTimeState>;
77
78/// The length of byte representation of [HttpDate].
79pub const DATE_VALUE_LENGTH: usize = 29;
80
81/// struct contains byte representation of [HttpDate] and [Instant].
82#[derive(Copy, Clone)]
83pub struct DateTimeState {
84    pub date: [u8; DATE_VALUE_LENGTH],
85    pub now: Instant,
86}
87
88impl Default for DateTimeState {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94impl DateTimeState {
95    pub fn new() -> Self {
96        let mut date = Self {
97            date: [0; DATE_VALUE_LENGTH],
98            now: Instant::now(),
99        };
100        let _ = write!(date, "{}", HttpDate::from(SystemTime::now()));
101        date
102    }
103}
104
105impl Write for DateTimeState {
106    fn write_str(&mut self, s: &str) -> fmt::Result {
107        self.date[..].copy_from_slice(s.as_bytes());
108        Ok(())
109    }
110}
111
112impl DateTime for DateTimeHandle {
113    const DATE_VALUE_LENGTH: usize = DATE_VALUE_LENGTH;
114
115    // TODO: remove this allow
116    #[allow(dead_code)]
117    #[inline]
118    fn with_date<F, O>(&self, f: F) -> O
119    where
120        F: FnOnce(&[u8]) -> O,
121    {
122        let date = self.borrow();
123        f(&date.date[..])
124    }
125
126    #[inline(always)]
127    fn now(&self) -> Instant {
128        self.borrow().now
129    }
130}
131
132/// Time handler powered by plain OS system time. useful for testing purpose.
133pub struct SystemTimeDateTimeHandler;
134
135impl DateTime for SystemTimeDateTimeHandler {
136    const DATE_VALUE_LENGTH: usize = DATE_VALUE_LENGTH;
137
138    // TODO: remove this allow
139    #[allow(dead_code)]
140    fn with_date<F, O>(&self, f: F) -> O
141    where
142        F: FnOnce(&[u8]) -> O,
143    {
144        let date = HttpDate::from(SystemTime::now()).to_string();
145        f(date.as_bytes())
146    }
147
148    fn now(&self) -> Instant {
149        Instant::now()
150    }
151}