Skip to content

Commit

Permalink
feat(http): add http crate integration
Browse files Browse the repository at this point in the history
Signed-off-by: Roman Volosatovs <[email protected]>
  • Loading branch information
rvolosatovs committed May 13, 2024
1 parent 606318f commit a1963fa
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 9 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ jobs:
- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/cli_command.wasm -o component.wasm
- run: wasmtime run component.wasm

- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/http_proxy.wasm -o component.wasm
- run: wasm-tools component targets wit component.wasm -w wasi:http/proxy

- run: cargo build --examples --target wasm32-wasi --no-default-features --features rand

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

- run: cargo build --examples --target wasm32-unknown-unknown --features http

- run: wasm-tools component new ./target/wasm32-unknown-unknown/debug/examples/http_proxy.wasm -o component.wasm
- run: wasm-tools component targets wit component.wasm -w wasi:http/proxy

rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
Expand Down
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ compiler_builtins = { version = "0.1", optional = true }
core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" }
rustc-std-workspace-alloc = { version = "1.0", optional = true }

# When built with `http` crate integration
http = { version = "1.1", default-features = false, optional = true }

# When built with `rand` crate integration
rand = { version = "0.8.5", default-features = false, optional = true }

[features]
default = ["std"]
std = []
http = ["dep:http", "http/std", "std"]
# Unstable feature to support being a libstd dependency
rustc-dep-of-std = ["compiler_builtins", "core", "rustc-std-workspace-alloc"]

Expand All @@ -48,7 +52,7 @@ crate-type = ["cdylib"]
[[example]]
name = "http-proxy"
crate-type = ["cdylib"]
required-features = ["std"]
required-features = ["http", "std"]

[[example]]
name = "rand-no_std"
Expand Down
27 changes: 22 additions & 5 deletions examples/http-proxy.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
use std::io::Write as _;

use wasi::http::types::{
Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam,
};
use wasi::http::types::{IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam};

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

struct Example;

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

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

let mut out = body.write().unwrap();

let method = http::Method::try_from(request.method()).unwrap();
writeln!(out, "method: {method}").unwrap();

if let Some(scheme) = request.scheme() {
let scheme = http::uri::Scheme::try_from(scheme).unwrap();
writeln!(out, "scheme: {scheme}").unwrap();
}

out.write_all(b"Hello, WASI!").unwrap();
out.flush().unwrap();
drop(out);
Expand Down
119 changes: 119 additions & 0 deletions src/ext/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
pub use http;

use core::fmt::Display;

impl From<http::Method> for crate::http::types::Method {
fn from(method: http::Method) -> Self {
use std::string::ToString;

match method.as_str() {
"GET" => Self::Get,
"HEAD" => Self::Head,
"POST" => Self::Post,
"PUT" => Self::Put,
"DELETE" => Self::Delete,
"CONNECT" => Self::Connect,
"OPTIONS" => Self::Options,
"TRACE" => Self::Trace,
"PATCH" => Self::Patch,
_ => Self::Other(method.to_string()),
}
}
}

impl TryFrom<crate::http::types::Method> for http::Method {
type Error = http::method::InvalidMethod;

fn try_from(method: crate::http::types::Method) -> Result<Self, Self::Error> {
match method {
crate::http::types::Method::Get => Ok(Self::GET),
crate::http::types::Method::Head => Ok(Self::HEAD),
crate::http::types::Method::Post => Ok(Self::POST),
crate::http::types::Method::Put => Ok(Self::PUT),
crate::http::types::Method::Delete => Ok(Self::DELETE),
crate::http::types::Method::Connect => Ok(Self::CONNECT),
crate::http::types::Method::Options => Ok(Self::OPTIONS),
crate::http::types::Method::Trace => Ok(Self::TRACE),
crate::http::types::Method::Patch => Ok(Self::PATCH),
crate::http::types::Method::Other(method) => method.parse(),
}
}
}

impl From<http::uri::Scheme> for crate::http::types::Scheme {
fn from(scheme: http::uri::Scheme) -> Self {
use std::string::ToString;

match scheme.as_str() {
"http" => Self::Http,
"https" => Self::Https,
_ => Self::Other(scheme.to_string()),
}
}
}

impl TryFrom<crate::http::types::Scheme> for http::uri::Scheme {
type Error = http::uri::InvalidUri;

fn try_from(scheme: crate::http::types::Scheme) -> Result<Self, Self::Error> {
match scheme {
crate::http::types::Scheme::Http => Ok(Self::HTTP),
crate::http::types::Scheme::Https => Ok(Self::HTTPS),
crate::http::types::Scheme::Other(scheme) => scheme.parse(),
}
}
}

#[derive(Debug)]
pub enum FieldsToHeaderMapError {
InvalidHeaderName(http::header::InvalidHeaderName),
InvalidHeaderValue(http::header::InvalidHeaderValue),
}

impl Display for FieldsToHeaderMapError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FieldsToHeaderMapError::InvalidHeaderName(e) => write!(f, "invalid header name: {e}"),
FieldsToHeaderMapError::InvalidHeaderValue(e) => write!(f, "invalid header value: {e}"),
}
}
}

impl std::error::Error for FieldsToHeaderMapError {}

impl TryFrom<crate::http::types::Fields> for http::HeaderMap {
type Error = FieldsToHeaderMapError;

fn try_from(fields: crate::http::types::Fields) -> Result<Self, Self::Error> {
let mut headers = http::HeaderMap::new();
for (name, value) in fields.entries() {
let name = http::HeaderName::try_from(name)
.map_err(FieldsToHeaderMapError::InvalidHeaderName)?;
let value = http::HeaderValue::try_from(value)
.map_err(FieldsToHeaderMapError::InvalidHeaderValue)?;
match headers.entry(name) {
http::header::Entry::Vacant(entry) => {
entry.insert(value);
}
http::header::Entry::Occupied(mut entry) => {
entry.append(value);
}
};
}
Ok(headers)
}
}

impl From<http::HeaderMap> for crate::http::types::Fields {
fn from(headers: http::HeaderMap) -> Self {
use std::string::ToString;

let fields = crate::http::types::Fields::new();
for (name, value) in headers.iter() {
fields
.append(&name.to_string(), &value.as_bytes().to_vec())
.expect("failed to append header")
}
fields
}
}
3 changes: 3 additions & 0 deletions src/ext/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#[cfg(feature = "std")]
mod std;

#[cfg(feature = "http")]
pub mod http;

#[cfg(feature = "rand")]
pub mod rand;

Expand Down

0 comments on commit a1963fa

Please sign in to comment.