| // Copyright (c) 2016 The Rouille developers |
| // Licensed under the Apache License, Version 2.0 |
| // <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT |
| // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
| // at your option. All files in the project carrying such |
| // notice may not be copied, modified, or distributed except |
| // according to those terms. |
| |
| use std::borrow::Cow; |
| use std::io; |
| use std::io::Cursor; |
| use std::io::Read; |
| use std::fs::File; |
| use std::fmt; |
| use serde; |
| use serde_json; |
| use url::percent_encoding; |
| use Request; |
| use Upgrade; |
| |
| // The AsciiExt import is needed for Rust older than 1.23.0. These two lines can |
| // be removed when supporting older Rust is no longer needed. |
| #[allow(unused_imports)] |
| use std::ascii::AsciiExt; |
| |
| /// Contains a prototype of a response. |
| /// |
| /// The response is only sent to the client when you return the `Response` object from your |
| /// request handler. This means that you are free to create as many `Response` objects as you want. |
| pub struct Response { |
| /// The status code to return to the user. |
| pub status_code: u16, |
| |
| /// List of headers to be returned in the response. |
| /// |
| /// The value of the following headers will be ignored from this list, even if present: |
| /// |
| /// - Accept-Ranges |
| /// - Connection |
| /// - Content-Length |
| /// - Content-Range |
| /// - Trailer |
| /// - Transfer-Encoding |
| /// |
| /// Additionnaly, the `Upgrade` header is ignored as well unless the `upgrade` field of the |
| /// `Response` is set to something. |
| /// |
| /// The reason for this is that these headers are too low-level and are directly handled by |
| /// the underlying HTTP response system. |
| /// |
| /// The value of `Content-Length` is automatically determined by the `ResponseBody` object of |
| /// the `data` member. |
| /// |
| /// If you want to send back `Connection: upgrade`, you should set the value of the `upgrade` |
| /// field to something. |
| pub headers: Vec<(Cow<'static, str>, Cow<'static, str>)>, |
| |
| /// An opaque type that contains the body of the response. |
| pub data: ResponseBody, |
| |
| /// If set, rouille will give ownership of the client socket to the `Upgrade` object. |
| /// |
| /// In all circumstances, the value of the `Connection` header is managed by the framework and |
| /// cannot be customized. If this value is set, the response will automatically contain |
| /// `Connection: Upgrade`. |
| pub upgrade: Option<Box<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 { |
| /// Returns true if the status code of this `Response` indicates success. |
| /// |
| /// This is the range [200-399]. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::text("hello world"); |
| /// assert!(response.is_success()); |
| /// ``` |
| #[inline] |
| pub fn is_success(&self) -> bool { |
| self.status_code >= 200 && self.status_code < 400 |
| } |
| |
| /// Shortcut for `!response.is_success()`. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::empty_400(); |
| /// assert!(response.is_error()); |
| /// ``` |
| #[inline] |
| pub fn is_error(&self) -> bool { |
| !self.is_success() |
| } |
| |
| /// Builds a `Response` that redirects the user to another URL with a 301 status code. This |
| /// semantically means a permanent redirect. |
| /// |
| /// > **Note**: If you're uncertain about which status code to use for a redirection, 303 is |
| /// > the safest choice. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::redirect_301("/foo"); |
| /// ``` |
| #[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, |
| } |
| } |
| |
| /// Builds a `Response` that redirects the user to another URL with a 302 status code. This |
| /// semantically means a temporary redirect. |
| /// |
| /// > **Note**: If you're uncertain about which status code to use for a redirection, 303 is |
| /// > the safest choice. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::redirect_302("/bar"); |
| /// ``` |
| #[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, |
| } |
| } |
| |
| /// Builds a `Response` that redirects the user to another URL with a 303 status code. This |
| /// means "See Other" and is usually used to indicate where the response of a query is |
| /// located. |
| /// |
| /// For example when a user sends a POST request to URL `/foo` the server can return a 303 |
| /// response with a target to `/bar`, in which case the browser will automatically change |
| /// the page to `/bar` (with a GET request to `/bar`). |
| /// |
| /// > **Note**: If you're uncertain about which status code to use for a redirection, 303 is |
| /// > the safest choice. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let user_id = 5; |
| /// let response = Response::redirect_303(format!("/users/{}", user_id)); |
| /// ``` |
| #[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, |
| } |
| } |
| |
| /// Builds a `Response` that redirects the user to another URL with a 307 status code. This |
| /// semantically means a permanent redirect. |
| /// |
| /// The difference between 307 and 301 is that the client must keep the same method after |
| /// the redirection. For example if the browser sends a POST request to `/foo` and that route |
| /// returns a 307 redirection to `/bar`, then the browser will make a POST request to `/bar`. |
| /// With a 301 redirection it would use a GET request instead. |
| /// |
| /// > **Note**: If you're uncertain about which status code to use for a redirection, 303 is |
| /// > the safest choice. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::redirect_307("/foo"); |
| /// ``` |
| #[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, |
| } |
| } |
| |
| /// Builds a `Response` that redirects the user to another URL with a 302 status code. This |
| /// semantically means a temporary redirect. |
| /// |
| /// The difference between 308 and 302 is that the client must keep the same method after |
| /// the redirection. For example if the browser sends a POST request to `/foo` and that route |
| /// returns a 308 redirection to `/bar`, then the browser will make a POST request to `/bar`. |
| /// With a 302 redirection it would use a GET request instead. |
| /// |
| /// > **Note**: If you're uncertain about which status code to use for a redirection, 303 is |
| /// > the safest choice. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::redirect_302("/bar"); |
| /// ``` |
| #[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, |
| } |
| } |
| |
| /// Builds a 200 `Response` with data. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::from_data("application/octet-stream", vec![1, 2, 3, 4]); |
| /// ``` |
| #[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, |
| } |
| } |
| |
| /// Builds a 200 `Response` with the content of a file. |
| /// |
| /// # Example |
| /// |
| /// ```no_run |
| /// use std::fs::File; |
| /// use rouille::Response; |
| /// |
| /// let file = File::open("image.png").unwrap(); |
| /// let response = Response::from_file("image/png", file); |
| /// ``` |
| #[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, |
| } |
| } |
| |
| /// Builds a `Response` that outputs HTML. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::html("<p>hello <strong>world</strong></p>"); |
| /// ``` |
| #[inline] |
| pub fn html<D>(content: D) -> Response where D: Into<String> { |
| Response { |
| status_code: 200, |
| headers: vec![("Content-Type".into(), "text/html; charset=utf8".into())], |
| data: ResponseBody::from_string(content), |
| upgrade: None, |
| } |
| } |
| |
| /// Builds a `Response` that outputs SVG. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::svg("<svg xmlns='http://www.w3.org/2000/svg'/>"); |
| /// ``` |
| #[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=utf8".into())], |
| data: ResponseBody::from_string(content), |
| upgrade: None, |
| } |
| } |
| |
| /// Builds a `Response` that outputs plain text. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::text("hello world"); |
| /// ``` |
| #[inline] |
| pub fn text<S>(text: S) -> Response where S: Into<String> { |
| Response { |
| status_code: 200, |
| headers: vec![("Content-Type".into(), "text/plain; charset=utf8".into())], |
| data: ResponseBody::from_string(text), |
| upgrade: None, |
| } |
| } |
| |
| /// Builds a `Response` that outputs JSON. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// extern crate serde; |
| /// #[macro_use] extern crate serde_derive; |
| /// #[macro_use] extern crate rouille; |
| /// use rouille::Response; |
| /// # fn main() { |
| /// |
| /// #[derive(Serialize)] |
| /// struct MyStruct { |
| /// field1: String, |
| /// field2: i32, |
| /// } |
| /// |
| /// let response = Response::json(&MyStruct { field1: "hello".to_owned(), field2: 5 }); |
| /// // The Response will contain something like `{ field1: "hello", field2: 5 }` |
| /// # } |
| /// ``` |
| #[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".into())], |
| data: ResponseBody::from_data(data), |
| upgrade: None, |
| } |
| } |
| |
| /// Builds a `Response` that returns a `401 Not Authorized` status |
| /// and a `WWW-Authenticate` header. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::basic_http_auth_login_required("realm"); |
| /// ``` |
| #[inline] |
| pub fn basic_http_auth_login_required(realm: &str) -> Response { |
| // TODO: escape the realm |
| Response { |
| status_code: 401, |
| headers: vec![("WWW-Authenticate".into(), format!("Basic realm=\"{}\"", realm).into())], |
| data: ResponseBody::empty(), |
| upgrade: None, |
| } |
| } |
| |
| /// Builds an empty `Response` with a 204 status code. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::empty_204(); |
| /// ``` |
| #[inline] |
| pub fn empty_204() -> Response { |
| Response { |
| status_code: 204, |
| headers: vec![], |
| data: ResponseBody::empty(), |
| upgrade: None, |
| } |
| } |
| |
| /// Builds an empty `Response` with a 400 status code. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::empty_400(); |
| /// ``` |
| #[inline] |
| pub fn empty_400() -> Response { |
| Response { |
| status_code: 400, |
| headers: vec![], |
| data: ResponseBody::empty(), |
| upgrade: None, |
| } |
| } |
| |
| /// Builds an empty `Response` with a 404 status code. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::empty_404(); |
| /// ``` |
| #[inline] |
| pub fn empty_404() -> Response { |
| Response { |
| status_code: 404, |
| headers: vec![], |
| data: ResponseBody::empty(), |
| upgrade: None, |
| } |
| } |
| |
| /// Builds an empty `Response` with a 406 status code. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::empty_406(); |
| /// ``` |
| #[inline] |
| pub fn empty_406() -> Response { |
| Response { |
| status_code: 406, |
| headers: vec![], |
| data: ResponseBody::empty(), |
| upgrade: None, |
| } |
| } |
| |
| /// Changes the status code of the response. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::Response; |
| /// let response = Response::text("hello world").with_status_code(500); |
| /// ``` |
| #[inline] |
| pub fn with_status_code(mut self, code: u16) -> Response { |
| self.status_code = code; |
| self |
| } |
| |
| /// Removes all headers from the response that match `header`. |
| pub fn without_header(mut self, header: &str) -> Response { |
| self.headers.retain(|&(ref h, _)| !h.eq_ignore_ascii_case(header)); |
| self |
| } |
| |
| /// Adds an additional header to the response. |
| #[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 |
| } |
| |
| /// Removes all headers from the response whose names are `header`, and replaces them . |
| 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>> |
| { |
| // If Vec::retain provided a mutable reference this code would be much simpler and would |
| // only need to iterate once. |
| // See https://github.com/rust-lang/rust/issues/25477 |
| |
| // TODO: if the response already has a matching header we shouldn't have to build a Cow |
| // from the header |
| |
| 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) |
| } |
| } |
| |
| /// Adds or replaces a `ETag` header to the response, and turns the response into an empty 304 |
| /// response if the ETag matches a `If-None-Match` header of the request. |
| /// |
| /// An ETag is a unique representation of the content of a resource. If the content of the |
| /// resource changes, the ETag should change as well. |
| /// The purpose of using ETags is that a client can later ask the server to send the body of |
| /// a response only if it still matches a certain ETag the client has stored in memory. |
| /// |
| /// > **Note**: You should always try to specify an ETag for responses that have a large body. |
| /// |
| /// # Example |
| /// |
| /// ```rust |
| /// use rouille::Request; |
| /// use rouille::Response; |
| /// |
| /// fn handle(request: &Request) -> Response { |
| /// Response::text("hello world").with_etag(request, "my-etag-1234") |
| /// } |
| /// ``` |
| #[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) |
| } |
| |
| /// Turns the response into an empty 304 response if the `ETag` that is stored in it matches a |
| /// `If-None-Match` header of the 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 |
| } |
| |
| /// Adds a `ETag` header to the response, or replaces an existing header if there is one. |
| /// |
| /// > **Note**: Contrary to `with_etag`, this function doesn't try to turn the response into |
| /// > a 304 response. If you're unsure of what to do, prefer `with_etag`. |
| #[inline] |
| pub fn with_etag_keep<E>(self, etag: E) -> Response |
| where E: Into<Cow<'static, str>> |
| { |
| self.with_unique_header("ETag", etag) |
| } |
| |
| /// Adds or replace a `Content-Disposition` header of the response. Tells the browser that the |
| /// body of the request should fire a download popup instead of being shown in the browser. |
| /// |
| /// # Example |
| /// |
| /// ```rust |
| /// use rouille::Request; |
| /// use rouille::Response; |
| /// |
| /// fn handle(request: &Request) -> Response { |
| /// Response::text("hello world").with_content_disposition_attachment("book.txt") |
| /// } |
| /// ``` |
| /// |
| /// When the response is sent back to the browser, it will show a popup asking the user to |
| /// download the file "book.txt" whose content will be "hello world". |
| pub fn with_content_disposition_attachment(mut self, filename: &str) -> Response { |
| // The name must be percent-encoded. |
| let name = percent_encoding::percent_encode(filename.as_bytes(), |
| percent_encoding::DEFAULT_ENCODE_SET); |
| |
| // If you find a more elegant way to do the thing below, don't hesitate to open a PR |
| |
| // Support for this format varies browser by browser, so this may not be the most |
| // ideal thing. |
| // TODO: it's maybe possible to specify multiple file names |
| 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 |
| } |
| |
| /// Adds or replaces a `Cache-Control` header that specifies that the resource is public and |
| /// can be cached for the given number of seconds. |
| /// |
| /// > **Note**: This function doesn't do any caching itself. It just indicates that clients |
| /// > that receive this response are allowed to cache it. |
| #[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") |
| } |
| |
| /// Adds or replaces a `Cache-Control` header that specifies that the resource is private and |
| /// can be cached for the given number of seconds. |
| /// |
| /// Only the browser or the final client is authorized to cache the resource. Intermediate |
| /// proxies must not cache it. |
| /// |
| /// > **Note**: This function doesn't do any caching itself. It just indicates that clients |
| /// > that receive this response are allowed to cache it. |
| #[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") |
| } |
| |
| /// Adds or replaces a `Cache-Control` header that specifies that the client must not cache |
| /// the resource. |
| #[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") |
| } |
| } |
| |
| /// An opaque type that represents the body of a response. |
| /// |
| /// You can't access the inside of this struct, but you can build one by using one of the provided |
| /// constructors. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::ResponseBody; |
| /// let body = ResponseBody::from_string("hello world"); |
| /// ``` |
| pub struct ResponseBody { |
| data: Box<Read + Send>, |
| data_length: Option<usize>, |
| } |
| |
| impl ResponseBody { |
| /// Builds a `ResponseBody` that doesn't return any data. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::ResponseBody; |
| /// let body = ResponseBody::empty(); |
| /// ``` |
| #[inline] |
| pub fn empty() -> ResponseBody { |
| ResponseBody { |
| data: Box::new(io::empty()), |
| data_length: Some(0), |
| } |
| } |
| |
| /// Builds a new `ResponseBody` that will read the data from a `Read`. |
| /// |
| /// Note that this is suboptimal compared to other constructors because the length |
| /// isn't known in advance. |
| /// |
| /// # Example |
| /// |
| /// ```no_run |
| /// use std::io; |
| /// use std::io::Read; |
| /// use rouille::ResponseBody; |
| /// |
| /// let body = ResponseBody::from_reader(io::stdin().take(128)); |
| /// ``` |
| #[inline] |
| pub fn from_reader<R>(data: R) -> ResponseBody where R: Read + Send + 'static { |
| ResponseBody { |
| data: Box::new(data), |
| data_length: None, |
| } |
| } |
| |
| /// Builds a new `ResponseBody` that returns the given data. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::ResponseBody; |
| /// let body = ResponseBody::from_data(vec![12u8, 97, 34]); |
| /// ``` |
| #[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), |
| } |
| } |
| |
| /// Builds a new `ResponseBody` that returns the content of the given file. |
| /// |
| /// # Example |
| /// |
| /// ```no_run |
| /// use std::fs::File; |
| /// use rouille::ResponseBody; |
| /// |
| /// let file = File::open("page.html").unwrap(); |
| /// let body = ResponseBody::from_file(file); |
| /// ``` |
| #[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, |
| } |
| } |
| |
| /// Builds a new `ResponseBody` that returns an UTF-8 string. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// use rouille::ResponseBody; |
| /// let body = ResponseBody::from_string("hello world"); |
| /// ``` |
| #[inline] |
| pub fn from_string<S>(data: S) -> ResponseBody where S: Into<String> { |
| ResponseBody::from_data(data.into().into_bytes()) |
| } |
| |
| /// Extracts the content of the response. |
| /// |
| /// Returns the size of the body and the body itself. If the size is `None`, then it is |
| /// unknown. |
| #[inline] |
| pub fn into_reader_and_size(self) -> (Box<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())); |
| } |
| } |