sausage-plugin part 2: import! with typed clients (sausage_interface::client_rs), TCP endpoints with rpc.endpoint, examples
Part 1 fixes: complete stdout writes, buffered stdin, Rust type names in schemas, enum variant docs, -32603 for serde errors in handlers, crate docs and modules. End-to-end check of a generated consumer through a fake core.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
//! Serve files from `/state/www` and expose a control endpoint at `/self/control`.
|
||||
use sausage_plugin::{Ctx, Result, background, listen, pid, rpc, schema, state_dir};
|
||||
use std::{cell::Cell, rc::Rc};
|
||||
use wstd::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
iter::AsyncIterator,
|
||||
net::TcpListener,
|
||||
};
|
||||
struct Www;
|
||||
/// The server configuration.
|
||||
#[schema]
|
||||
struct StartParams {
|
||||
/// The HTTP port on all network interfaces.
|
||||
port: u16,
|
||||
}
|
||||
#[rpc(endpoints(control=Control),grace_seconds=5)]
|
||||
impl Www {
|
||||
/// Start the HTTP server and return its process ID.
|
||||
async fn start(&self, p: StartParams) -> Result<String> {
|
||||
let tcp = TcpListener::bind(&format!("0.0.0.0:{}", p.port)).await?;
|
||||
let control = Rc::new(Control { hits: Cell::new(0) });
|
||||
listen("/self/control", control.clone()).await?;
|
||||
background(serve(tcp, control));
|
||||
Ok(pid())
|
||||
}
|
||||
}
|
||||
struct Control {
|
||||
hits: Cell<u64>,
|
||||
}
|
||||
#[rpc]
|
||||
impl Control {
|
||||
/// Count the HTTP requests this server answered.
|
||||
fn stats(&self) -> Result<u64> {
|
||||
Ok(self.hits.get())
|
||||
}
|
||||
/// End this process after the response reaches the client.
|
||||
fn stop(&self, ctx: &Ctx) -> Result<()> {
|
||||
ctx.exit_after_response();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
async fn serve(tcp: TcpListener, control: Rc<Control>) {
|
||||
let mut incoming = tcp.incoming();
|
||||
while let Some(Ok(mut stream)) = incoming.next().await {
|
||||
let mut request = [0; 8192];
|
||||
let Ok(size) = stream.read(&mut request).await else {
|
||||
continue;
|
||||
};
|
||||
let line = String::from_utf8_lossy(&request[..size]);
|
||||
let path = line
|
||||
.split_whitespace()
|
||||
.nth(1)
|
||||
.unwrap_or("/")
|
||||
.trim_start_matches('/');
|
||||
let (status, body) = match std::fs::read(state_dir().join(if path.is_empty() {
|
||||
"index.html"
|
||||
} else {
|
||||
path
|
||||
})) {
|
||||
Ok(body) => ("200 OK", body),
|
||||
Err(_) => ("404 Not Found", b"Not found".to_vec()),
|
||||
};
|
||||
control.hits.set(control.hits.get() + 1);
|
||||
let header = format!(
|
||||
"HTTP/1.1 {status}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
|
||||
body.len()
|
||||
);
|
||||
let _ = stream.write_all(header.as_bytes()).await;
|
||||
let _ = stream.write_all(&body).await;
|
||||
let _ = stream.flush().await;
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
sausage_plugin::run(Www)
|
||||
}
|
||||
Reference in New Issue
Block a user