Skip to content

Commit 3d0972b

Browse files
committed
Implement a proxy framework.
Add a wstd API for creating proxy applications, wrapping the WASI proxy world trait and macro. Compiling the example with `RUSTFLAGS="-Clink-arg=--wasi-adapter=proxy"` produces a program that runs in `wasmtime serve`.
1 parent f191348 commit 3d0972b

File tree

6 files changed

+187
-13
lines changed

6 files changed

+187
-13
lines changed

examples/proxy.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![no_main]
2+
3+
pub use wstd::http::{HeaderMap, Proxy, Request, Response};
4+
pub use wstd::io::{empty, Empty};
5+
6+
struct MyIncomingHandler;
7+
8+
impl Proxy<Empty> for MyIncomingHandler {
9+
fn handle(_request: Request<Empty>) -> Response<Empty> {
10+
let hdrs = HeaderMap::new();
11+
let body = empty();
12+
13+
Response::new(hdrs, body)
14+
}
15+
}
16+
17+
wstd::proxy!(MyIncomingHandler);

src/http/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
//! HTTP networking support
22
//!
3-
pub use http::uri::Uri;
3+
pub use http::uri::{Authority, PathAndQuery, Scheme, Uri};
44

55
#[doc(inline)]
66
pub use body::{Body, IntoBody};
77
pub use client::Client;
88
pub use error::{Error, Result};
99
pub use fields::{HeaderMap, HeaderName, HeaderValue};
1010
pub use method::Method;
11+
pub use proxy::Proxy;
1112
pub use request::Request;
1213
pub use response::Response;
1314
pub use status_code::StatusCode;
@@ -21,3 +22,5 @@ mod method;
2122
mod request;
2223
mod response;
2324
mod status_code;
25+
#[macro_use]
26+
mod proxy;

src/http/proxy.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use crate::http::{Body, Request, Response};
2+
3+
/// Implement this trait to implement the methods for a proxy application,
4+
/// which accepts requests and produces responses.
5+
pub trait Proxy<B: Body> {
6+
/// Handle a single incoming request, producing a response.
7+
fn handle(request: Request<B>) -> Response<B>;
8+
}
9+
10+
/// Create a proxy application. See examples/proxy.rs for an example.
11+
#[macro_export]
12+
macro_rules! proxy {
13+
($ty:ident) => {
14+
$crate::proxy!($ty with_types_in wasi);
15+
};
16+
($ty:ident with_types_in $($path_to_types_root:tt)*) => {
17+
struct Wrapper($ty);
18+
19+
impl wasi::exports::http::incoming_handler::Guest for Wrapper {
20+
fn handle(
21+
request: wasi::http::types::IncomingRequest,
22+
response_out: wasi::http::types::ResponseOutparam
23+
) {
24+
// First convert the request from the WASI type into wstd's public type.
25+
//
26+
// TODO: Do we need to do error handling for all these things,
27+
// or can we assume WASI has done them already, and just unwrap?
28+
let method = match request.method() {
29+
wasi::http::types::Method::Get => $crate::http::Method::GET,
30+
wasi::http::types::Method::Head => $crate::http::Method::HEAD,
31+
wasi::http::types::Method::Post => $crate::http::Method::POST,
32+
wasi::http::types::Method::Put => $crate::http::Method::PUT,
33+
wasi::http::types::Method::Delete => $crate::http::Method::DELETE,
34+
wasi::http::types::Method::Connect => $crate::http::Method::CONNECT,
35+
wasi::http::types::Method::Options => $crate::http::Method::OPTIONS,
36+
wasi::http::types::Method::Trace => $crate::http::Method::TRACE,
37+
wasi::http::types::Method::Patch => $crate::http::Method::PATCH,
38+
wasi::http::types::Method::Other(_) => {
39+
wasi::http::types::ResponseOutparam::set(
40+
response_out,
41+
Err(wasi::http::types::ErrorCode::HttpRequestMethodInvalid)
42+
);
43+
return;
44+
}
45+
};
46+
let scheme = match request.scheme() {
47+
Some(wasi::http::types::Scheme::Http) => Some($crate::http::Scheme::HTTP),
48+
Some(wasi::http::types::Scheme::Https) => Some($crate::http::Scheme::HTTPS),
49+
Some(wasi::http::types::Scheme::Other(other)) => {
50+
wasi::http::types::ResponseOutparam::set(
51+
response_out,
52+
Err(wasi::http::types::ErrorCode::HttpRequestUriInvalid)
53+
);
54+
return;
55+
}
56+
None => None,
57+
};
58+
let authority = match request.authority() {
59+
Some(authority) => match $crate::http::Authority::from_maybe_shared(authority) {
60+
Ok(authority) => Some(authority),
61+
Err(_) => {
62+
wasi::http::types::ResponseOutparam::set(
63+
response_out,
64+
Err(wasi::http::types::ErrorCode::HttpRequestUriInvalid)
65+
);
66+
return;
67+
}
68+
}
69+
None => None,
70+
};
71+
let path_with_query = match request.path_with_query() {
72+
Some(path_with_query) => match $crate::http::PathAndQuery::from_maybe_shared(path_with_query) {
73+
Ok(path_with_query) => Some(path_with_query),
74+
Err(_) => {
75+
wasi::http::types::ResponseOutparam::set(
76+
response_out,
77+
Err(wasi::http::types::ErrorCode::HttpRequestUriInvalid)
78+
);
79+
return;
80+
}
81+
}
82+
None => None,
83+
};
84+
let request = $crate::http::Request::incoming(
85+
method,
86+
scheme,
87+
authority,
88+
path_with_query
89+
);
90+
91+
// Send the converted request to the user's handle function.
92+
let response = $ty::handle(request);
93+
94+
// Convert the response from the public wstd type to the WASI type.
95+
let headers = wasi::http::types::Fields::new();
96+
for (name, value) in response.headers() {
97+
let name = name.as_str().to_owned();
98+
let value = value.as_bytes().to_owned();
99+
match headers.append(&name, &value) {
100+
Ok(()) => {}
101+
Err(err) => {
102+
wasi::http::types::ResponseOutparam::set(
103+
response_out,
104+
Err(wasi::http::types::ErrorCode::InternalError(Some(err.to_string())))
105+
);
106+
return;
107+
}
108+
}
109+
}
110+
wasi::http::types::ResponseOutparam::set(
111+
response_out,
112+
Ok(wasi::http::types::OutgoingResponse::new(headers))
113+
);
114+
}
115+
}
116+
117+
wasi::http::proxy::export!(Wrapper with_types_in $($path_to_types_root)*);
118+
};
119+
}

