-
Notifications
You must be signed in to change notification settings - Fork 11
Add a http server proxy example #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Run the example with: | ||
// 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 | ||
// Test with `curl --no-buffer -v 127.0.0.1:8080/proxy/` | ||
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"); | ||
println!("Proxying to {target_url}"); | ||
|
||
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); | ||
} | ||
|
||
// 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"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
Client::finish(client_request_body, None) | ||
.map_err(|_http_err| { | ||
std::io::Error::new( | ||
std::io::ErrorKind::InvalidData, | ||
"Failed write the HTTP request body", | ||
) | ||
}) | ||
.and(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()); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And similarly, could this avoid copying headers individually? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to above, this can do There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Run the example with: | ||
// cargo build --example http_server_proxy_simple --target=wasm32-wasip2 | ||
// wasmtime serve -Scli -Shttp --env TARGET_URL=https://example.com target/wasm32-wasip2/debug/examples/http_server_proxy_simple.wasm | ||
// Test with `curl -v 127.0.0.1:8080` | ||
use wstd::http::body::IncomingBody; | ||
use wstd::http::server::{Finished, Responder}; | ||
use wstd::http::{Client, Request, Response, Uri}; | ||
|
||
#[wstd::http_server] | ||
async fn main(server_req: Request<IncomingBody>, responder: Responder) -> Finished { | ||
let api_prefixed_path = server_req.uri().path_and_query().unwrap().as_str(); | ||
let target_url = std::env::var("TARGET_URL").expect("missing environment variable TARGET_URL"); | ||
let target_url: Uri = format!("{target_url}{}", api_prefixed_path) | ||
.parse() | ||
.expect("final target url should be parseable"); | ||
println!("Proxying to {target_url}"); | ||
|
||
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. | ||
let (server_req_parts, server_req_body) = server_req.into_parts(); | ||
*client_req.headers_mut().unwrap() = server_req_parts.headers; | ||
// Send the whole request. | ||
let client_req = client_req | ||
.body(server_req_body) | ||
.expect("client_req.body failed"); | ||
|
||
let client_resp: Response<IncomingBody> = | ||
client.send(client_req).await.expect("client.send failed"); | ||
let mut server_resp = Response::builder(); | ||
let (client_resp_parts, client_resp_body) = client_resp.into_parts(); | ||
*server_resp.headers_mut().unwrap() = client_resp_parts.headers; | ||
// Send the response. | ||
let server_resp = server_resp | ||
.body(client_resp_body) | ||
.expect("server_resp.body failed"); | ||
responder.respond(server_resp).await | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.