Skip to content

Commit e3aeefa

Browse files
committed
Add a http server proxy example
1 parent 2218692 commit e3aeefa

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ serde_json = { workspace = true, optional = true }
3131
[dev-dependencies]
3232
anyhow.workspace = true
3333
clap.workspace = true
34+
futures-concurrency.workspace = true
3435
futures-lite.workspace = true
3536
humantime.workspace = true
3637
serde = { workspace = true, features = ["derive"] }
@@ -61,6 +62,7 @@ authors = [
6162
anyhow = "1"
6263
cargo_metadata = "0.18.1"
6364
clap = { version = "4.5.26", features = ["derive"] }
65+
futures-concurrency = "7.6.3"
6466
futures-core = "0.3.19"
6567
futures-lite = "1.12.0"
6668
humantime = "2.1.0"

examples/http_server_proxy.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Run the example with:
2+
// wasmtime serve -Scli -Shttp --env TARGET_URL=https://example.com http_server_proxy.wasm
3+
use futures_concurrency::prelude::*;
4+
use wstd::http::body::{BodyForthcoming, IncomingBody};
5+
use wstd::http::server::{Finished, Responder};
6+
use wstd::http::{Client, Request, Response, StatusCode, Uri};
7+
use wstd::io::{copy, empty};
8+
9+
const PROXY_PREFIX: &str = "/proxy";
10+
11+
#[wstd::http_server]
12+
async fn main(mut incoming_req: Request<IncomingBody>, incoming_responder: Responder) -> Finished {
13+
match incoming_req.uri().path_and_query().unwrap().as_str() {
14+
api_prefixed_path if api_prefixed_path.starts_with(PROXY_PREFIX) => {
15+
// Remove PROXY_PREFIX
16+
let target_url =
17+
std::env::var("TARGET_URL").expect("missing environment variable TARGET_URL");
18+
let target_url: Uri = format!(
19+
"{target_url}{}",
20+
api_prefixed_path
21+
.strip_prefix(PROXY_PREFIX)
22+
.expect("checked above")
23+
)
24+
.parse()
25+
.expect("final target url should be parseable");
26+
27+
let client = Client::new();
28+
let mut client_req = Request::builder();
29+
client_req = client_req.uri(target_url).method(incoming_req.method());
30+
31+
// Copy headers from incoming request to the client request.
32+
for (key, value) in incoming_req.headers() {
33+
client_req = client_req.header(key, value);
34+
}
35+
36+
// Send the request.
37+
let client_req = client_req
38+
.body(BodyForthcoming)
39+
.expect("client_request.body failed");
40+
let (mut client_outgoing_body, client_resp) = client
41+
.start_request(client_req)
42+
.await
43+
.expect("client.start_request failed");
44+
45+
// Copy the incoming request body to client's outgoing body.
46+
let incoming_req_to_client_req = async {
47+
let res = copy(incoming_req.body_mut(), &mut client_outgoing_body).await;
48+
// TODO: Convert to io error if necessary
49+
let _ = Client::finish(client_outgoing_body, None);
50+
res
51+
};
52+
53+
// Copy the client_response headers to incoming response.
54+
let client_resp_to_incoming_resp = async {
55+
let client_resp = client_resp.await.unwrap();
56+
let mut incoming_resp = Response::builder();
57+
for (key, value) in client_resp.headers() {
58+
incoming_resp
59+
.headers_mut()
60+
.unwrap()
61+
.append(key, value.clone());
62+
}
63+
// Start sending the incoming response.
64+
let incoming_resp = incoming_resp.body(BodyForthcoming).unwrap();
65+
let mut incoming_resp = incoming_responder.start_response(incoming_resp);
66+
67+
(
68+
copy(client_resp.into_body(), &mut incoming_resp).await,
69+
incoming_resp,
70+
)
71+
};
72+
73+
let (incoming_req_to_client_req, (client_resp_to_incoming_resp, incoming_resp)) =
74+
(incoming_req_to_client_req, client_resp_to_incoming_resp)
75+
.join()
76+
.await;
77+
let is_success = incoming_req_to_client_req.and(client_resp_to_incoming_resp);
78+
Finished::finish(incoming_resp, is_success, None)
79+
}
80+
_ => http_not_found(incoming_req, incoming_responder).await,
81+
}
82+
}
83+
84+
async fn http_not_found(_request: Request<IncomingBody>, responder: Responder) -> Finished {
85+
let response = Response::builder()
86+
.status(StatusCode::NOT_FOUND)
87+
.body(empty())
88+
.unwrap();
89+
responder.respond(response).await
90+
}

0 commit comments

Comments
 (0)