summaryrefslogtreecommitdiff
path: root/server/src/main.rs
diff options
context:
space:
mode:
authorstuebinm2021-04-27 17:40:40 +0200
committerstuebinm2021-04-27 17:40:40 +0200
commite0c6f2cd7a2ef02fb9b7947816b54f3a43ef522d (patch)
tree8e5f1829d40e159086ac9aa4c2f248e8d633e110 /server/src/main.rs
parent445c3f73d77fe957c1663666df4fde852fd4de14 (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 '')
-rw-r--r--server/src/main.rs121
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?;