Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into subrouter
Browse files Browse the repository at this point in the history
  • Loading branch information
tirr-c committed Nov 24, 2018
2 parents 6e3530c + 17f4940 commit fe0b77c
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 30 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ serde = "1.0.80"
serde_derive = "1.0.80"
serde_json = "1.0.32"
typemap = "0.3.3"
serde_qs = "0.4.1"
multipart = "0.15.3"

[dependencies.futures-preview]
features = ["compat"]
Expand Down
7 changes: 7 additions & 0 deletions examples/body_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,18 @@ async fn echo_json(msg: body::Json<Message>) -> body::Json<Message> {
msg
}

async fn echo_form(msg: body::Form<Message>) -> body::Form<Message> {
println!("Form: {:?}", msg.0);

msg
}

fn main() {
let mut app = tide::App::new(());
app.at("/echo/string").post(echo_string);
app.at("/echo/string_lossy").post(echo_string_lossy);
app.at("/echo/vec").post(echo_vec);
app.at("/echo/json").post(echo_json);
app.at("/echo/form").post(echo_form);
app.serve("127.0.0.1:8000");
}
74 changes: 74 additions & 0 deletions examples/multipart-form/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#![feature(async_await, futures_api)]

#[macro_use]
extern crate serde_derive;

use http::status::StatusCode;
use std::io::Read;
use tide::{body, App};

#[derive(Serialize, Deserialize, Clone)]
struct Message {
key1: Option<String>,
key2: Option<String>,
file: Option<String>,
}

async fn upload_file(
multipart_form: body::MultipartForm,
) -> Result<body::Json<Message>, StatusCode> {
// https://stackoverflow.com/questions/43424982/how-to-parse-multipart-forms-using-abonander-multipart-with-rocket
let mut message = Message {
key1: None,
key2: None,
file: None,
};

let mut multipart = multipart_form.0;
multipart
.foreach_entry(|mut entry| match entry.headers.name.as_str() {
"file" => {
let mut vec = Vec::new();
entry.data.read_to_end(&mut vec).expect("can't read");
message.file = String::from_utf8(vec).ok();
println!("key file got");
}

"key1" => {
let mut vec = Vec::new();
entry.data.read_to_end(&mut vec).expect("can't read");
message.key1 = String::from_utf8(vec).ok();
println!("key1 got");
}

"key2" => {
let mut vec = Vec::new();
entry.data.read_to_end(&mut vec).expect("can't read");
message.key2 = String::from_utf8(vec).ok();
println!("key2 got");
}

_ => {
// as multipart has a bug https://github.com/abonander/multipart/issues/114
// we manually do read_to_end here
let mut _vec = Vec::new();
entry.data.read_to_end(&mut _vec).expect("can't read");
println!("key neglected");
}
})
.expect("Unable to iterate multipart?");

Ok(body::Json(message))
}

fn main() {
let mut app = App::new(());

app.at("/upload_file").post(upload_file);

app.serve("127.0.0.1:7878");
}

// Test with:
// curl -X POST http://localhost:7878/upload_file -H 'content-type: multipart/form-data' -F file=@examples/multipart-form/test.txt
// curl -X POST http://localhost:7878/upload_file -H 'content-type: multipart/form-data' -F key1=v1, -F key2=v2
1 change: 1 addition & 0 deletions examples/multipart-form/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a test file.
7 changes: 3 additions & 4 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,15 @@ impl<Data: Clone + Send + Sync + 'static> Service for Server<Data> {
}) = router.route(&path, &method)
{
for m in middleware.iter() {
match await!(m.request(&mut data, req, &params)) {
Ok(new_req) => req = new_req,
Err(resp) => return Ok(resp.map(Into::into)),
if let Err(resp) = await!(m.request(&mut data, &mut req, &params)) {
return Ok(resp.map(Into::into));
}
}

let (head, mut resp) = await!(endpoint.call(data.clone(), req, params));

for m in middleware.iter() {
resp = await!(m.response(&mut data, &head, resp))
await!(m.response(&mut data, &head, &mut resp));
}

Ok(resp.map(Into::into))
Expand Down
73 changes: 72 additions & 1 deletion src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
use futures::{compat::Compat01As03, future::FutureObj, prelude::*, stream::StreamObj};
use http::status::StatusCode;
use multipart::server::Multipart;
use pin_utils::pin_mut;
use std::io::Cursor;

use crate::{Extract, IntoResponse, Request, Response, RouteMatch};

Expand Down Expand Up @@ -117,6 +119,39 @@ fn mk_err<T>(_: T) -> Response {
StatusCode::BAD_REQUEST.into_response()
}

/// A wrapper for multipart form
///
/// This type is useable as an extractor (argument to an endpoint) for getting
/// a Multipart type defined in the multipart crate
pub struct MultipartForm(pub Multipart<Cursor<Vec<u8>>>);

impl<S: 'static> Extract<S> for MultipartForm {
// Note: cannot use `existential type` here due to ICE
type Fut = FutureObj<'static, Result<Self, Response>>;

fn extract(data: &mut S, req: &mut Request, params: &RouteMatch<'_>) -> Self::Fut {
// https://stackoverflow.com/questions/43424982/how-to-parse-multipart-forms-using-abonander-multipart-with-rocket

const BOUNDARY: &str = "boundary=";
let boundary = req.headers().get("content-type").and_then(|ct| {
let ct = ct.to_str().ok()?;
let idx = ct.find(BOUNDARY)?;
Some(ct[idx + BOUNDARY.len()..].to_string())
});

let mut body = std::mem::replace(req.body_mut(), Body::empty());

FutureObj::new(Box::new(
async move {
let body = await!(body.read_to_vec()).map_err(mk_err)?;
let boundary = boundary.ok_or(()).map_err(mk_err)?;
let mp = Multipart::with_body(Cursor::new(body), boundary);
Ok(MultipartForm(mp))
},
))
}
}

/// A wrapper for json (de)serialization of bodies.
///
/// This type is usable both as an extractor (argument to an endpoint) and as a response
Expand Down Expand Up @@ -144,12 +179,48 @@ impl<T: 'static + Send + serde::Serialize> IntoResponse for Json<T> {
// TODO: think about how to handle errors
http::Response::builder()
.status(http::status::StatusCode::OK)
.header("Content-Type", "Application/json")
.header("Content-Type", "application/json")
.body(Body::from(serde_json::to_vec(&self.0).unwrap()))
.unwrap()
}
}

