Verified Commit 90f6138f authored by Benjamin Lee's avatar Benjamin Lee 💬

added option to propagate errors from child

parent cc10bf84
......@@ -5,3 +5,7 @@ command = "notify-send \"got a webhook request\""
[handlers."/echo"]
command = "cowsay"
[handlers."/slow-error"]
command = "sleep 5s; echo error; exit 1"
wait_for_errors = true
......@@ -10,11 +10,14 @@ use std::path::PathBuf;
use std::process::Stdio;
use std::sync::Arc;
use structopt::StructOpt;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, AsyncReadExt};
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::process::Command;
use tokio::sync::mpsc::channel;
use tokio::task::JoinHandle;
use tokio_stream::StreamExt;
use tracing::{debug, error, debug_span, info, warn, Instrument, Span};
use tracing::{
debug, debug_span, error, error_span, info, warn, Instrument, Span,
};
#[derive(StructOpt)]
struct Cli {
......@@ -34,6 +37,15 @@ struct Handler {
/// Working directory to run the command in.
working_dir: Option<PathBuf>,
/// Return an HTTP error code if the command exits unsucessfully.
///
/// If this is `false`, the server will return 200 as soon as the body has
/// been recieved, without waiting for the child process to exit.
///
/// Defaults to `false`.
#[serde(default)]
wait_for_errors: bool,
}
#[derive(Deserialize)]
......@@ -61,8 +73,9 @@ async fn main() {
let handlers = Arc::clone(&handlers);
async move {
Ok::<_, Infallible>(service_fn(move |req| {
debug!(?req, "handling request");
let handlers = Arc::clone(&handlers);
let span = debug_span!("handle", ?req);
let span = error_span!("handle", url=?req.uri());
handle(handlers, req).instrument(span)
}))
}
......@@ -89,7 +102,6 @@ async fn handle_status(
req: Request<Body>,
) -> StatusCode {
let path = req.uri().path().to_owned();
debug!("handling {:?} {}", req.method(), path);
let handler = match handlers.get(&path) {
Some(handler) => handler,
......@@ -99,13 +111,24 @@ async fn handle_status(
};
match handler.spawn(req).await {
Ok(_) => StatusCode::OK,
Ok(result) => {
if handler.wait_for_errors {
if let Ok(true) = result.await {
StatusCode::OK
} else {
StatusCode::INTERNAL_SERVER_ERROR
}
} else {
StatusCode::OK
}
}
Err(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
impl Handler {
async fn spawn(&self, req: Request<Body>) -> io::Result<()> {
/// `JoinHandle` resolves to `true` if the child process exited sucessfully.
async fn spawn(&self, req: Request<Body>) -> io::Result<JoinHandle<bool>> {
let mut command = Command::new("sh");
command
.arg("-c")
......@@ -155,6 +178,7 @@ impl Handler {
match status {
Ok(status) if status.success() => {
info!("handler done");
return true;
},
Ok(status) => {
error!("handler exitted with status {}", status);
......@@ -163,7 +187,7 @@ impl Handler {
error!("error waiting for handler: {}", err);
}
}
break;
return false;
},
chunk = body_rx.recv(), if stdin.is_some() => match chunk {
......@@ -232,8 +256,9 @@ impl Handler {
}
}
};
let span = Span::current();
tokio::spawn(task.instrument(span));
let span = error_span!("child");
span.follows_from(Span::current());
let task = tokio::spawn(task.instrument(span));
// Wait for the whole body to come in before returning.
let mut body = req.into_body();
......@@ -244,6 +269,6 @@ impl Handler {
}
}
Ok(())
Ok(task)
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment