Skip to content

Commit 233a6bf

Browse files
committed
p3-http: improve docs
Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
1 parent aaa5901 commit 233a6bf

File tree

7 files changed

+65
-15
lines changed

7 files changed

+65
-15
lines changed

crates/wasi-http/src/p3/body.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub(crate) enum Body {
4242
}
4343

4444
impl Body {
45+
/// Implementation of `consume-body` shared between requests and responses
4546
pub(crate) fn consume<T>(
4647
self,
4748
mut store: Access<'_, T, WasiHttp>,
@@ -105,6 +106,7 @@ impl Body {
105106
}
106107
}
107108

109+
/// Implementation of `drop` shared between requests and responses
108110
pub(crate) fn drop(self, mut store: impl AsContextMut) {
109111
if let Body::Guest {
110112
contents_rx,
@@ -120,7 +122,8 @@ impl Body {
120122
}
121123
}
122124

123-
pub(crate) enum GuestBodyKind {
125+
/// The kind of body, used for error reporting
126+
pub(crate) enum BodyKind {
124127
Request,
125128
Response,
126129
}
@@ -141,20 +144,22 @@ impl ContentLength {
141144
}
142145
}
143146

147+
/// [StreamConsumer] implementation for bodies originating in the guest.
144148
struct GuestBodyConsumer {
145149
contents_tx: PollSender<Result<Bytes, ErrorCode>>,
146150
result_tx: Option<oneshot::Sender<Result<(), ErrorCode>>>,
147151
content_length: Option<ContentLength>,
148-
kind: GuestBodyKind,
152+
kind: BodyKind,
149153
// `true` when the other side of `contents_tx` was unexpectedly closed
150154
closed: bool,
151155
}
152156

153157
impl GuestBodyConsumer {
158+
/// Constructs the approprite body size error given the [BodyKind]
154159
fn body_size_error(&self, n: Option<u64>) -> ErrorCode {
155160
match self.kind {
156-
GuestBodyKind::Request => ErrorCode::HttpRequestBodySize(n),
157-
GuestBodyKind::Response => ErrorCode::HttpResponseBodySize(n),
161+
BodyKind::Request => ErrorCode::HttpRequestBodySize(n),
162+
BodyKind::Response => ErrorCode::HttpResponseBodySize(n),
158163
}
159164
}
160165

@@ -235,20 +240,22 @@ impl<D> StreamConsumer<D> for GuestBodyConsumer {
235240
}
236241
}
237242

243+
/// [http_body::Body] implementation for bodies originating in the guest.
238244
pub(crate) struct GuestBody {
239245
contents_rx: Option<mpsc::Receiver<Result<Bytes, ErrorCode>>>,
240246
trailers_rx: Option<oneshot::Receiver<Result<Option<Arc<http::HeaderMap>>, ErrorCode>>>,
241247
content_length: Option<u64>,
242248
}
243249

244250
impl GuestBody {
251+
/// Construct a new [GuestBody]
245252
pub(crate) fn new<T: 'static>(
246253
mut store: impl AsContextMut<Data = T>,
247254
contents_rx: Option<StreamReader<u8>>,
248255
trailers_rx: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
249256
result_tx: oneshot::Sender<Result<(), ErrorCode>>,
250257
content_length: Option<u64>,
251-
kind: GuestBodyKind,
258+
kind: BodyKind,
252259
getter: fn(&mut T) -> WasiHttpCtxView<'_>,
253260
) -> Self {
254261
let (trailers_http_tx, trailers_http_rx) = oneshot::channel();
@@ -290,10 +297,15 @@ impl http_body::Body for GuestBody {
290297
cx: &mut Context<'_>,
291298
) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
292299
if let Some(contents_rx) = self.contents_rx.as_mut() {
300+
// `contents_rx` has not been closed yet, poll it
293301
while let Some(res) = ready!(contents_rx.poll_recv(cx)) {
294302
match res {
295303
Ok(buf) => {
296304
if let Some(n) = self.content_length.as_mut() {
305+
// Substract frame length from `content_length`,
306+
// [GuestBodyConsumer] already performs the validation, so
307+
// just keep count as optimization for
308+
// `is_end_stream` and `size_hint`
297309
*n = n.saturating_sub(buf.len().try_into().unwrap_or(u64::MAX));
298310
}
299311
return Poll::Ready(Some(Ok(http_body::Frame::data(buf))));
@@ -303,14 +315,17 @@ impl http_body::Body for GuestBody {
303315
}
304316
}
305317
}
318+
// Record that `contents_rx` is closed
306319
self.contents_rx = None;
307320
}
308321

309322
let Some(trailers_rx) = self.trailers_rx.as_mut() else {
323+
// `trailers_rx` has already terminated - this is the end of stream
310324
return Poll::Ready(None);
311325
};
312326

313327
let res = ready!(Pin::new(trailers_rx).poll(cx));
328+
// Record that `trailers_rx` has terminated
314329
self.trailers_rx = None;
315330
match res {
316331
Ok(Ok(Some(trailers))) => Poll::Ready(Some(Ok(http_body::Frame::trailers(
@@ -328,14 +343,18 @@ impl http_body::Body for GuestBody {
328343
|| !contents_rx.is_closed()
329344
|| self.content_length.is_some_and(|n| n > 0)
330345
{
346+
// `contents_rx` might still produce data frames
331347
return false;
332348
}
333349
}
334350
if let Some(trailers_rx) = self.trailers_rx.as_ref() {
335351
if !trailers_rx.is_terminated() {
352+
// `trailers_rx` has not terminated yet
336353
return false;
337354
}
338355
}
356+
357+
// no data left
339358
return true;
340359
}
341360

@@ -348,6 +367,7 @@ impl http_body::Body for GuestBody {
348367
}
349368
}
350369

370+
/// [http_body::Body] that has been consumed.
351371
pub(crate) struct ConsumedBody;
352372

353373
impl http_body::Body for ConsumedBody {
@@ -372,6 +392,7 @@ impl http_body::Body for ConsumedBody {
372392
}
373393
}
374394

395+
/// [FutureConsumer] implementation for trailers originating in the guest.
375396
struct GuestTrailerConsumer<T> {
376397
tx: Option<oneshot::Sender<Result<Option<Arc<HeaderMap>>, ErrorCode>>>,
377398
getter: fn(&mut T) -> WasiHttpCtxView<'_>,
@@ -409,6 +430,7 @@ where
409430
}
410431
}
411432

433+
/// [StreamProducer] implementation for bodies originating in the host.
412434
struct HostBodyStreamProducer<T> {
413435
body: BoxBody<Bytes, ErrorCode>,
414436
trailers: Option<oneshot::Sender<Result<Option<Resource<Trailers>>, ErrorCode>>>,
@@ -447,6 +469,8 @@ where
447469
let cap = match dst.remaining(&mut store).map(NonZeroUsize::new) {
448470
Some(Some(cap)) => Some(cap),
449471
Some(None) => {
472+
// On 0-length the best we can do is check that underlying stream has not
473+
// reached the end yet
450474
if self.body.is_end_stream() {
451475
break 'result Ok(None);
452476
} else {
@@ -463,11 +487,13 @@ where
463487
let n = frame.len();
464488
let cap = cap.into();
465489
if n > cap {
490+
// data frame does not fit in destination, fill it and buffer the rest
466491
dst.set_buffer(Cursor::new(frame.split_off(cap)));
467492
let mut dst = dst.as_direct(store, cap);
468493
dst.remaining().copy_from_slice(&frame);
469494
dst.mark_written(cap);
470495
} else {
496+
// copy the whole frame into the destination
471497
let mut dst = dst.as_direct(store, n);
472498
dst.remaining()[..n].copy_from_slice(&frame);
473499
dst.mark_written(n);

crates/wasi-http/src/p3/host/handler.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::p3::bindings::http::handler::{Host, HostWithStore};
22
use crate::p3::bindings::http::types::{ErrorCode, Request, Response};
3-
use crate::p3::body::{Body, ConsumedBody, GuestBody, GuestBodyKind};
3+
use crate::p3::body::{Body, BodyKind, ConsumedBody, GuestBody};
44
use crate::p3::{HttpError, HttpResult, WasiHttp, WasiHttpCtxView, get_content_length};
55
use anyhow::Context as _;
66
use core::pin::Pin;
@@ -135,6 +135,7 @@ impl HostWithStore for WasiHttp {
135135
result_tx,
136136
} => {
137137
let (http_result_tx, http_result_rx) = oneshot::channel();
138+
// `Content-Length` header value is validated in `fields` implementation
138139
let content_length = get_content_length(&headers)
139140
.map_err(|err| ErrorCode::InternalError(Some(format!("{err:#}"))))?;
140141
_ = result_tx.send(Box::new(async move {
@@ -149,7 +150,7 @@ impl HostWithStore for WasiHttp {
149150
trailers_rx,
150151
http_result_tx,
151152
content_length,
152-
GuestBodyKind::Request,
153+
BodyKind::Request,
153154
getter,
154155
)
155156
.with_state(io_task_rx)
@@ -199,6 +200,7 @@ impl HostWithStore for WasiHttp {
199200
req,
200201
options.as_deref().copied(),
201202
Box::new(async {
203+
// Forward the response processing result to `WasiHttpCtx` implementation
202204
let Ok(fut) = res_result_rx.await else {
203205
return Ok(());
204206
};

crates/wasi-http/src/p3/host/types.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,6 @@ impl HostRequestWithStore for WasiHttp {
315315
let (result_tx, result_rx) = oneshot::channel();
316316
let WasiHttpCtxView { table, .. } = store.get();
317317
let headers = delete_fields(table, headers)?;
318-
// `Content-Length` header value is validated in `fields` implementation
319318
let options = options
320319
.map(|options| delete_request_options(table, options))
321320
.transpose()?;

crates/wasi-http/src/p3/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ pub trait WasiHttpCtx: Send {
8989
}
9090

9191
/// Send an outgoing request.
92+
///
93+
/// This implementation will be used by the `wasi:http/handler#handle` implementation.
94+
///
95+
/// The specified [Future] `fut` can be used to communicate
96+
/// a request processing error, if any, to the constructor of the request.
97+
/// For example, if the request was constructed via `wasi:http/types.request#new`,
98+
/// a result sent on `fut` will be forwarded to the guest on the future handle returned.
99+
///
100+
/// The returned [Future] can be used to communicate
101+
/// a request processing error, if any, to the constructor of the request.
102+
/// For example, if the request was constructed via `wasi:http/types.request#new`,
103+
/// a result resolved from it will be forwarded to the guest on the future handle returned.
92104
#[cfg(feature = "default-send-request")]
93105
fn send_request(
94106
&mut self,

crates/wasi-http/src/p3/proxy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use anyhow::Context as _;
55
use wasmtime::component::Accessor;
66

77
impl Proxy {
8-
/// Call `handle` on [Proxy] getting a [Future] back.
8+
/// Call `wasi:http/handler#handle` on [Proxy] getting a [Response] back.
99
pub async fn handle(
1010
&self,
1111
store: &Accessor<impl WasiHttpView>,

crates/wasi-http/src/p3/request.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub struct Request {
4141
impl Request {
4242
/// Construct a new [Request]
4343
///
44-
/// This returns a [Future] that the guest will use to communicate
44+
/// This returns a [Future] that the will be used to communicate
4545
/// a request processing error, if any.
4646
pub fn new(
4747
method: Method,
@@ -78,7 +78,7 @@ impl Request {
7878

7979
/// Construct a new [Request] from [http::Request].
8080
///
81-
/// This returns a [Future] that the guest will use to communicate
81+
/// This returns a [Future] that will be used to communicate
8282
/// a request processing error, if any.
8383
pub fn from_http<T>(
8484
req: http::Request<T>,
@@ -121,6 +121,11 @@ impl Request {
121121
///
122122
/// This implementation is used by the `wasi:http/handler` interface
123123
/// default implementation.
124+
///
125+
/// The returned [Future] can be used to communicate
126+
/// a request processing error, if any, to the constructor of the request.
127+
/// For example, if the request was constructed via `wasi:http/types.request#new`,
128+
/// a result resolved from it will be forwarded to the guest on the future handle returned.
124129
#[cfg(feature = "default-send-request")]
125130
pub async fn default_send_request(
126131
mut req: http::Request<impl http_body::Body<Data = Bytes, Error = ErrorCode> + Send + 'static>,
@@ -202,7 +207,10 @@ pub async fn default_send_request(
202207
{
203208
return Err(dns_error("address not available".to_string(), 0));
204209
}
205-
Ok(Err(..)) => return Err(ErrorCode::ConnectionRefused),
210+
Ok(Err(err)) => {
211+
tracing::warn!(?err, "connection refused");
212+
return Err(ErrorCode::ConnectionRefused);
213+
}
206214
Err(..) => return Err(ErrorCode::ConnectionTimeout),
207215
};
208216
let stream = if use_tls {

crates/wasi-http/src/p3/response.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::p3::bindings::http::types::ErrorCode;
2-
use crate::p3::body::{Body, ConsumedBody, GuestBody, GuestBodyKind};
2+
use crate::p3::body::{Body, BodyKind, ConsumedBody, GuestBody};
33
use crate::p3::{WasiHttpView, get_content_length};
44
use anyhow::Context as _;
55
use bytes::Bytes;
@@ -40,7 +40,9 @@ impl Response {
4040
/// Convert [Response] into [http::Response].
4141
///
4242
/// The specified [Future] `fut` can be used to communicate
43-
/// a response processing error, if any, to the guest.
43+
/// a response processing error, if any, to the constructor of the response.
44+
/// For example, if the response was constructed via `wasi:http/types.response#new`,
45+
/// a result sent on `fut` will be forwarded to the guest on the future handle returned.
4446
pub fn into_http<T: WasiHttpView + 'static>(
4547
self,
4648
store: impl AsContextMut<Data = T>,
@@ -55,6 +57,7 @@ impl Response {
5557
result_tx,
5658
} => {
5759
let (http_result_tx, http_result_rx) = oneshot::channel();
60+
// `Content-Length` header value is validated in `fields` implementation
5861
let content_length =
5962
get_content_length(&res.headers).context("failed to parse `content-length`")?;
6063
_ = result_tx.send(Box::new(async move {
@@ -69,7 +72,7 @@ impl Response {
6972
trailers_rx,
7073
http_result_tx,
7174
content_length,
72-
GuestBodyKind::Response,
75+
BodyKind::Response,
7376
T::http,
7477
)
7578
.boxed()

0 commit comments

Comments
 (0)