From feea21d0673873d7feffa7c773990dcf212f032d Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 31 Dec 2024 17:20:52 +0200 Subject: [PATCH] feat: add HTTP -> TCP proxy example Signed-off-by: Roman Volosatovs --- Cargo.lock | 16 ++++ Cargo.toml | 3 + examples/rust/hello-http-tcp-proxy/Cargo.toml | 29 +++++++ .../rust/hello-http-tcp-proxy/src/main.rs | 78 +++++++++++++++++++ .../rust/hello-http-tcp-proxy/wit/deps.lock | 4 + .../rust/hello-http-tcp-proxy/wit/deps.toml | 1 + .../wit/deps/hello/hello.wit | 13 ++++ .../rust/hello-http-tcp-proxy/wit/world.wit | 5 ++ examples/rust/hello-tcp-client/src/main.rs | 2 +- 9 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 examples/rust/hello-http-tcp-proxy/Cargo.toml create mode 100644 examples/rust/hello-http-tcp-proxy/src/main.rs create mode 100644 examples/rust/hello-http-tcp-proxy/wit/deps.lock create mode 100644 examples/rust/hello-http-tcp-proxy/wit/deps.toml create mode 100644 examples/rust/hello-http-tcp-proxy/wit/deps/hello/hello.wit create mode 100644 examples/rust/hello-http-tcp-proxy/wit/world.wit diff --git a/Cargo.lock b/Cargo.lock index d086bc10..18d541f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1396,6 +1396,22 @@ dependencies = [ "wrpc-transport", ] +[[package]] +name = "hello-http-tcp-proxy" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "http 1.2.0", + "hyper 1.5.1", + "hyper-util", + "tokio", + "tracing", + "tracing-subscriber", + "wit-bindgen-wrpc", + "wrpc-transport", +] + [[package]] name = "hello-nats-client" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3478799b..25d94a3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,7 +128,10 @@ clap = { version = "4", default-features = false } criterion = { version = "0.5", default-features = false } futures = { version = "0.3", default-features = false } heck = { version = "0.5", default-features = false } +http = { version = "1", default-features = false } humantime = { version = "2.1", default-features = false } +hyper = { version = "1", default-features = false } +hyper-util = { version = "0.1", default-features = false } nuid = { version = "0.5", default-features = false } pin-project-lite = { version = "0.2", default-features = false } prettyplease = { version = "0.2.25", default-features = false } diff --git a/examples/rust/hello-http-tcp-proxy/Cargo.toml b/examples/rust/hello-http-tcp-proxy/Cargo.toml new file mode 100644 index 00000000..792ee200 --- /dev/null +++ b/examples/rust/hello-http-tcp-proxy/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "hello-http-tcp-proxy" +version = "0.1.0" +authors.workspace = true +categories.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow = { workspace = true } +clap = { workspace = true, features = [ + "color", + "derive", + "error-context", + "help", + "std", + "suggestions", + "usage", +] } +http = { workspace = true } +hyper = { workspace = true } +hyper-util = { workspace = true, features = ["http1", "http2", "server", "tokio"] } +tokio = { workspace = true, features = ["rt-multi-thread"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["ansi", "fmt"] } +wit-bindgen-wrpc = { workspace = true } +wrpc-transport = { workspace = true, features = ["net"] } diff --git a/examples/rust/hello-http-tcp-proxy/src/main.rs b/examples/rust/hello-http-tcp-proxy/src/main.rs new file mode 100644 index 00000000..fbeabf28 --- /dev/null +++ b/examples/rust/hello-http-tcp-proxy/src/main.rs @@ -0,0 +1,78 @@ +use std::sync::Arc; + +use anyhow::Context as _; +use clap::Parser; +use hyper_util::rt::{TokioExecutor, TokioIo}; +use tokio::net::TcpListener; +use tracing::{error, info}; + +mod bindings { + wit_bindgen_wrpc::generate!({ + with: { + "wrpc-examples:hello/handler": generate + } + }); +} + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Address to serve `wrpc-examples:hello/handler.hello` on + #[arg(default_value = "[::1]:8080")] + ingress: String, + + /// Address to invoke `wrpc-examples:hello/handler.hello` on + #[arg(default_value = "[::1]:7761")] + egress: String, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + + let Args { egress, ingress } = Args::parse(); + let wrpc = wrpc_transport::tcp::Client::from(egress); + let wrpc = Arc::new(wrpc); + + let svc = hyper::service::service_fn(move |req| { + let wrpc = Arc::clone(&wrpc); + async move { + let (http::request::Parts { method, uri, .. }, _) = req.into_parts(); + match (method.as_str(), uri.path_and_query().map(|pq| pq.as_str())) { + ("GET", Some("/hello")) => { + match bindings::wrpc_examples::hello::handler::hello(wrpc.as_ref(), ()) + .await + .context("failed to invoke `wrpc-examples.hello/handler.hello`") + { + Ok(hello) => Ok(http::Response::new(format!(r#""{hello}""#))), + Err(err) => Err(format!("{err:#}")), + } + } + (method, Some(path)) => { + Err(format!("method `{method}` not supported for path `{path}`")) + } + (method, None) => Err(format!("method `{method}` not supported")), + } + } + }); + let srv = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); + let socket = TcpListener::bind(&ingress) + .await + .with_context(|| format!("failed to bind on {ingress}"))?; + loop { + let stream = match socket.accept().await { + Ok((stream, addr)) => { + info!(?addr, "accepted HTTP connection"); + stream + } + Err(err) => { + error!(?err, "failed to accept HTTP endpoint connection"); + continue; + } + }; + let svc = svc.clone(); + if let Err(err) = srv.serve_connection(TokioIo::new(stream), svc).await { + error!(?err, "failed to serve HTTP endpoint connection"); + } + } +} diff --git a/examples/rust/hello-http-tcp-proxy/wit/deps.lock b/examples/rust/hello-http-tcp-proxy/wit/deps.lock new file mode 100644 index 00000000..275cb66c --- /dev/null +++ b/examples/rust/hello-http-tcp-proxy/wit/deps.lock @@ -0,0 +1,4 @@ +[hello] +path = "../../../wit/hello" +sha256 = "3680bb734f3fa9f7325674142a2a9b558efd34ea2cb2df7ccb651ad869078d27" +sha512 = "688fdae594dc43bd65bd15ea66b77a8f97cb4bc1c3629719e91d6c1391c66f7c8c6517d096f686cca996188f64f075c4ccb0d70a40097ce76b8b4bcc71dc7506" diff --git a/examples/rust/hello-http-tcp-proxy/wit/deps.toml b/examples/rust/hello-http-tcp-proxy/wit/deps.toml new file mode 100644 index 00000000..084f03eb --- /dev/null +++ b/examples/rust/hello-http-tcp-proxy/wit/deps.toml @@ -0,0 +1 @@ +hello = "../../../wit/hello" diff --git a/examples/rust/hello-http-tcp-proxy/wit/deps/hello/hello.wit b/examples/rust/hello-http-tcp-proxy/wit/deps/hello/hello.wit new file mode 100644 index 00000000..6c84d66c --- /dev/null +++ b/examples/rust/hello-http-tcp-proxy/wit/deps/hello/hello.wit @@ -0,0 +1,13 @@ +package wrpc-examples:hello; + +interface handler { + hello: func() -> string; +} + +world client { + import handler; +} + +world server { + export handler; +} diff --git a/examples/rust/hello-http-tcp-proxy/wit/world.wit b/examples/rust/hello-http-tcp-proxy/wit/world.wit new file mode 100644 index 00000000..f9a08b60 --- /dev/null +++ b/examples/rust/hello-http-tcp-proxy/wit/world.wit @@ -0,0 +1,5 @@ +package wrpc-examples:hello-rust-client; + +world client { + include wrpc-examples:hello/client; +} diff --git a/examples/rust/hello-tcp-client/src/main.rs b/examples/rust/hello-tcp-client/src/main.rs index eb281a7c..c14ff6e2 100644 --- a/examples/rust/hello-tcp-client/src/main.rs +++ b/examples/rust/hello-tcp-client/src/main.rs @@ -22,7 +22,7 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt().init(); let Args { addr } = Args::parse(); - let wrpc = wrpc_transport::tcp::Client::from(addr); + let wrpc = wrpc_transport::tcp::Client::from(&addr); let hello = bindings::wrpc_examples::hello::handler::hello(&wrpc, ()) .await .context("failed to invoke `wrpc-examples.hello/handler.hello`")?;