Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/http/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub trait Body: AsyncRead {
/// Returns the exact remaining length of the iterator, if known.
fn len(&self) -> Option<usize>;

/// Returns `true`` if the body is known to be empty.
/// Returns `true` if the body is known to be empty.
fn is_empty(&self) -> bool {
matches!(self.len(), Some(0))
}
Expand Down
8 changes: 5 additions & 3 deletions src/http/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::{response::IncomingBody, Body, Error, Request, Response, Result};
use super::{body::IncomingBody, Body, Error, Request, Response, Result};
use crate::http::request::into_outgoing;
use crate::http::response::try_from_incoming_response;
use crate::io::{self, AsyncOutputStream, AsyncPollable};
use crate::time::Duration;
use wasi::http::types::{OutgoingBody, RequestOptions as WasiRequestOptions};
Expand All @@ -18,7 +20,7 @@ impl Client {

/// Send an HTTP request.
pub async fn send<B: Body>(&self, req: Request<B>) -> Result<Response<IncomingBody>> {
let (wasi_req, body) = req.into_outgoing()?;
let (wasi_req, body) = into_outgoing(req)?;
let wasi_body = wasi_req.body().unwrap();
let body_stream = wasi_body.write().unwrap();

Expand All @@ -39,7 +41,7 @@ impl Client {
// is to trap if we try and get the response more than once. The final
// `?` is to raise the actual error if there is one.
let res = res.get().unwrap().unwrap()?;
Ok(Response::try_from_incoming_response(res)?)
try_from_incoming_response(res)
}

/// Set timeout on connecting to HTTP server
Expand Down
3 changes: 1 addition & 2 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! HTTP networking support
//!
pub use http::status::StatusCode;
pub use http::uri::Uri;

#[doc(inline)]
Expand All @@ -10,7 +11,6 @@ pub use fields::{HeaderMap, HeaderName, HeaderValue};
pub use method::Method;
pub use request::Request;
pub use response::Response;
pub use status_code::StatusCode;

pub mod body;

Expand All @@ -20,4 +20,3 @@ mod fields;
mod method;
mod request;
mod response;
mod status_code;
129 changes: 39 additions & 90 deletions src/http/request.rs
Original file line number Diff line number Diff line change
@@ -1,96 +1,45 @@
use crate::io::{empty, Empty};

use super::{
fields::header_map_to_wasi, method::to_wasi_method, Body, Error, HeaderMap, IntoBody, Method,
Result,
};
use http::uri::Uri;
use super::{fields::header_map_to_wasi, method::to_wasi_method, Error, Result};
use wasi::http::outgoing_handler::OutgoingRequest;
use wasi::http::types::Scheme;

/// An HTTP request
#[derive(Debug)]
pub struct Request<B: Body> {
method: Method,
uri: Uri,
headers: HeaderMap,
body: B,
}

impl Request<Empty> {
/// Create a new HTTP request to send off to the client.
pub fn new(method: Method, uri: Uri) -> Self {
Self {
body: empty(),
method,
uri,
headers: HeaderMap::new(),
}
}
}

impl<B: Body> Request<B> {
/// Get the HTTP headers from the impl
pub fn headers(&self) -> &HeaderMap {
&self.headers
}

/// Mutably get the HTTP headers from the impl
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}

/// Set an HTTP body.
pub fn set_body<C: IntoBody>(self, body: C) -> Request<C::IntoBody> {
let Self {
method,
uri,
headers,
..
} = self;
Request {
method,
uri,
headers,
body: body.into_body(),
}
}

pub(crate) fn into_outgoing(self) -> Result<(OutgoingRequest, B)> {
let wasi_req = OutgoingRequest::new(header_map_to_wasi(&self.headers)?);

// Set the HTTP method
let method = to_wasi_method(self.method);
wasi_req
.set_method(&method)
.map_err(|()| Error::other(format!("method rejected by wasi-http: {method:?}",)))?;

// Set the url scheme
let scheme = match self.uri.scheme().map(|s| s.as_str()) {
Some("http") => Scheme::Http,
Some("https") | None => Scheme::Https,
Some(other) => Scheme::Other(other.to_owned()),
};
pub use http::Request;

pub(crate) fn into_outgoing<T>(request: Request<T>) -> Result<(OutgoingRequest, T)> {
let wasi_req = OutgoingRequest::new(header_map_to_wasi(request.headers())?);

let (parts, body) = request.into_parts();

// Set the HTTP method
let method = to_wasi_method(parts.method);
wasi_req
.set_method(&method)
.map_err(|()| Error::other(format!("method rejected by wasi-http: {method:?}",)))?;

// Set the url scheme
let scheme = match parts.uri.scheme().map(|s| s.as_str()) {
Some("http") => Scheme::Http,
Some("https") | None => Scheme::Https,
Some(other) => Scheme::Other(other.to_owned()),
};
wasi_req
.set_scheme(Some(&scheme))
.map_err(|()| Error::other(format!("scheme rejected by wasi-http: {scheme:?}")))?;

// Set authority
let authority = parts.uri.authority().map(|a| a.as_str());
wasi_req
.set_authority(authority)
.map_err(|()| Error::other(format!("authority rejected by wasi-http {authority:?}")))?;

// Set the url path + query string
if let Some(p_and_q) = parts.uri.path_and_query() {
wasi_req
.set_scheme(Some(&scheme))
.map_err(|()| Error::other(format!("scheme rejected by wasi-http: {scheme:?}")))?;

// Set authority
let authority = self.uri.authority().map(|a| a.as_str());
wasi_req
.set_authority(authority)
.map_err(|()| Error::other(format!("authority rejected by wasi-http {authority:?}")))?;

// Set the url path + query string
if let Some(p_and_q) = self.uri.path_and_query() {
wasi_req
.set_path_with_query(Some(&p_and_q.to_string()))
.map_err(|()| {
Error::other(format!("path and query rejected by wasi-http {p_and_q:?}"))
})?;
}

// All done; request is ready for send-off
Ok((wasi_req, self.body))
.set_path_with_query(Some(&p_and_q.to_string()))
.map_err(|()| {
Error::other(format!("path and query rejected by wasi-http {p_and_q:?}"))
})?;
}

// All done; request is ready for send-off
Ok((wasi_req, body))
}
80 changes: 30 additions & 50 deletions src/http/response.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
use wasi::http::types::{IncomingBody as WasiIncomingBody, IncomingResponse};

use super::{fields::header_map_from_wasi, Body, Error, HeaderMap, Result, StatusCode};
use super::{fields::header_map_from_wasi, Body, Error, HeaderMap, Result};
use crate::io::{AsyncInputStream, AsyncRead};
use http::StatusCode;

/// An HTTP response
#[derive(Debug)]
pub struct Response<B: Body> {
headers: HeaderMap,
status: StatusCode,
body: B,
}
pub use http::Response;

#[derive(Debug)]
enum BodyKind {
Expand All @@ -35,54 +30,39 @@ impl BodyKind {
}
}

impl Response<IncomingBody> {
pub(crate) fn try_from_incoming_response(incoming: IncomingResponse) -> Result<Self> {
let headers: HeaderMap = header_map_from_wasi(incoming.headers())?;
let status = incoming.status().into();
pub(crate) fn try_from_incoming_response(
incoming: IncomingResponse,
) -> Result<Response<IncomingBody>> {
let headers: HeaderMap = header_map_from_wasi(incoming.headers())?;
// TODO: Does WASI guarantee that the incoming status is valid?
let status =
StatusCode::from_u16(incoming.status()).map_err(|err| Error::other(err.to_string()))?;

let kind = BodyKind::from_headers(&headers)?;
// `body_stream` is a child of `incoming_body` which means we cannot
// drop the parent before we drop the child
let incoming_body = incoming
.consume()
.expect("cannot call `consume` twice on incoming response");
let body_stream = incoming_body
.stream()
.expect("cannot call `stream` twice on an incoming body");
let kind = BodyKind::from_headers(&headers)?;
// `body_stream` is a child of `incoming_body` which means we cannot
// drop the parent before we drop the child
let incoming_body = incoming
.consume()
.expect("cannot call `consume` twice on incoming response");
let body_stream = incoming_body
.stream()
.expect("cannot call `stream` twice on an incoming body");

let body = IncomingBody {
kind,
body_stream: AsyncInputStream::new(body_stream),
_incoming_body: incoming_body,
};
let body = IncomingBody {
kind,
body_stream: AsyncInputStream::new(body_stream),
_incoming_body: incoming_body,
};

Ok(Self {
headers,
body,
status,
})
}
}
let mut builder = Response::builder().status(status);

impl<B: Body> Response<B> {
// Get the HTTP status code
pub fn status_code(&self) -> StatusCode {
self.status
if let Some(headers_mut) = builder.headers_mut() {
*headers_mut = headers;
}

/// Get the HTTP headers from the impl
pub fn headers(&self) -> &HeaderMap {
&self.headers
}

/// Mutably get the HTTP headers from the impl
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}

pub fn body(&mut self) -> &mut B {
&mut self.body
}
builder
.body(body)
.map_err(|err| Error::other(err.to_string()))
}

/// An incoming HTTP body
Expand Down
Loading
Loading