use percent_encoding;
use serde;
use serde_json;
use std::borrow::Cow;
use std::fmt;
use std::fs::File;
use std::io;
use std::io::Cursor;
use std::io::Read;
use Request;
use Upgrade;
pub struct Response {
pub status_code: u16,
pub headers: Vec<(Cow<'static, str>, Cow<'static, str>)>,
pub data: ResponseBody,
pub upgrade: Option<Box<dyn Upgrade + Send>>,
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Response")
.field("status_code", &self.status_code)
.field("headers", &self.headers)
.finish()
}
}
impl Response {
#[inline]
pub fn is_success(&self) -> bool {
self.status_code >= 200 && self.status_code < 400
}
#[inline]
pub fn is_error(&self) -> bool {
!self.is_success()
}
#[inline]
pub fn redirect_301<S>(target: S) -> Response
where
S: Into<Cow<'static, str>>,
{
Response {
status_code: 301,
headers: vec![("Location".into(), target.into())],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn redirect_302<S>(target: S) -> Response
where
S: Into<Cow<'static, str>>,
{
Response {
status_code: 302,
headers: vec![("Location".into(), target.into())],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn redirect_303<S>(target: S) -> Response
where
S: Into<Cow<'static, str>>,
{
Response {
status_code: 303,
headers: vec![("Location".into(), target.into())],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn redirect_307<S>(target: S) -> Response
where
S: Into<Cow<'static, str>>,
{
Response {
status_code: 307,
headers: vec![("Location".into(), target.into())],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn redirect_308<S>(target: S) -> Response
where
S: Into<Cow<'static, str>>,
{
Response {
status_code: 308,
headers: vec![("Location".into(), target.into())],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn from_data<C, D>(content_type: C, data: D) -> Response
where
C: Into<Cow<'static, str>>,
D: Into<Vec<u8>>,
{
Response {
status_code: 200,
headers: vec![("Content-Type".into(), content_type.into())],
data: ResponseBody::from_data(data),
upgrade: None,
}
}
#[inline]
pub fn from_file<C>(content_type: C, file: File) -> Response
where
C: Into<Cow<'static, str>>,
{
Response {
status_code: 200,
headers: vec![("Content-Type".into(), content_type.into())],
data: ResponseBody::from_file(file),
upgrade: None,
}
}
#[inline]
pub fn html<D>(content: D) -> Response
where
D: Into<String>,
{
Response {
status_code: 200,
headers: vec![("Content-Type".into(), "text/html; charset=utf-8".into())],
data: ResponseBody::from_string(content),
upgrade: None,
}
}
#[inline]
pub fn svg<D>(content: D) -> Response
where
D: Into<String>,
{
Response {
status_code: 200,
headers: vec![("Content-Type".into(), "image/svg+xml; charset=utf-8".into())],
data: ResponseBody::from_string(content),
upgrade: None,
}
}
#[inline]
pub fn text<S>(text: S) -> Response
where
S: Into<String>,
{
Response {
status_code: 200,
headers: vec![("Content-Type".into(), "text/plain; charset=utf-8".into())],
data: ResponseBody::from_string(text),
upgrade: None,
}
}
#[inline]
pub fn json<T>(content: &T) -> Response
where
T: serde::Serialize,
{
let data = serde_json::to_string(content).unwrap();
Response {
status_code: 200,
headers: vec![(
"Content-Type".into(),
"application/json; charset=utf-8".into(),
)],
data: ResponseBody::from_data(data),
upgrade: None,
}
}
#[inline]
pub fn basic_http_auth_login_required(realm: &str) -> Response {
Response {
status_code: 401,
headers: vec![(
"WWW-Authenticate".into(),
format!("Basic realm=\"{}\"", realm).into(),
)],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn empty_204() -> Response {
Response {
status_code: 204,
headers: vec![],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn empty_400() -> Response {
Response {
status_code: 400,
headers: vec![],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn empty_404() -> Response {
Response {
status_code: 404,
headers: vec![],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn empty_406() -> Response {
Response {
status_code: 406,
headers: vec![],
data: ResponseBody::empty(),
upgrade: None,
}
}
#[inline]
pub fn with_status_code(mut self, code: u16) -> Response {
self.status_code = code;
self
}
pub fn without_header(mut self, header: &str) -> Response {
self.headers
.retain(|&(ref h, _)| !h.eq_ignore_ascii_case(header));
self
}
#[inline]
pub fn with_additional_header<H, V>(mut self, header: H, value: V) -> Response
where
H: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>,
{
self.headers.push((header.into(), value.into()));
self
}
pub fn with_unique_header<H, V>(mut self, header: H, value: V) -> Response
where
H: Into<Cow<'static, str>>,
V: Into<Cow<'static, str>>,
{
let header = header.into();
let mut found_one = false;
self.headers.retain(|&(ref h, _)| {
if h.eq_ignore_ascii_case(&header) {
if !found_one {
found_one = true;
true
} else {
false
}
} else {
true
}
});
if found_one {
for &mut (ref h, ref mut v) in &mut self.headers {
if !h.eq_ignore_ascii_case(&header) {
continue;
}
*v = value.into();
break;
}
self
} else {
self.with_additional_header(header, value)
}
}
#[inline]
pub fn with_etag<E>(self, request: &Request, etag: E) -> Response
where
E: Into<Cow<'static, str>>,
{
self.with_etag_keep(etag).simplify_if_etag_match(request)
}
pub fn simplify_if_etag_match(mut self, request: &Request) -> Response {
if self.status_code < 200 || self.status_code >= 300 {
return self;
}
let mut not_modified = false;
for &(ref key, ref etag) in &self.headers {
if !key.eq_ignore_ascii_case("ETag") {
continue;
}
not_modified = request
.header("If-None-Match")
.map(|header| header == etag)
.unwrap_or(false);
}
if not_modified {
self.data = ResponseBody::empty();
self.status_code = 304;
}
self
}
#[inline]
pub fn with_etag_keep<E>(self, etag: E) -> Response
where
E: Into<Cow<'static, str>>,
{
self.with_unique_header("ETag", etag)
}
pub fn with_content_disposition_attachment(mut self, filename: &str) -> Response {
let name = percent_encoding::percent_encode(filename.as_bytes(), super::DEFAULT_ENCODE_SET);
let mut header = Some(format!("attachment; filename*=UTF8''{}", name).into());
for &mut (ref key, ref mut val) in &mut self.headers {
if key.eq_ignore_ascii_case("Content-Disposition") {
*val = header.take().unwrap();
break;
}
}
if let Some(header) = header {
self.headers.push(("Content-Disposition".into(), header));
}
self
}
#[inline]
pub fn with_public_cache(self, max_age_seconds: u64) -> Response {
self.with_unique_header(
"Cache-Control",
format!("public, max-age={}", max_age_seconds),
)
.without_header("Expires")
.without_header("Pragma")
}
#[inline]
pub fn with_private_cache(self, max_age_seconds: u64) -> Response {
self.with_unique_header(
"Cache-Control",
format!("private, max-age={}", max_age_seconds),
)
.without_header("Expires")
.without_header("Pragma")
}
#[inline]
pub fn with_no_cache(self) -> Response {
self.with_unique_header("Cache-Control", "no-cache, no-store, must-revalidate")
.with_unique_header("Expires", "0")
.with_unique_header("Pragma", "no-cache")
}
}
pub struct ResponseBody {
data: Box<dyn Read + Send>,
data_length: Option<usize>,
}
impl ResponseBody {
#[inline]
pub fn empty() -> ResponseBody {
ResponseBody {
data: Box::new(io::empty()),
data_length: Some(0),
}
}
#[inline]
pub fn from_reader<R>(data: R) -> ResponseBody
where
R: Read + Send + 'static,
{
ResponseBody {
data: Box::new(data),
data_length: None,
}
}
#[inline]
pub fn from_reader_and_size<R>(data: R, size: usize) -> ResponseBody
where
R: Read + Send + 'static,
{
ResponseBody {
data: Box::new(data),
data_length: Some(size),
}
}
#[inline]
pub fn from_data<D>(data: D) -> ResponseBody
where
D: Into<Vec<u8>>,
{
let data = data.into();
let len = data.len();
ResponseBody {
data: Box::new(Cursor::new(data)),
data_length: Some(len),
}
}
#[inline]
pub fn from_file(file: File) -> ResponseBody {
let len = file.metadata().map(|metadata| metadata.len() as usize).ok();
ResponseBody {
data: Box::new(file),
data_length: len,
}
}
#[inline]
pub fn from_string<S>(data: S) -> ResponseBody
where
S: Into<String>,
{
ResponseBody::from_data(data.into().into_bytes())
}
#[inline]
pub fn into_reader_and_size(self) -> (Box<dyn Read + Send>, Option<usize>) {
(self.data, self.data_length)
}
}
#[cfg(test)]
mod tests {
use Response;
#[test]
fn unique_header_adds() {
let r = Response {
headers: vec![],
..Response::empty_400()
};
let r = r.with_unique_header("Foo", "Bar");
assert_eq!(r.headers.len(), 1);
assert_eq!(r.headers[0], ("Foo".into(), "Bar".into()));
}
#[test]
fn unique_header_adds_without_touching() {
let r = Response {
headers: vec![("Bar".into(), "Foo".into())],
..Response::empty_400()
};
let r = r.with_unique_header("Foo", "Bar");
assert_eq!(r.headers.len(), 2);
assert_eq!(r.headers[0], ("Bar".into(), "Foo".into()));
assert_eq!(r.headers[1], ("Foo".into(), "Bar".into()));
}
#[test]
fn unique_header_replaces() {
let r = Response {
headers: vec![
("foo".into(), "A".into()),
("fOO".into(), "B".into()),
("Foo".into(), "C".into()),
],
..Response::empty_400()
};
let r = r.with_unique_header("Foo", "Bar");
assert_eq!(r.headers.len(), 1);
assert_eq!(r.headers[0], ("foo".into(), "Bar".into()));
}
}