diff options
author | stuebinm | 2021-04-27 17:40:40 +0200 |
---|---|---|
committer | stuebinm | 2021-04-27 17:40:40 +0200 |
commit | e0c6f2cd7a2ef02fb9b7947816b54f3a43ef522d (patch) | |
tree | 8e5f1829d40e159086ac9aa4c2f248e8d633e110 /server/src | |
parent | 445c3f73d77fe957c1663666df4fde852fd4de14 (diff) |
server: add config options, better outfile namesrustserver
e.g.
- ability to log ip addresses of respondents
- configurable bind address
- reasonably sane defaults (hopefully)
- survey name as part of output file names
Diffstat (limited to 'server/src')
-rw-r--r-- | server/src/main.rs | 121 |
1 files changed, 94 insertions, 27 deletions
diff --git a/server/src/main.rs b/server/src/main.rs index ec2c77c..b718d57 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,36 +1,92 @@ use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, Server, StatusCode}; +use hyper::server::conn::AddrStream; use sha2::{Sha256, Digest}; use std::io::Write; +use std::convert::Infallible; +use std::net::SocketAddr; +use std::path::PathBuf; +use structopt::StructOpt; + + + +#[derive(Debug, StructOpt, Clone)] +#[structopt(about = "simple server that eats slightly better surveys.")] +struct Settings { + #[structopt(short, long, help="log ip addresses of answers as part of their filename")] + log_ips: bool, + #[structopt(default_value="127.0.0.1:8000", help="address which the server should listen on")] + bind_address: SocketAddr, + #[structopt(parse(from_os_str), default_value=".", help="where to save incoming survey answers")] + out_path: PathBuf +} + + +async fn handle_survey(req: Request<Body>, remote_addr: &SocketAddr, settings: &Settings) -> Option<Response<Body>> { + let (parts, rawbody) = req.into_parts(); + let body = hyper::body::to_bytes(rawbody).await.ok()?; + let survey = parts.headers.get("survey")?; + + let hash = { + // we can use a hash of the content as filename; + // age ensures we won't have collisions even if + // two people handed in the same thing twice. + let mut hasher = Sha256::new(); + hasher.update(body.clone()); + hasher.finalize() + }; + + let mut filepath = settings.out_path.clone(); + filepath.set_file_name(match settings.log_ips { + true => { + let filename = format!( + "answers-{}-{}-{:.20}.age", + survey.to_str().ok()?, + remote_addr.ip(), + hex::encode(hash) + ); + println!( + "received survey response for survey {:?} from {}, saving to {}", + survey, + remote_addr.ip(), + filename + ); + filename + }, + false => { + let filename = format!( + "answers-{}-{:.20}.age", + survey.to_str().ok()?, + hex::encode(hash) + ); + println!( + "received survey response for survey {:?}, saving to {}", + survey, + filename + ); + filename + } + }); + + let mut file = std::fs::File::create(filepath).ok()?; + file.write_all(&body).unwrap(); + + Some(Response::new( + "thanks for handing in these answers!\n".into() + )) +} /// This is our service handler. It receives a Request, routes on its /// path, and returns a Future of a Response. -async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { +async fn server(req: Request<Body>, remote_addr: &SocketAddr, settings: &Settings) -> Result<Response<Body>, Infallible> { match (req.method(), req.uri().path()) { - // Serve some instructions at / - (&Method::GET, "/") => Ok(Response::new(Body::from( - "Try POSTing data to /echo such as: `curl localhost:8000/echo -XPOST -d 'hello world'`", - ))), - - // Simply echo the body back to the client. - (&Method::POST, "/echo") => Ok(Response::new(req.into_body())), (&Method::POST, "/survey") => { - let full_body = hyper::body::to_bytes(req.into_body()).await?; - let mut hasher = Sha256::new(); - hasher.update(full_body.clone()); - let hash = hasher.finalize(); - - let mut file = std::fs::File::create( - format!("answers-{}.age", hex::encode(hash)) - ).unwrap(); - - file.write_all(&full_body).unwrap(); - println!("got hash: {:?}", hash); - Ok(Response::new( - "thanks for hading in these answers!".into() - )) + match handle_survey(req, remote_addr, settings).await { + Some(response) => Ok(response), + None => Ok(Response::new("Something went wrong\n".into())) + } } // Return the 404 Not Found for other routes. @@ -46,17 +102,28 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { - let addr = ([127, 0, 0, 1], 8000).into(); + // this is a memory leak of constant size, to avoid the borrow checker + let opt: &'static Settings = Box::leak( + Box::new(Settings::from_args()) + ); + + // I have no idea what's wrong here ... + // look at https://docs.rs/hyper/0.14.7/hyper/service/fn.make_service_fn.html let service = make_service_fn( - |_| async { - Ok::<_, hyper::Error>(service_fn(echo)) + |socket: &AddrStream| { + let remote_addr = socket.remote_addr(); + async move { + Ok::<_, Infallible>(service_fn(move |req: Request<Body>| async move { + server(req, &remote_addr, &opt).await + })) + } } ); - let server = Server::bind(&addr).serve(service); + let server = Server::bind(&opt.bind_address).serve(service); - println!("Listening on http://{}", addr); + println!("Listening on http://{}", opt.bind_address); server.await?; |