Adds utility functions to `hyper::body` to help asynchronously collecting all the buffers of some `HttpBody` into one. - `aggregate` will collect all into an `impl Buf` without copying the contents. This is ideal if you don't need a contiguous buffer. - `to_bytes` will copy all the data into a single contiguous `Bytes` buffer.
		
			
				
	
	
		
			94 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			94 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| // #![deny(warnings)]  // FIXME: https://github.com/rust-lang/rust/issues/62411
 | |
| #![warn(rust_2018_idioms)]
 | |
| 
 | |
| use hyper::service::{make_service_fn, service_fn};
 | |
| use hyper::{Body, Method, Request, Response, Server, StatusCode};
 | |
| 
 | |
| use std::collections::HashMap;
 | |
| use url::form_urlencoded;
 | |
| 
 | |
| 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<Body>) -> Result<Response<Body>, hyper::Error> {
 | |
|     match (req.method(), req.uri().path()) {
 | |
|         (&Method::GET, "/") | (&Method::GET, "/post") => Ok(Response::new(INDEX.into())),
 | |
|         (&Method::POST, "/post") => {
 | |
|             // Concatenate the body...
 | |
|             let b = hyper::body::to_bytes(req.into_body()).await?;
 | |
|             // 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(MISSING.into())
 | |
|                     .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(NOTNUMERIC.into())
 | |
|                         .unwrap());
 | |
|                 }
 | |
|             } else {
 | |
|                 return Ok(Response::builder()
 | |
|                     .status(StatusCode::UNPROCESSABLE_ENTITY)
 | |
|                     .body(MISSING.into())
 | |
|                     .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(body.into()))
 | |
|         }
 | |
|         _ => Ok(Response::builder()
 | |
|             .status(StatusCode::NOT_FOUND)
 | |
|             .body(Body::empty())
 | |
|             .unwrap()),
 | |
|     }
 | |
| }
 | |
| 
 | |
| #[tokio::main]
 | |
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | |
|     pretty_env_logger::init();
 | |
| 
 | |
|     let addr = ([127, 0, 0, 1], 1337).into();
 | |
| 
 | |
|     let server = Server::bind(&addr).serve(make_service_fn(|_| {
 | |
|         async { Ok::<_, hyper::Error>(service_fn(param_example)) }
 | |
|     }));
 | |
| 
 | |
|     println!("Listening on http://{}", addr);
 | |
| 
 | |
|     server.await?;
 | |
| 
 | |
|     Ok(())
 | |
| }
 |