Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit 121c68a

Browse files
committed
feat(p3): implement wasi:http
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
1 parent a5436cc commit 121c68a

86 files changed

Lines changed: 6961 additions & 2945 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ tokio = { version = "1.43.0", features = [ "rt", "time" ] }
364364
hyper = "1.0.1"
365365
http = "1.0.0"
366366
http-body = "1.0.0"
367-
http-body-util = "0.1.0"
367+
http-body-util = "0.1.1"
368368
bytes = { version = "1.4", default-features = false }
369369
futures = { version = "0.3.27", default-features = false }
370370
indexmap = { version = "2.0.0", default-features = false }

ci/vendor-wit.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ make_vendor "wasi/src/p3" "
7070
random@9499404@wit-0.3.0-draft
7171
sockets@41d7079@wit-0.3.0-draft
7272
"
73+
# `wasi:http` from https://github.com/WebAssembly/wasi-http/pull/158
74+
make_vendor "wasi-http/src/p3" "
75+
cli@82b86d9@wit-0.3.0-draft
76+
clocks@646092f@wit-0.3.0-draft
77+
filesystem@740cd76@wit-0.3.0-draft
78+
random@9499404@wit-0.3.0-draft
79+
sockets@41d7079@wit-0.3.0-draft
80+
http@ae89575@wit-0.3.0-draft
81+
"
7382

7483
rm -rf $cache_dir
7584

