Skip to content

Commit a1963fa

Browse files
committed
feat(http): add http crate integration
Signed-off-by: Roman Volosatovs <[email protected]>
1 parent 606318f commit a1963fa

File tree

5 files changed

+154
-9
lines changed

5 files changed

+154
-9
lines changed

.github/workflows/main.yml

+5-3
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ jobs:
5151
- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/cli_command.wasm -o component.wasm
5252
- run: wasmtime run component.wasm
5353

54-
- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/http_proxy.wasm -o component.wasm
55-
- run: wasm-tools component targets wit component.wasm -w wasi:http/proxy
56-
5754
- run: cargo build --examples --target wasm32-wasi --no-default-features --features rand
5855

5956
- run: wasm-tools component new ./target/wasm32-wasi/debug/examples/rand-no_std.wasm --adapt ./wasi_snapshot_preview1.command.wasm -o component.wasm
@@ -64,6 +61,11 @@ jobs:
6461
- run: wasm-tools component new ./target/wasm32-wasi/debug/examples/rand.wasm --adapt ./wasi_snapshot_preview1.command.wasm -o component.wasm
6562
- run: wasmtime run component.wasm
6663

64+
- run: cargo build --examples --target wasm32-unknown-unknown --features http
65+
66+
- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/http_proxy.wasm -o component.wasm
67+
- run: wasm-tools component targets wit component.wasm -w wasi:http/proxy
68+
6769
rustfmt:
6870
name: Rustfmt
6971
runs-on: ubuntu-latest

Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ compiler_builtins = { version = "0.1", optional = true }
1919
core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" }
2020
rustc-std-workspace-alloc = { version = "1.0", optional = true }
2121

22+
# When built with `http` crate integration
23+
http = { version = "1.1", default-features = false, optional = true }
24+
2225
# When built with `rand` crate integration
2326
rand = { version = "0.8.5", default-features = false, optional = true }
2427

2528
[features]
2629
default = ["std"]
2730
std = []
31+
http = ["dep:http", "http/std", "std"]
2832
# Unstable feature to support being a libstd dependency
2933
rustc-dep-of-std = ["compiler_builtins", "core", "rustc-std-workspace-alloc"]
3034

@@ -48,7 +52,7 @@ crate-type = ["cdylib"]
4852
[[example]]
4953
name = "http-proxy"
5054
crate-type = ["cdylib"]
51-
required-features = ["std"]
55+
required-features = ["http", "std"]
5256

5357
[[example]]
5458
name = "rand-no_std"

examples/http-proxy.rs

+22-5
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
use std::io::Write as _;
22

3-
use wasi::http::types::{
4-
Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam,
5-
};
3+
use wasi::http::types::{IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam};
64

75
wasi::http::proxy::export!(Example);
86

97
struct Example;
108

