Implement streaming requests.

This commit is contained in:
Sergio Benitez 2016-10-09 04:29:02 -07:00
parent 07204a25dd
commit d8db812856
15 changed files with 271 additions and 67 deletions

View File

@ -25,4 +25,5 @@ members = [
"examples/handlebars_templates", "examples/handlebars_templates",
"examples/form_kitchen_sink", "examples/form_kitchen_sink",
"examples/config", "examples/config",
"examples/hello_alt_methods",
] ]

View File

@ -1,10 +1,8 @@
extern crate tera; extern crate tera;
use std::path::PathBuf; use std::path::PathBuf;
use self::tera::Renderer;
use super::serde::Serialize; use super::serde::Serialize;
use super::serde_json;
use super::{TemplateInfo, TEMPLATE_DIR}; use super::{TemplateInfo, TEMPLATE_DIR};
lazy_static! { lazy_static! {
@ -19,17 +17,13 @@ pub const EXT: &'static str = "tera";
pub fn render<T>(name: &str, info: &TemplateInfo, context: &T) -> Option<String> pub fn render<T>(name: &str, info: &TemplateInfo, context: &T) -> Option<String>
where T: Serialize where T: Serialize
{ {
let template = match TERA.get_template(&info.path.to_string_lossy()) { let template_name = &info.path.to_string_lossy();
Ok(template) => template, if TERA.get_template(template_name).is_err() {
Err(_) => { error_!("Tera template '{}' does not exist.", template_name);
error_!("Tera template '{}' does not exist.", name); return None;
return None;
}
}; };
let value = serde_json::to_value(&context); match TERA.value_render(template_name, &context) {
let mut renderer = Renderer::new_with_json(template, &TERA, value);
match renderer.render() {
Ok(string) => Some(string), Ok(string) => Some(string),
Err(e) => { Err(e) => {
error_!("Error rendering Tera template '{}': {}", name, e); error_!("Error rendering Tera template '{}': {}", name, e);

View File

@ -0,0 +1,9 @@
[package]
name = "hello_alt_methods"
version = "0.0.1"
authors = ["Sergio Benitez <sb@sergio.bz>"]
workspace = "../../"
[dependencies]
rocket = { path = "../../lib" }
rocket_codegen = { path = "../../codegen" }

View File

@ -0,0 +1,24 @@
#![feature(plugin)]
#![plugin(rocket_codegen)]
extern crate rocket;
use std::io;
use rocket::response::NamedFile;
#[get("/")]
fn index() -> io::Result<NamedFile> {
NamedFile::open("static/index.html")
}
#[put("/")]
fn put() -> &'static str {
"Hello, PUT request!"
}
fn main() {
rocket::ignite()
.mount("/", routes![index, put])
.launch();
}

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Hello Alt Methods</title>
</head>
<body>
<form action="/" method="post" accept-charset="utf-8">
<input type="hidden" name="_method" id="_method" value="put" />
<input type="submit" name="Submit" id="Submit" value="submit" />
</form>
</body>
</html>

View File

@ -30,10 +30,10 @@ fn upload(req: &Request, data: Data) -> Response {
return Response::failed(StatusCode::BadRequest); return Response::failed(StatusCode::BadRequest);
} }
let file = File::create("upload.txt"); let file = File::create("/tmp/upload.txt");
if let Ok(mut file) = file { if let Ok(mut file) = file {
if io::copy(&mut data.open(), &mut file).is_ok() { if let Ok(n) = io::copy(&mut data.open(), &mut file) {
return Response::complete("Upload successful."); return Response::complete(format!("OK: {} bytes uploaded.", n));
} }
println!(" => Failed copying."); println!(" => Failed copying.");

View File

@ -6,10 +6,14 @@ authors = ["Sergio Benitez <sb@sergio.bz>"]
[dependencies] [dependencies]
term-painter = "^0.2" term-painter = "^0.2"
log = "^0.3" log = "^0.3"
hyper = { version = "^0.9", default-features = false }
url = "^1" url = "^1"
# mime = "^0.2" # mime = "^0.2"
toml = "^0.2" toml = "^0.2"
[dependencies.hyper]
git = "https://github.com/SergioBenitez/hyper"
default-features = false
branch = "0.9.x"
[dev-dependencies] [dev-dependencies]
lazy_static = "*" lazy_static = "*"

View File

@ -1,4 +1,4 @@
// TODO: Removed from Rocket in favor of a more flexible HTTP library. // TODO: Remove from Rocket in favor of a more flexible HTTP library.
pub use hyper::server::Request as HyperRequest; pub use hyper::server::Request as HyperRequest;
pub use hyper::server::Response as HyperResponse; pub use hyper::server::Response as HyperResponse;
pub use hyper::server::Server as HyperServer; pub use hyper::server::Server as HyperServer;
@ -13,10 +13,19 @@ pub use hyper::header::SetCookie as HyperSetCookie;
pub use hyper::method::Method as HyperMethod; pub use hyper::method::Method as HyperMethod;
pub use hyper::uri::RequestUri as HyperRequestUri; pub use hyper::uri::RequestUri as HyperRequestUri;
pub use hyper::net::Fresh as HyperFresh; pub use hyper::net::Fresh as HyperFresh;
pub use hyper::net::HttpStream as HyperHttpStream;
pub use hyper::net::NetworkStream as HyperNetworkStream;
pub use hyper::http::h1::HttpReader as HyperHttpReader;
pub use hyper::header; pub use hyper::header;
// This is okay. // This is okay for now.
pub use hyper::status::StatusCode; pub use hyper::status::StatusCode;
// TODO: Removed from Rocket in favor of a more flexible HTTP library. // TODO: Remove from Rocket in favor of a more flexible HTTP library.
pub type FreshHyperResponse<'a> = self::HyperResponse<'a, self::HyperFresh>; pub type FreshHyperResponse<'a> = self::HyperResponse<'a, self::HyperFresh>;
// TODO: Remove from Rocket in favor of a more flexible HTTP library.
use hyper::buffer::BufReader;
pub type HyperBodyReader<'a, 'b> =
HyperHttpReader<&'a mut BufReader<&'b mut HyperNetworkStream>>;

View File

@ -74,6 +74,12 @@ impl Log for RocketLogger {
return; return;
} }
// Don't print Hyper's messages unless Debug is enabled.
let from_hyper = record.location().module_path().starts_with("hyper::");
if from_hyper && self.0 != LoggingLevel::Debug {
return;
}
// In Rocket, we abuse target with value "_" to indicate indentation. // In Rocket, we abuse target with value "_" to indicate indentation.
if record.target() == "_" && self.0 != LoggingLevel::Critical { if record.target() == "_" && self.0 != LoggingLevel::Critical {
print!(" {} ", White.paint("=>")); print!(" {} ", White.paint("=>"));

View File

@ -16,6 +16,15 @@ pub enum Outcome<T> {
} }
impl<T> Outcome<T> { impl<T> Outcome<T> {
pub fn of<A, B: fmt::Debug>(result: Result<A, B>) -> Outcome<T> {
if let Err(e) = result {
error_!("{:?}", e);
return Outcome::Failure;
}
Outcome::Success
}
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
match *self { match *self {
Outcome::Success => "Success", Outcome::Success => "Success",

View File

@ -1,23 +1,133 @@
use std::io::{BufRead, Read, Cursor, BufReader}; use std::io::{self, BufRead, Read, Cursor, BufReader, Chain, Take};
use std::time::Duration;
use std::net::Shutdown;
use http::hyper::{HyperBodyReader, HyperHttpStream, HyperHttpReader};
use http::hyper::HyperNetworkStream;
use http::hyper::HyperHttpReader::*;
type StreamReader = HyperHttpReader<HyperHttpStream>;
pub struct DataStream {
stream: Chain<Take<Cursor<Vec<u8>>>, BufReader<StreamReader>>,
network: HyperHttpStream,
}
impl Read for DataStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.stream.read(buf)
}
}
impl BufRead for DataStream {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
self.stream.fill_buf()
}
fn consume(&mut self, amt: usize) {
self.stream.consume(amt)
}
}
fn try_sinking<N: HyperNetworkStream>(net: &mut N) -> bool {
warn_!("Data left unread. Sinking 1k bytes.");
io::copy(&mut net.take(1024), &mut io::sink()).expect("sink");
// If there are any more bytes, kill it.
let mut buf = [0];
if let Ok(n) = net.read(&mut buf) {
if n > 0 {
warn_!("Data still remains. Force closing network stream.");
return net.close(Shutdown::Both).is_ok();
}
}
false
}
impl Drop for DataStream {
// Be a bad citizen and close the TCP stream if there's unread data.
// Unfortunately, Hyper forces us to do this.
fn drop(&mut self) {
try_sinking(&mut self.network);
}
}
pub struct Data { pub struct Data {
buffer: Vec<u8>, buffer: Vec<u8>,
stream: Cursor<Vec<u8>>, stream: StreamReader,
position: usize,
capacity: usize,
}
impl Drop for Data {
fn drop(&mut self) {
try_sinking(self.stream.get_mut());
}
} }
impl Data { impl Data {
pub fn open(self) -> impl BufRead { pub fn open(mut self) -> impl BufRead {
Cursor::new(self.buffer).chain(BufReader::new(self.stream)) // Swap out the buffer and stream for empty ones so we can move.
let mut buffer = vec![];
let mut stream = EmptyReader(self.stream.get_ref().clone());
::std::mem::swap(&mut buffer, &mut self.buffer);
::std::mem::swap(&mut stream, &mut self.stream);
// Setup the underlying reader at the correct pointers.
let mut cursor = Cursor::new(buffer);
cursor.set_position(self.position as u64);
let buffered = cursor.take((self.capacity - self.position) as u64);
// Create the actual DataSteam.
DataStream {
network: stream.get_ref().clone(),
stream: buffered.chain(BufReader::new(stream)),
}
}
#[doc(hidden)]
pub fn from_hyp(mut h_body: HyperBodyReader) -> Result<Data, &'static str> {
// FIXME: This is asolutely terrible, thanks to Hyper.
// Retrieve the underlying HTTPStream from Hyper.
let mut stream = match h_body.get_ref().get_ref()
.downcast_ref::<HyperHttpStream>() {
Some(s) => {
let owned_stream = s.clone();
let buf_len = h_body.get_ref().get_buf().len() as u64;
match h_body {
SizedReader(_, n) => SizedReader(owned_stream, n - buf_len),
EofReader(_) => EofReader(owned_stream),
EmptyReader(_) => EmptyReader(owned_stream),
ChunkedReader(_, n) =>
ChunkedReader(owned_stream, n.map(|k| k - buf_len)),
}
},
None => return Err("Stream is not an HTTP stream!"),
};
// Set the read timeout to 5 seconds.
stream.get_mut().set_read_timeout(Some(Duration::from_secs(5))).unwrap();
// Create the Data object from hyper's buffer.
let (vec, pos, cap) = h_body.get_mut().take_buf();
Ok(Data::new(vec, pos, cap, stream))
} }
pub fn peek(&self) -> &[u8] { pub fn peek(&self) -> &[u8] {
&self.buffer &self.buffer[self.position..self.capacity]
} }
pub fn new() -> Data { pub fn new(buf: Vec<u8>, pos: usize, cap: usize, stream: StreamReader) -> Data {
// TODO: Make sure we always try to get some number of bytes in the
// buffer so that peek actually does something.
// const PEEK_BYTES: usize = 4096;
Data { Data {
stream: Cursor::new(vec![]), buffer: buf,
buffer: vec![] stream: stream,
position: pos,
capacity: cap,
} }
} }
} }

View File

@ -31,7 +31,6 @@ impl<'a> Responder for Redirect {
res.headers_mut().set(header::ContentLength(0)); res.headers_mut().set(header::ContentLength(0));
res.headers_mut().set(header::Location(self.1.clone())); res.headers_mut().set(header::Location(self.1.clone()));
*(res.status_mut()) = self.0; *(res.status_mut()) = self.0;
res.send(b"").unwrap(); Outcome::of(res.send(b""))
Outcome::Success
} }
} }

View File

@ -21,8 +21,7 @@ impl<'a> Responder for &'a str {
res.headers_mut().set(header::ContentType(mime)); res.headers_mut().set(header::ContentType(mime));
} }
res.send(self.as_bytes()).unwrap(); Outcome::of(res.send(self.as_bytes()))
Outcome::Success
} }
} }
@ -32,47 +31,53 @@ impl Responder for String {
let mime = Mime(TopLevel::Text, SubLevel::Html, vec![]); let mime = Mime(TopLevel::Text, SubLevel::Html, vec![]);
res.headers_mut().set(header::ContentType(mime)); res.headers_mut().set(header::ContentType(mime));
} }
res.send(self.as_bytes()).unwrap();
Outcome::Success Outcome::of(res.send(self.as_bytes()))
} }
} }
impl Responder for File { impl Responder for File {
fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { fn respond<'a>(&mut self, mut res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
let size = self.metadata().unwrap().len(); let size = match self.metadata() {
Ok(md) => md.len(),
res.headers_mut().set(header::ContentLength(size)); Err(e) => {
*(res.status_mut()) = StatusCode::Ok; error_!("Failed to read file metadata: {:?}", e);
return Outcome::Forward((StatusCode::InternalServerError, res));
}
};
let mut v = Vec::new(); let mut v = Vec::new();
self.read_to_end(&mut v).unwrap(); if let Err(e) = self.read_to_end(&mut v) {
error_!("Failed to read file: {:?}", e);
return Outcome::Forward((StatusCode::InternalServerError, res));
}
let mut stream = res.start().unwrap(); res.headers_mut().set(header::ContentLength(size));
stream.write_all(&v).unwrap(); Outcome::of(res.start().and_then(|mut stream| stream.write_all(&v)))
Outcome::Success
} }
} }
impl<T: Responder> Responder for Option<T> { impl<T: Responder> Responder for Option<T> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
if self.is_none() { if let Some(ref mut val) = *self {
warn_!("response was `None`"); val.respond(res)
return Outcome::Forward((StatusCode::NotFound, res)); } else {
warn_!("Response was `None`.");
Outcome::Forward((StatusCode::NotFound, res))
} }
self.as_mut().unwrap().respond(res)
} }
} }
impl<T: Responder, E: fmt::Debug> Responder for Result<T, E> { impl<T: Responder, E: fmt::Debug> Responder for Result<T, E> {
// prepend with `default` when using impl specialization // prepend with `default` when using impl specialization
default fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { default fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
if self.is_err() { match *self {
error_!("{:?}", self.as_ref().err().unwrap()); Ok(ref mut val) => val.respond(res),
return Outcome::Forward((StatusCode::InternalServerError, res)); Err(ref e) => {
error_!("{:?}", e);
Outcome::Forward((StatusCode::InternalServerError, res))
}
} }
self.as_mut().unwrap().respond(res)
} }
} }