crates/test-programs/artifacts/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ fn build_and_generate_tests() {
7373
s if s.starts_with("filesystem_0_3") => "filesystem_0_3",
7474
s if s.starts_with("random_0_3") => "random_0_3",
7575
s if s.starts_with("sockets_0_3") => "sockets_0_3",
76+
s if s.starts_with("http_0_3") => "http_0_3",
7677
s if s.starts_with("http_") => "http",
7778
s if s.starts_with("preview1_") => "preview1",
7879
s if s.starts_with("preview2_") => "preview2",
@@ -111,6 +112,7 @@ fn build_and_generate_tests() {
111112
s if s.starts_with("filesystem_0_3") => &reactor_adapter,
112113
s if s.starts_with("random_0_3") => &reactor_adapter,
113114
s if s.starts_with("sockets_0_3") => &reactor_adapter,
115+
s if s.starts_with("http_0_3") => &reactor_adapter,
114116
s if s.starts_with("async_") => &reactor_adapter,
115117
s if s.starts_with("api_proxy") => &proxy_adapter,
116118
_ => &command_adapter,
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
struct Component;
2+
3+
test_programs::p3::export!(Component);
4+
5+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
6+
async fn run() -> Result<(), ()> {
7+
// TODO: adapt
8+
Ok(())
9+
}
10+
}
11+
12+
fn main() {}
13+
14+
//use test_programs::wasi::http::types as http_types;
15+
//
16+
//fn make_request() -> http_types::OutgoingRequest {
17+
// let request = http_types::OutgoingRequest::new(
18+
// http_types::Headers::from_list(&[("Content-Length".to_string(), b"11".to_vec())]).unwrap(),
19+
// );
20+
//
21+
// request
22+
// .set_method(&http_types::Method::Post)
23+
// .expect("setting method");
24+
// request
25+
// .set_scheme(Some(&http_types::Scheme::Http))
26+
// .expect("setting scheme");
27+
// let addr = std::env::var("HTTP_SERVER").unwrap();
28+
// request
29+
// .set_authority(Some(&addr))
30+
// .expect("setting authority");
31+
// request
32+
// .set_path_with_query(Some("/"))
33+
// .expect("setting path with query");
34+
//
35+
// request
36+
//}
37+
//
38+
//fn main() {
39+
// {
40+
// println!("writing enough");
41+
// let request = make_request();
42+
// let outgoing_body = request.body().unwrap();
43+
//
44+
// {
45+
// let request_body = outgoing_body.write().unwrap();
46+
// request_body
47+
// .blocking_write_and_flush("long enough".as_bytes())
48+
// .unwrap();
49+
// }
50+
//
51+
// http_types::OutgoingBody::finish(outgoing_body, None).expect("enough written")
52+
// }
53+
//
54+
// {
55+
// println!("writing too little");
56+
// let request = make_request();
57+
// let outgoing_body = request.body().unwrap();
58+
//
59+
// {
60+
// let request_body = outgoing_body.write().unwrap();
61+
// request_body
62+
// .blocking_write_and_flush("msg".as_bytes())
63+
// .unwrap();
64+
// }
65+
//
66+
// let e =
67+
// http_types::OutgoingBody::finish(outgoing_body, None).expect_err("finish should fail");
68+
//
69+
// assert!(
70+
// matches!(&e, http_types::ErrorCode::HttpRequestBodySize(Some(3))),
71+
// "unexpected error: {e:#?}"
72+
// );
73+
// }
74+
//
75+
// {
76+
// println!("writing too much");
77+
// let request = make_request();
78+
// let outgoing_body = request.body().unwrap();
79+
//
80+
// {
81+
// let request_body = outgoing_body.write().unwrap();
82+
// let e = request_body
83+
// .blocking_write_and_flush("more than 11 bytes".as_bytes())
84+
// .expect_err("write should fail");
85+
//
86+
// let e = match e {
87+
// test_programs::wasi::io::streams::StreamError::LastOperationFailed(e) => {
88+
// http_types::http_error_code(&e)
89+
// }
90+
// test_programs::wasi::io::streams::StreamError::Closed => panic!("request closed"),
91+
// };
92+
//
93+
// assert!(
94+
// matches!(
95+
// e,
96+
// Some(http_types::ErrorCode::HttpRequestBodySize(Some(18)))
97+
// ),
98+
// "unexpected error {e:?}"
99+
// );
100+
// }
101+
//
102+
// let e =
103+
// http_types::OutgoingBody::finish(outgoing_body, None).expect_err("finish should fail");
104+
//
105+
// assert!(
106+
// matches!(&e, http_types::ErrorCode::HttpRequestBodySize(Some(18))),
107+
// "unexpected error: {e:#?}"
108+
// );
109+
// }
110+
//}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use anyhow::Context;
2+
use test_programs::p3::wasi::http::types::{Method, Scheme};
3+
4+
struct Component;
5+
6+
test_programs::p3::export!(Component);
7+
8+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
9+
async fn run() -> Result<(), ()> {
10+
let addr = test_programs::p3::wasi::cli::environment::get_environment()
11+
.into_iter()
12+
.find_map(|(k, v)| k.eq("HTTP_SERVER").then_some(v))
13+
.unwrap();
14+
let res = test_programs::p3::http::request(
15+
Method::Get,
16+
Scheme::Http,
17+
&addr,
18+
"/get?some=arg&goes=here",
19+
None,
20+
None,
21+
None,
22+
None,
23+
None,
24+
)
25+
.await
26+
.context("/get")
27+
.unwrap();
28+
29+
println!("{addr} /get: {res:?}");
30+
assert_eq!(res.status, 200);
31+
let method = res.header("x-wasmtime-test-method").unwrap();
32+
assert_eq!(std::str::from_utf8(method).unwrap(), "GET");
33+
let uri = res.header("x-wasmtime-test-uri").unwrap();
34+
assert_eq!(
35+
std::str::from_utf8(uri).unwrap(),
36+
format!("/get?some=arg&goes=here")
37+
);
38+
assert_eq!(res.body, b"");
39+
Ok(())
40+
}
41+
}
42+
43+
fn main() {}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use test_programs::p3::wasi::http::types::{ErrorCode, Method, Scheme};
2+
3+
struct Component;
4+
5+
test_programs::p3::export!(Component);
6+
7+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
8+
async fn run() -> Result<(), ()> {
9+
let res = test_programs::p3::http::request(
10+
Method::Get,
11+
Scheme::Http,
12+
"some.invalid.dnsname:3000",
13+
"/",
14+
None,
15+
None,
16+
None,
17+
None,
18+
None,
19+
)
20+
.await;
21+
22+
let e = res.unwrap_err();
23+
assert!(
24+
matches!(
25+
e.downcast_ref::<ErrorCode>()
26+
.expect("expected a wasi-http ErrorCode"),
27+
ErrorCode::DnsError(_) | ErrorCode::ConnectionRefused,
28+
),
29+
"Unexpected error: {e:#?}"
30+
);
31+
Ok(())
32+
}
33+
}
34+
35+
fn main() {}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use test_programs::p3::wasi::http::types::{HeaderError, Headers, Request};
2+
use test_programs::p3::wit_future;
3+
4+
struct Component;
5+
6+
test_programs::p3::export!(Component);
7+
8+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
9+
async fn run() -> Result<(), ()> {
10+
let hdrs = Headers::new();
11+
assert!(matches!(
12+
hdrs.append("malformed header name", b"ok value".as_ref()),
13+
Err(HeaderError::InvalidSyntax)
14+
));
15+
16+
assert!(matches!(
17+
hdrs.append("ok-header-name", b"ok value".as_ref()),
18+
Ok(())
19+
));
20+
21+
assert!(matches!(
22+
hdrs.append("ok-header-name", b"bad\nvalue".as_ref()),
23+
Err(HeaderError::InvalidSyntax)
24+
));
25+
26+
assert!(matches!(
27+
hdrs.append("Connection", b"keep-alive".as_ref()),
28+
Err(HeaderError::Forbidden)
29+
));
30+
31+
assert!(matches!(
32+
hdrs.append("Keep-Alive", b"stuff".as_ref()),
33+
Err(HeaderError::Forbidden)
34+
));
35+
36+
assert!(matches!(
37+
hdrs.append("Host", b"example.com".as_ref()),
38+
Err(HeaderError::Forbidden)
39+
));
40+
41+
assert!(matches!(
42+
hdrs.append("custom-forbidden-header", b"keep-alive".as_ref()),
43+
Err(HeaderError::Forbidden)
44+
));
45+
46+
assert!(matches!(
47+
hdrs.append("Custom-Forbidden-Header", b"keep-alive".as_ref()),
48+
Err(HeaderError::Forbidden)
49+
));
50+
51+
assert!(matches!(
52+
Headers::from_list(&[("bad header".to_owned(), b"value".to_vec())]),
53+
Err(HeaderError::InvalidSyntax)
54+
));
55+
56+
assert!(matches!(
57+
Headers::from_list(&[("custom-forbidden-header".to_owned(), b"value".to_vec())]),
58+
Err(HeaderError::Forbidden)
59+
));
60+
61+
assert!(matches!(
62+
Headers::from_list(&[("ok-header-name".to_owned(), b"bad\nvalue".to_vec())]),
63+
Err(HeaderError::InvalidSyntax)
64+
));
65+
66+
let (_, rx) = wit_future::new();
67+
let (req, _) = Request::new(hdrs, None, rx, None);
68+
let hdrs = req.headers();
69+
70+
assert!(matches!(
71+
hdrs.set("Content-Length", &[b"10".to_vec()]),
72+
Err(HeaderError::Immutable),
73+
));
74+
75+
assert!(matches!(
76+
hdrs.append("Content-Length", b"10".as_ref()),
77+
Err(HeaderError::Immutable),
78+
));
79+
80+
assert!(matches!(
81+
hdrs.delete("Content-Length"),
82+
Err(HeaderError::Immutable),
83+
));
84+
Ok(())
85+
}
86+
}
87+
88+
fn main() {}

0 commit comments

Comments
 (0)