From b781608cecb607178454b2c14eddcbb41c602671 Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues Date: Thu, 27 Apr 2023 21:51:42 +0200 Subject: [PATCH 1/3] feat: rename wasi package to wasi-preview2-prototype --- wasi/Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wasi/Cargo.toml b/wasi/Cargo.toml index abb58b94..7f5f1b90 100644 --- a/wasi/Cargo.toml +++ b/wasi/Cargo.toml @@ -1,6 +1,8 @@ [package] -name = "wasi" -version = "0.12.0+wasi-snapshot-preview2" +# name = "wasi" +name = "wasi-preview2-prototype" +version = "0.0.1" +# version = "0.12.0+wasi-snapshot-preview2" description = "Experimental WASI Preview2 API bindings for Rust" edition.workspace = true publish = false From efdbd7d37720f694f43fe74f75ef9147915fc6bd Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues Date: Thu, 27 Apr 2023 22:02:37 +0200 Subject: [PATCH 2/3] feat: update http client for wasi crate --- Cargo.lock | 25 ++--- wasi/Cargo.toml | 10 +- wasi/src/http.rs | 144 ----------------------------- wasi/src/http_client.rs | 200 ++++++++++++++++++++++++++++++++++++++++ wasi/src/lib.rs | 4 +- 5 files changed, 223 insertions(+), 160 deletions(-) delete mode 100644 wasi/src/http.rs create mode 100644 wasi/src/http_client.rs diff --git a/Cargo.lock b/Cargo.lock index e3a9dcc5..51ca77bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -607,7 +607,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1555,15 +1555,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasi" -version = "0.12.0+wasi-snapshot-preview2" -dependencies = [ - "anyhow", - "http", - "wit-bindgen", -] - [[package]] name = "wasi-cap-std-sync" version = "0.0.0" @@ -1607,13 +1598,23 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "wasi-preview2-prototype" +version = "0.0.1" +dependencies = [ + "anyhow", + "bytes", + "http", + "wit-bindgen", +] + [[package]] name = "wasi-tests" version = "0.0.0" dependencies = [ "libc", "once_cell", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1622,7 +1623,7 @@ version = "0.0.0" dependencies = [ "byte-array", "object", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-encoder 0.25.0", "wit-bindgen", ] diff --git a/wasi/Cargo.toml b/wasi/Cargo.toml index 7f5f1b90..fabcf421 100644 --- a/wasi/Cargo.toml +++ b/wasi/Cargo.toml @@ -5,17 +5,23 @@ version = "0.0.1" # version = "0.12.0+wasi-snapshot-preview2" description = "Experimental WASI Preview2 API bindings for Rust" edition.workspace = true -publish = false +publish = true [dependencies] wit-bindgen = { workspace = true, features = ["macros", "realloc"] } # Dependencies for HTTP feature anyhow = { version = "1.0", optional = true } +bytes = { version = "1.4", optional = true } http = { version = "0.2", optional = true } [lib] crate-type = ["lib"] [features] -http = ["dep:anyhow", "dep:http"] +default = ["std"] +std = [] +http-client = ["dep:anyhow", "dep:bytes", "dep:http"] + +[badges] +maintenance = { status = "experimental" } diff --git a/wasi/src/http.rs b/wasi/src/http.rs deleted file mode 100644 index fb4da687..00000000 --- a/wasi/src/http.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::snapshots::preview_2::{default_outgoing_http, streams, types as http_types}; -use http::header::{HeaderName, HeaderValue}; -use std::{ops::Deref, str::FromStr}; - -pub struct DefaultClient { - options: Option, -} - -impl DefaultClient { - pub fn new(options: Option) -> Self { - Self { options } - } - - pub fn handle(&self, req: http::Request>) -> anyhow::Result>> { - let req = Request::from(req).to_owned(); - let res = default_outgoing_http::handle(req, self.options); - let res: http::Response> = Response(res).into(); - http_types::drop_outgoing_request(req); - Ok(res) - } -} - -pub struct Request(default_outgoing_http::OutgoingRequest); - -impl Deref for Request { - type Target = default_outgoing_http::OutgoingRequest; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From> for Request { - fn from(req: http::Request) -> Self { - let (parts, _) = req.into_parts(); - let path = parts.uri.path(); - let query = parts.uri.query(); - let method = Method::from(parts.method); - let headers = Headers::from(&parts.headers); - let scheme = match parts.uri.scheme_str().unwrap_or("") { - "http" => Some(http_types::SchemeParam::Http), - "https" => Some(http_types::SchemeParam::Https), - _ => None, - }; - Self(http_types::new_outgoing_request( - method.to_owned(), - path, - query.unwrap_or(""), - scheme, - parts.uri.authority().map(|a| a.as_str()).unwrap(), - headers.to_owned(), - )) - } -} - -pub struct Method<'a>(http_types::MethodParam<'a>); - -impl<'a> Deref for Method<'a> { - type Target = http_types::MethodParam<'a>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a> From for Method<'a> { - fn from(method: http::Method) -> Self { - Self(match method { - http::Method::GET => http_types::MethodParam::Get, - http::Method::POST => http_types::MethodParam::Post, - http::Method::PUT => http_types::MethodParam::Put, - http::Method::DELETE => http_types::MethodParam::Delete, - http::Method::PATCH => http_types::MethodParam::Patch, - http::Method::CONNECT => http_types::MethodParam::Connect, - http::Method::TRACE => http_types::MethodParam::Trace, - http::Method::HEAD => http_types::MethodParam::Head, - http::Method::OPTIONS => http_types::MethodParam::Options, - _ => panic!("failed due to unsupported method, currently supported methods are: GET, POST, PUT, DELETE, PATCH, CONNECT, TRACE, HEAD, and OPTIONS"), - }) - } -} - -pub struct Response(http_types::IncomingResponse); - -impl Deref for Response { - type Target = http_types::IncomingResponse; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for http::Response> { - fn from(val: Response) -> Self { - let res_pointer = val.to_owned(); - // TODO: Run drop when implemented - // poll::drop_pollable(res_pointer); - let status = http_types::incoming_response_status(res_pointer); - let header_handle = http_types::incoming_response_headers(res_pointer); - let headers = http_types::fields_entries(header_handle); - let stream = http_types::incoming_response_consume(res_pointer).unwrap(); - let len = 64 * 1024; - let mut body: Vec = vec![]; - loop { - let (b, finished) = streams::read(stream, len).unwrap(); - body.extend(b); - if finished { - break; - } - } - let mut res = http::Response::builder().status(status).body(body).unwrap(); - let headers_map = res.headers_mut(); - for (name, value) in headers { - headers_map.insert( - HeaderName::from_str(name.as_ref()).unwrap(), - HeaderValue::from_str(value.as_str()).unwrap(), - ); - } - streams::drop_input_stream(stream); - http_types::drop_incoming_response(res_pointer); - res - } -} - -pub struct Headers(http_types::Fields); - -impl Deref for Headers { - type Target = http_types::Fields; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<'a> From<&'a http::HeaderMap> for Headers { - fn from(headers: &'a http::HeaderMap) -> Self { - Self(http_types::new_fields( - &headers - .iter() - .map(|(name, value)| (name.as_str(), value.to_str().unwrap())) - .collect::>(), - )) - } -} diff --git a/wasi/src/http_client.rs b/wasi/src/http_client.rs new file mode 100644 index 00000000..4ddb9054 --- /dev/null +++ b/wasi/src/http_client.rs @@ -0,0 +1,200 @@ +use anyhow::{anyhow, Context}; +use bytes::{BufMut, Bytes, BytesMut}; +use http::header::{HeaderName, HeaderValue}; +#[cfg(feature = "std")] +use std::ops::Deref; + +use crate::snapshots::preview_2::{default_outgoing_http, streams, types as http_types}; + +pub struct DefaultClient { + options: Option, +} + +impl DefaultClient { + pub fn new(options: Option) -> Self { + Self { options } + } + + pub fn handle(&self, req: http::Request) -> anyhow::Result> { + let req = Request::try_from(req) + .context("converting http request")? + .to_owned(); + + let res = default_outgoing_http::handle(req, self.options); + http_types::drop_outgoing_request(req); + + let response = + http::Response::try_from(Response(res)).context("converting http response")?; + http_types::drop_incoming_response(res); + + Ok(response) + } +} + +pub struct Request(default_outgoing_http::OutgoingRequest); + +#[cfg(feature = "std")] +impl Deref for Request { + type Target = default_outgoing_http::OutgoingRequest; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom> for Request { + type Error = anyhow::Error; + + fn try_from(value: http::Request) -> Result { + let (parts, body) = value.into_parts(); + let path = parts.uri.path(); + let query = parts.uri.query(); + let method = Method::try_from(parts.method)?; + let headers = Headers::from(&parts.headers); + let scheme = match parts.uri.scheme_str().unwrap_or("") { + "http" => Some(http_types::SchemeParam::Http), + "https" => Some(http_types::SchemeParam::Https), + _ => None, + }; + let request = http_types::new_outgoing_request( + method.to_owned(), + path, + query.unwrap_or(""), + scheme, + parts + .uri + .authority() + .map(|a| a.as_str()) + .ok_or_else(|| anyhow!("unable to extract authority"))?, + headers.to_owned(), + ); + + let request_body = http_types::outgoing_request_write(request) + .map_err(|_| anyhow!("outgoing request write failed"))?; + + let mut body_cursor = 0; + if body.is_empty() { + streams::write(request_body, &[]).map_err(|_| anyhow!("writing request body"))?; + } else { + while body_cursor < body.len() { + let written = streams::write(request_body, &body[body_cursor..]) + .map_err(|_| anyhow!("writing request body"))?; + body_cursor += written as usize; + } + } + + Ok(Request(request)) + } +} + +pub struct Method<'a>(http_types::MethodParam<'a>); + +#[cfg(feature = "std")] +impl<'a> Deref for Method<'a> { + type Target = http_types::MethodParam<'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> TryFrom for Method<'a> { + type Error = anyhow::Error; + + fn try_from(method: http::Method) -> Result { + Ok(Self(match method { + http::Method::GET => http_types::MethodParam::Get, + http::Method::POST => http_types::MethodParam::Post, + http::Method::PUT => http_types::MethodParam::Put, + http::Method::DELETE => http_types::MethodParam::Delete, + http::Method::PATCH => http_types::MethodParam::Patch, + http::Method::CONNECT => http_types::MethodParam::Connect, + http::Method::TRACE => http_types::MethodParam::Trace, + http::Method::HEAD => http_types::MethodParam::Head, + http::Method::OPTIONS => http_types::MethodParam::Options, + _ => return Err(anyhow!("failed due to unsupported method, currently supported methods are: GET, POST, PUT, DELETE, PATCH, CONNECT, TRACE, HEAD, and OPTIONS")), + })) + } +} + +pub struct Response(http_types::IncomingResponse); + +#[cfg(feature = "std")] +impl Deref for Response { + type Target = http_types::IncomingResponse; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom for http::Response { + type Error = anyhow::Error; + + fn try_from(value: Response) -> Result { + let future_response = value.to_owned(); + // TODO: we could create a pollable from the future_response and + // poll on it here to test that its available immediately + // poll::drop_pollable(future_response); + + let incoming_response = http_types::future_incoming_response_get(future_response) + .ok_or_else(|| anyhow!("incoming response is available immediately"))? + .map_err(|e| anyhow!("incoming response error: {e:?}"))?; + http_types::drop_future_incoming_response(future_response); + + let status = http_types::incoming_response_status(incoming_response); + let headers_handle = http_types::incoming_response_headers(incoming_response); + + let body_stream = http_types::incoming_response_consume(incoming_response) + .map_err(|_| anyhow!("consuming incoming response"))?; + + let mut body = BytesMut::new(); + let mut eof = false; + while !eof { + let (body_chunk, stream_ended) = streams::read(body_stream, u64::MAX) + .map_err(|_| anyhow!("reading response body"))?; + eof = stream_ended; + body.put(body_chunk.as_slice()); + } + let mut res = http::Response::builder() + .status(status) + .body(body.freeze()) + .map_err(|_| anyhow!("building http response"))?; + + if headers_handle > 0 { + let headers_map = res.headers_mut(); + for (name, value) in http_types::fields_entries(headers_handle) { + headers_map.insert( + HeaderName::from_bytes(name.as_bytes()) + .map_err(|_| anyhow!("converting response header name"))?, + HeaderValue::from_str(value.as_str()) + .map_err(|_| anyhow!("converting response header value"))?, + ); + } + } + + Ok(res) + } +} + +pub struct Headers(http_types::Fields); + +#[cfg(feature = "std")] +impl Deref for Headers { + type Target = http_types::Fields; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> From<&'a http::HeaderMap> for Headers { + fn from(headers: &'a http::HeaderMap) -> Self { + Self(http_types::new_fields( + &headers + .iter() + .map(|(name, value)| (name.as_str(), value.to_str().unwrap())) + .collect::>(), + )) + } +} diff --git a/wasi/src/lib.rs b/wasi/src/lib.rs index 6e9ae0a0..8c9bdaf1 100644 --- a/wasi/src/lib.rs +++ b/wasi/src/lib.rs @@ -1,5 +1,5 @@ -#[cfg(feature = "http")] -pub mod http; +#[cfg(feature = "http-client")] +pub mod http_client; pub mod snapshots { pub mod preview_2 { From c6dda803dcf044b6419f88a5fb7b1e8a4c8cbbf1 Mon Sep 17 00:00:00 2001 From: Eduardo Rodrigues Date: Thu, 27 Apr 2023 23:25:38 +0200 Subject: [PATCH 3/3] chore: change based on feedback --- wasi/Cargo.toml | 6 +----- wasi/src/http_client.rs | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/wasi/Cargo.toml b/wasi/Cargo.toml index fabcf421..5c1305d2 100644 --- a/wasi/Cargo.toml +++ b/wasi/Cargo.toml @@ -19,9 +19,5 @@ http = { version = "0.2", optional = true } crate-type = ["lib"] [features] -default = ["std"] -std = [] +default = [] http-client = ["dep:anyhow", "dep:bytes", "dep:http"] - -[badges] -maintenance = { status = "experimental" } diff --git a/wasi/src/http_client.rs b/wasi/src/http_client.rs index 4ddb9054..9ba76aed 100644 --- a/wasi/src/http_client.rs +++ b/wasi/src/http_client.rs @@ -1,8 +1,7 @@ use anyhow::{anyhow, Context}; use bytes::{BufMut, Bytes, BytesMut}; +use core::ops::Deref; use http::header::{HeaderName, HeaderValue}; -#[cfg(feature = "std")] -use std::ops::Deref; use crate::snapshots::preview_2::{default_outgoing_http, streams, types as http_types}; @@ -33,7 +32,6 @@ impl DefaultClient { pub struct Request(default_outgoing_http::OutgoingRequest); -#[cfg(feature = "std")] impl Deref for Request { type Target = default_outgoing_http::OutgoingRequest; @@ -89,7 +87,6 @@ impl TryFrom> for Request { pub struct Method<'a>(http_types::MethodParam<'a>); -#[cfg(feature = "std")] impl<'a> Deref for Method<'a> { type Target = http_types::MethodParam<'a>; @@ -119,7 +116,6 @@ impl<'a> TryFrom for Method<'a> { pub struct Response(http_types::IncomingResponse); -#[cfg(feature = "std")] impl Deref for Response { type Target = http_types::IncomingResponse; @@ -179,7 +175,6 @@ impl TryFrom for http::Response { pub struct Headers(http_types::Fields); -#[cfg(feature = "std")] impl Deref for Headers { type Target = http_types::Fields;