-
Notifications
You must be signed in to change notification settings - Fork 30
[v0.3] model bodies after stream<u8, trailers, error-code>
, trap less
#162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
aba4ece
1b2455f
128af94
55db2ff
70ad39c
eac8416
f927cf4
865daa5
c3517c6
ae89575
848eaf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -230,77 +230,36 @@ interface types { | |
/// Trailers is an alias for Fields. | ||
type trailers = fields; | ||
|
||
/// Represents an HTTP Request or Response's Body. | ||
/// | ||
/// A body has both its contents - a stream of bytes - and a (possibly empty) | ||
/// set of trailers, indicating that the full contents of the body have been | ||
/// received. This resource represents the contents as a `stream<u8>` and the | ||
/// delivery of trailers as a `trailers`, and ensures that the user of this | ||
/// interface may only be consuming either the body contents or waiting on | ||
/// trailers at any given time. | ||
resource body { | ||
|
||
/// Construct a new `body` with the specified stream. | ||
/// | ||
/// This function returns a future, which will resolve | ||
/// to an error code if transmitting stream data fails. | ||
/// | ||
/// The returned future resolves to success once body stream | ||
/// is fully transmitted. | ||
new: static func( | ||
%stream: stream<u8>, | ||
) -> tuple<body, future<result<_, error-code>>>; | ||
|
||
/// Construct a new `body` with the specified stream and trailers. | ||
/// | ||
/// This function returns a future, which will resolve | ||
/// to an error code if transmitting stream data or trailers fails. | ||
/// | ||
/// The returned future resolves to success once body stream and trailers | ||
/// are fully transmitted. | ||
new-with-trailers: static func( | ||
%stream: stream<u8>, | ||
trailers: future<trailers> | ||
) -> tuple<body, future<result<_, error-code>>>; | ||
|
||
/// Returns the contents of the body, as a stream of bytes. | ||
/// | ||
/// This function may be called multiple times as long as any `stream`s | ||
/// returned by previous calls have been dropped first. | ||
/// | ||
/// On success, this function returns a stream and a future, which will resolve | ||
/// to an error code if receiving data from stream fails. | ||
/// The returned future resolves to success if body is closed. | ||
%stream: func() -> result<tuple<stream<u8>, future<result<_, error-code>>>>; | ||
|
||
/// Takes ownership of `body`, and returns an unresolved optional `trailers` result. | ||
/// | ||
/// This function will trap if a `stream` child is still alive. | ||
finish: static func(this: body) -> future<result<option<trailers>, error-code>>; | ||
} | ||
|
||
/// Represents an HTTP Request. | ||
resource request { | ||
|
||
/// Construct a new `request` with a default `method` of `GET`, and | ||
/// `none` values for `path-with-query`, `scheme`, and `authority`. | ||
/// | ||
/// * `headers` is the HTTP Headers for the Response. | ||
/// * `body` is the optional contents of the body, possibly including | ||
/// trailers. | ||
/// * `options` is optional `request-options` to be used if the request is | ||
/// sent over a network connection. | ||
/// `headers` is the HTTP Headers for the Request. | ||
/// | ||
/// `contents` is the optional body content stream with `none` | ||
/// representing a zero-length content stream. | ||
/// Once it is closed, `trailers` future must resolve to a result. | ||
/// If `trailers` resolves to an error, underlying connection | ||
/// will be closed immediately. | ||
/// | ||
/// `options` is optional `request-options` resource to be used | ||
/// if the request is sent over a network connection. | ||
/// | ||
/// It is possible to construct, or manipulate with the accessor functions | ||
/// below, an `request` with an invalid combination of `scheme` | ||
/// below, a `request` with an invalid combination of `scheme` | ||
/// and `authority`, or `headers` which are not permitted to be sent. | ||
/// It is the obligation of the `handler.handle` implementation | ||
/// to reject invalid constructions of `request`. | ||
constructor( | ||
/// | ||
/// The returned future resolves to result of transmission of this request. | ||
new: static func( | ||
headers: headers, | ||
body: option<body>, | ||
contents: option<stream<u8>>, | ||
trailers: future<result<option<trailers>, error-code>>, | ||
options: option<request-options> | ||
); | ||
) -> tuple<request, future<result<_, error-code>>>; | ||
|
||
/// Get the Method for the Request. | ||
method: func() -> method; | ||
|
@@ -348,22 +307,28 @@ interface types { | |
/// | ||
/// The returned `headers` resource is immutable: `set`, `append`, and | ||
/// `delete` operations will fail with `header-error.immutable`. | ||
/// | ||
/// This headers resource is a child: it must be dropped before the parent | ||
/// `request` is dropped, or its ownership is transferred to another | ||
/// component by e.g. `handler.handle`. | ||
headers: func() -> headers; | ||
|
||
/// Get the body associated with the Request, if any. | ||
/// Get body of the Request. | ||
/// | ||
/// This body resource is a child: it must be dropped before the parent | ||
/// `request` is dropped, or its ownership is transferred to another | ||
/// component by e.g. `handler.handle`. | ||
body: func() -> option<body>; | ||
|
||
/// Takes ownership of the `request` and returns the `headers`, `body` | ||
/// and `request-options`, if any. | ||
into-parts: static func(this: request) -> tuple<headers, option<body>, option<request-options>>; | ||
/// Stream returned by this method represents the contents of the body. | ||
/// Once the stream is reported as closed, callers should await the returned future | ||
/// to determine whether the body was received successfully. | ||
/// The future will only resolve after the stream is reported as closed. | ||
/// | ||
/// The stream and future returned by this method are children: | ||
/// they should be closed or consumed before the parent `response` | ||
/// is dropped, or its ownership is transferred to another component | ||
/// by e.g. `handler.handle`. | ||
/// | ||
/// This method may be called multiple times. | ||
/// | ||
/// This method will return an error if it is called while either: | ||
/// - a stream or future returned by a previous call to this method is still open | ||
/// - a stream returned by a previous call to this method has reported itself as closed | ||
/// Thus there will always be at most one readable stream open for a given body. | ||
/// Each subsequent stream picks up where the last stream left off, up until it is finished. | ||
body: func() -> result<tuple<stream<u8>, future<result<option<trailers>, error-code>>>>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on the answer to my question above, this might also have to change? Though even if we make the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather not introduce a distinction here using the same reasoning as laid out by @lukewagner in #162 (comment) If the E.g. if a component read one byte from The I would rather avoid having to document/specify this behavior making this interface more self-documenting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you for the explanation and references: that all makes sense to me! |
||
} | ||
|
||
/// Parameters for making an HTTP Request. Each of these parameters is | ||
|
@@ -400,6 +365,10 @@ interface types { | |
/// body stream. An error return value indicates that this timeout is not | ||
/// supported or that this handle is immutable. | ||
set-between-bytes-timeout: func(duration: option<duration>) -> result<_, request-options-error>; | ||
|
||
/// Make a deep copy of the `request-options`. | ||
/// The resulting `request-options` is mutable. | ||
clone: func() -> request-options; | ||
} | ||
|
||
/// This type corresponds to the HTTP standard Status Code. | ||
|
@@ -408,17 +377,24 @@ interface types { | |
/// Represents an HTTP Response. | ||
resource response { | ||
|
||
/// Construct an `response`, with a default `status-code` of `200`. If a | ||
/// different `status-code` is needed, it must be set via the | ||
/// Construct a new `response`, with a default `status-code` of `200`. | ||
/// If a different `status-code` is needed, it must be set via the | ||
/// `set-status-code` method. | ||
/// | ||
/// * `headers` is the HTTP Headers for the Response. | ||
/// * `body` is the optional contents of the body, possibly including | ||
/// trailers. | ||
constructor( | ||
/// `headers` is the HTTP Headers for the Response. | ||
/// | ||
/// `contents` is the optional body content stream with `none` | ||
/// representing a zero-length content stream. | ||
/// Once it is closed, `trailers` future must resolve to a result. | ||
/// If `trailers` resolves to an error, underlying connection | ||
/// will be closed immediately. | ||
/// | ||
/// The returned future resolves to result of transmission of this response. | ||
new: static func( | ||
headers: headers, | ||
body: option<body>, | ||
); | ||
contents: option<stream<u8>>, | ||
trailers: future<result<option<trailers>, error-code>>, | ||
) -> tuple<response, future<result<_, error-code>>>; | ||
|
||
/// Get the HTTP Status Code for the Response. | ||
status-code: func() -> status-code; | ||
|
@@ -427,25 +403,31 @@ interface types { | |
/// given is not a valid http status code. | ||
set-status-code: func(status-code: status-code) -> result; | ||
|
||
/// Get the headers associated with the Request. | ||
/// Get the headers associated with the Response. | ||
/// | ||
/// The returned `headers` resource is immutable: `set`, `append`, and | ||
/// `delete` operations will fail with `header-error.immutable`. | ||
/// | ||
/// This headers resource is a child: it must be dropped before the parent | ||
/// `response` is dropped, or its ownership is transferred to another | ||
/// component by e.g. `handler.handle`. | ||
headers: func() -> headers; | ||
|
||
/// Get the body associated with the Response, if any. | ||
/// Get body of the Response. | ||
/// | ||
/// This body resource is a child: it must be dropped before the parent | ||
/// `response` is dropped, or its ownership is transferred to another | ||
/// component by e.g. `handler.handle`. | ||
body: func() -> option<body>; | ||
|
||
/// Takes ownership of the `response` and returns the `headers` and `body`, | ||
/// if any. | ||
into-parts: static func(this: response) -> tuple<headers, option<body>>; | ||
/// Stream returned by this method represents the contents of the body. | ||
/// Once the stream is reported as closed, callers should await the returned future | ||
/// to determine whether the body was received successfully. | ||
/// The future will only resolve after the stream is reported as closed. | ||
/// | ||
/// The stream and future returned by this method are children: | ||
/// they should be closed or consumed before the parent `response` | ||
/// is dropped, or its ownership is transferred to another component | ||
/// by e.g. `handler.handle`. | ||
/// | ||
/// This method may be called multiple times. | ||
/// | ||
/// This method will return an error if it is called while either: | ||
/// - a stream or future returned by a previous call to this method is still open | ||
/// - a stream returned by a previous call to this method has reported itself as closed | ||
/// Thus there will always be at most one readable stream open for a given body. | ||
/// Each subsequent stream picks up where the last stream left off, up until it is finished. | ||
body: func() -> result<tuple<stream<u8>, future<result<option<trailers>, error-code>>>>; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thought on this: are there downsides to always having to pass a future here? I'm not sure what the overhead (both resource- and boilerplate-wise) of creating an managing a future is. If it's trivial, then this doesn't much matter, but otherwise, should we consider wrapping this into an
option<>
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the "stream<T, U, V> world", if we go with optional body in constructors, the parameter would be
option<stream<u8, option<trailers>, error-code>>
, meaning that users could either pass no body stream or pass a body stream, with potentially empty contents, but mandatory final element (option<trailers>
orerror-code
).From what I understand, there would not be a way to optionally pass a final stream element.
With that in mind, if we want to make the future optional, we'd need to take an
option<tuple<stream<u8>, future<result<option<trailers>, error-code>>>>
, which would then directly translate tooption<stream<u8, option<trailers>, error-code>>
in the future. (see also #162 (comment))FWIW, here's what it takes to create a new future in Rust guest and pass to a constructor: https://github.com/bytecodealliance/wasip3-prototyping/blob/ad4ccf03172b22320c4a22971c51b4f7249a2846/crates/test-programs/src/p3/http.rs#L78-L81
and then send a value: https://github.com/bytecodealliance/wasip3-prototyping/blob/ad4ccf03172b22320c4a22971c51b4f7249a2846/crates/test-programs/src/p3/http.rs#L112
So 2 LoC, plus an import of
futures::SinkExt
to exposefutures::SinkExt::send
I would not expect creating a future handle to be an expensive operation, especially considering the fact that we're most likely dealing with network I/O here.
I'm happy to switch to a
tuple
here, if that's what everyone prefersThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the overhead shouldn't be too significant. Also, it encourages the generally good engineering practice of explicitly signalling the final success or failure of the
request
/response
in theresult
.