First, a Rust webserver using Tide and async_std listening on a unix domain socket.
#[async_std::main]async fn main() -> Result<(), std::io::Error> {let mut app = tide::new();let sock = format!("http+unix://{}", "/var/tmp/toaster.sock");app.at("/").get(|_| async { Ok("Hello, world!") });app.listen(sock).await?;Ok(())}
We can test it by running the server with cargo run main.rs
.
curl --unix-socket ./toaster.sock http://localhost/Hello, world!%
Then in our node app we'll pull in undici.
note: I experienced segfaults on node v11, make sure you use something like v14 with undici
const undici = require("undici");main();async function main() {const client = new undici.Client(`http://localhost`, {socketPath: "/var/tmp/toaster.sock",});const res = await client.request({ path: "/", method: "GET" });res.body.on("data", (buf) => console.log(buf.toString()));client.close();}
With the cargo run main.rs
server running, we can hit the socket with node.
node index.jsHello, world!
This is great and we want to take it further. We want to be able to spawn node.js processes from Rust and have them communicate back. To do this we'll take further advantage of Rust's async support in our server. We take advantage of try_join!
use async_std::task;use futures::try_join;use std::io::{self, Write};use std::process::Command;#[async_std::main]async fn main() -> Result<(), std::io::Error> {// server creationlet mut app = tide::new();let sock = format!("http+unix://{}", "/var/tmp/toaster.sock");app.at("/").get(|_| async { Ok("Hello, world!") });let server = app.listen(sock);// node script creationlet child = task::spawn(async {let cmd = Command::new("node").arg("index.js").output();io::stdout().write_all(&cmd.unwrap().stdout).unwrap();Ok(())});// results of both futures// result is Result<((),()), Error>let result = try_join!(server, child);match result {Err(e) => Err(e),Ok(_) => Ok(()),}}
We don't need to modify our node client at all. The output comes back through the io stdout write that we're putting the node subcommand output into.
➜ cargo run src/main.rsFinished dev [unoptimized + debuginfo] target(s) in 0.28sRunning `target/debug/server src/main.rs`Hello, world!
The server then hangs, waiting for any other requests which we can continue to send it. Any other requests don't result in console output on the server though, since we're only logging the stdout from our node process.
We aren't handling the removal of the sock file after running the server, so if we run it twice we get an error returned Address already in use
.
Err(Os { code: 48, kind: AddrInUse, message: "Address already in use" })
You will have to rm /var/tmp/toaster.sock
in this case before starting the server again. We could handle this in the future (pun intended).