use std::convert::Infallible;
use chrono::{DateTime, Utc};
use futures::stream;
use hyper::{Body, Request, Response};
use crate::output;
use crate::output::OutputFormat;
use crate::payload::SharedHistory;
use crate::utils::date::{parse_http_date, format_http_date};
use super::errors::{bad_request, initial_validation};
pub fn handle_get(
req: &Request<Body>,
history: &SharedHistory,
) -> Option<Response<Body>> {
let format = OutputFormat::from_path(req.uri().path())?;
let (session, serial, created, snapshot, metrics) = {
let history = history.read();
(
history.session(),
history.serial(),
history.created(),
history.current(),
history.metrics()
)
};
let (snapshot, metrics, created) = match (snapshot, metrics, created) {
(Some(snapshot), Some(metrics), Some(created)) => {
(snapshot, metrics, created)
}
_ => return Some(initial_validation()),
};
let etag = format!("\"{:x}-{}\"", session, serial);
if let Some(response) = maybe_not_modified(req, &etag, created) {
return Some(response)
}
let selection = match output::Selection::from_query(req.uri().query()) {
Ok(selection) => selection,
Err(_) => return Some(bad_request()),
};
let stream = format.stream(snapshot, selection, metrics);
let builder = Response::builder()
.header("Content-Type", format.content_type())
.header("ETag", etag)
.header("Last-Modified", format_http_date(created));
Some(builder.body(Body::wrap_stream(stream::iter(
stream.map(Result::<_, Infallible>::Ok)
)))
.unwrap())
}
fn maybe_not_modified(
req: &Request<Body>,
etag: &str,
done: DateTime<Utc>,
) -> Option<Response<Body>> {
for value in req.headers().get_all("If-None-Match").iter() {
let value = match value.to_str() {
Ok(value) => value,
Err(_) => continue
};
let value = value.trim();
if value == "*" {
return Some(not_modified(etag, done))
}
for tag in EtagsIter(value) {
if tag.trim() == etag {
return Some(not_modified(etag, done))
}
}
}
if let Some(value) = req.headers().get("If-Modified-Since") {
if let Some(date) = parse_http_date(value.to_str().ok()?) {
if date >= done {
return Some(not_modified(etag, done))
}
}
}
None
}
fn not_modified(etag: &str, done: DateTime<Utc>) -> Response<Body> {
Response::builder()
.status(304)
.header("ETag", etag)
.header("Last-Modified", format_http_date(done))
.body(Body::empty()).unwrap()
}
struct EtagsIter<'a>(&'a str);
impl<'a> Iterator for EtagsIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.0 = self.0.trim_start();
if self.0.is_empty() {
return None
}
let prefix_len = if self.0.starts_with('"') {
1
}
else if self.0.starts_with("W/\"") {
3
}
else {
return None
};
let end = match self.0[prefix_len..].find('"') {
Some(index) => index + prefix_len + 1,
None => return None
};
let res = &self.0[0..end];
self.0 = self.0[end..].trim_start();
if self.0.starts_with(',') {
self.0 = self.0[1..].trim_start();
}
Some(res)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn etags_iter() {
assert_eq!(
EtagsIter("\"foo\", \"bar\", \"ba,zz\"").collect::<Vec<_>>(),
["\"foo\"", "\"bar\"", "\"ba,zz\""]
);
assert_eq!(
EtagsIter("\"foo\", W/\"bar\" , \"ba,zz\", ").collect::<Vec<_>>(),
["\"foo\"", "W/\"bar\"", "\"ba,zz\""]
);
}
}