/// A wrapper for form encoded (application/x-www-form-urlencoded) (de)serialization of bodies.
///
/// This type is usable both as an extractor (argument to an endpoint) and as a response
/// (return value from an endpoint), though returning a response with form data is uncommon
/// and probably not good practice.
pub struct Form<T>(pub T);

impl<T: Send + serde::de::DeserializeOwned + 'static, S: 'static> Extract<S> for Form<T> {
// Note: cannot use `existential type` here due to ICE
type Fut = FutureObj<'static, Result<Self, Response>>;

fn extract(data: &mut S, req: &mut Request, params: &RouteMatch<'_>) -> Self::Fut {
let mut body = std::mem::replace(req.body_mut(), Body::empty());
FutureObj::new(Box::new(
async move {
let body = await!(body.read_to_vec()).map_err(mk_err)?;
let data: T = serde_qs::from_bytes(&body).map_err(mk_err)?;
Ok(Form(data))
},
))
}
}

impl<T: 'static + Send + serde::Serialize> IntoResponse for Form<T> {
fn into_response(self) -> Response {
// TODO: think about how to handle errors
http::Response::builder()
.status(http::status::StatusCode::OK)
.header("Content-Type", "application/x-www-form-urlencoded")
.body(Body::from(
serde_qs::to_string(&self.0).unwrap().into_bytes(),
))
.unwrap()
}
}

pub struct Str(pub String);

impl<S: 'static> Extract<S> for Str {
Expand Down
5 changes: 5 additions & 0 deletions src/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ impl Head {
pub fn method(&self) -> &http::Method {
&self.inner.method
}

/// The HTTP headers
pub fn headers(&self) -> &http::header::HeaderMap<http::header::HeaderValue> {
&self.inner.headers
}
}

/// An extractor for path components.
Expand Down
25 changes: 14 additions & 11 deletions src/middleware/default_headers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,19 @@ impl DefaultHeaders {
}

impl<Data> Middleware<Data> for DefaultHeaders {
fn response(
&self,
data: &mut Data,
head: &Head,
mut resp: Response,
) -> FutureObj<'static, Response> {
let headers = resp.headers_mut();
for (key, value) in self.headers.iter() {
headers.entry(key).unwrap().or_insert_with(|| value.clone());
}
FutureObj::new(Box::new(async { resp }))
fn response<'a>(
&'a self,
data: &'a mut Data,
head: &'a Head,
resp: &'a mut Response,
) -> FutureObj<'a, ()> {
FutureObj::new(Box::new(
async move {
let headers = resp.headers_mut();
for (key, value) in self.headers.iter() {
headers.entry(key).unwrap().or_insert_with(|| value.clone());
}
},
))
}
}
28 changes: 14 additions & 14 deletions src/middleware/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ pub use self::default_headers::DefaultHeaders;
pub trait Middleware<Data>: Send + Sync {
/// Asynchronously transform the incoming request, or abort further handling by immediately
/// returning a response.
fn request(
&self,
data: &mut Data,
req: Request,
params: &RouteMatch<'_>,
) -> FutureObj<'static, Result<Request, Response>> {
FutureObj::new(Box::new(async { Ok(req) }))
fn request<'a>(
&'a self,
data: &'a mut Data,
req: &'a mut Request,
params: &'a RouteMatch<'_>,
) -> FutureObj<'a, Result<(), Response>> {
FutureObj::new(Box::new(async { Ok(()) }))
}

/// Asynchronously transform the outgoing response.
fn response(
&self,
data: &mut Data,
head: &Head,
resp: Response,
) -> FutureObj<'static, Response> {
FutureObj::new(Box::new(async { resp }))
fn response<'a>(
&'a self,
data: &'a mut Data,
head: &'a Head,
resp: &'a mut Response,
) -> FutureObj<'a, ()> {
FutureObj::new(Box::new(async {}))
}

// TODO: provide the following, intended to fire *after* the body has been fully sent
Expand Down
20 changes: 20 additions & 0 deletions src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,26 @@ impl<'a, Data> Resource<'a, Data> {
pub fn delete<T: Endpoint<Data, U>, U>(&mut self, ep: T) {
self.method(http::Method::DELETE, ep)
}

/// Add an endpoint for `OPTIONS` requests
pub fn options<T: Endpoint<Data, U>, U>(&mut self, ep: T) {
self.method(http::Method::OPTIONS, ep)
}

/// Add an endpoint for `CONNECT` requests
pub fn connect<T: Endpoint<Data, U>, U>(&mut self, ep: T) {
self.method(http::Method::CONNECT, ep)
}

/// Add an endpoint for `PATCH` requests
pub fn patch<T: Endpoint<Data, U>, U>(&mut self, ep: T) {
self.method(http::Method::PATCH, ep)
}

/// Add an endpoint for `TRACE` requests
pub fn trace<T: Endpoint<Data, U>, U>(&mut self, ep: T) {
self.method(http::Method::TRACE, ep)
}
}

#[cfg(test)]
Expand Down

0 comments on commit fe0b77c

Please sign in to comment.