119
impl wasi::exports::http::incoming_handler::Guest for Example {
12-
fn handle(_request: IncomingRequest, response_out: ResponseOutparam) {
13-
let resp = OutgoingResponse::new(Fields::new());
10+
fn handle(request: IncomingRequest, response_out: ResponseOutparam) {
11+
let req_headers =
12+
http::HeaderMap::try_from(request.headers()).expect("failed to parse headers");
13+
let mut resp_headers = http::HeaderMap::new();
14+
for (name, value) in req_headers.iter() {
15+
// Append `-orig` to all request headers and send them back to the client
16+
resp_headers.append(
17+
http::HeaderName::try_from(format!("{name}-orig")).unwrap(),
18+
value.clone(),
19+
);
20+
}
21+
let resp = OutgoingResponse::new(resp_headers.into());
1422
let body = resp.body().unwrap();
1523

1624
ResponseOutparam::set(response_out, Ok(resp));
1725

1826
let mut out = body.write().unwrap();
27+
28+
let method = http::Method::try_from(request.method()).unwrap();
29+
writeln!(out, "method: {method}").unwrap();
30+
31+
if let Some(scheme) = request.scheme() {
32+
let scheme = http::uri::Scheme::try_from(scheme).unwrap();
33+
writeln!(out, "scheme: {scheme}").unwrap();
34+
}
35+
1936
out.write_all(b"Hello, WASI!").unwrap();
2037
out.flush().unwrap();
2138
drop(out);

src/ext/http.rs

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
pub use http;
2+
3+
use core::fmt::Display;
4+
5+
impl From<http::Method> for crate::http::types::Method {
6+
fn from(method: http::Method) -> Self {
7+
use std::string::ToString;
8+
9+
match method.as_str() {
10+
"GET" => Self::Get,
11+
"HEAD" => Self::Head,
12+
"POST" => Self::Post,
13+
"PUT" => Self::Put,
14+
"DELETE" => Self::Delete,
15+
"CONNECT" => Self::Connect,
16+
"OPTIONS" => Self::Options,
17+
"TRACE" => Self::Trace,
18+
"PATCH" => Self::Patch,
19+
_ => Self::Other(method.to_string()),
20+
}
21+
}
22+
}
23+
24+
impl TryFrom<crate::http::types::Method> for http::Method {
25+
type Error = http::method::InvalidMethod;
26+
27+
fn try_from(method: crate::http::types::Method) -> Result<Self, Self::Error> {
28+
match method {
29+
crate::http::types::Method::Get => Ok(Self::GET),
30+
crate::http::types::Method::Head => Ok(Self::HEAD),
31+
crate::http::types::Method::Post => Ok(Self::POST),
32+
crate::http::types::Method::Put => Ok(Self::PUT),
33+
crate::http::types::Method::Delete => Ok(Self::DELETE),
34+
crate::http::types::Method::Connect => Ok(Self::CONNECT),
35+
crate::http::types::Method::Options => Ok(Self::OPTIONS),
36+
crate::http::types::Method::Trace => Ok(Self::TRACE),
37+
crate::http::types::Method::Patch => Ok(Self::PATCH),
38+
crate::http::types::Method::Other(method) => method.parse(),
39+
}
40+
}
41+
}
42+
43+
impl From<http::uri::Scheme> for crate::http::types::Scheme {
44+
fn from(scheme: http::uri::Scheme) -> Self {
45+
use std::string::ToString;
46+
47+
match scheme.as_str() {
48+
"http" => Self::Http,
49+
"https" => Self::Https,
50+
_ => Self::Other(scheme.to_string()),
51+
}
52+
}
53+
}
54+
55+
impl TryFrom<crate::http::types::Scheme> for http::uri::Scheme {
56+
type Error = http::uri::InvalidUri;
57+
58+
fn try_from(scheme: crate::http::types::Scheme) -> Result<Self, Self::Error> {
59+
match scheme {
60+
crate::http::types::Scheme::Http => Ok(Self::HTTP),
61+
crate::http::types::Scheme::Https => Ok(Self::HTTPS),
62+
crate::http::types::Scheme::Other(scheme) => scheme.parse(),
63+
}
64+
}
65+
}
66+
67+
#[derive(Debug)]
68+
pub enum FieldsToHeaderMapError {
69+
InvalidHeaderName(http::header::InvalidHeaderName),
70+
InvalidHeaderValue(http::header::InvalidHeaderValue),
71+
}
72+
73+
impl Display for FieldsToHeaderMapError {
74+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
75+
match self {
76+
FieldsToHeaderMapError::InvalidHeaderName(e) => write!(f, "invalid header name: {e}"),
77+
FieldsToHeaderMapError::InvalidHeaderValue(e) => write!(f, "invalid header value: {e}"),
78+
}
79+
}
80+
}
81+
82+
impl std::error::Error for FieldsToHeaderMapError {}
83+
84+
impl TryFrom<crate::http::types::Fields> for http::HeaderMap {
85+
type Error = FieldsToHeaderMapError;
86+
87+
fn try_from(fields: crate::http::types::Fields) -> Result<Self, Self::Error> {
88+
let mut headers = http::HeaderMap::new();
89+
for (name, value) in fields.entries() {
90+
let name = http::HeaderName::try_from(name)
91+
.map_err(FieldsToHeaderMapError::InvalidHeaderName)?;
92+
let value = http::HeaderValue::try_from(value)
93+
.map_err(FieldsToHeaderMapError::InvalidHeaderValue)?;
94+
match headers.entry(name) {
95+
http::header::Entry::Vacant(entry) => {
96+
entry.insert(value);
97+
}
98+
http::header::Entry::Occupied(mut entry) => {
99+
entry.append(value);
100+
}
101+
};
102+
}
103+
Ok(headers)
104+
}
105+
}
106+
107+
impl From<http::HeaderMap> for crate::http::types::Fields {
108+
fn from(headers: http::HeaderMap) -> Self {
109+
use std::string::ToString;
110+
111+
let fields = crate::http::types::Fields::new();
112+
for (name, value) in headers.iter() {
113+
fields
114+
.append(&name.to_string(), &value.as_bytes().to_vec())
115+
.expect("failed to append header")
116+
}
117+
fields
118+
}
119+
}

src/ext/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#[cfg(feature = "std")]
22
mod std;
33

4+
#[cfg(feature = "http")]
5+
pub mod http;
6+
47
#[cfg(feature = "rand")]
58
pub mod rand;
69

0 commit comments

Comments
 (0)