diff --git a/Dockerfile b/Dockerfile index 96659c1..a352508 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,5 +48,4 @@ RUN mkdir /app WORKDIR /app # Install go dependencies, build the wasm module, push it to the registry -#CMD ["sh", "-c", "go mod download && go mod verify && go mod tidy && wash build && wash push $REGISTRY build/*_s.wasm"] CMD ["sh", "-c", "go env -w GOFLAGS=-buildvcs=false && go mod download && go mod verify && wash build && wash push $REGISTRY build/*.wasm"] \ No newline at end of file diff --git a/README.md b/README.md index a08cab2..df44ae2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,4 @@ -# HTTP Hello World - -This is a simple TinyGo Wasm example that responds with a "Hello World" message for each request. - -## Prerequisites +# Prerequisites - `go` 1.23 - `tinygo` 0.33 @@ -20,6 +16,24 @@ wash build wash push gitea.rebus.ninja/lore/go-nats-client:1.x.x build/go_nats_client_s.wasm ``` +## Build and push to the registry using the dockerfile + +### Build the cointainer +```bash +docker build -t wash-image-build . +``` +### Build the component and push to the registry + +First setup your credentials +```bash +export REG_USER=user && export REG_PASS=pass +``` + +Build and push +```bash +docker run --rm -e REGISTRY=gitea.rebus.ninja/lore/wasm-nats-producer-client:1.0.x -e WASH_REG_USER=$REG_USER -e WASH_REG_PASSWORD=$REG_PASS -v "$(pwd):/app" wash-image-build +``` + ## Running with wasmtime You must have wasmtime 25.0.0 for this to work. Make sure to follow the build step above first. diff --git a/gen/wasi/http/incoming-handler/empty.s b/gen/wasi/http/incoming-handler/empty.s new file mode 100644 index 0000000..5444f20 --- /dev/null +++ b/gen/wasi/http/incoming-handler/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/gen/wasi/http/incoming-handler/incoming-handler.exports.go b/gen/wasi/http/incoming-handler/incoming-handler.exports.go new file mode 100644 index 0000000..673ccb4 --- /dev/null +++ b/gen/wasi/http/incoming-handler/incoming-handler.exports.go @@ -0,0 +1,22 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package incominghandler + +// Exports represents the caller-defined exports from "wasi:http/incoming-handler@0.2.0". +var Exports struct { + // Handle represents the caller-defined, exported function "handle". + // + // 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. + // + // 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. + // + // handle: func(request: incoming-request, response-out: response-outparam) + Handle func(request IncomingRequest, responseOut ResponseOutparam) +} diff --git a/gen/wasi/http/incoming-handler/incoming-handler.wit.go b/gen/wasi/http/incoming-handler/incoming-handler.wit.go new file mode 100644 index 0000000..55553ce --- /dev/null +++ b/gen/wasi/http/incoming-handler/incoming-handler.wit.go @@ -0,0 +1,21 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package incominghandler represents the exported interface "wasi:http/incoming-handler@0.2.0". +// +// This interface defines a handler of incoming HTTP Requests. It should +// be exported by components which can respond to HTTP Requests. +package incominghandler + +import ( + "gitea.rebus.ninja/lore/wasm-nats-producer-client/gen/wasi/http/types" +) + +// IncomingRequest represents the exported type alias "wasi:http/incoming-handler@0.2.0#incoming-request". +// +// See [types.IncomingRequest] for more information. +type IncomingRequest = types.IncomingRequest + +// ResponseOutparam represents the exported type alias "wasi:http/incoming-handler@0.2.0#response-outparam". +// +// See [types.ResponseOutparam] for more information. +type ResponseOutparam = types.ResponseOutparam diff --git a/gen/wasi/http/incoming-handler/incominghandler.wasm.go b/gen/wasi/http/incoming-handler/incominghandler.wasm.go new file mode 100644 index 0000000..6531b7f --- /dev/null +++ b/gen/wasi/http/incoming-handler/incominghandler.wasm.go @@ -0,0 +1,18 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package incominghandler + +import ( + "github.com/bytecodealliance/wasm-tools-go/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:http@0.2.0". + +//go:wasmexport wasi:http/incoming-handler@0.2.0#handle +//export wasi:http/incoming-handler@0.2.0#handle +func wasmexport_Handle(request0 uint32, responseOut0 uint32) { + request := cm.Reinterpret[IncomingRequest]((uint32)(request0)) + responseOut := cm.Reinterpret[ResponseOutparam]((uint32)(responseOut0)) + Exports.Handle(request, responseOut) + return +} diff --git a/gen/wasi/http/outgoing-handler/outgoing-handler.wit.go b/gen/wasi/http/outgoing-handler/outgoing-handler.wit.go index a2fdac9..c76f73c 100644 --- a/gen/wasi/http/outgoing-handler/outgoing-handler.wit.go +++ b/gen/wasi/http/outgoing-handler/outgoing-handler.wit.go @@ -1,6 +1,9 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package outgoinghandler represents the imported interface "wasi:http/outgoing-handler@0.2.0". +// +// This interface defines a handler of outgoing HTTP Requests. It should be +// imported by components which wish to make HTTP Requests. package outgoinghandler import ( @@ -30,6 +33,17 @@ type ErrorCode = types.ErrorCode // Handle represents the imported function "handle". // +// 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 // diff --git a/gen/wasi/http/types/types.wit.go b/gen/wasi/http/types/types.wit.go index e6948c1..4b53130 100644 --- a/gen/wasi/http/types/types.wit.go +++ b/gen/wasi/http/types/types.wit.go @@ -1,6 +1,10 @@ // Code generated by wit-bindgen-go. DO NOT EDIT. // Package types represents the imported interface "wasi:http/types@0.2.0". +// +// 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. package types import ( @@ -38,6 +42,8 @@ type Pollable = poll.Pollable // Method represents the variant "wasi:http/types@0.2.0#method". // +// This type corresponds to HTTP standard Methods. +// // variant method { // get, // head, @@ -181,6 +187,8 @@ func (v Method) String() string { // Scheme represents the variant "wasi:http/types@0.2.0#scheme". // +// This type corresponds to HTTP standard Related Schemes. +// // variant scheme { // HTTP, // HTTPS, @@ -233,6 +241,8 @@ func (v Scheme) String() string { // DNSErrorPayload represents the record "wasi:http/types@0.2.0#DNS-error-payload". // +// Defines the case payload type for `DNS-error` above: +// // record DNS-error-payload { // rcode: option, // info-code: option, @@ -245,6 +255,8 @@ type DNSErrorPayload struct { // TLSAlertReceivedPayload represents the record "wasi:http/types@0.2.0#TLS-alert-received-payload". // +// Defines the case payload type for `TLS-alert-received` above: +// // record TLS-alert-received-payload { // alert-id: option, // alert-message: option, @@ -257,6 +269,8 @@ type TLSAlertReceivedPayload struct { // FieldSizePayload represents the record "wasi:http/types@0.2.0#field-size-payload". // +// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: +// // record field-size-payload { // field-name: option, // field-size: option, @@ -269,6 +283,9 @@ type FieldSizePayload struct { // ErrorCode represents the variant "wasi:http/types@0.2.0#error-code". // +// These cases are inspired by the IANA HTTP Proxy Error Types: +// https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types +// // variant error-code { // DNS-timeout, // DNS-error(DNS-error-payload), @@ -717,6 +734,12 @@ func (self *ErrorCode) ConfigurationError() bool { } // ErrorCodeInternalError returns a [ErrorCode] of case "internal-error". +// +// This is a catch-all error for anything that doesn't fit cleanly into a +// more specific case. It also includes an optional string for an +// unstructured description of the error. Users should not depend on the +// string for diagnosing errors, as it's not required to be consistent +// between implementations. func ErrorCodeInternalError(data cm.Option[string]) ErrorCode { return cm.New[ErrorCode](38, data) } @@ -775,6 +798,9 @@ func (v ErrorCode) String() string { // HeaderError represents the variant "wasi:http/types@0.2.0#header-error". // +// This type enumerates the different kinds of errors that may occur when +// setting or appending to a `fields` resource. +// // variant header-error { // invalid-syntax, // forbidden, @@ -783,8 +809,17 @@ func (v ErrorCode) String() string { type HeaderError uint8 const ( + // This error indicates that a `field-key` or `field-value` was + // syntactically invalid when used with an operation that sets headers in a + // `fields`. HeaderErrorInvalidSyntax HeaderError = iota + + // This error indicates that a forbidden `field-key` was used when trying + // to set a header in a `fields`. HeaderErrorForbidden + + // This error indicates that the operation on the `fields` was not + // permitted because the fields are immutable. HeaderErrorImmutable ) @@ -801,16 +836,33 @@ func (e HeaderError) String() string { // FieldKey represents the string "wasi:http/types@0.2.0#field-key". // +// Field keys are always strings. +// // type field-key = string type FieldKey string // FieldValue represents the list "wasi:http/types@0.2.0#field-value". // +// Field values should always be ASCII strings. However, in +// reality, HTTP implementations often have to interpret malformed values, +// so they are provided as a list of bytes. +// // type field-value = list type FieldValue cm.List[uint8] // Fields represents the imported resource "wasi:http/types@0.2.0#fields". // +// This following block defines the `fields` resource which corresponds to +// HTTP standard Fields. Fields are a common representation used for both +// Headers and Trailers. +// +// 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`. +// // resource fields type Fields cm.Resource @@ -827,6 +879,10 @@ func (self Fields) ResourceDrop() { // NewFields represents the imported constructor for resource "fields". // +// Construct an empty HTTP Fields. +// +// The resulting `fields` is mutable. +// // constructor() // //go:nosplit @@ -838,6 +894,22 @@ func NewFields() (result Fields) { // FieldsFromList represents the imported static function "from-list". // +// Construct an HTTP Fields. +// +// The resulting `fields` is mutable. +// +// The list represents each key-value pair in the Fields. Keys +// which have multiple values are represented by multiple entries in this +// list with the same key. +// +// The tuple is a pair of the field key, represented as a string, and +// Value, represented as a list of bytes. In a valid Fields, all keys +// and values are valid UTF-8 strings. However, values are not always +// well-formed, so they are represented as a raw list of bytes. +// +// An error result will be returned if any header or value was +// syntactically invalid, or if a header was forbidden. +// // from-list: static func(entries: list>) -> result // @@ -850,6 +922,11 @@ func FieldsFromList(entries cm.List[cm.Tuple[FieldKey, FieldValue]]) (result cm. // Append represents the imported method "append". // +// Append a value for a key. Does not change or delete any existing +// values for that key. +// +// Fails with `header-error.immutable` if the `fields` are immutable. +// // append: func(name: field-key, value: field-value) -> result<_, header-error> // //go:nosplit @@ -863,6 +940,10 @@ func (self Fields) Append(name FieldKey, value FieldValue) (result cm.Result[Hea // Clone represents the imported method "clone". // +// Make a deep copy of the Fields. Equivelant in behavior to calling the +// `fields` constructor on the return value of `entries`. The resulting +// `fields` is mutable. +// // clone: func() -> fields // //go:nosplit @@ -875,6 +956,11 @@ func (self Fields) Clone() (result Fields) { // Delete represents the imported method "delete". // +// Delete all values for a key. Does nothing if no values for the key +// exist. +// +// Fails with `header-error.immutable` if the `fields` are immutable. +// // delete: func(name: field-key) -> result<_, header-error> // //go:nosplit @@ -887,6 +973,13 @@ func (self Fields) Delete(name FieldKey) (result cm.Result[HeaderError, struct{} // Entries represents the imported method "entries". // +// Retrieve the full set of keys and values in the Fields. Like the +// constructor, the list represents each key-value pair. +// +// The outer list represents each key-value pair in the Fields. Keys +// which have multiple values are represented by multiple entries in this +// list with the same key. +// // entries: func() -> list> // //go:nosplit @@ -898,6 +991,11 @@ func (self Fields) Entries() (result cm.List[cm.Tuple[FieldKey, FieldValue]]) { // Get represents the imported method "get". // +// Get all of the values corresponding to a key. If the key is not present +// in this `fields`, an empty list is returned. However, if the key is +// present but empty, this is represented by a list with one or more +// empty field-values present. +// // get: func(name: field-key) -> list // //go:nosplit @@ -910,6 +1008,9 @@ func (self Fields) Get(name FieldKey) (result cm.List[FieldValue]) { // Has represents the imported method "has". // +// Returns `true` when the key is present in this `fields`. If the key is +// syntactically invalid, `false` is returned. +// // has: func(name: field-key) -> bool // //go:nosplit @@ -923,6 +1024,11 @@ func (self Fields) Has(name FieldKey) (result bool) { // Set represents the imported method "set". // +// Set all of the values for a key. Clears any existing values for that +// key, if they have been set. +// +// Fails with `header-error.immutable` if the `fields` are immutable. +// // set: func(name: field-key, value: list) -> result<_, header-error> // //go:nosplit @@ -946,6 +1052,8 @@ type Trailers = Fields // IncomingRequest represents the imported resource "wasi:http/types@0.2.0#incoming-request". // +// Represents an incoming HTTP Request. +// // resource incoming-request type IncomingRequest cm.Resource @@ -962,6 +1070,8 @@ func (self IncomingRequest) ResourceDrop() { // Authority represents the imported method "authority". // +// Returns the authority from the request, if it was present. +// // authority: func() -> option // //go:nosplit @@ -973,6 +1083,9 @@ func (self IncomingRequest) Authority() (result cm.Option[string]) { // Consume represents the imported method "consume". // +// Gives the `incoming-body` associated with this request. Will only +// return success at most once, and subsequent calls will return error. +// // consume: func() -> result // //go:nosplit @@ -984,6 +1097,15 @@ func (self IncomingRequest) Consume() (result cm.Result[IncomingBody, IncomingBo // Headers represents the imported method "headers". // +// 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 // //go:nosplit @@ -996,6 +1118,8 @@ func (self IncomingRequest) Headers() (result Headers) { // Method represents the imported method "method". // +// Returns the method of the incoming request. +// // method: func() -> method // //go:nosplit @@ -1007,6 +1131,8 @@ func (self IncomingRequest) Method() (result Method) { // PathWithQuery represents the imported method "path-with-query". // +// Returns the path with query parameters from the request, as a string. +// // path-with-query: func() -> option // //go:nosplit @@ -1018,6 +1144,8 @@ func (self IncomingRequest) PathWithQuery() (result cm.Option[string]) { // Scheme represents the imported method "scheme". // +// Returns the protocol scheme from the request. +// // scheme: func() -> option // //go:nosplit @@ -1029,6 +1157,8 @@ func (self IncomingRequest) Scheme() (result cm.Option[Scheme]) { // OutgoingRequest represents the imported resource "wasi:http/types@0.2.0#outgoing-request". // +// Represents an outgoing HTTP Request. +// // resource outgoing-request type OutgoingRequest cm.Resource @@ -1045,6 +1175,17 @@ func (self OutgoingRequest) ResourceDrop() { // NewOutgoingRequest represents the imported constructor for resource "outgoing-request". // +// Construct a new `outgoing-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` +// 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`. +// // constructor(headers: headers) // //go:nosplit @@ -1057,6 +1198,10 @@ func NewOutgoingRequest(headers Headers) (result OutgoingRequest) { // Authority represents the imported method "authority". // +// Get the HTTP Authority for the Request. A value of `none` may be used +// with Related Schemes which do not require an Authority. The HTTP and +// HTTPS schemes always require an authority. +// // authority: func() -> option // //go:nosplit @@ -1068,6 +1213,13 @@ func (self OutgoingRequest) Authority() (result cm.Option[string]) { // Body represents the imported method "body". // +// Returns the resource corresponding to the outgoing 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 // //go:nosplit @@ -1079,6 +1231,15 @@ func (self OutgoingRequest) Body() (result cm.Result[OutgoingBody, OutgoingBody, // Headers represents the imported method "headers". // +// 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`. +// // headers: func() -> headers // //go:nosplit @@ -1091,6 +1252,8 @@ func (self OutgoingRequest) Headers() (result Headers) { // Method represents the imported method "method". // +// Get the Method for the Request. +// // method: func() -> method // //go:nosplit @@ -1102,6 +1265,9 @@ func (self OutgoingRequest) Method() (result Method) { // PathWithQuery represents the imported method "path-with-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 // //go:nosplit @@ -1113,6 +1279,9 @@ func (self OutgoingRequest) PathWithQuery() (result cm.Option[string]) { // Scheme represents the imported method "scheme". // +// Get the HTTP Related Scheme for the Request. When `none`, the +// implementation may choose an appropriate default scheme. +// // scheme: func() -> option // //go:nosplit @@ -1124,6 +1293,11 @@ func (self OutgoingRequest) Scheme() (result cm.Option[Scheme]) { // SetAuthority represents the imported method "set-authority". // +// Set the HTTP Authority for the Request. A value of `none` may be used +// with Related Schemes which do not require an Authority. The HTTP and +// HTTPS schemes always require an authority. Fails if the string given is +// not a syntactically valid uri authority. +// // set-authority: func(authority: option) -> result // //go:nosplit @@ -1137,6 +1311,9 @@ func (self OutgoingRequest) SetAuthority(authority cm.Option[string]) (result cm // SetMethod represents the imported method "set-method". // +// Set the Method for the Request. Fails if the string present in a +// `method.other` argument is not a syntactically valid method. +// // set-method: func(method: method) -> result // //go:nosplit @@ -1150,6 +1327,10 @@ func (self OutgoingRequest) SetMethod(method Method) (result cm.BoolResult) { // SetPathWithQuery represents the imported method "set-path-with-query". // +// 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 // //go:nosplit @@ -1163,6 +1344,10 @@ func (self OutgoingRequest) SetPathWithQuery(pathWithQuery cm.Option[string]) (r // SetScheme represents the imported method "set-scheme". // +// Set the HTTP Related Scheme for the Request. When `none`, the +// implementation may choose an appropriate default scheme. Fails if the +// string given is not a syntactically valid uri scheme. +// // set-scheme: func(scheme: option) -> result // //go:nosplit @@ -1176,6 +1361,13 @@ func (self OutgoingRequest) SetScheme(scheme cm.Option[Scheme]) (result cm.BoolR // RequestOptions represents the imported resource "wasi:http/types@0.2.0#request-options". // +// 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 +// blocking call to `wasi:io/poll.poll`. +// // resource request-options type RequestOptions cm.Resource @@ -1192,6 +1384,8 @@ func (self RequestOptions) ResourceDrop() { // NewRequestOptions represents the imported constructor for resource "request-options". // +// Construct a default `request-options` value. +// // constructor() // //go:nosplit @@ -1203,6 +1397,9 @@ func NewRequestOptions() (result RequestOptions) { // BetweenBytesTimeout represents the imported method "between-bytes-timeout". // +// The timeout for receiving subsequent chunks of bytes in the Response +// body stream. +// // between-bytes-timeout: func() -> option // //go:nosplit @@ -1214,6 +1411,8 @@ func (self RequestOptions) BetweenBytesTimeout() (result cm.Option[Duration]) { // ConnectTimeout represents the imported method "connect-timeout". // +// The timeout for the initial connect to the HTTP Server. +// // connect-timeout: func() -> option // //go:nosplit @@ -1225,6 +1424,8 @@ func (self RequestOptions) ConnectTimeout() (result cm.Option[Duration]) { // FirstByteTimeout represents the imported method "first-byte-timeout". // +// The timeout for receiving the first byte of the Response body. +// // first-byte-timeout: func() -> option // //go:nosplit @@ -1236,6 +1437,10 @@ func (self RequestOptions) FirstByteTimeout() (result cm.Option[Duration]) { // SetBetweenBytesTimeout represents the imported method "set-between-bytes-timeout". // +// 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 // //go:nosplit @@ -1249,6 +1454,9 @@ func (self RequestOptions) SetBetweenBytesTimeout(duration cm.Option[Duration]) // SetConnectTimeout represents the imported method "set-connect-timeout". // +// 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 // //go:nosplit @@ -1262,6 +1470,9 @@ func (self RequestOptions) SetConnectTimeout(duration cm.Option[Duration]) (resu // SetFirstByteTimeout represents the imported method "set-first-byte-timeout". // +// 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 // //go:nosplit @@ -1275,6 +1486,12 @@ func (self RequestOptions) SetFirstByteTimeout(duration cm.Option[Duration]) (re // ResponseOutparam represents the imported resource "wasi:http/types@0.2.0#response-outparam". // +// 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 type ResponseOutparam cm.Resource @@ -1291,6 +1508,16 @@ func (self ResponseOutparam) ResourceDrop() { // ResponseOutparamSet represents the imported static function "set". // +// 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) // @@ -1304,11 +1531,15 @@ func ResponseOutparamSet(param ResponseOutparam, response cm.Result[ErrorCodeSha // StatusCode represents the u16 "wasi:http/types@0.2.0#status-code". // +// This type corresponds to the HTTP standard Status Code. +// // type status-code = u16 type StatusCode uint16 // IncomingResponse represents the imported resource "wasi:http/types@0.2.0#incoming-response". // +// Represents an incoming HTTP Response. +// // resource incoming-response type IncomingResponse cm.Resource @@ -1325,6 +1556,9 @@ func (self IncomingResponse) ResourceDrop() { // Consume represents the imported method "consume". // +// Returns the incoming body. May be called at most once. Returns error +// if called additional times. +// // consume: func() -> result // //go:nosplit @@ -1336,6 +1570,14 @@ func (self IncomingResponse) Consume() (result cm.Result[IncomingBody, IncomingB // Headers represents the imported method "headers". // +// 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 // //go:nosplit @@ -1348,6 +1590,8 @@ func (self IncomingResponse) Headers() (result Headers) { // Status represents the imported method "status". // +// Returns the status code from the incoming response. +// // status: func() -> status-code // //go:nosplit @@ -1360,6 +1604,15 @@ func (self IncomingResponse) Status() (result StatusCode) { // IncomingBody represents the imported resource "wasi:http/types@0.2.0#incoming-body". // +// Represents an incoming 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 type IncomingBody cm.Resource @@ -1376,6 +1629,9 @@ func (self IncomingBody) ResourceDrop() { // IncomingBodyFinish represents the imported static function "finish". // +// 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 // //go:nosplit @@ -1388,6 +1644,22 @@ func IncomingBodyFinish(this IncomingBody) (result FutureTrailers) { // Stream represents the imported method "stream". // +// 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`. +// +// 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 // //go:nosplit @@ -1399,6 +1671,12 @@ func (self IncomingBody) Stream() (result cm.Result[InputStream, InputStream, st // FutureTrailers represents the imported resource "wasi:http/types@0.2.0#future-trailers". // +// 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 type FutureTrailers cm.Resource @@ -1415,6 +1693,26 @@ func (self FutureTrailers) ResourceDrop() { // Get represents the imported method "get". // +// 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>>> // //go:nosplit @@ -1426,6 +1724,10 @@ func (self FutureTrailers) Get() (result cm.Option[cm.Result[cm.Result[ErrorCode // Subscribe represents the imported method "subscribe". // +// 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 // //go:nosplit @@ -1438,6 +1740,8 @@ func (self FutureTrailers) Subscribe() (result Pollable) { // OutgoingResponse represents the imported resource "wasi:http/types@0.2.0#outgoing-response". // +// Represents an outgoing HTTP Response. +// // resource outgoing-response type OutgoingResponse cm.Resource @@ -1454,6 +1758,12 @@ func (self OutgoingResponse) ResourceDrop() { // NewOutgoingResponse represents the imported constructor for resource "outgoing-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 +// `set-status-code` method. +// +// * `headers` is the HTTP Headers for the Response. +// // constructor(headers: headers) // //go:nosplit @@ -1466,6 +1776,12 @@ func NewOutgoingResponse(headers Headers) (result OutgoingResponse) { // Body represents the imported method "body". // +// 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 // //go:nosplit @@ -1477,6 +1793,15 @@ func (self OutgoingResponse) Body() (result cm.Result[OutgoingBody, OutgoingBody // Headers represents the imported method "headers". // +// 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`. +// // headers: func() -> headers // //go:nosplit @@ -1489,6 +1814,9 @@ func (self OutgoingResponse) Headers() (result Headers) { // SetStatusCode represents the imported method "set-status-code". // +// Set the HTTP Status Code for the Response. Fails if the status-code +// given is not a valid http status code. +// // set-status-code: func(status-code: status-code) -> result // //go:nosplit @@ -1502,6 +1830,8 @@ func (self OutgoingResponse) SetStatusCode(statusCode StatusCode) (result cm.Boo // StatusCode represents the imported method "status-code". // +// Get the HTTP Status Code for the Response. +// // status-code: func() -> status-code // //go:nosplit @@ -1514,6 +1844,23 @@ func (self OutgoingResponse) StatusCode() (result StatusCode) { // OutgoingBody represents the imported resource "wasi:http/types@0.2.0#outgoing-body". // +// 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 type OutgoingBody cm.Resource @@ -1530,6 +1877,16 @@ func (self OutgoingBody) ResourceDrop() { // OutgoingBodyFinish represents the imported static function "finish". // +// 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> // @@ -1543,6 +1900,16 @@ func OutgoingBodyFinish(this OutgoingBody, trailers cm.Option[Trailers]) (result // Write represents the imported method "write". // +// 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 // //go:nosplit @@ -1554,6 +1921,12 @@ func (self OutgoingBody) Write() (result cm.Result[OutputStream, OutputStream, s // FutureIncomingResponse represents the imported resource "wasi:http/types@0.2.0#future-incoming-response". // +// 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 type FutureIncomingResponse cm.Resource @@ -1570,6 +1943,21 @@ func (self FutureIncomingResponse) ResourceDrop() { // Get represents the imported method "get". // +// 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. +// +// 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>> // //go:nosplit @@ -1581,6 +1969,10 @@ func (self FutureIncomingResponse) Get() (result cm.Option[cm.Result[cm.Result[E // Subscribe represents the imported method "subscribe". // +// 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 // //go:nosplit @@ -1593,6 +1985,18 @@ func (self FutureIncomingResponse) Subscribe() (result Pollable) { // HTTPErrorCode represents the imported function "http-error-code". // +// Attempts to extract a http-related `error` from the wasi:io `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. +// +// Note that this function is fallible because not all io-errors are +// http-related errors. +// // http-error-code: func(err: borrow) -> option // //go:nosplit diff --git a/gen/wasmcloud/hello/hello/hello.wit b/gen/wasmcloud/hello/hello/hello.wit index f8132a6..362155c 100644 --- a/gen/wasmcloud/hello/hello/hello.wit +++ b/gen/wasmcloud/hello/hello/hello.wit @@ -38,77 +38,8 @@ package wasmcloud:hello { import wasi:random/random@0.2.0; import wasi:random/insecure@0.2.0; import wasi:random/insecure-seed@0.2.0; - } -} - -package wasi:cli@0.2.0 { - interface environment { - get-environment: func() -> list>; - get-arguments: func() -> list; - initial-cwd: func() -> option; - } - - interface exit { - exit: func(status: result); - } - - interface stdin { - use wasi:io/streams@0.2.0.{input-stream}; - get-stdin: func() -> input-stream; - } - - interface stdout { - use wasi:io/streams@0.2.0.{output-stream}; - get-stdout: func() -> output-stream; - } - - interface stderr { - use wasi:io/streams@0.2.0.{output-stream}; - get-stderr: func() -> output-stream; - } - - interface terminal-input { - resource terminal-input; - } - - interface terminal-output { - resource terminal-output; - } - - interface terminal-stdin { - use terminal-input.{terminal-input}; - get-terminal-stdin: func() -> option; - } - - interface terminal-stdout { - use terminal-output.{terminal-output}; - get-terminal-stdout: func() -> option; - } - - interface terminal-stderr { - use terminal-output.{terminal-output}; - get-terminal-stderr: func() -> option; - } -} - -package wasi:clocks@0.2.0 { - interface monotonic-clock { - use wasi:io/poll@0.2.0.{pollable}; - type instant = u64; - type duration = u64; - now: func() -> instant; - resolution: func() -> duration; - subscribe-instant: func(when: instant) -> pollable; - subscribe-duration: func(when: duration) -> pollable; - } - - interface wall-clock { - record datetime { - seconds: u64, - nanoseconds: u32, - } - now: func() -> datetime; - resolution: func() -> datetime; + export wasi:http/incoming-handler@0.2.0; + export wasmcloud:messaging/handler@0.2.0; } } @@ -282,12 +213,17 @@ package wasi:filesystem@0.2.0 { } package wasi:http@0.2.0 { + /// 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. interface types { use wasi:clocks/monotonic-clock@0.2.0.{duration}; use wasi:io/streams@0.2.0.{input-stream}; use wasi:io/streams@0.2.0.{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 { get, head, @@ -300,19 +236,30 @@ package wasi:http@0.2.0 { patch, other(string), } + + /// This type corresponds to HTTP standard Related Schemes. variant scheme { HTTP, HTTPS, other(string) } + + /// Defines the case payload type for `DNS-error` above: record DNS-error-payload { rcode: option, info-code: option, } + + /// Defines the case payload type for `TLS-alert-received` above: record TLS-alert-received-payload { alert-id: option, alert-message: option, } + + /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: record field-size-payload { field-name: option, field-size: option, } + + /// These cases are inspired by the IANA HTTP Proxy Error Types: + /// https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types variant error-code { DNS-timeout, DNS-error(DNS-error-payload), @@ -352,102 +299,568 @@ package wasi:http@0.2.0 { HTTP-protocol-error, loop-detected, configuration-error, + /// This is a catch-all error for anything that doesn't fit cleanly into a + /// more specific case. It also includes an optional string for an + /// unstructured description of the error. Users should not depend on the + /// string for diagnosing errors, as it's not required to be consistent + /// between implementations. internal-error(option), } + + /// This type enumerates the different kinds of errors that may occur when + /// setting or appending to a `fields` resource. variant header-error { + /// This error indicates that a `field-key` or `field-value` was + /// syntactically invalid when used with an operation that sets headers in a + /// `fields`. invalid-syntax, + /// This error indicates that a forbidden `field-key` was used when trying + /// to set a header in a `fields`. forbidden, + /// This error indicates that the operation on the `fields` was not + /// permitted because the fields are immutable. immutable, } + + /// Field keys are always strings. type field-key = string; + + /// Field values should always be ASCII strings. However, in + /// reality, HTTP implementations often have to interpret malformed values, + /// so they are provided as a list of bytes. type field-value = list; + + /// This following block defines the `fields` resource which corresponds to + /// HTTP standard Fields. Fields are a common representation used for both + /// Headers and Trailers. + /// + /// 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`. resource fields { + /// Construct an empty HTTP Fields. + /// + /// The resulting `fields` is mutable. constructor(); + + /// Append a value for a key. Does not change or delete any existing + /// values for that key. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. append: func(name: field-key, value: field-value) -> result<_, header-error>; + + /// Make a deep copy of the Fields. Equivelant in behavior to calling the + /// `fields` constructor on the return value of `entries`. The resulting + /// `fields` is mutable. clone: func() -> fields; + + /// Delete all values for a key. Does nothing if no values for the key + /// exist. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. delete: func(name: field-key) -> result<_, header-error>; + + /// Retrieve the full set of keys and values in the Fields. Like the + /// constructor, the list represents each key-value pair. + /// + /// The outer list represents each key-value pair in the Fields. Keys + /// which have multiple values are represented by multiple entries in this + /// list with the same key. entries: func() -> list>; + + /// Get all of the values corresponding to a key. If the key is not present + /// in this `fields`, an empty list is returned. However, if the key is + /// present but empty, this is represented by a list with one or more + /// empty field-values present. get: func(name: field-key) -> list; + + /// Returns `true` when the key is present in this `fields`. If the key is + /// syntactically invalid, `false` is returned. has: func(name: field-key) -> bool; + + /// Set all of the values for a key. Clears any existing values for that + /// key, if they have been set. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. set: func(name: field-key, value: list) -> result<_, header-error>; + + /// Construct an HTTP Fields. + /// + /// The resulting `fields` is mutable. + /// + /// The list represents each key-value pair in the Fields. Keys + /// which have multiple values are represented by multiple entries in this + /// list with the same key. + /// + /// The tuple is a pair of the field key, represented as a string, and + /// Value, represented as a list of bytes. In a valid Fields, all keys + /// and values are valid UTF-8 strings. However, values are not always + /// well-formed, so they are represented as a raw list of bytes. + /// + /// An error result will be returned if any header or value was + /// syntactically invalid, or if a header was forbidden. from-list: static func(entries: list>) -> result; } + + /// Headers is an alias for Fields. type headers = fields; + + /// Trailers is an alias for Fields. type trailers = fields; + + /// Represents an incoming HTTP Request. resource incoming-request { + + /// Returns the authority from the request, if it was present. authority: func() -> option; + + /// Gives the `incoming-body` associated with this request. Will only + /// return success at most once, and subsequent calls will return error. consume: func() -> result; + + /// 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; + + /// 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; } + + /// Represents an outgoing HTTP Request. resource outgoing-request { + /// Construct a new `outgoing-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` + /// 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`. constructor(headers: headers); + + /// Get the HTTP Authority for the Request. A value of `none` may be used + /// with Related Schemes which do not require an Authority. The HTTP and + /// HTTPS schemes always require an authority. authority: func() -> option; + + /// Returns the resource corresponding to the outgoing 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; + + /// 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`. headers: func() -> headers; + + /// Get the Method for the Request. method: func() -> method; + + /// 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; + + /// Get the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. scheme: func() -> option; + + /// Set the HTTP Authority for the Request. A value of `none` may be used + /// with Related Schemes which do not require an Authority. The HTTP and + /// HTTPS schemes always require an authority. Fails if the string given is + /// not a syntactically valid uri authority. set-authority: func(authority: option) -> result; + + /// Set the Method for the Request. Fails if the string present in a + /// `method.other` argument is not a syntactically valid method. set-method: func(method: method) -> result; + + /// 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; + + /// Set the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. Fails if the + /// string given is not a syntactically valid uri scheme. set-scheme: func(scheme: option) -> result; } + + /// 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 + /// blocking call to `wasi:io/poll.poll`. resource request-options { + /// Construct a default `request-options` value. constructor(); + + /// The timeout for receiving subsequent chunks of bytes in the Response + /// body stream. between-bytes-timeout: func() -> option; + + /// The timeout for the initial connect to the HTTP Server. connect-timeout: func() -> option; + + /// The timeout for receiving the first byte of the Response body. first-byte-timeout: func() -> option; + + /// 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; + + /// 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; + + /// 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; } + + /// 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); } + + /// This type corresponds to the HTTP standard Status Code. type status-code = u16; + + /// Represents an incoming HTTP Response. resource incoming-response { + + /// Returns the incoming body. May be called at most once. Returns error + /// if called additional times. consume: func() -> result; + + /// 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 status code from the incoming response. status: func() -> status-code; } + + /// Represents an incoming 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 { + + /// 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`. + /// + /// 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; } + + /// 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 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>>>; + + /// 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; } + + /// Represents an outgoing HTTP Response. resource outgoing-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 + /// `set-status-code` method. + /// + /// * `headers` is the HTTP Headers for the Response. constructor(headers: 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; + + /// 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`. headers: func() -> headers; + + /// Set the HTTP Status Code for the Response. Fails if the status-code + /// given is not a valid http status code. set-status-code: func(status-code: status-code) -> result; + + /// Get the HTTP Status Code for the Response. status-code: func() -> status-code; } + + /// 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 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. + /// + /// 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 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; } + + /// Attempts to extract a http-related `error` from the wasi:io `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. + /// + /// Note that this function is fallible because not all io-errors are + /// http-related errors. http-error-code: func(err: borrow) -> option; } + /// 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}; + use types.{response-outparam}; + + /// 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. + /// + /// 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. + 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}; use types.{request-options}; use types.{future-incoming-response}; use types.{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; } } +package wasi:cli@0.2.0 { + interface stdout { + use wasi:io/streams@0.2.0.{output-stream}; + get-stdout: func() -> output-stream; + } + + interface stderr { + use wasi:io/streams@0.2.0.{output-stream}; + get-stderr: func() -> output-stream; + } + + interface stdin { + use wasi:io/streams@0.2.0.{input-stream}; + get-stdin: func() -> input-stream; + } + + interface environment { + get-environment: func() -> list>; + get-arguments: func() -> list; + initial-cwd: func() -> option; + } + + interface exit { + exit: func(status: result); + } + + interface terminal-input { + resource terminal-input; + } + + interface terminal-output { + resource terminal-output; + } + + interface terminal-stdin { + use terminal-input.{terminal-input}; + get-terminal-stdin: func() -> option; + } + + interface terminal-stdout { + use terminal-output.{terminal-output}; + get-terminal-stdout: func() -> option; + } + + interface terminal-stderr { + use terminal-output.{terminal-output}; + get-terminal-stderr: func() -> option; + } +} + package wasi:io@0.2.0 { interface poll { resource pollable { @@ -706,6 +1119,27 @@ package wasi:sockets@0.2.0 { } } +package wasi:clocks@0.2.0 { + interface monotonic-clock { + use wasi:io/poll@0.2.0.{pollable}; + type instant = u64; + type duration = u64; + now: func() -> instant; + resolution: func() -> duration; + subscribe-instant: func(when: instant) -> pollable; + subscribe-duration: func(when: duration) -> pollable; + } + + interface wall-clock { + record datetime { + seconds: u64, + nanoseconds: u32, + } + now: func() -> datetime; + resolution: func() -> datetime; + } +} + package wasmcloud:bus@1.0.0 { interface lattice { resource call-target-interface { @@ -726,6 +1160,13 @@ package wasmcloud:messaging@0.2.0 { } } + interface handler { + use types.{broker-message}; + + /// Callback handled to invoke a function when a message is received from a subscription + handle-message: func(msg: broker-message) -> result<_, string>; + } + interface consumer { use types.{broker-message}; diff --git a/gen/wasmcloud/messaging/handler/abi.go b/gen/wasmcloud/messaging/handler/abi.go new file mode 100644 index 0000000..657cbb6 --- /dev/null +++ b/gen/wasmcloud/messaging/handler/abi.go @@ -0,0 +1,22 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package handler + +import ( + "gitea.rebus.ninja/lore/wasm-nats-producer-client/gen/wasmcloud/messaging/types" + "github.com/bytecodealliance/wasm-tools-go/cm" +) + +func lift_OptionString(f0 uint32, f1 *uint8, f2 uint32) (v cm.Option[string]) { + if f0 == 0 { + return + } + return (cm.Option[string])(cm.Some[string](cm.LiftString[string]((*uint8)(f1), (uint32)(f2)))) +} + +func lift_BrokerMessage(f0 *uint8, f1 uint32, f2 *uint8, f3 uint32, f4 uint32, f5 *uint8, f6 uint32) (v types.BrokerMessage) { + v.Subject = cm.LiftString[string](f0, f1) + v.Body = cm.LiftList[cm.List[uint8]](f2, f3) + v.ReplyTo = lift_OptionString(f4, f5, f6) + return +} diff --git a/gen/wasmcloud/messaging/handler/empty.s b/gen/wasmcloud/messaging/handler/empty.s new file mode 100644 index 0000000..5444f20 --- /dev/null +++ b/gen/wasmcloud/messaging/handler/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/gen/wasmcloud/messaging/handler/handler.exports.go b/gen/wasmcloud/messaging/handler/handler.exports.go new file mode 100644 index 0000000..d240bc9 --- /dev/null +++ b/gen/wasmcloud/messaging/handler/handler.exports.go @@ -0,0 +1,17 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package handler + +import ( + "github.com/bytecodealliance/wasm-tools-go/cm" +) + +// Exports represents the caller-defined exports from "wasmcloud:messaging/handler@0.2.0". +var Exports struct { + // HandleMessage represents the caller-defined, exported function "handle-message". + // + // Callback handled to invoke a function when a message is received from a subscription + // + // handle-message: func(msg: broker-message) -> result<_, string> + HandleMessage func(msg BrokerMessage) (result cm.Result[string, struct{}, string]) +} diff --git a/gen/wasmcloud/messaging/handler/handler.wasm.go b/gen/wasmcloud/messaging/handler/handler.wasm.go new file mode 100644 index 0000000..5c22887 --- /dev/null +++ b/gen/wasmcloud/messaging/handler/handler.wasm.go @@ -0,0 +1,18 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package handler + +import ( + "github.com/bytecodealliance/wasm-tools-go/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasmcloud:messaging@0.2.0". + +//go:wasmexport wasmcloud:messaging/handler@0.2.0#handle-message +//export wasmcloud:messaging/handler@0.2.0#handle-message +func wasmexport_HandleMessage(msg0 *uint8, msg1 uint32, msg2 *uint8, msg3 uint32, msg4 uint32, msg5 *uint8, msg6 uint32) (result *cm.Result[string, struct{}, string]) { + msg := lift_BrokerMessage((*uint8)(msg0), (uint32)(msg1), (*uint8)(msg2), (uint32)(msg3), (uint32)(msg4), (*uint8)(msg5), (uint32)(msg6)) + result_ := Exports.HandleMessage(msg) + result = &result_ + return +} diff --git a/gen/wasmcloud/messaging/handler/handler.wit.go b/gen/wasmcloud/messaging/handler/handler.wit.go new file mode 100644 index 0000000..c9ca8a5 --- /dev/null +++ b/gen/wasmcloud/messaging/handler/handler.wit.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package handler represents the exported interface "wasmcloud:messaging/handler@0.2.0". +package handler + +import ( + "gitea.rebus.ninja/lore/wasm-nats-producer-client/gen/wasmcloud/messaging/types" +) + +// BrokerMessage represents the type alias "wasmcloud:messaging/handler@0.2.0#broker-message". +// +// See [types.BrokerMessage] for more information. +type BrokerMessage = types.BrokerMessage diff --git a/go.mod b/go.mod index e967911..81f282d 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/ulikunitz/xz v0.5.12 // indirect github.com/urfave/cli/v3 v3.0.0-beta1 // indirect - go.bytecodealliance.org v0.4.1 // indirect + go.bytecodealliance.org v0.4.0 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect diff --git a/go.sum b/go.sum index 1776f8c..ae7ead7 100644 --- a/go.sum +++ b/go.sum @@ -1,52 +1,52 @@ -github.com/bytecodealliance/wasm-tools-go v0.3.2 h1:LKni9PS8yCG5/A79L8tcTKthgf7WN5RZD83W1m6wEE0= -github.com/bytecodealliance/wasm-tools-go v0.3.2/go.mod h1:fdysX1+SiPxcIhdpg8TLhoxz23k28/5cQ0/L9J4mgig= -github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= -github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= -github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/regclient/regclient v0.8.0 h1:xNAMDlADcyMvFAlGXoqDOxlSUBG4mqWBFgjQqVTP8Og= -github.com/regclient/regclient v0.8.0/go.mod h1:h9+Y6dBvqBkdlrj6EIhbTOv0xUuIFl7CdI1bZvEB42g= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= -github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= -go.bytecodealliance.org v0.4.1 h1:Y3Nnz+C061i9Hw4qQlSkZTg3IBTsTLC92+sdh4xCADQ= -go.bytecodealliance.org v0.4.1/go.mod h1:jxAxqtTxs+6Q2q6bQnWa/lF+Q+2/LkdbqIgNJYxEmFA= -go.wasmcloud.dev/component v0.0.5 h1:z9+fq1CJKm/yom33ctd5wfo8TVunm5IaI7EJdY2i9hk= -go.wasmcloud.dev/component v0.0.5/go.mod h1:PvVHQ7Xp8D9kZnVB3fm8lqWAcC6Yxd7CI/snXZUAG8E= -go.wasmcloud.dev/wadge v0.7.0 h1:eUt0jODh6xQ5HEof1PFSgDp+evrNs+lD1LYCYeRR2Cg= -go.wasmcloud.dev/wadge v0.7.0/go.mod h1:SMnPSWZFTkXyUX0GJ11mcdc7ZoMITtbAlPLlpvGJd4M= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +github.com/bytecodealliance/wasm-tools-go v0.3.2 h1:LKni9PS8yCG5/A79L8tcTKthgf7WN5RZD83W1m6wEE0= +github.com/bytecodealliance/wasm-tools-go v0.3.2/go.mod h1:fdysX1+SiPxcIhdpg8TLhoxz23k28/5cQ0/L9J4mgig= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= +github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/regclient/regclient v0.8.0 h1:xNAMDlADcyMvFAlGXoqDOxlSUBG4mqWBFgjQqVTP8Og= +github.com/regclient/regclient v0.8.0/go.mod h1:h9+Y6dBvqBkdlrj6EIhbTOv0xUuIFl7CdI1bZvEB42g= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= +github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= +go.bytecodealliance.org v0.4.0 h1:SRwgZIcXR54AmbJg9Y3AMgDlZlvD8dffteBYW+nCD3k= +go.bytecodealliance.org v0.4.0/go.mod h1:hkdjfgQ/bFZYUucnm9cn0Q8/SHO3iT0rzskYlkV4Jy0= +go.wasmcloud.dev/component v0.0.5 h1:z9+fq1CJKm/yom33ctd5wfo8TVunm5IaI7EJdY2i9hk= +go.wasmcloud.dev/component v0.0.5/go.mod h1:PvVHQ7Xp8D9kZnVB3fm8lqWAcC6Yxd7CI/snXZUAG8E= +go.wasmcloud.dev/wadge v0.7.0 h1:eUt0jODh6xQ5HEof1PFSgDp+evrNs+lD1LYCYeRR2Cg= +go.wasmcloud.dev/wadge v0.7.0/go.mod h1:SMnPSWZFTkXyUX0GJ11mcdc7ZoMITtbAlPLlpvGJd4M= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 36f40d3..67732d7 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,25 @@ package main import ( + "net/http" + "fmt" + + "go.wasmcloud.dev/component/net/wasihttp" + "gitea.rebus.ninja/lore/wasm-nats-producer-client/gen/wasmcloud/messaging/handler" ) func init() { - loop() + wasihttp.HandleFunc(handleRequest) + handler.Exports.HandleMessage = handleMessage +} + +func handleRequest(w http.ResponseWriter, r *http.Request) { + + // get body as string + handleHttp(r.FormValue("data")) + + // send response + fmt.Fprintf(w, "Message sent!\n") } // Since we don't run this program like a CLI, the `main` function is empty. Instead, diff --git a/main_test.go b/main_test.go deleted file mode 100644 index 36e9ecf..0000000 --- a/main_test.go +++ /dev/null @@ -1,54 +0,0 @@ -//go:generate go run go.wasmcloud.dev/wadge/cmd/wadge-bindgen-go - -package main - -import ( - "bytes" - "io" - "log" - "log/slog" - "net/http" - "os" - "testing" - - incominghandler "go.wasmcloud.dev/component/gen/wasi/http/incoming-handler" - "go.wasmcloud.dev/wadge" - "go.wasmcloud.dev/wadge/wadgehttp" -) - -func init() { - log.SetFlags(0) - slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ - Level: slog.LevelDebug, ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { - if a.Key == slog.TimeKey { - return slog.Attr{} - } - return a - }, - }))) -} - -func TestIncomingHandler(t *testing.T) { - wadge.RunTest(t, func() { - req, err := http.NewRequest("", "/", nil) - if err != nil { - t.Fatalf("failed to create new HTTP request: %s", err) - } - resp, err := wadgehttp.HandleIncomingRequest(incominghandler.Exports.Handle, req) - if err != nil { - t.Fatalf("failed to handle incoming HTTP request: %s", err) - } - if want, got := http.StatusOK, resp.StatusCode; want != got { - t.Fatalf("unexpected status code: want %d, got %d", want, got) - } - buf, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("failed to read HTTP response body: %s", err) - } - defer resp.Body.Close() - - if want, got := []byte("Hello from Go!\n"), buf; !bytes.Equal(want, got) { - t.Fatalf("unexpected response body: want %q, got %q", want, got) - } - }) -} diff --git a/messaging.go b/messaging.go index b13c542..3da97d2 100644 --- a/messaging.go +++ b/messaging.go @@ -6,7 +6,6 @@ import ( "gitea.rebus.ninja/lore/wasm-nats-producer-client/gen/wasmcloud/messaging/types" logger "gitea.rebus.ninja/lore/wasm-nats-producer-client/gen/wasi/logging/logging" //config "gitea.rebus.ninja/lore/wasm-nats-producer-client/gen/wasi/config/runtime" - "time" ) type messagingConsumerAdapter struct { @@ -18,42 +17,49 @@ var messagingConsumer = &messagingConsumerAdapter{ Publish: consumer.Publish, } -func loop() cm.Result[string, struct{}, string] { +func handleHttp(httpData string) { // TODO implement the logic to get the destination topic from the config // dest_topic := config.GetAll() - dest_topic := "test.reply" - counter := 0 + dest_topic := "streaming" - // TASK - for { + // generate random message + data := []byte(httpData) - // generate random message - data := []byte("Hello, World! " + string(counter)) + // Send reply + reply := types.BrokerMessage{ + Subject: dest_topic, + Body: cm.ToList(data), + ReplyTo: cm.None[string](), + } - // Send reply - reply := types.BrokerMessage{ - Subject: dest_topic, - Body: cm.ToList(data), - ReplyTo: cm.None[string](), - } - res := messagingConsumer.Publish(reply) - if res.IsErr() { - logger.Log(logger.LevelError, "MessageHandler", "Failed to send reply, error: " + *res.Err()) - return res - } + res := messagingConsumer.Publish(reply) - counter++ + if res.IsErr() { + logger.Log(logger.LevelError, "MessageHandler", "Failed to send reply, error: " + *res.Err()) + } - if counter == 1000 { - break - } +} - // sleep for 1 second - time.Sleep(1 * time.Second) +func handleMessage(msg types.BrokerMessage) cm.Result[string, struct{}, string] { + logger.Log(logger.LevelInfo,"MessageHandler", "Received message on subject" + msg.Subject) + + // TODO implement the logic to get the destination topic from the config + // dest_topic := config.GetAll() + dest_topic := "streaming" + + // Send reply + reply := types.BrokerMessage{ + Subject: dest_topic, + Body: msg.Body, + ReplyTo: cm.None[string](), + } + res := messagingConsumer.Publish(reply) + if res.IsErr() { + logger.Log(logger.LevelError, "MessageHandler", "Failed to send reply, error: " + *res.Err()) + return res } return cm.OK[cm.Result[string, struct{}, string]](struct{}{}) } - diff --git a/wadm.yaml b/wadm.yaml index 3743acf..478b7bd 100644 --- a/wadm.yaml +++ b/wadm.yaml @@ -3,32 +3,36 @@ kind: Application metadata: name: go-data-producer annotations: - description: 'HTTP hello world demo in Golang (TinyGo), using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)' - wasmcloud.dev/authors: wasmCloud team - wasmcloud.dev/source-url: https://github.com/wasmCloud/wasmCloud/blob/main/examples/golang/components/http-hello-world/wadm.yaml - wasmcloud.dev/readme-md-url: https://github.com/wasmCloud/wasmCloud/blob/main/examples/golang/components/http-hello-world/README.md - wasmcloud.dev/homepage: https://github.com/wasmCloud/wasmCloud/tree/main/examples/golang/components/http-hello-world + description: 'Data producer using NATS topic in Golang (TinyGo), using the WebAssembly Component Model and WebAssembly Interfaces Types (WIT)' + wasmcloud.dev/authors: Lorenzo Venerandi + wasmcloud.dev/source-url: https://gitea.rebus.ninja/lore/wasm-nats-producer-client.git/wadm.yaml + wasmcloud.dev/readme-md-url: https://gitea.rebus.ninja/lore/wasm-nats-producer-client.git/README.md + wasmcloud.dev/homepage: https://gitea.rebus.ninja/lore/wasm-nats-producer-client.git wasmcloud.dev/categories: | - http,outgoing-http,http-server,tinygo,golang,example + data-producer,nats-client,tinygo,golang spec: components: - - name: echo + - name: go-data-producer type: component properties: # To use the locally compiled code in this folder, use the line below instead after running `wash build`: # image: file://./build/echo_messaging_s.wasm - image: gitea.rebus.ninja/lore/go-nats-client:1.0.1 - id: echo - config: - - name: nats-topic - properties: - dest-topic: wasmcloud.echo.reply + image: gitea.rebus.ninja/lore/wasm-nats-producer-client:1.0.3 + id: producer + # config: + # - name: nats-topic + # properties: + # dest-topic: wasmcloud.echo.reply traits: # Govern the spread/scheduling of the component - type: spreadscaler properties: instances: 1 spread: + - name: cloud + weight: 0 + requirements: + host-type: cloud - name: edge weight: 100 requirements: @@ -63,10 +67,54 @@ spec: # # Here we link the `nats` provider (the "source"), to the `echo` component (the "target"), # so that so the provider can deliver messages to the component (by invoking the wasmcloud:messaging/handler interface) . + - type: link + properties: + target: go-data-producer + namespace: wasmcloud + package: messaging + interfaces: [handler] + source_config: + - name: simple-subscription + properties: + subscriptions: producer + - type: spreadscaler properties: instances: 1 spread: + - name: cloud + weight: 0 + requirements: + host-type: cloud - name: edge weight: 100 requirements: host-type: edge + - name: httpserver + type: capability + properties: + image: ghcr.io/wasmcloud/http-server:0.23.2 + traits: + # Link to Echo, and inform it to listen on port 8000 + # on the local machine + - type: link + properties: + target: go-data-producer + namespace: wasmcloud + package: http + interfaces: [incoming-handler] + source_config: + - name: default-http + properties: + address: 0.0.0.0:8000 + - type: spreadscaler + properties: + instances: 1 + spread: + - name: cloud + weight: 0 + requirements: + host-type: cloud + - name: edge + weight: 100 + requirements: + host-type: edge \ No newline at end of file diff --git a/wasmcloud.lock b/wasmcloud.lock index 42660f2..2653f2e 100644 --- a/wasmcloud.lock +++ b/wasmcloud.lock @@ -11,6 +11,15 @@ requirement = "=0.2.0-draft" version = "0.2.0-draft" digest = "sha256:aa2d36d0843999edad80a13bf22f4529277f7b6012429f8a5d1f9499f3793c1a" +[[packages]] +name = "wasi:http" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.0" +version = "0.2.0" +digest = "sha256:5a568e6e2d60c1ce51220e1833cdd5b88db9f615720edc762a9b4a6f36b383bd" + [[packages]] name = "wasi:logging" registry = "wasi.dev" diff --git a/wit/deps/wasi-cli-0.2.0/package.wit b/wit/deps/wasi-cli-0.2.0/package.wit index 20147d0..f8f1976 100644 --- a/wit/deps/wasi-cli-0.2.0/package.wit +++ b/wit/deps/wasi-cli-0.2.0/package.wit @@ -1,23 +1,5 @@ package wasi:cli@0.2.0; -interface environment { - get-environment: func() -> list>; - - get-arguments: func() -> list; - - initial-cwd: func() -> option; -} - -interface exit { - exit: func(status: result); -} - -interface stdin { - use wasi:io/streams@0.2.0.{input-stream}; - - get-stdin: func() -> input-stream; -} - interface stdout { use wasi:io/streams@0.2.0.{output-stream}; @@ -30,6 +12,24 @@ interface stderr { get-stderr: func() -> output-stream; } +interface stdin { + use wasi:io/streams@0.2.0.{input-stream}; + + get-stdin: func() -> input-stream; +} + +interface environment { + get-environment: func() -> list>; + + get-arguments: func() -> list; + + initial-cwd: func() -> option; +} + +interface exit { + exit: func(status: result); +} + interface terminal-input { resource terminal-input; } diff --git a/wit/deps/wasi-http-0.2.0/package.wit b/wit/deps/wasi-http-0.2.0/package.wit index 63031a8..11f7ff4 100644 --- a/wit/deps/wasi-http-0.2.0/package.wit +++ b/wit/deps/wasi-http-0.2.0/package.wit @@ -1,11 +1,15 @@ package wasi:http@0.2.0; +/// 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. 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 { get, head, @@ -19,27 +23,33 @@ interface types { other(string), } + /// This type corresponds to HTTP standard Related Schemes. variant scheme { HTTP, HTTPS, other(string), } + /// Defines the case payload type for `DNS-error` above: record DNS-error-payload { rcode: option, info-code: option, } + /// Defines the case payload type for `TLS-alert-received` above: record TLS-alert-received-payload { alert-id: option, alert-message: option, } + /// Defines the case payload type for `HTTP-response-{header,trailer}-size` above: record field-size-payload { field-name: option, field-size: option, } + /// These cases are inspired by the IANA HTTP Proxy Error Types: + /// https://www.iana.org/assignments/http-proxy-status/http-proxy-status.xhtml#table-http-proxy-error-types variant error-code { DNS-timeout, DNS-error(DNS-error-payload), @@ -79,120 +89,483 @@ interface types { HTTP-protocol-error, loop-detected, configuration-error, + /// This is a catch-all error for anything that doesn't fit cleanly into a + /// more specific case. It also includes an optional string for an + /// unstructured description of the error. Users should not depend on the + /// string for diagnosing errors, as it's not required to be consistent + /// between implementations. internal-error(option), } + /// This type enumerates the different kinds of errors that may occur when + /// setting or appending to a `fields` resource. variant header-error { + /// This error indicates that a `field-key` or `field-value` was + /// syntactically invalid when used with an operation that sets headers in a + /// `fields`. invalid-syntax, + /// This error indicates that a forbidden `field-key` was used when trying + /// to set a header in a `fields`. forbidden, + /// This error indicates that the operation on the `fields` was not + /// permitted because the fields are immutable. immutable, } + /// Field keys are always strings. type field-key = string; + /// Field values should always be ASCII strings. However, in + /// reality, HTTP implementations often have to interpret malformed values, + /// so they are provided as a list of bytes. type field-value = list; + /// This following block defines the `fields` resource which corresponds to + /// HTTP standard Fields. Fields are a common representation used for both + /// Headers and Trailers. + /// + /// 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`. resource fields { + /// Construct an empty HTTP Fields. + /// + /// The resulting `fields` is mutable. constructor(); + /// Construct an HTTP Fields. + /// + /// The resulting `fields` is mutable. + /// + /// The list represents each key-value pair in the Fields. Keys + /// which have multiple values are represented by multiple entries in this + /// list with the same key. + /// + /// The tuple is a pair of the field key, represented as a string, and + /// Value, represented as a list of bytes. In a valid Fields, all keys + /// and values are valid UTF-8 strings. However, values are not always + /// well-formed, so they are represented as a raw list of bytes. + /// + /// An error result will be returned if any header or value was + /// syntactically invalid, or if a header was forbidden. from-list: static func(entries: list>) -> result; + /// Get all of the values corresponding to a key. If the key is not present + /// in this `fields`, an empty list is returned. However, if the key is + /// present but empty, this is represented by a list with one or more + /// empty field-values present. get: func(name: field-key) -> list; + /// Returns `true` when the key is present in this `fields`. If the key is + /// syntactically invalid, `false` is returned. has: func(name: field-key) -> bool; + /// Set all of the values for a key. Clears any existing values for that + /// key, if they have been set. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. set: func(name: field-key, value: list) -> result<_, header-error>; + /// Delete all values for a key. Does nothing if no values for the key + /// exist. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. delete: func(name: field-key) -> result<_, header-error>; + /// Append a value for a key. Does not change or delete any existing + /// values for that key. + /// + /// Fails with `header-error.immutable` if the `fields` are immutable. append: func(name: field-key, value: field-value) -> result<_, header-error>; + /// Retrieve the full set of keys and values in the Fields. Like the + /// constructor, the list represents each key-value pair. + /// + /// The outer list represents each key-value pair in the Fields. Keys + /// which have multiple values are represented by multiple entries in this + /// list with the same key. entries: func() -> list>; + /// Make a deep copy of the Fields. Equivelant in behavior to calling the + /// `fields` constructor on the return value of `entries`. The resulting + /// `fields` is mutable. clone: func() -> fields; } + /// Headers is an alias for Fields. type headers = fields; + /// 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 { + /// Construct a new `outgoing-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` + /// 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`. constructor(headers: headers); + /// Returns the resource corresponding to the outgoing 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; + /// Get the Method for the Request. method: func() -> method; + /// Set the Method for the Request. Fails if the string present in a + /// `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. 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 + /// string given is not a syntactically valid path and query uri component. set-path-with-query: func(path-with-query: option) -> result; + /// Get the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. scheme: func() -> option; + /// Set the HTTP Related Scheme for the Request. When `none`, the + /// implementation may choose an appropriate default scheme. Fails if the + /// string given is not a syntactically valid uri scheme. set-scheme: func(scheme: option) -> result; + /// Get the HTTP Authority for the Request. A value of `none` may be used + /// with Related Schemes which do not require an Authority. The HTTP and + /// HTTPS schemes always require an authority. authority: func() -> option; + /// Set the HTTP Authority for the Request. A value of `none` may be used + /// with Related Schemes which do not require an Authority. The HTTP and + /// HTTPS schemes always require an authority. Fails if the string given is + /// not a syntactically valid uri authority. set-authority: func(authority: option) -> result; + /// 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`. headers: func() -> headers; } + /// 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 + /// blocking call to `wasi:io/poll.poll`. resource request-options { + /// Construct a default `request-options` value. constructor(); + /// The timeout for the initial connect to the HTTP Server. 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; + /// 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; + /// The timeout for receiving subsequent chunks of bytes in the Response + /// body stream. between-bytes-timeout: func() -> option; + /// 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); } + /// 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. + /// + /// 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 { + /// 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`. + /// + /// 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; } + /// 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>>>; } + /// Represents an outgoing HTTP Response. resource outgoing-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 + /// `set-status-code` method. + /// + /// * `headers` is the HTTP Headers for the Response. constructor(headers: headers); + /// Get the HTTP Status Code for the Response. status-code: func() -> status-code; + /// Set the HTTP Status Code for the Response. Fails if the status-code + /// given is not a valid http status code. set-status-code: func(status-code: status-code) -> result; + /// 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`. 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. + /// + /// 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>>; } + /// Attempts to extract a http-related `error` from the wasi:io `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. + /// + /// Note that this function is fallible because not all io-errors are + /// http-related errors. http-error-code: func(err: borrow) -> option; } +/// 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 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. + /// + /// 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. 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; } +/// 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 { + import wasi:random/random@0.2.0; + import wasi:io/error@0.2.0; + import wasi:io/poll@0.2.0; + import wasi:io/streams@0.2.0; + import wasi:cli/stdout@0.2.0; + import wasi:cli/stderr@0.2.0; + import wasi:cli/stdin@0.2.0; + import wasi:clocks/monotonic-clock@0.2.0; + import types; + import outgoing-handler; + import wasi:clocks/wall-clock@0.2.0; + + export incoming-handler; +} diff --git a/wit/world.wit b/wit/world.wit index a5e8a04..a78258c 100644 --- a/wit/world.wit +++ b/wit/world.wit @@ -5,6 +5,9 @@ world hello { import wasi:config/runtime@0.2.0-draft; + export wasi:http/incoming-handler@0.2.0; + + export wasmcloud:messaging/handler@0.2.0; import wasmcloud:messaging/consumer@0.2.0; import wasi:logging/logging@0.1.0-draft; }