From 61877f6f6ec22aac463306bd57e6c098cbb84540 Mon Sep 17 00:00:00 2001 From: Jeff Olhoeft Date: Tue, 12 Dec 2017 18:32:19 -0800 Subject: [PATCH] docs(server): Add a forms server example Add an example program illustrating parsing a request body through generating a response body. --- Cargo.toml | 1 + examples/params.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 examples/params.rs diff --git a/Cargo.toml b/Cargo.toml index 3fdd82e8ce..7342022629 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ unicase = "2.0" num_cpus = "1.0" pretty_env_logger = "0.1" spmc = "0.2" +url = "1.0" [features] default = ["server-proto"] diff --git a/examples/params.rs b/examples/params.rs new file mode 100644 index 0000000000..86c00cbc5a --- /dev/null +++ b/examples/params.rs @@ -0,0 +1,106 @@ +#![deny(warnings)] +extern crate futures; +extern crate hyper; +extern crate pretty_env_logger; +extern crate url; + +use futures::{Future, Stream}; + +use hyper::{Get, Post, StatusCode}; +use hyper::header::ContentLength; +use hyper::server::{Http, Service, Request, Response}; + +use std::collections::HashMap; +use url::form_urlencoded; + +static INDEX: &[u8] = b"
Name:
Number:
"; +static MISSING: &[u8] = b"Missing field"; +static NOTNUMERIC: &[u8] = b"Number field is not numeric"; + +struct ParamExample; + +impl Service for ParamExample { + type Request = Request; + type Response = Response; + type Error = hyper::Error; + type Future = Box>; + + fn call(&self, req: Request) -> Self::Future { + match (req.method(), req.path()) { + (&Get, "/") | (&Get, "/post") => { + Box::new(futures::future::ok(Response::new() + .with_header(ContentLength(INDEX.len() as u64)) + .with_body(INDEX))) + }, + (&Post, "/post") => { + Box::new(req.body().concat2().map(|b| { + // Parse the request body. form_urlencoded::parse + // always succeeds, but in general parsing may + // fail (for example, an invalid post of json), so + // returning early with BadRequest may be + // necessary. + // + // Warning: this is a simplified use case. In + // principle names can appear multiple times in a + // form, and the values should be rolled up into a + // HashMap>. However in this + // example the simpler approach is sufficient. + let params = form_urlencoded::parse(b.as_ref()).into_owned().collect::>(); + + // Validate the request parameters, returning + // early if an invalid input is detected. + let name = if let Some(n) = params.get("name") { + n + } else { + return Response::new() + .with_status(StatusCode::UnprocessableEntity) + .with_header(ContentLength(MISSING.len() as u64)) + .with_body(MISSING); + }; + let number = if let Some(n) = params.get("number") { + if let Ok(v) = n.parse::() { + v + } else { + return Response::new() + .with_status(StatusCode::UnprocessableEntity) + .with_header(ContentLength(NOTNUMERIC.len() as u64)) + .with_body(NOTNUMERIC); + } + } else { + return Response::new() + .with_status(StatusCode::UnprocessableEntity) + .with_header(ContentLength(MISSING.len() as u64)) + .with_body(MISSING); + }; + + // Render the response. This will often involve + // calls to a database or web service, which will + // require creating a new stream for the response + // body. Since those may fail, other error + // responses such as InternalServiceError may be + // needed here, too. + let body = format!("Hello {}, your number is {}", name, number); + Response::new() + .with_header(ContentLength(body.len() as u64)) + .with_body(body) + })) + }, + _ => { + Box::new(futures::future::ok(Response::new() + .with_status(StatusCode::NotFound))) + } + } + } + +} + + +fn main() { + pretty_env_logger::init().unwrap(); + let addr = "127.0.0.1:1337".parse().unwrap(); + + let mut server = Http::new().bind(&addr, || Ok(ParamExample)).unwrap(); + server.no_proto(); + println!("Listening on http://{} with 1 thread.", server.local_addr().unwrap()); + server.run().unwrap(); +}