Document all of the core response types.

This commit is contained in:
Sergio Benitez 2016-11-03 17:05:41 +01:00
parent 129268506e
commit 553082f026
7 changed files with 221 additions and 31 deletions

View File

@ -77,7 +77,9 @@ impl ContentType {
/// Returns the Content-Type associated with the extension `ext`. Not all
/// extensions are recognized. If an extensions is not recognized, then this
/// method returns a ContentType of `any`.
/// method returns a ContentType of `any`. The currently recognized
/// extensions are: txt, html, htm, xml, js, css, json, png, gif, bmp, jpeg,
/// jpg, and pdf.
///
/// # Example
///

View File

@ -7,34 +7,79 @@ use response::{Responder, Outcome};
use http::hyper::{header, FreshHyperResponse};
use http::ContentType;
/// A file with an associated name; responds with the Content-Type based on the
/// file extension.
#[derive(Debug)]
pub struct NamedFile(PathBuf, File);
impl NamedFile {
/// Attempts to open a file in read-only mode.
///
/// # Errors
///
/// This function will return an error if path does not already exist. Other
/// errors may also be returned according to
/// [OpenOptions::open](https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.open).
///
/// # Examples
///
/// ```rust
/// use rocket::response::NamedFile;
///
/// let file = NamedFile::open("foo.txt");
/// ```
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
let file = File::open(path.as_ref())?;
Ok(NamedFile(path.as_ref().to_path_buf(), file))
}
/// Retrieve the underlying `File`.
#[inline(always)]
pub fn file(&self) -> &File {
&self.1
}
/// Retrieve a mutable borrow to the underlying `File`.
#[inline(always)]
pub fn file_mut(&mut self) -> &mut File {
&mut self.1
}
/// Retrieve the path of this file.
///
/// # Examples
///
/// ```rust
/// # use std::io;
/// use rocket::response::NamedFile;
///
/// # fn demo_path() -> io::Result<()> {
/// let file = NamedFile::open("foo.txt")?;
/// assert_eq!(file.path().as_os_str(), "foo.txt");
/// # Ok(())
/// # }
/// ```
#[inline(always)]
pub fn path(&self) -> &Path {
self.0.as_path()
}
}
/// Streams the named file to the client. Sets or overrides the Content-Type in
/// the response according to the file's extension if the extension is
/// recognized. See
/// [ContentType::from_extension](/rocket/http/struct.ContentType.html#method.from_extension)
/// for more information. If you would like to stream a file with a different
/// Content-Type than that implied by its extension, use a `File` directly.
///
/// # Failure
///
/// If reading the file fails permanently at any point during the response, an
/// `Outcome` of `Failure` is returned, and the response is terminated abrubtly.
impl Responder for NamedFile {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> {
if let Some(ext) = self.path().extension() {
// TODO: Use Cow for lowercase.
let ext_string = ext.to_string_lossy().to_lowercase();
let content_type = ContentType::from_extension(&ext_string);
if !content_type.is_any() {

View File

@ -2,31 +2,102 @@ use response::{Outcome, Responder};
use http::hyper::{header, FreshHyperResponse, StatusCode};
use outcome::IntoOutcome;
/// An empty redirect response to a given URL.
///
/// This type simplifies returning a redirect response to the client.
#[derive(Debug)]
pub struct Redirect(StatusCode, String);
impl Redirect {
/// Construct a temporary "see other" (303) redirect response. This is the
/// typical response when redirecting a user to another page. This type of
/// redirect indicates that the client should look elsewhere, but always via
/// a `GET` request, for a given resource.
///
/// # Examples
///
/// ```rust
/// use rocket::response::Redirect;
///
/// let redirect = Redirect::to("/other_url");
/// ```
pub fn to(uri: &str) -> Redirect {
Redirect(StatusCode::Found, String::from(uri))
}
pub fn created(uri: &str) -> Redirect {
Redirect(StatusCode::Created, String::from(uri))
}
pub fn other(uri: &str) -> Redirect {
Redirect(StatusCode::SeeOther, String::from(uri))
}
/// Construct a "temporary" (307) redirect response. This response instructs
/// the client to reissue the current request to a different URL,
/// maintaining the contents of the request identically. This means that,
/// for example, a `POST` request will be resent, contents included, to the
/// requested URL.
///
/// # Examples
///
/// ```rust
/// use rocket::response::Redirect;
///
/// let redirect = Redirect::temporary("/other_url");
/// ```
pub fn temporary(uri: &str) -> Redirect {
Redirect(StatusCode::TemporaryRedirect, String::from(uri))
}
/// Construct a "permanent" (308) redirect response. This redirect must only
/// be used for permanent redirects as it is cached by clients. This
/// response instructs the client to reissue requests tot he current URL to
/// a different URL, now and in the future, maintaining the contents of the
/// request identically. This means that, for example, a `POST` request will
/// be resent, contents included, to the requested URL.
///
/// # Examples
///
/// ```rust
/// use rocket::response::Redirect;
///
/// let redirect = Redirect::permanent("/other_url");
/// ```
pub fn permanent(uri: &str) -> Redirect {
Redirect(StatusCode::PermanentRedirect, String::from(uri))
}
pub fn temporary(uri: &str) -> Redirect {
Redirect(StatusCode::TemporaryRedirect, String::from(uri))
/// Construct a temporary "found" (302) redirect response. This response
/// instructs the client to reissue the current request to a different URL,
/// ideally maintaining the contents of the request identically.
/// Unfortunately, different clients may respond differently to this type of
/// redirect, so `303` or `307` redirects, which disambiguate, are
/// preferred.
///
/// # Examples
///
/// ```rust
/// use rocket::response::Redirect;
///
/// let redirect = Redirect::found("/other_url");
/// ```
pub fn found(uri: &str) -> Redirect {
Redirect(StatusCode::Found, String::from(uri))
}
/// Construct a permanent "moved" (301) redirect response. This response
/// should only be used for permanent redirects as it can be cached by
/// browsers. Because different clients may respond differently to this type
/// of redirect, a `308` redirect, which disambiguates, is preferred.
///
/// # Examples
///
/// ```rust
/// use rocket::response::Redirect;
///
/// let redirect = Redirect::moved("/other_url");
/// ```
pub fn moved(uri: &str) -> Redirect {
Redirect(StatusCode::MovedPermanently, String::from(uri))
}
}
/// Constructs a response with the appropriate status code and the given URL in
/// the `Location` header field. The body of the response is empty. This
/// responder does not fail.
impl<'a> Responder for Redirect {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
res.headers_mut().set(header::ContentLength(0));

View File

@ -1,4 +1,3 @@
use std::io::{Read, Write};
use std::fs::File;
use std::fmt;
@ -6,6 +5,8 @@ use http::mime::{Mime, TopLevel, SubLevel};
use http::hyper::{header, FreshHyperResponse, StatusCode};
use outcome::{self, IntoOutcome};
use outcome::Outcome::*;
use response::Stream;
/// Type alias for the `Outcome` of a `Responder`.
pub type Outcome<'a> = outcome::Outcome<(), (), (StatusCode, FreshHyperResponse<'a>)>;
@ -55,6 +56,47 @@ impl<'a, T, E> IntoOutcome<(), (), (StatusCode, FreshHyperResponse<'a>)> for Res
/// `StatusCode`. This requires that a response wasn't started and thus is
/// still fresh.
///
/// # Provided Implementations
///
/// Rocket implements `Responder` for several standard library types. Their
/// behavior is documented here. Note that the `Result` implementation is
/// overloaded, allowing for two `Responder`s to be used at once, depending on
/// the variant.
///
/// * **impl<'a> Responder for &'a str**
///
/// Sets the `Content-Type`t to `text/plain` if it is not already set. Sends
/// the string as the body of the response.
///
/// * **impl Responder for String**
///
/// Sets the `Content-Type`t to `text/html` if it is not already set. Sends
/// the string as the body of the response.
///
/// * **impl Responder for File**
///
/// Streams the `File` to the client. This is essentially an alias to
/// Stream<File>.
///
/// * **impl<T: Responder> Responder for Option<T>**
///
/// If the `Option` is `Some`, the wrapped responder is used to respond to
/// respond to the client. Otherwise, the response is forwarded to the 404
/// error catcher and a warning is printed to the console.
///
/// * **impl<T: Responder, E: Debug> Responder for Result<T, E>**
///
/// If the `Result` is `Ok`, the wrapped responder is used to respond to the
/// client. Otherwise, the response is forwarded to the 500 error catcher
/// and the error is printed to the console using the `Debug`
/// implementation.
///
/// * **impl<T: Responder, E: Responder + Debug> Responder for Result<T, E>**
///
/// If the `Result` is `Ok`, the wrapped `Ok` responder is used to respond
/// to the client. If the `Result` is `Err`, the wrapped error responder is
/// used to respond to the client.
///
/// # Implementation Tips
///
/// This section describes a few best practices to take into account when
@ -85,6 +127,8 @@ pub trait Responder {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a>;
}
/// Sets the `Content-Type`t to `text/plain` if it is not already set. Sends the
/// string as the body of the response.
impl<'a> Responder for &'a str {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
if res.headers().get::<header::ContentType>().is_none() {
@ -107,24 +151,10 @@ impl Responder for String {
}
}
/// Essentially aliases Stream<File>.
impl Responder for File {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> Outcome<'a> {
let size = match self.metadata() {
Ok(md) => md.len(),
Err(e) => {
error_!("Failed to read file metadata: {:?}", e);
return Forward((StatusCode::InternalServerError, res));
}
};
let mut v = Vec::new();
if let Err(e) = self.read_to_end(&mut v) {
error_!("Failed to read file: {:?}", e);
return Forward((StatusCode::InternalServerError, res));
}
res.headers_mut().set(header::ContentLength(size));
res.start().and_then(|mut stream| stream.write_all(&v)).into_outcome()
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
Stream::from(self).respond(res)
}
}

View File

@ -3,6 +3,7 @@ use outcome::{self, Outcome};
use http::hyper::StatusCode;
use response::{Responder, StatusResponse};
/// Type alias for the `Outcome` of a `Handler`.
pub type Response<'a> = outcome::Outcome<Box<Responder + 'a>, StatusCode, Data>;
impl<'a> Response<'a> {

View File

@ -9,11 +9,30 @@ use outcome::Outcome::*;
/// The default size of each chunk in the streamed response.
pub const CHUNK_SIZE: usize = 4096;
pub struct Stream<T: Read>(Box<T>);
/// Streams a response to a client from an arbitrary `Read`er type.
///
/// The client is sent a "chunked" response, where the chunk size is at most
/// 4KiB. This means that at most 4KiB are stored in memory while the response
/// is being sent. This type should be used when sending responses that are
/// arbitrarily large in size, such as when streaming from a local socket.
pub struct Stream<T: Read>(T);
impl<T: Read> Stream<T> {
/// Create a new stream from the given `reader`.
///
/// # Example
///
/// Stream a response from whatever is in `stdin`. Note: you probably
/// shouldn't do this.
///
/// ```rust
/// use std::io;
/// use rocket::response::Stream;
///
/// let response = Stream::from(io::stdin());
/// ```
pub fn from(reader: T) -> Stream<T> {
Stream(Box::new(reader))
Stream(reader)
}
// pub fn chunked(mut self, size: usize) -> Self {
@ -33,6 +52,14 @@ impl<T: Read + Debug> Debug for Stream<T> {
}
}
/// Sends a response to the client using the "Chunked" transfer encoding. The
/// maximum chunk size is 4KiB.
///
/// # Failure
///
/// If reading from the input stream fails at any point during the response, the
/// response is abandoned, and the response ends abruptly. An error is printed
/// to the console with an indication of what went wrong.
impl<T: Read> Responder for Stream<T> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> {
let mut stream = match res.start() {

View File

@ -1,6 +1,8 @@
use response::{Responder, Outcome};
use http::hyper::{StatusCode, FreshHyperResponse};
/// Responds to the client using a wrapped `Responder` and a given
/// `StatusCode`.
#[derive(Debug)]
pub struct StatusResponse<R: Responder> {
status: StatusCode,
@ -8,6 +10,16 @@ pub struct StatusResponse<R: Responder> {
}
impl<R: Responder> StatusResponse<R> {
/// Creates a response with the given status code and underyling responder.
///
/// # Example
///
/// ```rust
/// use rocket::response::StatusResponse;
/// use rocket::http::StatusCode;
///
/// let response = StatusResponse::new(StatusCode::ImATeapot, "Hi!");
/// ```
pub fn new(status: StatusCode, responder: R) -> StatusResponse<R> {
StatusResponse {
status: status,
@ -16,6 +28,8 @@ impl<R: Responder> StatusResponse<R> {
}
}
/// Sets the status code of the response and then delegates the remainder of the
/// response to the wrapped responder.
impl<R: Responder> Responder for StatusResponse<R> {
fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> {
*(res.status_mut()) = self.status;