From 74f7520b1cfffd998750d6e59f692c7e82f0bc1c Mon Sep 17 00:00:00 2001 From: Vijay Karanjkar Date: Mon, 13 Apr 2026 15:57:11 -0700 Subject: [PATCH] feat: add HTTP invocation example Signed-off-by: Vijay Karanjkar --- examples/Cargo.toml | 10 ++++++ examples/src/invoke/http/README.md | 15 +++++++++ examples/src/invoke/http/client.rs | 53 ++++++++++++++++++++++++++++++ examples/src/invoke/http/dapr.yaml | 15 +++++++++ examples/src/invoke/http/server.rs | 43 ++++++++++++++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 examples/src/invoke/http/README.md create mode 100644 examples/src/invoke/http/client.rs create mode 100644 examples/src/invoke/http/dapr.yaml create mode 100644 examples/src/invoke/http/server.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 36fcc08..f5ce0f6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -14,6 +14,7 @@ dapr = { path = "../dapr" } dapr-macros = { path = "../dapr-macros" } env_logger = "0.11" log = "0.4" +reqwest = { version = "0.12", default-features = false } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["full"] } @@ -74,8 +75,17 @@ path = "src/invoke/grpc-proxying/client.rs" name = "invoke-grpc-proxying-server" path = "src/invoke/grpc-proxying/server.rs" +[[example]] +name = "invoke-http-client" +path = "src/invoke/http/client.rs" + +[[example]] +name = "invoke-http-server" +path = "src/invoke/http/server.rs" + [[example]] name = "jobs" + path = "src/jobs/jobs.rs" [[example]] diff --git a/examples/src/invoke/http/README.md b/examples/src/invoke/http/README.md new file mode 100644 index 0000000..c8752cf --- /dev/null +++ b/examples/src/invoke/http/README.md @@ -0,0 +1,15 @@ +# Invoke HTTP Example + +This example demonstrates how to use the Dapr HTTP proxying feature to proxy HTTP requests sent via the Rust SDK (using `reqwest`) through Dapr to reach another application. + +1. Build the examples: +```bash +cargo build --examples +``` + +2. Run with Dapr in the example/invoke/http directory: +```bash +dapr run -f . +``` + +If everything worked, you should see the `invoke-http-client` successfully sending an HTTP request via the Dapr sidecar to the `invoke-http-server`, with `"Hello, test! (from HTTP server)"` printed to the stdout. diff --git a/examples/src/invoke/http/client.rs b/examples/src/invoke/http/client.rs new file mode 100644 index 0000000..2c2446d --- /dev/null +++ b/examples/src/invoke/http/client.rs @@ -0,0 +1,53 @@ +use std::time::Duration; + +#[tokio::main] +async fn main() -> Result<(), Box> { + tokio::time::sleep(Duration::from_secs(5)).await; + + let address = "https://127.0.0.1".to_string(); + + let client_result = dapr::Client::::connect(address).await; + + let value = "test"; + let method_to_call = format!("hello/{}", value); + + match client_result { + Ok(mut _client) => { + let http_client = reqwest::Client::builder() + .timeout(Duration::from_secs(2)) + .build() + .unwrap(); + + let target_app_id = "invoke-http-server"; + let dapr_http_port: u16 = std::env::var("DAPR_HTTP_PORT") + .unwrap_or_else(|_| "3500".to_string()) + .parse()?; + + let url = format!("http://127.0.0.1:{}/{}", dapr_http_port, method_to_call); + let req = http_client.post(url).header("dapr-app-id", target_app_id); + + let response_result = req.send().await; + match response_result { + Ok(response) => { + let body_result = response.text().await; + match body_result { + Ok(body) => { + println!("Response: {:?}", body); + } + Err(e) => { + eprintln!("Error 1: {}", e); + } + } + } + Err(e) => { + eprintln!("Error 2: {}", e); + } + } + } + Err(e) => { + eprintln!("Dapr client error: {}", e); + } + } + + Ok(()) +} diff --git a/examples/src/invoke/http/dapr.yaml b/examples/src/invoke/http/dapr.yaml new file mode 100644 index 0000000..413fcc4 --- /dev/null +++ b/examples/src/invoke/http/dapr.yaml @@ -0,0 +1,15 @@ +version: 1 +common: + daprdLogDestination: console +apps: + - appID: invoke-http-server + appDirPath: ./ + appProtocol: http + appPort: 8080 + logLevel: debug + command: ["cargo", "run", "--example", "invoke-http-server"] + - appID: invoke-http-client + appDirPath: ./ + appProtocol: http + logLevel: debug + command: ["cargo", "run", "--example", "invoke-http-client"] diff --git a/examples/src/invoke/http/server.rs b/examples/src/invoke/http/server.rs new file mode 100644 index 0000000..0600010 --- /dev/null +++ b/examples/src/invoke/http/server.rs @@ -0,0 +1,43 @@ +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpListener; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "127.0.0.1:8080"; + let listener = TcpListener::bind(&addr).await?; + println!("Server running on {}", addr); + + loop { + let (mut stream, _) = listener.accept().await?; + + tokio::spawn(async move { + let mut buf = [0; 1024]; + if let Ok(n) = stream.read(&mut buf).await { + if n > 0 { + let request_str = String::from_utf8_lossy(&buf[..n]); + + if let Some(line) = request_str.lines().next() { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 { + let path = parts[1]; + + if let Some(value) = path.strip_prefix("/hello/") { + let body = format!("Hello, {}! (from HTTP server)", value); + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {}\r\n\r\n{}", + body.len(), + body + ); + let _ = stream.write_all(response.as_bytes()).await; + return; + } + } + } + + let response = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n"; + let _ = stream.write_all(response.as_bytes()).await; + } + } + }); + } +}