Skip to content

Commit

Permalink
Update to match the implementation in wasmtime
Browse files Browse the repository at this point in the history
  • Loading branch information
elliottt committed Oct 6, 2023
1 parent 0043e7c commit 8584f46
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 137 deletions.
22 changes: 11 additions & 11 deletions wit/handler.wit
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@
// that takes a `request` parameter and returns a `response` result.
//
interface incoming-handler {
use types.{incoming-request, response-outparam}
use types.{incoming-request, response-outparam};

// The `handle` function takes an outparam instead of returning its response
// so that the component may stream its response while streaming any other
// request or response bodies. The callee MUST write a response to the
// `response-out` and then finish the response before returning. The caller
// is expected to start streaming the response once `set-response-outparam`
// is called and finish streaming the response when `drop-response-outparam`
// is called. The `handle` function is then allowed to continue executing
// any post-response logic before returning. While this post-response
// execution is taken off the critical path, since there is no return value,
// there is no way to report its success or failure.
// `response-outparam` and then finish the response before returning. The `handle`
// function is allowed to continue execution after finishing the response's
// output stream. While this post-response execution is taken off the
// critical path, since there is no return value, there is no way to report
// its success or failure.
handle: func(
request: incoming-request,
response-out: response-outparam
)
);
}

// The `wasi:http/outgoing-handler` interface is meant to be imported by
Expand All @@ -33,13 +31,15 @@ interface incoming-handler {
// that takes a `request` parameter and returns a `response` result.
//
interface outgoing-handler {
use types.{outgoing-request, request-options, future-incoming-response}
use types.{outgoing-request, request-options, future-incoming-response, error};

// The parameter and result types of the `handle` function allow the caller
// to concurrently stream the bodies of the outgoing request and the incoming
// response.
// Consumes the outgoing-request. Gives an error if the outgoing-request
// is invalid or cannot be satisfied by this handler.
handle: func(
request: outgoing-request,
options: option<request-options>
) -> future-incoming-response
) -> result<future-incoming-response, error>;
}
20 changes: 10 additions & 10 deletions wit/proxy.wit
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
package wasi:http
package wasi:http;

