From 31886ee2879b691b8109fbf1735041d7a29b140c Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 14 Feb 2024 08:25:25 -0700 Subject: [PATCH 1/5] [DO NOT MERGE] first draft of wasi:http@0.3.0 This is intended to start a conversation about what we expect wasi:http@0.3.0 to look like once we have support for `stream`s, `future`s, stream `error`s, and asynchronous lifts and lowers in the component model. In addition, I'm planning to implement a prototype of this interface using [isyswasfa](https://github.com/dicej/isyswasfa) in the near future, so I want to make sure the design is at least plausible. High level description: - The `incoming-handler` and `outgoing-handler` interfaces have been combined into a single `handler` interface. - The `incoming-` and `outgoing-` variations of `request` and `response` have been combined. Likewise for `body`. - I've added a `option` field to `request` since it would be awkward to leave it as a parameter of `handler.handle` (e.g. what would it mean to receive such a parameter for an incoming request or for a request passed from one component to the other without any use of the network?). - I've added a `request-options-error` (analogous to `header-error`) to distinguish between unsupported fields and immutable handles. I do not intend for this to be merged into the `main` branch of the `wasi-http` repo (since we'll presumably be doing 0.2.x work there), but I _would_ like for it to live somewhere we can iterate on it. A few options come to mind: - Keep it in my fork of the `wasi-http` repo for now - Make a long-lived branch in the official repo for it - Put it in another repo under the `WebAssembly` org (e.g. `wasi-http-0.3.0`) - Other ideas? Signed-off-by: Joel Dice --- wit/handler.wit | 52 ++----- wit/proxy.wit | 12 +- wit/types.wit | 356 ++++++++++++++---------------------------------- 3 files changed, 122 insertions(+), 298 deletions(-) diff --git a/wit/handler.wit b/wit/handler.wit index a34a064..099d094 100644 --- a/wit/handler.wit +++ b/wit/handler.wit @@ -1,43 +1,17 @@ -/// This interface defines a handler of incoming HTTP Requests. It should -/// be exported by components which can respond to HTTP Requests. -interface incoming-handler { - use types.{incoming-request, response-outparam}; +/// This interface defines a handler of HTTP Requests. It may be imported by +/// components which wish to send HTTP Requests and also exported by components +/// which can respond to HTTP Requests. In addition, it may be used to pass +/// a request from one component to another without any use of a network. +interface handler { + use types.{request, response, error-code}; - /// This function is invoked with an incoming HTTP Request, and a resource - /// `response-outparam` which provides the capability to reply with an HTTP - /// Response. The response is sent by calling the `response-outparam.set` - /// method, which allows execution to continue after the response has been - /// sent. This enables both streaming to the response body, and performing other - /// work. + /// When exported, this function may be called with either an incoming + /// request read from the network or a request synthesized or forwarded by + /// another component. /// - /// The implementor of this function must write a response to the - /// `response-outparam` before returning, or else the caller will respond - /// with an error on its behalf. + /// When imported, this function may be used to either send an outgoing + /// request over the network or pass it to another component. handle: func( - request: incoming-request, - response-out: response-outparam - ); -} - -/// This interface defines a handler of outgoing HTTP Requests. It should be -/// imported by components which wish to make HTTP Requests. -interface outgoing-handler { - use types.{ - outgoing-request, request-options, future-incoming-response, error-code - }; - - /// This function is invoked with an outgoing HTTP Request, and it returns - /// a resource `future-incoming-response` which represents an HTTP Response - /// which may arrive in the future. - /// - /// The `options` argument accepts optional parameters for the HTTP - /// protocol's transport layer. - /// - /// This function may return an error if the `outgoing-request` is invalid - /// or not allowed to be made. Otherwise, protocol errors are reported - /// through the `future-incoming-response`. - handle: func( - request: outgoing-request, - options: option - ) -> result; + request: request, + ) -> result; } diff --git a/wit/proxy.wit b/wit/proxy.wit index 26a975e..16e6fd3 100644 --- a/wit/proxy.wit +++ b/wit/proxy.wit @@ -1,4 +1,4 @@ -package wasi:http@0.2.0; +package wasi:http@0.3.0-draft-2024-02-14; /// The `wasi:http/imports` world imports all the APIs for HTTP proxies. /// It is intended to be `include`d in other worlds. @@ -20,7 +20,10 @@ world imports { /// This is the default handler to use when user code simply wants to make an /// HTTP request (e.g., via `fetch()`). - import outgoing-handler; + /// + /// This may also be used to pass synthesized or forwarded requests to another + /// component. + import handler; } /// The `wasi:http/proxy` world captures a widely-implementable intersection of @@ -34,5 +37,8 @@ world proxy { /// `handle` function of this exported interface. A host may arbitrarily reuse /// or not reuse component instance when delivering incoming HTTP requests and /// thus a component must be able to handle 0..N calls to `handle`. - export incoming-handler; + /// + /// This may also be used to receive synthesized or forwarded requests from + /// another component. + export handler; } diff --git a/wit/types.wit b/wit/types.wit index 755ac6a..8616d6c 100644 --- a/wit/types.wit +++ b/wit/types.wit @@ -1,11 +1,7 @@ -/// This interface defines all of the types and methods for implementing -/// HTTP Requests and Responses, both incoming and outgoing, as well as -/// their headers, trailers, and bodies. +/// This interface defines all of the types and methods for implementing HTTP +/// Requests and Responses, as well as their headers, trailers, and bodies. interface types { use wasi:clocks/monotonic-clock@0.2.0.{duration}; - use wasi:io/streams@0.2.0.{input-stream, output-stream}; - use wasi:io/error@0.2.0.{error as io-error}; - use wasi:io/poll@0.2.0.{pollable}; /// This type corresponds to HTTP standard Methods. variant method { @@ -95,18 +91,17 @@ interface types { field-size: option } - /// Attempts to extract a http-related `error` from the wasi:io `error` + /// Attempts to extract a http-related `error-code` from the stream `error` /// provided. /// - /// Stream operations which return - /// `wasi:io/stream/stream-error::last-operation-failed` have a payload of - /// type `wasi:io/error/error` with more information about the operation - /// that failed. This payload can be passed through to this function to see - /// if there's http-related information about the error to return. + /// Stream operations may fail with a stream `error` with more information + /// about the operation that failed. This `error` can be passed to this + /// function to see if there's http-related information about the error to + /// return. /// - /// Note that this function is fallible because not all io-errors are + /// Note that this function is fallible because not all stream errors are /// http-related errors. - http-error-code: func(err: borrow) -> option; + http-error-code: func(err: borrow) -> option; /// This type enumerates the different kinds of errors that may occur when /// setting or appending to a `fields` resource. @@ -124,6 +119,17 @@ interface types { /// permitted because the fields are immutable. immutable, } + + /// This type enumerates the different kinds of errors that may occur when + /// setting fields of a `request-options` resource. + variant request-options-error { + /// Indicates the specified field is not supported by this implementation. + not-supported, + + /// Indicates that the operation on the `request-options` was not permitted + /// because it is immutable. + immutable, + } /// Field keys are always strings. type field-key = string; @@ -140,9 +146,9 @@ interface types { /// A `fields` may be mutable or immutable. A `fields` created using the /// constructor, `from-list`, or `clone` will be mutable, but a `fields` /// resource given by other means (including, but not limited to, - /// `incoming-request.headers`, `outgoing-request.headers`) might be be - /// immutable. In an immutable fields, the `set`, `append`, and `delete` - /// operations will fail with `header-error.immutable`. + /// `request.headers`) might be be immutable. In an immutable fields, the + /// `set`, `append`, and `delete` operations will fail with + /// `header-error.immutable`. resource fields { /// Construct an empty HTTP Fields. @@ -217,60 +223,32 @@ interface types { /// Trailers is an alias for Fields. type trailers = fields; - /// Represents an incoming HTTP Request. - resource incoming-request { - - /// Returns the method of the incoming request. - method: func() -> method; - - /// Returns the path with query parameters from the request, as a string. - path-with-query: func() -> option; - - /// Returns the protocol scheme from the request. - scheme: func() -> option; - - /// Returns the authority from the request, if it was present. - authority: func() -> option; - - /// Get the `headers` associated with the request. - /// - /// The returned `headers` resource is immutable: `set`, `append`, and - /// `delete` operations will fail with `header-error.immutable`. - /// - /// The `headers` returned are a child resource: it must be dropped before - /// the parent `incoming-request` is dropped. Dropping this - /// `incoming-request` before all children are dropped will trap. - headers: func() -> headers; - - /// Gives the `incoming-body` associated with this request. Will only - /// return success at most once, and subsequent calls will return error. - consume: func() -> result; - } - - /// Represents an outgoing HTTP Request. - resource outgoing-request { + /// Represents an HTTP Request. + resource request { - /// Construct a new `outgoing-request` with a default `method` of `GET`, and + /// 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 Request. /// /// It is possible to construct, or manipulate with the accessor functions - /// below, an `outgoing-request` with an invalid combination of `scheme` + /// below, an `request` with an invalid combination of `scheme` /// and `authority`, or `headers` which are not permitted to be sent. - /// It is the obligation of the `outgoing-handler.handle` implementation - /// to reject invalid constructions of `outgoing-request`. + /// It is the obligation of the `handler.handle` implementation + /// to reject invalid constructions of `request`. constructor( - headers: headers + body: body, + headers: headers, + trailers: option>, + options: option ); - /// Returns the resource corresponding to the outgoing Body for this - /// Request. + /// Returns the resource corresponding to the Body for this Request. /// - /// Returns success on the first call: the `outgoing-body` resource for - /// this `outgoing-request` can be retrieved at most once. Subsequent - /// calls will return error. - body: func() -> result; + /// Returns success on the first call: the `body` resource for this + /// `request` can be retrieved at most once. Subsequent calls will return + /// error. + body: func() -> result; /// Get the Method for the Request. method: func() -> method; @@ -278,11 +256,11 @@ interface types { /// `method.other` argument is not a syntactically valid method. set-method: func(method: method) -> result; - /// Get the combination of the HTTP Path and Query for the Request. - /// When `none`, this represents an empty Path and empty Query. + /// Get the combination of the HTTP Path and Query for the Request. When + /// `none`, this represents an empty Path and empty Query. path-with-query: func() -> option; - /// Set the combination of the HTTP Path and Query for the Request. - /// When `none`, this represents an empty Path and empty Query. Fails is the + /// Set the combination of the HTTP Path and Query for the Request. When + /// `none`, this represents an empty Path and empty Query. Fails is the /// string given is not a syntactically valid path and query uri component. set-path-with-query: func(path-with-query: option) -> result; @@ -304,14 +282,24 @@ interface types { /// not a syntactically valid uri authority. set-authority: func(authority: option) -> result; + /// Set the `request-options` to be associated with this request + /// + /// The returned `request-options` resource is immutable: `set-*` operations + /// will fail if invoked. + /// + /// This `request-options` resource is a child: it must be dropped before + /// the parent `request` is dropped, or its ownership is transfered to + /// another component by e.g. `handler.handle`. + set-options: func(options: option); + /// Get the headers associated with the Request. /// /// 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 - /// `outgoing-request` is dropped, or its ownership is transfered to - /// another component by e.g. `outgoing-handler.handle`. + /// `request` is dropped, or its ownership is transfered to another + /// component by e.g. `handler.handle`. headers: func() -> headers; } @@ -319,8 +307,8 @@ interface types { /// currently an optional timeout applicable to the transport layer of the /// HTTP protocol. /// - /// These timeouts are separate from any the user may use to bound a - /// blocking call to `wasi:io/poll.poll`. + /// These timeouts are separate from any the user may use to bound a an + /// asynchronous call. resource request-options { /// Construct a default `request-options` value. constructor(); @@ -329,15 +317,17 @@ interface types { connect-timeout: func() -> option; /// Set the timeout for the initial connect to the HTTP Server. An error - /// return value indicates that this timeout is not supported. - set-connect-timeout: func(duration: option) -> result; + /// return value indicates that this timeout is not supported or that this + /// handle is immutable. + set-connect-timeout: func(duration: option) -> result<_, request-options-error>; /// The timeout for receiving the first byte of the Response body. first-byte-timeout: func() -> option; /// Set the timeout for receiving the first byte of the Response body. An - /// error return value indicates that this timeout is not supported. - set-first-byte-timeout: func(duration: option) -> result; + /// error return value indicates that this timeout is not supported or that + /// this handle is immutable. + set-first-byte-timeout: func(duration: option) -> result<_, request-options-error>; /// The timeout for receiving subsequent chunks of bytes in the Response /// body stream. @@ -345,130 +335,57 @@ interface types { /// Set the timeout for receiving subsequent chunks of bytes in the Response /// body stream. An error return value indicates that this timeout is not - /// supported. - set-between-bytes-timeout: func(duration: option) -> result; - } - - /// Represents the ability to send an HTTP Response. - /// - /// This resource is used by the `wasi:http/incoming-handler` interface to - /// allow a Response to be sent corresponding to the Request provided as the - /// other argument to `incoming-handler.handle`. - resource response-outparam { - - /// Set the value of the `response-outparam` to either send a response, - /// or indicate an error. - /// - /// This method consumes the `response-outparam` to ensure that it is - /// called at most once. If it is never called, the implementation - /// will respond with an error. - /// - /// The user may provide an `error` to `response` to allow the - /// implementation determine how to respond with an HTTP error response. - set: static func( - param: response-outparam, - response: result, - ); + /// supported or that this handle is immutable. + set-between-bytes-timeout: func(duration: option) -> result<_, request-options-error>; } /// This type corresponds to the HTTP standard Status Code. type status-code = u16; - /// Represents an incoming HTTP Response. - resource incoming-response { - - /// Returns the status code from the incoming response. - status: func() -> status-code; - - /// Returns the headers from the incoming 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 - /// `incoming-response` is dropped. - headers: func() -> headers; - - /// Returns the incoming body. May be called at most once. Returns error - /// if called additional times. - consume: func() -> result; - } - - /// Represents an incoming HTTP Request or Response's Body. + /// 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 - /// an `input-stream` and the delivery of trailers as a `future-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 incoming-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 an `stream` 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 retrieving + /// trailers at any given time. + resource body { /// Returns the contents of the body, as a stream of bytes. /// /// Returns success on first call: the stream representing the contents /// can be retrieved at most once. Subsequent calls will return error. /// - /// The returned `input-stream` resource is a child: it must be dropped - /// before the parent `incoming-body` is dropped, or consumed by - /// `incoming-body.finish`. + /// The returned `stream` is a child: it must be dropped before the parent + /// `body` is dropped, or consumed by `body.finish`. /// - /// This invariant ensures that the implementation can determine whether - /// the user is consuming the contents of the body, waiting on the - /// `future-trailers` to be ready, or neither. This allows for network - /// backpressure is to be applied when the user is consuming the body, - /// and for that backpressure to not inhibit delivery of the trailers if - /// the user does not read the entire body. - %stream: func() -> result; - - /// Takes ownership of `incoming-body`, and returns a `future-trailers`. - /// This function will trap if the `input-stream` child is still alive. - finish: static func(this: incoming-body) -> future-trailers; - } + /// This invariant ensures that the implementation can determine whether the + /// user is consuming the contents of the body, waiting on the trailers to + /// be ready, or neither. This allows for network backpressure is to be + /// applied when the user is consuming the body, and for that backpressure + /// to not inhibit delivery of the trailers if the user does not read the + /// entire body. + %stream: func() -> result>; - /// Represents a future which may eventaully return trailers, or an error. - /// - /// In the case that the incoming HTTP Request or Response did not have any - /// trailers, this future will resolve to the empty set of trailers once the - /// complete Request or Response body has been received. - resource future-trailers { - - /// Returns a pollable which becomes ready when either the trailers have - /// been received, or an error has occured. When this pollable is ready, - /// the `get` method will return `some`. - subscribe: func() -> pollable; - - /// Returns the contents of the trailers, or an error which occured, - /// once the future is ready. - /// - /// The outer `option` represents future readiness. Users can wait on this - /// `option` to become `some` using the `subscribe` method. - /// - /// The outer `result` is used to retrieve the trailers or error at most - /// once. It will be success on the first call in which the outer option - /// is `some`, and error on subsequent calls. - /// - /// The inner `result` represents that either the HTTP Request or Response - /// body, as well as any trailers, were received successfully, or that an - /// error occured receiving them. The optional `trailers` indicates whether - /// or not trailers were present in the body. - /// - /// When some `trailers` are returned by this method, the `trailers` - /// resource is immutable, and a child. Use of the `set`, `append`, or - /// `delete` methods will return an error, and the resource must be - /// dropped before the parent `future-trailers` is dropped. - get: func() -> option, error-code>>>; + /// Takes ownership of `body`, and returns a `trailers`. This function will + /// trap if the `stream` child is still alive. + finish: static func(this: body) -> trailers; } - /// Represents an outgoing HTTP Response. - resource outgoing-response { + /// Represents an HTTP Response. + resource response { - /// Construct an `outgoing-response`, with a default `status-code` of `200`. - /// If a different `status-code` is needed, it must be set via the + /// Construct an `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. - constructor(headers: headers); + constructor( + body: body, + headers: headers, + trailers: option> + ); /// Get the HTTP Status Code for the Response. status-code: func() -> status-code; @@ -483,88 +400,15 @@ interface types { /// `delete` operations will fail with `header-error.immutable`. /// /// This headers resource is a child: it must be dropped before the parent - /// `outgoing-request` is dropped, or its ownership is transfered to - /// another component by e.g. `outgoing-handler.handle`. + /// `response` is dropped, or its ownership is transfered to another + /// component by e.g. `handler.handle`. headers: func() -> headers; - /// Returns the resource corresponding to the outgoing Body for this Response. - /// - /// Returns success on the first call: the `outgoing-body` resource for - /// this `outgoing-response` can be retrieved at most once. Subsequent - /// calls will return error. - body: func() -> result; - } - - /// Represents an outgoing HTTP Request or Response's Body. - /// - /// A body has both its contents - a stream of bytes - and a (possibly - /// empty) set of trailers, inducating the full contents of the body - /// have been sent. This resource represents the contents as an - /// `output-stream` child resource, and the completion of the body (with - /// optional trailers) with a static function that consumes the - /// `outgoing-body` resource, and ensures that the user of this interface - /// may not write to the body contents after the body has been finished. - /// - /// If the user code drops this resource, as opposed to calling the static - /// method `finish`, the implementation should treat the body as incomplete, - /// and that an error has occured. The implementation should propogate this - /// error to the HTTP protocol by whatever means it has available, - /// including: corrupting the body on the wire, aborting the associated - /// Request, or sending a late status code for the Response. - resource outgoing-body { - - /// Returns a stream for writing the body contents. - /// - /// The returned `output-stream` is a child resource: it must be dropped - /// before the parent `outgoing-body` resource is dropped (or finished), - /// otherwise the `outgoing-body` drop or `finish` will trap. - /// - /// Returns success on the first call: the `output-stream` resource for - /// this `outgoing-body` may be retrieved at most once. Subsequent calls - /// will return error. - write: func() -> result; - - /// Finalize an outgoing body, optionally providing trailers. This must be - /// called to signal that the response is complete. If the `outgoing-body` - /// is dropped without calling `outgoing-body.finalize`, the implementation - /// should treat the body as corrupted. - /// - /// Fails if the body's `outgoing-request` or `outgoing-response` was - /// constructed with a Content-Length header, and the contents written - /// to the body (via `write`) does not match the value given in the - /// Content-Length. - finish: static func( - this: outgoing-body, - trailers: option - ) -> result<_, error-code>; - } - - /// Represents a future which may eventaully return an incoming HTTP - /// Response, or an error. - /// - /// This resource is returned by the `wasi:http/outgoing-handler` interface to - /// provide the HTTP Response corresponding to the sent Request. - resource future-incoming-response { - /// Returns a pollable which becomes ready when either the Response has - /// been received, or an error has occured. When this pollable is ready, - /// the `get` method will return `some`. - subscribe: func() -> pollable; - - /// Returns the incoming HTTP Response, or an error, once one is ready. - /// - /// The outer `option` represents future readiness. Users can wait on this - /// `option` to become `some` using the `subscribe` method. + /// Returns the resource corresponding to the Body for this Response. /// - /// The outer `result` is used to retrieve the response or error at most - /// once. It will be success on the first call in which the outer option - /// is `some`, and error on subsequent calls. - /// - /// The inner `result` represents that either the incoming HTTP Response - /// status and headers have recieved successfully, or that an error - /// occured. Errors may also occur while consuming the response body, - /// but those will be reported by the `incoming-body` and its - /// `output-stream` child. - get: func() -> option>>; - + /// Returns success on the first call: the `body` resource for this + /// `response` can be retrieved at most once. Subsequent calls will return + /// error. + body: func() -> result; } } From 3d9a7ce6e3658a79ea7999920ddfb9e034448c94 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 14 Feb 2024 12:13:07 -0700 Subject: [PATCH 2/5] eliminate `body` resource This moves the methods of that resource directly to `request` and `response`. Signed-off-by: Joel Dice --- wit/types.wit | 99 ++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/wit/types.wit b/wit/types.wit index 8616d6c..b98955b 100644 --- a/wit/types.wit +++ b/wit/types.wit @@ -229,7 +229,12 @@ interface types { /// 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 Request. + /// * `body` is the contents of the body, as a stream of bytes. + /// * `headers` is the HTTP Headers for the Response. + /// * `trailers` is a `future` which resolves to the HTTP Trailers for the + /// Response. + /// * `options` is optional `request-options` 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` @@ -237,19 +242,12 @@ interface types { /// It is the obligation of the `handler.handle` implementation /// to reject invalid constructions of `request`. constructor( - body: body, + body: stream, headers: headers, - trailers: option>, + trailers: future, options: option ); - /// Returns the resource corresponding to the Body for this Request. - /// - /// Returns success on the first call: the `body` resource for this - /// `request` can be retrieved at most once. Subsequent calls will return - /// error. - body: func() -> result; - /// Get the Method for the Request. method: func() -> method; /// Set the Method for the Request. Fails if the string present in a @@ -301,6 +299,26 @@ interface types { /// `request` is dropped, or its ownership is transfered to another /// component by e.g. `handler.handle`. headers: func() -> headers; + + /// Returns the contents of the body, as a stream of bytes. + /// + /// The returned `stream` is a child: it must be dropped before the parent + /// `request` is dropped, or consumed by `request.finish`. + /// + /// This invariant ensures that the implementation can determine whether the + /// user is consuming the contents of the body, waiting on the trailers to + /// be ready, or neither. This allows for network backpressure is to be + /// applied when the user is consuming the body, and for that backpressure + /// to not inhibit delivery of the trailers if the user does not read the + /// entire body. + /// + /// This function may be called multiple times as long as any `stream`s + /// returned by previous calls have been dropped first. + body: func() -> result>; + + /// Takes ownership of `request`, and returns a `trailers`. This function will + /// trap if a `stream` child is still alive. + finish: static func(this: request) -> trailers; } /// Parameters for making an HTTP Request. Each of these parameters is @@ -342,37 +360,6 @@ interface types { /// This type corresponds to the HTTP standard Status Code. type status-code = u16; - /// 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 an `stream` 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 retrieving - /// trailers at any given time. - resource body { - - /// Returns the contents of the body, as a stream of bytes. - /// - /// Returns success on first call: the stream representing the contents - /// can be retrieved at most once. Subsequent calls will return error. - /// - /// The returned `stream` is a child: it must be dropped before the parent - /// `body` is dropped, or consumed by `body.finish`. - /// - /// This invariant ensures that the implementation can determine whether the - /// user is consuming the contents of the body, waiting on the trailers to - /// be ready, or neither. This allows for network backpressure is to be - /// applied when the user is consuming the body, and for that backpressure - /// to not inhibit delivery of the trailers if the user does not read the - /// entire body. - %stream: func() -> result>; - - /// Takes ownership of `body`, and returns a `trailers`. This function will - /// trap if the `stream` child is still alive. - finish: static func(this: body) -> trailers; - } - /// Represents an HTTP Response. resource response { @@ -380,11 +367,14 @@ interface types { /// different `status-code` is needed, it must be set via the /// `set-status-code` method. /// + /// * `body` is the contents of the body, as a stream of bytes. /// * `headers` is the HTTP Headers for the Response. + /// * `trailers` is a `future` which resolves to the HTTP Trailers for the + /// Response. constructor( - body: body, + body: stream, headers: headers, - trailers: option> + trailers: future ); /// Get the HTTP Status Code for the Response. @@ -404,11 +394,24 @@ interface types { /// component by e.g. `handler.handle`. headers: func() -> headers; - /// Returns the resource corresponding to the Body for this Response. + /// Returns the contents of the body, as a stream of bytes. /// - /// Returns success on the first call: the `body` resource for this - /// `response` can be retrieved at most once. Subsequent calls will return - /// error. - body: func() -> result; + /// The returned `stream` is a child: it must be dropped before the parent + /// `response` is dropped, or consumed by `response.finish`. + /// + /// This invariant ensures that the implementation can determine whether the + /// user is consuming the contents of the body, waiting on the trailers to + /// be ready, or neither. This allows for network backpressure is to be + /// applied when the user is consuming the body, and for that backpressure + /// to not inhibit delivery of the trailers if the user does not read the + /// entire body. + /// + /// This function may be called multiple times as long as any `stream`s + /// returned by previous calls have been dropped first. + body: func() -> result>; + + /// Takes ownership of `response`, and returns a `trailers`. This function will + /// trap if a `stream` child is still alive. + finish: static func(this: response) -> trailers; } } From d510ff653e0096b4231a9f753890f49226f08844 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 14 Feb 2024 12:25:52 -0700 Subject: [PATCH 3/5] reorder constructor params to reflect wire order Signed-off-by: Joel Dice --- wit/types.wit | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wit/types.wit b/wit/types.wit index b98955b..36fa271 100644 --- a/wit/types.wit +++ b/wit/types.wit @@ -229,8 +229,8 @@ interface types { /// Construct a new `request` with a default `method` of `GET`, and /// `none` values for `path-with-query`, `scheme`, and `authority`. /// - /// * `body` is the contents of the body, as a stream of bytes. /// * `headers` is the HTTP Headers for the Response. + /// * `body` is the contents of the body, as a stream of bytes. /// * `trailers` is a `future` which resolves to the HTTP Trailers for the /// Response. /// * `options` is optional `request-options` to be used if the request is @@ -242,8 +242,8 @@ interface types { /// It is the obligation of the `handler.handle` implementation /// to reject invalid constructions of `request`. constructor( - body: stream, headers: headers, + body: stream, trailers: future, options: option ); @@ -367,13 +367,13 @@ interface types { /// different `status-code` is needed, it must be set via the /// `set-status-code` method. /// - /// * `body` is the contents of the body, as a stream of bytes. /// * `headers` is the HTTP Headers for the Response. + /// * `body` is the contents of the body, as a stream of bytes. /// * `trailers` is a `future` which resolves to the HTTP Trailers for the /// Response. constructor( - body: stream, headers: headers, + body: stream, trailers: future ); From f1887d8b2f853e33abb356d2ef19b8b0271d59ff Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Wed, 14 Feb 2024 14:28:22 -0700 Subject: [PATCH 4/5] make trailers optional Signed-off-by: Joel Dice --- wit/types.wit | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/wit/types.wit b/wit/types.wit index 36fa271..11503b0 100644 --- a/wit/types.wit +++ b/wit/types.wit @@ -231,8 +231,8 @@ interface types { /// /// * `headers` is the HTTP Headers for the Response. /// * `body` is the contents of the body, as a stream of bytes. - /// * `trailers` is a `future` which resolves to the HTTP Trailers for the - /// Response. + /// * `trailers` is an optional `future` which resolves to the HTTP Trailers + /// for the Response. /// * `options` is optional `request-options` to be used if the request is /// sent over a network connection. /// @@ -244,7 +244,7 @@ interface types { constructor( headers: headers, body: stream, - trailers: future, + trailers: option>, options: option ); @@ -318,14 +318,14 @@ interface types { /// Takes ownership of `request`, and returns a `trailers`. This function will /// trap if a `stream` child is still alive. - finish: static func(this: request) -> trailers; + finish: static func(this: request) -> result, error-code>; } /// Parameters for making an HTTP Request. Each of these parameters is /// currently an optional timeout applicable to the transport layer of the /// HTTP protocol. /// - /// These timeouts are separate from any the user may use to bound a an + /// These timeouts are separate from any the user may use to bound an /// asynchronous call. resource request-options { /// Construct a default `request-options` value. @@ -369,12 +369,12 @@ interface types { /// /// * `headers` is the HTTP Headers for the Response. /// * `body` is the contents of the body, as a stream of bytes. - /// * `trailers` is a `future` which resolves to the HTTP Trailers for the - /// Response. + /// * `trailers` is an optional `future` which resolves to the HTTP Trailers + /// for the Response. constructor( headers: headers, body: stream, - trailers: future + trailers: option> ); /// Get the HTTP Status Code for the Response. @@ -412,6 +412,6 @@ interface types { /// Takes ownership of `response`, and returns a `trailers`. This function will /// trap if a `stream` child is still alive. - finish: static func(this: response) -> trailers; + finish: static func(this: response) -> result, error-code>; } } From 3c41c719c2bb59eee6eeed899c1d17b0639c2f25 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 29 Feb 2024 12:06:31 -0700 Subject: [PATCH 5/5] use `wasi:io/error/error`; fix cut-and-paste issue - Rather than assume the existence of a built-in WIT `error` type, I've switched back to using `wasi:io/error/error` for the time being so I don't have to teach `wit-parser` and friends about the new built-in type (yet). The built-in type is just a vague idea at this point, so it's too early to try to use it, plus it's unlikely to be very interesting compared to `stream` and `future`. - Fix a cut-and-paste issue for `request`'s `options` method, which was both misnamed and misdocumented. Signed-off-by: Joel Dice --- wit/proxy.wit | 2 +- wit/types.wit | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/wit/proxy.wit b/wit/proxy.wit index 16e6fd3..ad48784 100644 --- a/wit/proxy.wit +++ b/wit/proxy.wit @@ -1,4 +1,4 @@ -package wasi:http@0.3.0-draft-2024-02-14; +package wasi:http@0.3.0-draft-2024-02-29; /// The `wasi:http/imports` world imports all the APIs for HTTP proxies. /// It is intended to be `include`d in other worlds. diff --git a/wit/types.wit b/wit/types.wit index 11503b0..13bbdc7 100644 --- a/wit/types.wit +++ b/wit/types.wit @@ -2,6 +2,7 @@ /// Requests and Responses, as well as their headers, trailers, and bodies. interface types { use wasi:clocks/monotonic-clock@0.2.0.{duration}; + use wasi:io/error@0.2.0.{error}; /// This type corresponds to HTTP standard Methods. variant method { @@ -119,7 +120,7 @@ interface types { /// permitted because the fields are immutable. immutable, } - + /// This type enumerates the different kinds of errors that may occur when /// setting fields of a `request-options` resource. variant request-options-error { @@ -280,7 +281,7 @@ interface types { /// not a syntactically valid uri authority. set-authority: func(authority: option) -> result; - /// Set the `request-options` to be associated with this request + /// Get the `request-options` to be associated with this request /// /// The returned `request-options` resource is immutable: `set-*` operations /// will fail if invoked. @@ -288,7 +289,7 @@ interface types { /// This `request-options` resource is a child: it must be dropped before /// the parent `request` is dropped, or its ownership is transfered to /// another component by e.g. `handler.handle`. - set-options: func(options: option); + options: func() -> option; /// Get the headers associated with the Request. ///