-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
params.rs
141 lines (128 loc) · 5.23 KB
/
params.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// #![deny(warnings)] // FIXME: https://github.com/rust-lang/rust/issues/62411
#![warn(rust_2018_idioms)]
use bytes::Bytes;
use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Method, Request, Response, StatusCode};
use tokio::net::TcpListener;
use std::collections::HashMap;
use std::convert::Infallible;
use std::net::SocketAddr;
#[path = "../benches/support/mod.rs"]
mod support;
use support::TokioIo;
static INDEX: &[u8] = b"<html><body><form action=\"post\" method=\"post\">Name: <input type=\"text\" name=\"name\"><br>Number: <input type=\"text\" name=\"number\"><br><input type=\"submit\"></body></html>";
static MISSING: &[u8] = b"Missing field";
static NOTNUMERIC: &[u8] = b"Number field is not numeric";
// Using service_fn, we can turn this function into a `Service`.
async fn param_example(
req: Request<hyper::body::Incoming>,
) -> Result<Response<BoxBody<Bytes, Infallible>>, hyper::Error> {
match (req.method(), req.uri().path()) {
(&Method::GET, "/") | (&Method::GET, "/post") => Ok(Response::new(full(INDEX))),
(&Method::POST, "/post") => {
// Concatenate the body...
let b = req.collect().await?.to_bytes();
// 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<String, Vec<String>>. However in this
// example the simpler approach is sufficient.
let params = form_urlencoded::parse(b.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
// Validate the request parameters, returning
// early if an invalid input is detected.
let name = if let Some(n) = params.get("name") {
n
} else {
return Ok(Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(full(MISSING))
.unwrap());
};
let number = if let Some(n) = params.get("number") {
if let Ok(v) = n.parse::<f64>() {
v
} else {
return Ok(Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(full(NOTNUMERIC))
.unwrap());
}
} else {
return Ok(Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(full(MISSING))
.unwrap());
};
// 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);
Ok(Response::new(full(body)))
}
(&Method::GET, "/get") => {
let query = if let Some(q) = req.uri().query() {
q
} else {
return Ok(Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(full(MISSING))
.unwrap());
};
let params = form_urlencoded::parse(query.as_bytes())
.into_owned()
.collect::<HashMap<String, String>>();
let page = if let Some(p) = params.get("page") {
p
} else {
return Ok(Response::builder()
.status(StatusCode::UNPROCESSABLE_ENTITY)
.body(full(MISSING))
.unwrap());
};
let body = format!("You requested {}", page);
Ok(Response::new(full(body)))
}
_ => Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(empty())
.unwrap()),
}
}
fn empty() -> BoxBody<Bytes, Infallible> {
Empty::<Bytes>::new().boxed()
}
fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, Infallible> {
Full::new(chunk.into()).boxed()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
pretty_env_logger::init();
let addr: SocketAddr = ([127, 0, 0, 1], 1337).into();
let listener = TcpListener::bind(addr).await?;
println!("Listening on http://{}", addr);
loop {
let (stream, _) = listener.accept().await?;
let io = TokioIo::new(stream);
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(io, service_fn(param_example))
.await
{
println!("Error serving connection: {:?}", err);
}
});
}
}