View File

@ -27,7 +27,14 @@ impl<T: Read> Stream<T> {
impl<T: Read> Responder for Stream<T> { impl<T: Read> Responder for Stream<T> {
fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> { fn respond<'a>(&mut self, res: FreshHyperResponse<'a>) -> ResponseOutcome<'a> {
let mut stream = res.start().unwrap(); let mut stream = match res.start() {
Ok(s) => s,
Err(e) => {
error_!("Failed opening response stream: {:?}", e);
return Outcome::Failure;
}
};
let mut buffer = [0; CHUNK_SIZE]; let mut buffer = [0; CHUNK_SIZE];
let mut complete = false; let mut complete = false;
while !complete { while !complete {

View File

@ -9,7 +9,7 @@ use term_painter::ToStyle;
use config; use config;
use logger; use logger;
use request::{Request, Data, FormItems}; use request::{Request, Data, FormItems};
use response::{Response}; use response::Response;
use router::{Router, Route}; use router::{Router, Route};
use catcher::{self, Catcher}; use catcher::{self, Catcher};
use outcome::Outcome; use outcome::Outcome;
@ -45,14 +45,11 @@ impl Rocket {
let uri = hyp_req.uri.to_string(); let uri = hyp_req.uri.to_string();
// Get all of the information from Hyper. // Get all of the information from Hyper.
let (_, h_method, h_headers, h_uri, _, mut _body) = hyp_req.deconstruct(); let (_, h_method, h_headers, h_uri, _, h_body) = hyp_req.deconstruct();
// Try to create a Rocket request from the hyper request info. // Try to create a Rocket request from the hyper request info.
let request = match Request::new(h_method, h_headers, h_uri) { let mut request = match Request::new(h_method, h_headers, h_uri) {
Ok(mut req) => { Ok(req) => req,
self.preprocess_request(&mut req);
req
}
Err(ref reason) => { Err(ref reason) => {
let mock_request = Request::mock(Method::Get, uri.as_str()); let mock_request = Request::mock(Method::Get, uri.as_str());
debug_!("Bad request: {}", reason); debug_!("Bad request: {}", reason);
@ -62,9 +59,20 @@ impl Rocket {
}; };
// Retrieve the data from the request. // Retrieve the data from the request.
let mut data = Data::new(); let mut data = match Data::from_hyp(h_body) {
Ok(data) => data,
Err(reason) => {
debug_!("Bad data in request: {}", reason);
return self.handle_error(StatusCode::InternalServerError,
&request, res);
}
};
// Preprocess the request.
self.preprocess_request(&mut request, &data);
info!("{}:", request); info!("{}:", request);
info_!("Peek size: {} bytes", data.peek().len());
let matches = self.router.route(&request); let matches = self.router.route(&request);
for route in matches { for route in matches {
// Retrieve and set the requests parameters. // Retrieve and set the requests parameters.
@ -108,14 +116,14 @@ impl Rocket {
/// Preprocess the request for Rocket-specific things. At this time, we're /// Preprocess the request for Rocket-specific things. At this time, we're
/// only checking for _method in forms. /// only checking for _method in forms.
fn preprocess_request(&self, req: &mut Request) { fn preprocess_request(&self, req: &mut Request, data: &Data) {
// Check if this is a form and if the form contains the special _method // Check if this is a form and if the form contains the special _method
// field which we use to reinterpret the request's method. // field which we use to reinterpret the request's method.
let data_len = req.data.len(); let data_len = data.peek().len();
let (min_len, max_len) = ("_method=get".len(), "_method=delete".len()); let (min_len, max_len) = ("_method=get".len(), "_method=delete".len());
if req.content_type().is_form() && data_len >= min_len { if req.content_type().is_form() && data_len >= min_len {
let form = unsafe { let form = unsafe {
from_utf8_unchecked(&req.data.as_slice()[..min(data_len, max_len)]) from_utf8_unchecked(&data.peek()[..min(data_len, max_len)])
}; };
let mut form_items = FormItems(form); let mut form_items = FormItems(form);
@ -132,8 +140,13 @@ impl Rocket {
code: StatusCode, code: StatusCode,
req: &'r Request, req: &'r Request,
response: FreshHyperResponse) { response: FreshHyperResponse) {
error_!("Dispatch failed: {}.", code);
let catcher = self.catchers.get(&code.to_u16()).unwrap(); // Find the catcher or use the one for internal server errors.
let catcher = self.catchers.get(&code.to_u16()).unwrap_or_else(|| {
error_!("No catcher found for {}.", code);
warn_!("Using internal server error catcher.");
self.catchers.get(&500).expect("500 Catcher")
});
if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() { if let Some(mut responder) = catcher.handle(Error::NoRoute, req).responder() {
if responder.respond(response) != Outcome::Success { if responder.respond(response) != Outcome::Success {
@ -144,7 +157,8 @@ impl Rocket {
} else { } else {
error_!("Catcher returned an incomplete response."); error_!("Catcher returned an incomplete response.");
warn_!("Using default error response."); warn_!("Using default error response.");
let catcher = self.default_catchers.get(&code.to_u16()).unwrap(); let catcher = self.default_catchers.get(&code.to_u16())
.unwrap_or(self.default_catchers.get(&500).expect("500 default"));
let responder = catcher.handle(Error::Internal, req).responder(); let responder = catcher.handle(Error::Internal, req).responder();
responder.unwrap().respond(response).expect_success() responder.unwrap().respond(response).expect_success()
} }
@ -166,8 +180,7 @@ impl Rocket {
pub fn catch(mut self, catchers: Vec<Catcher>) -> Self { pub fn catch(mut self, catchers: Vec<Catcher>) -> Self {
info!("👾 {}:", Magenta.paint("Catchers")); info!("👾 {}:", Magenta.paint("Catchers"));
for c in catchers { for c in catchers {
if self.catchers.contains_key(&c.code) && if self.catchers.get(&c.code).map_or(false, |e| e.is_default()) {
!self.catchers.get(&c.code).unwrap().is_default() {
let msg = format!("warning: overrides {} catcher!", c.code); let msg = format!("warning: overrides {} catcher!", c.code);
warn!("{} ({})", c, Yellow.paint(msg.as_str())); warn!("{} ({})", c, Yellow.paint(msg.as_str()));
} else { } else {