#[cfg(all(target_os = "wasi", feature = "http_wasi"))]
pub mod sync_impl {
use std::io::{self, Read, Write};
use http::{header, Method, Request, Response};
use wasi::{
http::{
outgoing_handler::{self, OutgoingRequest},
types::{self, Fields, IncomingBody, InputStream, OutgoingBody},
},
io::streams,
};
use crate::http::{HttpResolverError, SyncHttpResolver};
pub type Impl = SyncWasiResolver;
pub fn new() -> Impl {
SyncWasiResolver::new()
}
pub fn with_redirects() -> Option<Impl> {
None
}
struct WasiStream {
stream: InputStream,
_body: IncomingBody,
}
impl Read for WasiStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
Read::read(&mut self.stream, buf)
}
}
pub struct SyncWasiResolver {}
impl SyncWasiResolver {
pub fn new() -> Self {
Self {}
}
}
impl SyncHttpResolver for SyncWasiResolver {
fn http_resolve(
&self,
request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError> {
let fields = Fields::new();
for (name, value) in request.headers() {
if name != header::HOST {
fields.set(name.as_str(), &[value.as_bytes().to_vec()])?;
}
}
let wasi_request = OutgoingRequest::new(fields);
let path_with_query = request
.uri()
.path_and_query()
.map(|path_and_query| path_and_query.as_str());
wasi_request
.set_path_with_query(path_with_query)
.map_err(|_| WasiError)?;
let authority = request
.uri()
.authority()
.map(|authority| authority.as_str());
wasi_request
.set_authority(authority)
.map_err(|_| WasiError)?;
let scheme = match request.uri().scheme_str() {
Some(scheme) => match scheme {
"http" => Some(wasi::http::types::Scheme::Http),
"https" => Some(wasi::http::types::Scheme::Https),
scheme => Some(wasi::http::types::Scheme::Other(scheme.to_owned())),
},
None => None,
};
wasi_request
.set_scheme(scheme.as_ref())
.map_err(|_| WasiError)?;
let method = match request.method() {
&Method::GET => wasi::http::types::Method::Get,
&Method::HEAD => wasi::http::types::Method::Head,
&Method::POST => wasi::http::types::Method::Post,
&Method::PUT => wasi::http::types::Method::Put,
&Method::DELETE => wasi::http::types::Method::Delete,
&Method::CONNECT => wasi::http::types::Method::Connect,
&Method::OPTIONS => wasi::http::types::Method::Options,
&Method::TRACE => wasi::http::types::Method::Trace,
&Method::PATCH => wasi::http::types::Method::Patch,
method => wasi::http::types::Method::Other(method.as_str().to_owned()),
};
wasi_request.set_method(&method).map_err(|_| WasiError)?;
let body = wasi_request.body().map_err(|_| WasiError)?;
{
let mut stream = body.write().map_err(|_| WasiError)?;
stream.write_all(request.body())?;
}
OutgoingBody::finish(body, None)?;
let wasi_response = outgoing_handler::handle(wasi_request, None)?;
wasi_response.subscribe().block();
let wasi_response = wasi_response
.get()
.ok_or(WasiError)?
.map_err(|_| WasiError)??;
let mut response = Response::builder().status(wasi_response.status());
for (name, value) in wasi_response.headers().entries() {
response = response.header(name, value);
}
let body = wasi_response.consume().map_err(|_| WasiError)?;
let stream = body.stream().map_err(|_| WasiError)?;
let stream = WasiStream {
stream,
_body: body,
};
Ok(response.body(Box::new(stream) as Box<dyn Read>)?)
}
}
#[derive(Debug, thiserror::Error)]
#[error("an unknown error occurred in `wasi`")]
struct WasiError;
impl From<()> for WasiError {
fn from(_: ()) -> Self {
Self
}
}
impl From<WasiError> for HttpResolverError {
fn from(value: WasiError) -> Self {
HttpResolverError::Other(Box::new(value))
}
}
impl From<outgoing_handler::ErrorCode> for HttpResolverError {
fn from(value: outgoing_handler::ErrorCode) -> Self {
Self::Other(Box::new(value))
}
}
impl From<types::HeaderError> for HttpResolverError {
fn from(value: types::HeaderError) -> Self {
Self::Other(Box::new(value))
}
}
impl From<streams::StreamError> for HttpResolverError {
fn from(value: streams::StreamError) -> Self {
Self::Other(Box::new(value))
}
}
}
#[cfg(all(target_os = "wasi", feature = "http_wstd"))]
pub mod async_impl {
use std::io::{Cursor, Read};
use async_trait::async_trait;
use http::{Request, Response};
use wstd::http::body::StreamedBody;
use crate::http::{AsyncHttpResolver, HttpResolverError};
pub type Impl = wstd::http::Client;
pub fn new() -> Impl {
wstd::http::Client::new()
}
pub fn with_redirects() -> Option<Impl> {
None
}
#[async_trait(?Send)]
impl AsyncHttpResolver for wstd::http::Client {
async fn http_resolve_async(
&self,
request: Request<Vec<u8>>,
) -> Result<Response<Box<dyn Read>>, HttpResolverError> {
let request = request.map(|body| StreamedBody::new(wstd::io::Cursor::new(body)));
let mut response = self.send(request).await?;
let bytes = response.body_mut().bytes().await?;
Ok(response.map(|_| Box::new(Cursor::new(bytes)) as Box<dyn Read>))
}
}
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
struct WstdError(String);
impl From<wstd::http::error::Error> for HttpResolverError {
fn from(value: wstd::http::error::Error) -> Self {
Self::Other(Box::new(WstdError(value.to_string())))
}
}
}