From 553082f026dfa38e0604914fcea41b304f153217 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 3 Nov 2016 17:05:41 +0100 Subject: [PATCH] Document all of the core response types. --- lib/src/http/content_type.rs | 4 +- lib/src/response/named_file.rs | 45 ++++++++++++++++ lib/src/response/redirect.rs | 91 +++++++++++++++++++++++++++++---- lib/src/response/responder.rs | 66 +++++++++++++++++------- lib/src/response/response.rs | 1 + lib/src/response/stream.rs | 31 ++++++++++- lib/src/response/with_status.rs | 14 +++++ 7 files changed, 221 insertions(+), 31 deletions(-) diff --git a/lib/src/http/content_type.rs b/lib/src/http/content_type.rs index 985b21ef..82bff9cd 100644 --- a/lib/src/http/content_type.rs +++ b/lib/src/http/content_type.rs @@ -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 /// diff --git a/lib/src/response/named_file.rs b/lib/src/response/named_file.rs index 57301374..6da24ca7 100644 --- a/lib/src/response/named_file.rs +++ b/lib/src/response/named_file.rs @@ -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>(path: P) -> io::Result { 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() { diff --git a/lib/src/response/redirect.rs b/lib/src/response/redirect.rs index a6dad443..8bf82868 100644 --- a/lib/src/response/redirect.rs +++ b/lib/src/response/redirect.rs @@ -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)); diff --git a/lib/src/response/responder.rs b/lib/src/response/responder.rs index 88153394..8b6acfc5 100644 --- a/lib/src/response/responder.rs +++ b/lib/src/response/responder.rs @@ -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. +/// +/// * **impl Responder for Option** +/// +/// 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 Responder for Result** +/// +/// 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 Responder for Result** +/// +/// 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::().is_none() { @@ -107,24 +151,10 @@ impl Responder for String { } } +/// Essentially aliases Stream. 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) } } diff --git a/lib/src/response/response.rs b/lib/src/response/response.rs index 14dae683..e5c1fa19 100644 --- a/lib/src/response/response.rs +++ b/lib/src/response/response.rs @@ -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, StatusCode, Data>; impl<'a> Response<'a> { diff --git a/lib/src/response/stream.rs b/lib/src/response/stream.rs index 013d55bd..bb3b4af4 100644 --- a/lib/src/response/stream.rs +++ b/lib/src/response/stream.rs @@ -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(Box); +/// 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); impl Stream { + /// 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 { - Stream(Box::new(reader)) + Stream(reader) } // pub fn chunked(mut self, size: usize) -> Self { @@ -33,6 +52,14 @@ impl Debug for Stream { } } +/// 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 Responder for Stream { fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> Outcome<'a> { let mut stream = match res.start() { diff --git a/lib/src/response/with_status.rs b/lib/src/response/with_status.rs index e89ce1d6..9e94a7a1 100644 --- a/lib/src/response/with_status.rs +++ b/lib/src/response/with_status.rs @@ -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 { status: StatusCode, @@ -8,6 +10,16 @@ pub struct StatusResponse { } impl StatusResponse { + /// 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 { StatusResponse { status: status, @@ -16,6 +28,8 @@ impl StatusResponse { } } +/// Sets the status code of the response and then delegates the remainder of the +/// response to the wrapped responder. impl Responder for StatusResponse { fn respond<'b>(&mut self, mut res: FreshHyperResponse<'b>) -> Outcome<'b> { *(res.status_mut()) = self.status;