Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ serde_json = { workspace = true, optional = true }
[dev-dependencies]
anyhow.workspace = true
clap.workspace = true
futures-concurrency.workspace = true
futures-lite.workspace = true
humantime.workspace = true
serde = { workspace = true, features = ["derive"] }
Expand Down Expand Up @@ -61,6 +62,7 @@ authors = [
anyhow = "1"
cargo_metadata = "0.18.1"
clap = { version = "4.5.26", features = ["derive"] }
futures-concurrency = "7.6.3"
futures-core = "0.3.19"
futures-lite = "1.12.0"
humantime = "2.1.0"
Expand Down
90 changes: 90 additions & 0 deletions examples/http_server_proxy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Run the example with:
// wasmtime serve -Scli -Shttp --env TARGET_URL=https://example.com http_server_proxy.wasm
use futures_concurrency::prelude::*;
use wstd::http::body::{BodyForthcoming, IncomingBody};
use wstd::http::server::{Finished, Responder};
use wstd::http::{Client, Request, Response, StatusCode, Uri};
use wstd::io::{copy, empty};

const PROXY_PREFIX: &str = "/proxy";

#[wstd::http_server]
async fn main(mut server_req: Request<IncomingBody>, responder: Responder) -> Finished {
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");

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 request to the client request.
for (key, value) in server_req.headers() {
client_req = client_req.header(key, value);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of copying the headers one at a time, could this do what the http server example does?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in the simplified example.


// Send the request.
let client_req = client_req
.body(BodyForthcoming)
.expect("client_req.body failed");
let (mut client_request_body, client_resp) = client
.start_request(client_req)
.await
.expect("client.start_request failed");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using start_request works here, though is a little more verbose than needed for a simple proxy. If you change the request above to use .body(server_req.into_body()), then you can use plain send instead of start_request and manually copying the body.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in the simplified example.


// Copy the server request body to client's request body.
let server_req_to_client_req = async {
let res = copy(server_req.body_mut(), &mut client_request_body).await;
// TODO: Convert to io error if necessary
let _ = Client::finish(client_request_body, None);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, can we map io errors to ErrorVariant::BodyIo error?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am constructing a std::io::Error now.

res
};

// Copy the client response headers to server response.
let client_resp_to_server_resp = async {
let client_resp = client_resp.await.unwrap();
let mut server_resp = Response::builder();
for (key, value) in client_resp.headers() {
server_resp
.headers_mut()
.unwrap()
.append(key, value.clone());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And similarly, could this avoid copying headers individually?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in the simplified example.

// Start sending the server response.
let server_resp = server_resp.body(BodyForthcoming).unwrap();
let mut server_resp = responder.start_response(server_resp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, this can do server_resp.body(client_resp.into_body()) and plain respond.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in the simplified example.


(
copy(client_resp.into_body(), &mut server_resp).await,
server_resp,
)
};

let (server_req_to_client_req, (client_resp_to_server_resp, server_resp)) =
(server_req_to_client_req, client_resp_to_server_resp)
.join()
.await;
let is_success = server_req_to_client_req.and(client_resp_to_server_resp);
Finished::finish(server_resp, is_success, None)
}
_ => http_not_found(server_req, responder).await,
}
}

async fn http_not_found(_request: Request<IncomingBody>, responder: Responder) -> Finished {
let response = Response::builder()
.status(StatusCode::NOT_FOUND)
.body(empty())
.unwrap();
responder.respond(response).await
}
Loading