Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions examples/http_server_proxy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! Run the example with:
//! ```sh
//! cargo build --example http_server_proxy --target=wasm32-wasip2
//! wasmtime serve -Scli -Shttp --env TARGET_URL=https://example.com/ target/wasm32-wasip2/debug/examples/http_server_proxy.wasm
//! curl --no-buffer -v 127.0.0.1:8080/proxy/
//! ```
use wstd::http::body::Body;
use wstd::http::{Client, Error, Request, Response, StatusCode, Uri};

const PROXY_PREFIX: &str = "/proxy/";

#[wstd::http_server]
async fn main(server_req: Request<Body>) -> Result<Response<Body>, Error> {
match server_req.uri().path_and_query().unwrap().as_str() {
api_prefixed_path if api_prefixed_path.starts_with(PROXY_PREFIX) => {
// Remove PROXY_PREFIX
let target_url =
std::env::var("TARGET_URL").expect("missing environment variable TARGET_URL");
let target_url: Uri = format!(
"{target_url}{}",
api_prefixed_path
.strip_prefix(PROXY_PREFIX)
.expect("checked above")
)
.parse()
.expect("final target url should be parseable");
println!("Proxying to {target_url}");
proxy(server_req, target_url).await
}
_ => Ok(http_not_found(server_req)),
}
}

async fn proxy(server_req: Request<Body>, target_url: Uri) -> Result<Response<Body>, Error> {
let client = Client::new();
let mut client_req = Request::builder();
client_req = client_req.uri(target_url).method(server_req.method());

// Copy headers from `server_req` to the `client_req`.
for (key, value) in server_req.headers() {
client_req = client_req.header(key, value);
}

// Stream the request body.
let client_req = client_req.body(server_req.into_body())?;
// Send the request.
let client_resp = client.send(client_req).await?;
// Copy headers from `client_resp` to `server_resp`.
let mut server_resp = Response::builder();
for (key, value) in client_resp.headers() {
server_resp
.headers_mut()
.expect("no errors could be in ResponseBuilder")
.append(key, value.clone());
}
Ok(server_resp.body(client_resp.into_body())?)
}

fn http_not_found(_request: Request<Body>) -> Response<Body> {
Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
.unwrap()
}
23 changes: 17 additions & 6 deletions test-programs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use std::process::{Child, Command};
use std::thread::sleep;
use std::time::Duration;

const DEFAULT_SERVER_PORT: u16 = 8081;

/// Manages exclusive access to port 8081, and kills the process when dropped
pub struct WasmtimeServe {
#[expect(dead_code, reason = "exists to live for as long as wasmtime process")]
Expand All @@ -22,26 +24,35 @@ impl WasmtimeServe {
///
/// Kills the wasmtime process, and releases the lock, once dropped.
pub fn new(guest: &str) -> std::io::Result<Self> {
Self::new_with_config(guest, DEFAULT_SERVER_PORT, &[])
}

pub fn new_with_config(guest: &str, port: u16, env_vars: &[&str]) -> std::io::Result<Self> {
let mut lockfile = std::env::temp_dir();
lockfile.push("TEST_PROGRAMS_WASMTIME_SERVE.lock");
lockfile.push(format!("TEST_PROGRAMS_WASMTIME_SERVE_{port}.lock"));
let lockfile = File::create(&lockfile)?;
lockfile.lock()?;

// Run wasmtime serve.
// Enable -Scli because we currently don't have a way to build with the
// proxy adapter, so we build with the default adapter.
let process = Command::new("wasmtime")
let mut process = Command::new("wasmtime");
let listening_addr = format!("127.0.0.1:{port}");
process
.arg("serve")
.arg("-Scli")
.arg("--addr=127.0.0.1:8081")
.arg(guest)
.spawn()?;
.arg("--addr")
.arg(&listening_addr);
for env_var in env_vars {
process.arg("--env").arg(env_var);
}
let process = process.arg(guest).spawn()?;
let w = WasmtimeServe { lockfile, process };

// Clumsily wait for the server to accept connections.
'wait: loop {
sleep(Duration::from_millis(100));
if TcpStream::connect("127.0.0.1:8081").is_ok() {
if TcpStream::connect(&listening_addr).is_ok() {
break 'wait;
}
}
Expand Down
20 changes: 20 additions & 0 deletions test-programs/tests/http_server_proxy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use anyhow::Result;

#[test_log::test]
fn http_server_proxy() -> Result<()> {
// Run wasmtime serve for the proxy and the target HTTP server.
let _serve_target = test_programs::WasmtimeServe::new(test_programs::HTTP_SERVER)?;
let _serve_proxy = test_programs::WasmtimeServe::new_with_config(
test_programs::HTTP_SERVER_PROXY,
8082,
&["TARGET_URL=http://127.0.0.1:8081"],
)?;

// TEST / of the `http_server` example through the proxy
let body: String = ureq::get("http://127.0.0.1:8082/proxy/")
.call()?
.body_mut()
.read_to_string()?;
assert_eq!(body, "Hello, wasi:http/proxy world!\n");
Ok(())
}
Loading