// The `wasi:http/proxy` world captures a widely-implementable intersection of
// hosts that includes HTTP forward and reverse proxies. Components targeting
// this world may concurrently stream in and out any number of incoming and
// outgoing HTTP requests.
world proxy {
// HTTP proxies have access to time and randomness.
import wasi:clocks/wall-clock
import wasi:clocks/monotonic-clock
import wasi:clocks/timezone
import wasi:random/random
import wasi:clocks/wall-clock;
import wasi:clocks/monotonic-clock;
import wasi:clocks/timezone;
import wasi:random/random;

// Proxies have standard output and error streams which are expected to
// terminate in a developer-facing console provided by the host.
import wasi:cli/stdout
import wasi:cli/stderr
import wasi:cli/stdout;
import wasi:cli/stderr;

// TODO: this is a temporary workaround until component tooling is able to
// gracefully handle the absence of stdin. Hosts must return an eof stream
// for this import, which is what wasi-libc + tooling will do automatically
// when this import is properly removed.
import wasi:cli/stdin
import wasi:cli/stdin;

// This is the default handler to use when user code simply wants to make an
// HTTP request (e.g., via `fetch()`).
import outgoing-handler
import outgoing-handler;

// The host delivers incoming HTTP requests to a component by calling the
// `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
export incoming-handler;
}
257 changes: 141 additions & 116 deletions wit/types.wit
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// define the HTTP resource types and operations used by the component's
// imported and exported interfaces.
interface types {
use wasi:io/streams.{input-stream, output-stream}
use wasi:poll/poll.{pollable}
use wasi:io/streams.{input-stream, output-stream};
use wasi:io/poll.{pollable};

// This type corresponds to HTTP standard Methods.
variant method {
get,
Expand All @@ -30,101 +30,84 @@ interface types {
// This type enumerates the different kinds of errors that may occur when
// initially returning a response.
variant error {
invalid-url(string),
timeout-error(string),
protocol-error(string),
unexpected-error(string)
invalid-url(string),
timeout-error(string),
protocol-error(string),
unexpected-error(string)
}

// This following block defines the `fields` resource which corresponds to
// HTTP standard Fields. Soon, when resource types are added, the `type
// fields = u32` type alias can be replaced by a proper `resource fields`
// definition containing all the functions using the method syntactic sugar.
type fields = u32
drop-fields: func(fields: fields)
new-fields: func(entries: list<tuple<string,list<u8>>>) -> fields
// Returns an empty list if `name` is not present.
fields-get: func(fields: fields, name: string) -> list<list<u8>>
fields-set: func(fields: fields, name: string, value: list<list<u8>>)
fields-delete: func(fields: fields, name: string)
fields-append: func(fields: fields, name: string, value: list<u8>)
fields-entries: func(fields: fields) -> list<tuple<string,list<u8>>>
fields-clone: func(fields: fields) -> fields

type headers = fields
type trailers = fields

// The following block defines stream types which corresponds to the HTTP
// standard Contents and Trailers. With Preview3, all of these fields can be
// replaced by a stream<u8, option<trailers>>. In the interim, we need to
// build on separate resource types defined by `wasi:io/streams`. The
// `finish-` functions emulate the stream's result value and MUST be called
// exactly once after the final read/write from/to the stream before dropping
// the stream. The optional `future-` types describe the asynchronous result of
// reading/writing the optional HTTP trailers and MUST be waited on and dropped
// to complete streaming the request/response.
type incoming-stream = input-stream
type outgoing-stream = output-stream
finish-incoming-stream: func(s: incoming-stream) -> option<future-trailers>
finish-outgoing-stream: func(s: outgoing-stream)
finish-outgoing-stream-with-trailers: func(s: outgoing-stream, trailers: trailers) -> future-write-trailers-result

// The following block defines the `future-trailers` resource, which is
// returned when finishing an `incoming-stream` to asychronously produce the
// final trailers.
type future-trailers = u32
drop-future-trailers: func(f: future-trailers)
future-trailers-get: func(f: future-trailers) -> option<result<trailers, error>>
listen-to-future-trailers: func(f: future-trailers) -> pollable

// The following block defines the `future-write-trailers-result` resource,
// which is returned when finishing an `outgoing-stream` and asychronously
// indicates the success or failure of writing the trailers.
type future-write-trailers-result = u32
drop-future-write-trailers-result: func(f: future-write-trailers-result)
future-write-trailers-result-get: func(f: future-write-trailers-result) -> option<result<_, error>>
listen-to-future-write-trailers-result: func(f: future-write-trailers-result) -> pollable
resource fields {
// Multiple values for a header are multiple entries in the list with the
// same key.
constructor(entries: list<tuple<string,list<u8>>>);

// Values off wire are not necessarily well formed, so they are given by
// list<u8> instead of string.
get: func(name: string) -> list<list<u8>>;

// Values off wire are not necessarily well formed, so they are given by
// list<u8> instead of string.
set: func(name: string, value: list<list<u8>>);
delete: func(name: string);
append: func(name: string, value: list<u8>);

// Values off wire are not necessarily well formed, so they are given by
// list<u8> instead of string.
entries: func() -> list<tuple<string,list<u8>>>;

// Deep copy of all contents in a fields.
clone: func() -> fields;
}

type headers = fields;
type trailers = fields;

// The following block defines the `incoming-request` and `outgoing-request`
// resource types that correspond to HTTP standard Requests. Soon, when
// resource types are added, the `u32` type aliases can be replaced by proper
// `resource` type definitions containing all the functions as methods.
// Later, Preview2 will allow both types to be merged together into a single
// `request` type (that uses the single `stream` type mentioned above). The
// `consume` and `write` methods may only be called once (and return failure
// thereafter). The `headers` and `trailers` passed into and out of requests
// are shared with the request, with all mutations visible to all uses.
// Components MUST avoid updating `headers` and `trailers` after passing a
// request that points to them to the outside world.
// The streams returned by `consume` and `write` are owned by the request and
// response objects. The streams are destroyed when the request/response is
// dropped, thus a client MUST drop any handle referring to a request/response stream
// before dropping the request/response or passing ownership of the request/response
// to the outside world. The caller can also call drop on the stream before the
// request/response is dropped if they want to release resources earlier.
type incoming-request = u32
type outgoing-request = u32
drop-incoming-request: func(request: incoming-request)
drop-outgoing-request: func(request: outgoing-request)
incoming-request-method: func(request: incoming-request) -> method
incoming-request-path-with-query: func(request: incoming-request) -> option<string>
incoming-request-scheme: func(request: incoming-request) -> option<scheme>
incoming-request-authority: func(request: incoming-request) -> option<string>
incoming-request-headers: func(request: incoming-request) -> headers
incoming-request-consume: func(request: incoming-request) -> result<incoming-stream>
new-outgoing-request: func(
method: method,
path-with-query: option<string>,
scheme: option<scheme>,
authority: option<string>,
headers: headers
) -> result<outgoing-request, error>
outgoing-request-write: func(request: outgoing-request) -> result<outgoing-stream>
// resource types are added, the `u32` type aliases can be replaced by
// proper `resource` type definitions containing all the functions as
// methods. Later, Preview2 will allow both types to be merged together into
// a single `request` type (that uses the single `stream` type mentioned
// above). The `consume` and `write` methods may only be called once (and
// return failure thereafter).
resource incoming-request {
method: func() -> method;

path-with-query: func() -> option<string>;

scheme: func() -> option<scheme>;

authority: func() -> option<string>;

headers: func() -> headers;

// Will return the incoming-body child at most once. If called more than
// once, subsequent calls will return error.
consume: func() -> result<incoming-body>;
}

resource outgoing-request {
constructor(
method: method,
path-with-query: option<string>,
scheme: option<scheme>,
authority: option<string>,
headers: borrow<headers>
);

// Will return the outgoing-body child at most once. If called more than
// once, subsequent calls will return error.
write: func() -> result<outgoing-body>;
}

// Additional optional parameters that can be set when making a request.
record request-options {
// The following timeouts are specific to the HTTP protocol and work
// independently of the overall timeouts passed to `io.poll.poll-oneoff`.
// independently of the overall timeouts passed to `io.poll.poll-list`.

// The timeout for the initial connect.
connect-timeout-ms: option<u32>,
Expand All @@ -143,12 +126,12 @@ interface types {
// definition. Later, with Preview3, the need for an outparam goes away entirely
// (the `wasi:http/handler` interface used for both incoming and outgoing can
// simply return a `stream`).
type response-outparam = u32
drop-response-outparam: func(response: response-outparam)
set-response-outparam: func(param: response-outparam, response: result<outgoing-response, error>) -> result
resource response-outparam {
set: static func(param: response-outparam, response: result<outgoing-response, error>);
}

// This type corresponds to the HTTP standard Status Code.
type status-code = u16
type status-code = u16;

// The following block defines the `incoming-response` and `outgoing-response`
// resource types that correspond to HTTP standard Responses. Soon, when
Expand All @@ -157,32 +140,74 @@ interface types {
// Preview2 will allow both types to be merged together into a single `response`
// type (that uses the single `stream` type mentioned above). The `consume` and
// `write` methods may only be called once (and return failure thereafter).
// The `headers` and `trailers` passed into and out of responses are shared
// with the response, with all mutations visible to all uses. Components MUST
// avoid updating `headers` and `trailers` after passing a response that
// points to them to the outside world.
type incoming-response = u32
type outgoing-response = u32
drop-incoming-response: func(response: incoming-response)
drop-outgoing-response: func(response: outgoing-response)
incoming-response-status: func(response: incoming-response) -> status-code
incoming-response-headers: func(response: incoming-response) -> headers
incoming-response-consume: func(response: incoming-response) -> result<incoming-stream>
new-outgoing-response: func(
status-code: status-code,
headers: headers
) -> result<outgoing-response, error>
outgoing-response-write: func(response: outgoing-response) -> result<outgoing-stream>
resource incoming-response {
status: func() -> status-code;

// The following block defines a special resource type used by the
// `wasi:http/outgoing-handler` interface to emulate
// `future<result<response, error>>` in advance of Preview3. Given a
// `future-incoming-response`, the client can call the non-blocking `get`
// method to get the result if it is available. If the result is not available,
// the client can call `listen` to get a `pollable` that can be passed to
// `io.poll.poll-oneoff`.
type future-incoming-response = u32
drop-future-incoming-response: func(f: future-incoming-response)
future-incoming-response-get: func(f: future-incoming-response) -> option<result<incoming-response, error>>
listen-to-future-incoming-response: func(f: future-incoming-response) -> pollable
headers: func() -> headers;

// May be called at most once. returns error if called additional times.
// TODO: make incoming-request-consume work the same way, giving a child
// incoming-body.
consume: func() -> result<incoming-body>;
}

resource incoming-body {
// returned input-stream is a child - the implementation may trap if
// incoming-body is dropped (or consumed by call to
// incoming-body-finish) before the input-stream is dropped.
// May be called at most once. returns error if called additional times.
%stream: func() -> result<input-stream>;

// takes ownership of incoming-body. this will trap if the
// incoming-body-stream child is still alive!
finish: static func(this: incoming-body) -> future-trailers;
}

resource future-trailers {
/// Pollable that resolves when the body has been fully read, and the trailers
/// are ready to be consumed.
subscribe: func() -> pollable;

/// Retrieve reference to trailers, if they are ready.
get: func() -> option<result<trailers, error>>;
}

resource outgoing-response {
constructor(status-code: status-code, headers: borrow<headers>);

/// Will give the child outgoing-response at most once. subsequent calls will
/// return an error.
write: func() -> result<outgoing-body>;
}

resource outgoing-body {
/// Will give the child output-stream at most once. subsequent calls will
/// return an error.
write: func() -> result<output-stream>;

/// 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.
finish: static func(this: outgoing-body, trailers: option<trailers>);
}

/// The following block defines a special resource type used by the
/// `wasi:http/outgoing-handler` interface to emulate
/// `future<result<response, error>>` in advance of Preview3. Given a
/// `future-incoming-response`, the client can call the non-blocking `get`
/// method to get the result if it is available. If the result is not available,
/// the client can call `listen` to get a `pollable` that can be passed to
/// `wasi:io/poll.poll-list`.
resource future-incoming-response {
/// option indicates readiness.
/// outer result indicates you are allowed to get the
/// incoming-response-or-error at most once. subsequent calls after ready
/// will return an error here.
/// inner result indicates whether the incoming-response was available, or an
/// error occured.
get: func() -> option<result<result<incoming-response, error>>>;

subscribe: func() -> pollable;
}
}

0 comments on commit 8584f46

Please sign in to comment.