summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuebinm2021-04-27 17:40:40 +0200
committerstuebinm2021-04-27 17:40:40 +0200
commite0c6f2cd7a2ef02fb9b7947816b54f3a43ef522d (patch)
tree8e5f1829d40e159086ac9aa4c2f248e8d633e110
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
-rw-r--r--server/Cargo.lock90
-rw-r--r--server/Cargo.toml1
-rw-r--r--server/src/main.rs121
-rw-r--r--todo.org5
4 files changed, 188 insertions, 29 deletions
diff --git a/server/Cargo.lock b/server/Cargo.lock
index 5800be3..a7a7799 100644
--- a/server/Cargo.lock
+++ b/server/Cargo.lock
@@ -34,6 +34,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "bitflags",
+ "textwrap",
+ "unicode-width",
+]
+
+[[package]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -182,6 +193,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
+name = "heck"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -426,6 +446,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -478,6 +522,7 @@ dependencies = [
"hex",
"hyper",
"sha2",
+ "structopt",
"tokio",
]
@@ -526,6 +571,30 @@ dependencies = [
]
[[package]]
+name = "structopt"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "syn"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -537,6 +606,15 @@ dependencies = [
]
[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
name = "tokio"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -620,6 +698,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
+name = "unicode-segmentation"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 82b4f20..8fbbea2 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -12,3 +12,4 @@ tokio = { version = "1", features = ["full"] }
futures = "0.3"
sha2 = "0.9.3"
hex = "0.4.3"
+structopt = { version = "0.3", default-features = false }
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?;
diff --git a/todo.org b/todo.org
index 746f97c..9e3154f 100644
--- a/todo.org
+++ b/todo.org
@@ -13,12 +13,13 @@
* TODO Server
** DONE possibly switch from guile to rust or something
** TODO implement missing features:
-*** TODO truncate hashes
+*** DONE truncate hashes
*** TODO accept unencrypted answers and store them in a single file?
*** TODO validate answers?
This seems difficult, but I guess it could at least check whether
the age-encryption header is there.
-*** TODO config options: listen address, output path
+*** DONE config options: listen address, output path
+*** TODO limit answers from one ip address
** TODO short description of what it does
** TODO nix module