From 57d4ca6f6a8181f6c583b28ac726781b7901b913 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Wed, 27 Sep 2023 14:44:25 -0700 Subject: [PATCH] Add `runestone server` --- Cargo.toml | 5 +++++ src/main.rs | 18 +++++++++++++++++- tests/lib.rs | 5 ++++- tests/server.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/server.rs diff --git a/Cargo.toml b/Cargo.toml index 843e8e6..7a77d7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,18 @@ license = "CC0-1.0" autotests = false [dependencies] +anyhow = "1.0.75" +axum = "0.6.1" +axum-server = "0.5.0" bitcoin = "0.30.1" clap = { version = "4.4.5", features = ["derive"] } serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.107" +tokio = { version = "1.17.0", features = ["rt-multi-thread"] } [dev-dependencies] executable-path = "1.0.0" +reqwest = { version = "0.11.20", features = ["blocking"] } [[test]] name = "integration" diff --git a/src/main.rs b/src/main.rs index d67b390..3b2147a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ use { + axum::routing::get, + axum::Router, bitcoin::{consensus::Decodable, Transaction}, clap::{ builder::{ @@ -8,7 +10,8 @@ use { Parser, }, runestone::Runestone, - std::{error::Error, io}, + std::{error::Error, io, net::ToSocketAddrs}, + tokio::runtime::Runtime, }; #[derive(Parser)] @@ -25,6 +28,8 @@ enum Subcommand { about = "Read a bitcoin transaction from standard input and print a JSON representation of its runestone." )] Decipher, + #[command(about = "Start the explorer.")] + Server, } fn main() -> Result<(), Box> { @@ -38,7 +43,18 @@ fn main() -> Result<(), Box> { serde_json::to_writer_pretty(&io::stdout(), &message)?; println!(); } + Subcommand::Server => Runtime::new()?.block_on(async { + let addr = ("0.0.0.0", 80).to_socket_addrs()?.next().unwrap(); + + axum_server::Server::bind(addr) + .serve(Router::new().route("/", get(home)).into_make_service()) + .await + })?, } Ok(()) } + +async fn home() -> &'static str { + "Hello, world!" +} diff --git a/tests/lib.rs b/tests/lib.rs index f2f4b4e..a1c1ee8 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,11 +1,14 @@ use { bitcoin::{consensus::Encodable, locktime, opcodes, script, Transaction, TxOut}, executable_path::executable_path, + reqwest::blocking as reqwest, std::{ io::Write, process::{Command, Stdio}, - str, + str, thread, + time::Duration, }, }; mod decipher; +mod server; diff --git a/tests/server.rs b/tests/server.rs new file mode 100644 index 0000000..d658752 --- /dev/null +++ b/tests/server.rs @@ -0,0 +1,40 @@ +use super::*; + +struct KillOnDrop(std::process::Child); + +impl Drop for KillOnDrop { + fn drop(&mut self) { + assert!(Command::new("kill") + .arg(self.0.id().to_string()) + .status() + .unwrap() + .success()); + } +} + +#[test] +fn server_returns_homepage() { + let _server = KillOnDrop( + Command::new(executable_path("runestone")) + .arg("server") + .spawn() + .unwrap(), + ); + + for i in 0..100 { + if reqwest::get("http://localhost").is_ok() { + break; + } + + if i == 99 { + panic!("server failed to start"); + } + + thread::sleep(Duration::from_millis(100)); + } + + assert_eq!( + reqwest::get("http://localhost").unwrap().text().unwrap(), + "Hello, world!" + ); +}