src/http/request.rs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,48 @@ use super::{
44
fields::header_map_to_wasi, method::to_wasi_method, Body, Error, HeaderMap, IntoBody, Method,
55
Result,
66
};
7-
use http::uri::Uri;
7+
use http::uri::{Authority, PathAndQuery, Scheme, Uri};
88
use wasi::http::outgoing_handler::OutgoingRequest;
9-
use wasi::http::types::Scheme;
109

1110
/// An HTTP request
1211
#[derive(Debug)]
1312
pub struct Request<B: Body> {
1413
method: Method,
15-
uri: Uri,
14+
scheme: Option<Scheme>,
15+
authority: Option<Authority>,
16+
path_and_query: Option<PathAndQuery>,
1617
headers: HeaderMap,
1718
body: B,
1819
}
1920

2021
impl Request<Empty> {
2122
/// Create a new HTTP request to send off to the client.
2223
pub fn new(method: Method, uri: Uri) -> Self {
24+
let parts = uri.into_parts();
2325
Self {
2426
body: empty(),
2527
method,
26-
uri,
28+
scheme: parts.scheme,
29+
authority: parts.authority,
30+
path_and_query: parts.path_and_query,
31+
headers: HeaderMap::new(),
32+
}
33+
}
34+
35+
/// Create a new HTTP request to send off to the client from parts
36+
/// rather than a full `uri`.
37+
pub fn incoming(
38+
method: Method,
39+
scheme: Option<Scheme>,
40+
authority: Option<Authority>,
41+
path_and_query: Option<PathAndQuery>,
42+
) -> Self {
43+
Self {
44+
body: empty(),
45+
method,
46+
scheme,
47+
authority,
48+
path_and_query,
2749
headers: HeaderMap::new(),
2850
}
2951
}
@@ -44,13 +66,17 @@ impl<B: Body> Request<B> {
4466
pub fn set_body<C: IntoBody>(self, body: C) -> Request<C::IntoBody> {
4567
let Self {
4668
method,
47-
uri,
69+
scheme,
70+
authority,
71+
path_and_query,
4872
headers,
4973
..
5074
} = self;
5175
Request {
5276
method,
53-
uri,
77+
scheme,
78+
authority,
79+
path_and_query,
5480
headers,
5581
body: body.into_body(),
5682
}
@@ -66,23 +92,23 @@ impl<B: Body> Request<B> {
6692
.map_err(|()| Error::other(format!("method rejected by wasi-http: {method:?}",)))?;
6793

6894
// Set the url scheme
69-
let scheme = match self.uri.scheme().map(|s| s.as_str()) {
70-
Some("http") => Scheme::Http,
71-
Some("https") | None => Scheme::Https,
72-
Some(other) => Scheme::Other(other.to_owned()),
95+
let scheme = match self.scheme.as_ref().map(|s| s.as_str()) {
96+
Some("http") => wasi::http::types::Scheme::Http,
97+
Some("https") | None => wasi::http::types::Scheme::Https,
98+
Some(other) => wasi::http::types::Scheme::Other(other.to_owned()),
7399
};
74100
wasi_req
75101
.set_scheme(Some(&scheme))
76102
.map_err(|()| Error::other(format!("scheme rejected by wasi-http: {scheme:?}")))?;
77103

78104
// Set authority
79-
let authority = self.uri.authority().map(|a| a.as_str());
105+
let authority = self.authority.as_ref().map(|a| a.as_str());
80106
wasi_req
81107
.set_authority(authority)
82108
.map_err(|()| Error::other(format!("authority rejected by wasi-http {authority:?}")))?;
83109

84110
// Set the url path + query string
85-
if let Some(p_and_q) = self.uri.path_and_query() {
111+
if let Some(p_and_q) = self.path_and_query {
86112
wasi_req
87113
.set_path_with_query(Some(&p_and_q.to_string()))
88114
.map_err(|()| {

src/http/response.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ impl Response<IncomingBody> {
7272
}
7373

7474
impl<B: Body> Response<B> {
75+
pub fn new(headers: HeaderMap, body: B) -> Self {
76+
Self {
77+
headers,
78+
status: StatusCode::Ok,
79+
body,
80+
}
81+
}
82+
7583
// Get the HTTP status code
7684
pub fn status_code(&self) -> StatusCode {
7785
self.status

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
//! is specific to that are exposed from here.
5050
5151
pub mod future;
52+
#[macro_use]
5253
pub mod http;
5354
pub mod io;
5455
pub mod iter;

0 commit comments

Comments
 (0)