diff --git a/docs/docs/languages/go.md b/docs/docs/languages/go.md index 9cac573c..46820289 100644 --- a/docs/docs/languages/go.md +++ b/docs/docs/languages/go.md @@ -21,7 +21,7 @@ In this example, the worker will get a request and print all the related informa 1. Add the Wasm Workers Server Go dependency ``` - go get -u github.com/vmware-labs/wasm-workers-server/kits/go/worker + go get -u github.com/vmware-labs/wasm-workers-server/kits/go/worker@v1.3.0 ``` 1. Create a `worker.go` file with the following contents: @@ -142,7 +142,7 @@ To add a KV store to your worker, follow these steps: 1. Add the Wasm Workers Server Go dependency ``` - go get -u github.com/vmware-labs/wasm-workers-server/kits/go/worker + go get -u github.com/vmware-labs/wasm-workers-server/kits/go/worker@v1.3.0 ``` 1. Create a `worker-kv.go` file with the following contents: diff --git a/examples/go-basic/go.mod b/examples/go-basic/go.mod new file mode 100644 index 00000000..8b5953b0 --- /dev/null +++ b/examples/go-basic/go.mod @@ -0,0 +1,12 @@ +module go-basic + +go 1.20 + +require github.com/vmware-labs/wasm-workers-server v1.3.0 + +require ( + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect +) diff --git a/examples/go-basic/go.sum b/examples/go-basic/go.sum new file mode 100644 index 00000000..ee18e775 --- /dev/null +++ b/examples/go-basic/go.sum @@ -0,0 +1,12 @@ +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/vmware-labs/wasm-workers-server v1.3.0 h1:Sm+Ycp327kRIaQIlWDXjKUITnCsaZaEqUDnPQZGPrKk= +github.com/vmware-labs/wasm-workers-server v1.3.0/go.mod h1:cigUhoitjUTLsUzR4+q0cz2FymdvJtfrfIS2hYAj69c= diff --git a/examples/go-fetch/README.md b/examples/go-fetch/README.md new file mode 100644 index 00000000..3ae6a048 --- /dev/null +++ b/examples/go-fetch/README.md @@ -0,0 +1,31 @@ +# Go fetch example + +Compile a Go worker to WebAssembly and run it in Wasm Workers Server. It performs a POST call to the [JSON placeholder API](https://jsonplaceholder.typicode.com/). + +## Prerequisites + +* Wasm Workers Server (wws): + + ```shell-session + curl -fsSL https://workers.wasmlabs.dev/install | bash + ``` + +* [Go](https://go.dev/) +* [TinyGo](https://tinygo.org/getting-started/install/) + +## Build + +```shell-session +tinygo build -o index.wasm -target wasi main.go +``` + +## Run + +```shell-session +wws . +``` + +## Resources + +* [Go documentation](https://workers.wasmlabs.dev/docs/languages/go) +* [Announcing Go support for Wasm Workers Server](https://wasmlabs.dev/articles/go-support-on-wasm-workers-server/) diff --git a/examples/go-fetch/go.mod b/examples/go-fetch/go.mod new file mode 100644 index 00000000..481204c4 --- /dev/null +++ b/examples/go-fetch/go.mod @@ -0,0 +1,15 @@ +module go-basic + +go 1.20 + +require github.com/vmware-labs/wasm-workers-server v1.3.0 + +require ( + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect +) + +replace github.com/vmware-labs/wasm-workers-server => ../../ +replace github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings => ../../kits/go/worker/bindings diff --git a/examples/go-fetch/go.sum b/examples/go-fetch/go.sum new file mode 100644 index 00000000..ee18e775 --- /dev/null +++ b/examples/go-fetch/go.sum @@ -0,0 +1,12 @@ +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/vmware-labs/wasm-workers-server v1.3.0 h1:Sm+Ycp327kRIaQIlWDXjKUITnCsaZaEqUDnPQZGPrKk= +github.com/vmware-labs/wasm-workers-server v1.3.0/go.mod h1:cigUhoitjUTLsUzR4+q0cz2FymdvJtfrfIS2hYAj69c= diff --git a/examples/go-fetch/main.go b/examples/go-fetch/main.go new file mode 100644 index 00000000..a54f47eb --- /dev/null +++ b/examples/go-fetch/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "bytes" + "io" + "net/http" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" + + "github.com/tidwall/sjson" +) + +func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + // Build a JSON body + body, _ := sjson.Set("", "title", "New POST!") + body, _ = sjson.Set(body, "body", "This is the body") + body, _ = sjson.Set(body, "userId", 1) + + url := "https://jsonplaceholder.typicode.com/posts" + + // Create the request + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBufferString(body)) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", "application/json") + + res, err := worker.SendHttpRequest(req) + if err != nil { + panic(err) + } + + // Read the response + resBody, err := io.ReadAll(res.Body) + if err != nil { + panic(err) + } + res.Body.Close() + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(resBody)) + }) +} diff --git a/kits/go/worker/README.md b/kits/go/worker/README.md new file mode 100644 index 00000000..cd4ea43d --- /dev/null +++ b/kits/go/worker/README.md @@ -0,0 +1,61 @@ +# Go kit + +This folder contains the Go kit or SDK for Wasm Workers Server. Currently, it uses the regular STDIN / STDOUT approach to receive the request and provide the response. In the latest version we introduced the new HTTP bindings to send HTTP requests from inside the worker. + +## Bindings + +Wasm Workers Server is on the road to adopt Wasm components, but it's not there yet. However, we started adopting WIT to generate the bindings for the different languages. + +The host (Wasm Workers Server) and other languages like Rust and JavaScript rely on [wit-bindgen v0.2](https://github.com/bytecodealliance/wit-bindgen/tree/v0.2.0). However, the Go bindings were not available on that version so it caused some extra work to generate the Go bindings. + +These are the steps to recreate the current Go bindings: + +- Clone the wit-binding repository and checkout to the [35cb45f2](https://github.com/bytecodealliance/wit-bindgen/commit/35cb45f25eb113b54406f269778d46a37716a7c5) commit (between v0.6 - v0.7). This commit produces compatible binding identifiers and fixes an error with the types on the generated C / Go code: + + ```shell-session + git clone https://github.com/bytecodealliance/wit-bindgen/tree/main && \ + git checkout 35cb45f25eb113b54406f269778d46a37716a7c5 + ``` + +- Compile the project: + + ```shell-session + cargo build --release + ``` + +- Change your current directory to `wasm-workers-server/kits/go/worker/bindings`. +- Now, you need to use the compiled `wit-bindgen`: + + ```shell-session + ~/YOUR_LOCATION/wit-bindgen/target/release/wit-bindgen tiny-go ../../../../wit/go-ephemeral/ + ``` + +- Just note that we're using a specific `wit` folder for Go. The reason is that the syntax changed from v0.3. We will consolidate it once we adopt components. +- Edit the `bindings.c` file to define the `canonical_abi_realloc` and `canonical_abi_free`. wit-bindgen v0.2 expects these methods to be exported. However, the first method was renamed to `cabi_realloc` and the second was removed on v3.0. To fix it, locate the `__attribute__((__weak__, __export_name__("cabi_realloc")))` and replace it with the following two methods: + + ```c + __attribute__((__weak__, __export_name__("canonical_abi_realloc"))) void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) + { + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; + } + + __attribute__((weak, export_name("canonical_abi_free"))) void canonical_abi_free( + void *ptr, + size_t size, + size_t align) + { + free(ptr); + } + ``` + +- Done! + +## References + +* [Go documentation](https://workers.wasmlabs.dev/docs/languages/go) +* [Announcing Go Support for Wasm Workers Server](https://wasmlabs.dev/articles/go-support-on-wasm-workers-server/) diff --git a/kits/go/worker/bindings/bindings.c b/kits/go/worker/bindings/bindings.c new file mode 100644 index 00000000..7060edf6 --- /dev/null +++ b/kits/go/worker/bindings/bindings.c @@ -0,0 +1,223 @@ +// Generated by `wit-bindgen` 0.6.0. DO NOT EDIT! +#include "bindings.h" + +__attribute__((__import_module__("http"), __import_name__("send-http-request"))) void __wasm_import_http_send_http_request(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t); + +__attribute__((__weak__, __export_name__("canonical_abi_realloc"))) void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) +{ + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +__attribute__((weak, export_name("canonical_abi_free"))) void canonical_abi_free( + void *ptr, + size_t size, + size_t align) +{ + free(ptr); +} + +// Helper Functions + +void http_types_uri_free(http_types_uri_t *ptr) +{ + bindings_string_free(ptr); +} + +void http_types_http_param_free(http_types_http_param_t *ptr) +{ + bindings_string_free(&ptr->f0); + bindings_string_free(&ptr->f1); +} + +void http_types_http_params_free(http_types_http_params_t *ptr) +{ + for (size_t i = 0; i < ptr->len; i++) + { + http_types_http_param_free(&ptr->ptr[i]); + } + if (ptr->len > 0) + { + free(ptr->ptr); + } +} + +void http_types_http_header_free(http_types_http_header_t *ptr) +{ + bindings_string_free(&ptr->f0); + bindings_string_free(&ptr->f1); +} + +void http_types_http_headers_free(http_types_http_headers_t *ptr) +{ + for (size_t i = 0; i < ptr->len; i++) + { + http_types_http_header_free(&ptr->ptr[i]); + } + if (ptr->len > 0) + { + free(ptr->ptr); + } +} + +void http_types_http_request_error_free(http_types_http_request_error_t *ptr) +{ + bindings_string_free(&ptr->message); +} + +void http_types_http_body_free(http_types_http_body_t *ptr) +{ + if (ptr->len > 0) + { + free(ptr->ptr); + } +} + +void bindings_option_http_body_free(bindings_option_http_body_t *ptr) +{ + if (ptr->is_some) + { + http_types_http_body_free(&ptr->val); + } +} + +void http_types_http_response_free(http_types_http_response_t *ptr) +{ + bindings_option_http_body_free(&ptr->body); + http_types_http_headers_free(&ptr->headers); +} + +void http_types_http_request_free(http_types_http_request_t *ptr) +{ + bindings_option_http_body_free(&ptr->body); + http_types_http_headers_free(&ptr->headers); + http_types_http_params_free(&ptr->params); + http_types_uri_free(&ptr->uri); +} + +void http_interface_http_request_free(http_interface_http_request_t *ptr) +{ + http_types_http_request_free(ptr); +} + +void http_interface_http_response_free(http_interface_http_response_t *ptr) +{ + http_types_http_response_free(ptr); +} + +void http_interface_http_request_error_free(http_interface_http_request_error_t *ptr) +{ + http_types_http_request_error_free(ptr); +} + +void bindings_result_http_response_http_request_error_free(bindings_result_http_response_http_request_error_t *ptr) +{ + if (!ptr->is_err) + { + http_interface_http_response_free(&ptr->val.ok); + } + else + { + http_interface_http_request_error_free(&ptr->val.err); + } +} + +void bindings_string_set(bindings_string_t *ret, const char *s) +{ + ret->ptr = (char *)s; + ret->len = strlen(s); +} + +void bindings_string_dup(bindings_string_t *ret, const char *s) +{ + ret->len = strlen(s); + ret->ptr = cabi_realloc(NULL, 0, 1, ret->len * 1); + memcpy(ret->ptr, s, ret->len * 1); +} + +void bindings_string_free(bindings_string_t *ret) +{ + if (ret->len > 0) + { + free(ret->ptr); + } + ret->ptr = NULL; + ret->len = 0; +} + +// Component Adapters + +void http_send_http_request(http_interface_http_request_t *request, bindings_result_http_response_http_request_error_t *ret) +{ + __attribute__((__aligned__(4))) + uint8_t ret_area[28]; + int32_t option; + int32_t option1; + int32_t option2; + if (((*request).body).is_some) + { + const http_types_http_body_t *payload0 = &((*request).body).val; + option = 1; + option1 = (int32_t)(*payload0).ptr; + option2 = (int32_t)(*payload0).len; + } + else + { + option = 0; + option1 = 0; + option2 = 0; + } + + int32_t ptr = (int32_t)&ret_area; + __wasm_import_http_send_http_request(option, option1, option2, (int32_t)((*request).headers).ptr, (int32_t)((*request).headers).len, (int32_t)(*request).method, (int32_t)((*request).params).ptr, (int32_t)((*request).params).len, (int32_t)((*request).uri).ptr, (int32_t)((*request).uri).len, ptr); + bindings_result_http_response_http_request_error_t result; + switch ((int32_t)(*((uint8_t *)(ptr + 0)))) + { + case 0: + { + result.is_err = false; + bindings_option_http_body_t option3; + switch ((int32_t)(*((uint8_t *)(ptr + 4)))) + { + case 0: + { + option3.is_some = false; + break; + } + case 1: + { + option3.is_some = true; + option3.val = (http_types_http_body_t){(uint8_t *)(*((int32_t *)(ptr + 8))), (size_t)(*((int32_t *)(ptr + 12)))}; + break; + } + } + + result.val.ok = (http_types_http_response_t){ + option3, + (http_types_http_headers_t){(http_types_http_header_t *)(*((int32_t *)(ptr + 16))), (size_t)(*((int32_t *)(ptr + 20)))}, + (uint16_t)((int32_t)(*((uint16_t *)(ptr + 24)))), + }; + break; + } + case 1: + { + result.is_err = true; + result.val.err = (http_types_http_request_error_t){ + (int32_t)(*((uint8_t *)(ptr + 4))), + (bindings_string_t){(char *)(*((int32_t *)(ptr + 8))), (size_t)(*((int32_t *)(ptr + 12)))}, + }; + break; + } + } + *ret = result; +} + +extern void __component_type_object_force_link_bindings(void); +void __component_type_object_force_link_bindings_public_use_in_this_compilation_unit(void) +{ + __component_type_object_force_link_bindings(); +} diff --git a/kits/go/worker/bindings/bindings.go b/kits/go/worker/bindings/bindings.go new file mode 100644 index 00000000..3d10f5fd --- /dev/null +++ b/kits/go/worker/bindings/bindings.go @@ -0,0 +1,337 @@ +package bindings + +// #include "bindings.h" +import "C" + +import "unsafe" + +// http-types +type HttpTypesUri = string +type HttpTypesHttpStatus = uint16 +type HttpTypesHttpParam struct { + F0 string + F1 string +} + +type HttpTypesHttpParams = HttpTypesHttpParam +type HttpTypesHttpMethodKind int + +const ( +HttpTypesHttpMethodKindGet HttpTypesHttpMethodKind = iota +HttpTypesHttpMethodKindPost +HttpTypesHttpMethodKindPut +HttpTypesHttpMethodKindPatch +HttpTypesHttpMethodKindDelete +HttpTypesHttpMethodKindOptions +HttpTypesHttpMethodKindHead +) + +type HttpTypesHttpMethod struct { + kind HttpTypesHttpMethodKind +} + +func (n HttpTypesHttpMethod) Kind() HttpTypesHttpMethodKind { + return n.kind +} + +func HttpTypesHttpMethodGet() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindGet} +} + +func HttpTypesHttpMethodPost() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindPost} +} + +func HttpTypesHttpMethodPut() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindPut} +} + +func HttpTypesHttpMethodPatch() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindPatch} +} + +func HttpTypesHttpMethodDelete() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindDelete} +} + +func HttpTypesHttpMethodOptions() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindOptions} +} + +func HttpTypesHttpMethodHead() HttpTypesHttpMethod{ + return HttpTypesHttpMethod{kind: HttpTypesHttpMethodKindHead} +} + +type HttpTypesHttpHeader struct { + F0 string + F1 string +} + +type HttpTypesHttpHeaders = HttpTypesHttpHeader +type HttpTypesHttpErrorKind int + +const ( +HttpTypesHttpErrorKindInvalidRequest HttpTypesHttpErrorKind = iota +HttpTypesHttpErrorKindInvalidRequestBody +HttpTypesHttpErrorKindInvalidResponseBody +HttpTypesHttpErrorKindNotAllowed +HttpTypesHttpErrorKindInternalError +HttpTypesHttpErrorKindTimeout +HttpTypesHttpErrorKindRedirectLoop +) + +type HttpTypesHttpError struct { + kind HttpTypesHttpErrorKind +} + +func (n HttpTypesHttpError) Kind() HttpTypesHttpErrorKind { + return n.kind +} + +func HttpTypesHttpErrorInvalidRequest() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindInvalidRequest} +} + +func HttpTypesHttpErrorInvalidRequestBody() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindInvalidRequestBody} +} + +func HttpTypesHttpErrorInvalidResponseBody() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindInvalidResponseBody} +} + +func HttpTypesHttpErrorNotAllowed() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindNotAllowed} +} + +func HttpTypesHttpErrorInternalError() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindInternalError} +} + +func HttpTypesHttpErrorTimeout() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindTimeout} +} + +func HttpTypesHttpErrorRedirectLoop() HttpTypesHttpError{ + return HttpTypesHttpError{kind: HttpTypesHttpErrorKindRedirectLoop} +} + +type HttpTypesHttpRequestError struct { + Error HttpTypesHttpError + Message string +} + +type HttpTypesHttpBody = uint8 +type HttpTypesHttpResponse struct { + Body Option[[]uint8] + Headers []HttpTypesHttpHeader + Status uint16 +} + +type HttpTypesHttpRequest struct { + Body Option[[]uint8] + Headers []HttpTypesHttpHeader + Method HttpTypesHttpMethod + Params []HttpTypesHttpParam + Uri string +} + +// http +type HttpHttpRequest = HttpTypesHttpRequest +type HttpHttpResponse = HttpTypesHttpResponse +type HttpHttpRequestError = HttpTypesHttpRequestError +func HttpSendHttpRequest(request HttpTypesHttpRequest) Result[HttpTypesHttpResponse, HttpTypesHttpRequestError] { + var lower_request C.http_types_http_request_t + var lower_request_val C.http_types_http_request_t + var lower_request_val_body C.bindings_option_http_body_t + if request.Body.IsSome() { + var lower_request_val_body_val C.http_types_http_body_t + if len(request.Body.Unwrap()) == 0 { + lower_request_val_body_val.ptr = nil + lower_request_val_body_val.len = 0 + } else { + var empty_lower_request_val_body_val C.uint8_t + lower_request_val_body_val.ptr = (*C.uint8_t)(C.malloc(C.size_t(len(request.Body.Unwrap())) * C.size_t(unsafe.Sizeof(empty_lower_request_val_body_val)))) + lower_request_val_body_val.len = C.size_t(len(request.Body.Unwrap())) + for lower_request_val_body_val_i := range request.Body.Unwrap() { + lower_request_val_body_val_ptr := (*C.uint8_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lower_request_val_body_val.ptr)) + + uintptr(lower_request_val_body_val_i)*unsafe.Sizeof(empty_lower_request_val_body_val))) + lower_request_val_body_val_ptr_value := C.uint8_t(request.Body.Unwrap()[lower_request_val_body_val_i]) + *lower_request_val_body_val_ptr = lower_request_val_body_val_ptr_value + } + } + lower_request_val_body.val = lower_request_val_body_val + lower_request_val_body.is_some = true + } + lower_request_val.body = lower_request_val_body + var lower_request_val_headers C.http_types_http_headers_t + if len(request.Headers) == 0 { + lower_request_val_headers.ptr = nil + lower_request_val_headers.len = 0 + } else { + var empty_lower_request_val_headers C.http_types_http_header_t + lower_request_val_headers.ptr = (*C.http_types_http_header_t)(C.malloc(C.size_t(len(request.Headers)) * C.size_t(unsafe.Sizeof(empty_lower_request_val_headers)))) + lower_request_val_headers.len = C.size_t(len(request.Headers)) + for lower_request_val_headers_i := range request.Headers { + lower_request_val_headers_ptr := (*C.http_types_http_header_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lower_request_val_headers.ptr)) + + uintptr(lower_request_val_headers_i)*unsafe.Sizeof(empty_lower_request_val_headers))) + var lower_request_val_headers_ptr_value C.http_types_http_header_t + var lower_request_val_headers_ptr_value_f0 C.bindings_string_t + + lower_request_val_headers_ptr_value_f0.ptr = C.CString(request.Headers[lower_request_val_headers_i].F0) + lower_request_val_headers_ptr_value_f0.len = C.size_t(len(request.Headers[lower_request_val_headers_i].F0)) + lower_request_val_headers_ptr_value.f0 = lower_request_val_headers_ptr_value_f0 + var lower_request_val_headers_ptr_value_f1 C.bindings_string_t + + lower_request_val_headers_ptr_value_f1.ptr = C.CString(request.Headers[lower_request_val_headers_i].F1) + lower_request_val_headers_ptr_value_f1.len = C.size_t(len(request.Headers[lower_request_val_headers_i].F1)) + lower_request_val_headers_ptr_value.f1 = lower_request_val_headers_ptr_value_f1 + *lower_request_val_headers_ptr = lower_request_val_headers_ptr_value + } + } + lower_request_val.headers = lower_request_val_headers + var lower_request_val_method C.http_types_http_method_t + if request.Method.Kind() == HttpTypesHttpMethodKindGet { + lower_request_val_method = 0 + } + if request.Method.Kind() == HttpTypesHttpMethodKindPost { + lower_request_val_method = 1 + } + if request.Method.Kind() == HttpTypesHttpMethodKindPut { + lower_request_val_method = 2 + } + if request.Method.Kind() == HttpTypesHttpMethodKindPatch { + lower_request_val_method = 3 + } + if request.Method.Kind() == HttpTypesHttpMethodKindDelete { + lower_request_val_method = 4 + } + if request.Method.Kind() == HttpTypesHttpMethodKindOptions { + lower_request_val_method = 5 + } + if request.Method.Kind() == HttpTypesHttpMethodKindHead { + lower_request_val_method = 6 + } + lower_request_val.method = lower_request_val_method + var lower_request_val_params C.http_types_http_params_t + if len(request.Params) == 0 { + lower_request_val_params.ptr = nil + lower_request_val_params.len = 0 + } else { + var empty_lower_request_val_params C.http_types_http_param_t + lower_request_val_params.ptr = (*C.http_types_http_param_t)(C.malloc(C.size_t(len(request.Params)) * C.size_t(unsafe.Sizeof(empty_lower_request_val_params)))) + lower_request_val_params.len = C.size_t(len(request.Params)) + for lower_request_val_params_i := range request.Params { + lower_request_val_params_ptr := (*C.http_types_http_param_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lower_request_val_params.ptr)) + + uintptr(lower_request_val_params_i)*unsafe.Sizeof(empty_lower_request_val_params))) + var lower_request_val_params_ptr_value C.http_types_http_param_t + var lower_request_val_params_ptr_value_f0 C.bindings_string_t + + lower_request_val_params_ptr_value_f0.ptr = C.CString(request.Params[lower_request_val_params_i].F0) + lower_request_val_params_ptr_value_f0.len = C.size_t(len(request.Params[lower_request_val_params_i].F0)) + lower_request_val_params_ptr_value.f0 = lower_request_val_params_ptr_value_f0 + var lower_request_val_params_ptr_value_f1 C.bindings_string_t + + lower_request_val_params_ptr_value_f1.ptr = C.CString(request.Params[lower_request_val_params_i].F1) + lower_request_val_params_ptr_value_f1.len = C.size_t(len(request.Params[lower_request_val_params_i].F1)) + lower_request_val_params_ptr_value.f1 = lower_request_val_params_ptr_value_f1 + *lower_request_val_params_ptr = lower_request_val_params_ptr_value + } + } + lower_request_val.params = lower_request_val_params + var lower_request_val_uri C.bindings_string_t + var lower_request_val_uri_val C.bindings_string_t + + lower_request_val_uri_val.ptr = C.CString(request.Uri) + lower_request_val_uri_val.len = C.size_t(len(request.Uri)) + lower_request_val_uri = lower_request_val_uri_val + lower_request_val.uri = lower_request_val_uri + lower_request = lower_request_val + defer C.http_interface_http_request_free(&lower_request) + var ret C.bindings_result_http_response_http_request_error_t + C.http_send_http_request(&lower_request, &ret) + var lift_ret Result[HttpTypesHttpResponse, HttpTypesHttpRequestError] + if ret.is_err { + lift_ret_ptr := *(*C.http_interface_http_request_error_t)(unsafe.Pointer(&ret.val)) + var lift_ret_val HttpTypesHttpRequestError + var lift_ret_val_val HttpTypesHttpRequestError + var lift_ret_val_val_Error HttpTypesHttpError + if lift_ret_ptr.error == 0 { + lift_ret_val_val_Error = HttpTypesHttpErrorInvalidRequest() + } + if lift_ret_ptr.error == 1 { + lift_ret_val_val_Error = HttpTypesHttpErrorInvalidRequestBody() + } + if lift_ret_ptr.error == 2 { + lift_ret_val_val_Error = HttpTypesHttpErrorInvalidResponseBody() + } + if lift_ret_ptr.error == 3 { + lift_ret_val_val_Error = HttpTypesHttpErrorNotAllowed() + } + if lift_ret_ptr.error == 4 { + lift_ret_val_val_Error = HttpTypesHttpErrorInternalError() + } + if lift_ret_ptr.error == 5 { + lift_ret_val_val_Error = HttpTypesHttpErrorTimeout() + } + if lift_ret_ptr.error == 6 { + lift_ret_val_val_Error = HttpTypesHttpErrorRedirectLoop() + } + lift_ret_val_val.Error = lift_ret_val_val_Error + var lift_ret_val_val_Message string + lift_ret_val_val_Message = C.GoStringN(lift_ret_ptr.message.ptr, C.int(lift_ret_ptr.message.len)) + lift_ret_val_val.Message = lift_ret_val_val_Message + lift_ret_val = lift_ret_val_val + lift_ret.SetErr(lift_ret_val) + } else { + lift_ret_ptr := *(*C.http_interface_http_response_t)(unsafe.Pointer(&ret.val)) + var lift_ret_val HttpTypesHttpResponse + var lift_ret_val_val HttpTypesHttpResponse + var lift_ret_val_val_Body Option[[]uint8] + if lift_ret_ptr.body.is_some { + var lift_ret_val_val_Body_val []uint8 + lift_ret_val_val_Body_val = make([]uint8, lift_ret_ptr.body.val.len) + if lift_ret_ptr.body.val.len > 0 { + for lift_ret_val_val_Body_val_i := 0; lift_ret_val_val_Body_val_i < int(lift_ret_ptr.body.val.len); lift_ret_val_val_Body_val_i++ { + var empty_lift_ret_val_val_Body_val C.uint8_t + lift_ret_val_val_Body_val_ptr := *(*C.uint8_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lift_ret_ptr.body.val.ptr)) + + uintptr(lift_ret_val_val_Body_val_i)*unsafe.Sizeof(empty_lift_ret_val_val_Body_val))) + var list_lift_ret_val_val_Body_val uint8 + list_lift_ret_val_val_Body_val = uint8(lift_ret_val_val_Body_val_ptr) + lift_ret_val_val_Body_val[lift_ret_val_val_Body_val_i] = list_lift_ret_val_val_Body_val + } + } + lift_ret_val_val_Body.Set(lift_ret_val_val_Body_val) + } else { + lift_ret_val_val_Body.Unset() + } + lift_ret_val_val.Body = lift_ret_val_val_Body + var lift_ret_val_val_Headers []HttpTypesHttpHeader + lift_ret_val_val_Headers = make([]HttpTypesHttpHeader, lift_ret_ptr.headers.len) + if lift_ret_ptr.headers.len > 0 { + for lift_ret_val_val_Headers_i := 0; lift_ret_val_val_Headers_i < int(lift_ret_ptr.headers.len); lift_ret_val_val_Headers_i++ { + var empty_lift_ret_val_val_Headers C.http_types_http_header_t + lift_ret_val_val_Headers_ptr := *(*C.http_types_http_header_t)(unsafe.Pointer(uintptr(unsafe.Pointer(lift_ret_ptr.headers.ptr)) + + uintptr(lift_ret_val_val_Headers_i)*unsafe.Sizeof(empty_lift_ret_val_val_Headers))) + var list_lift_ret_val_val_Headers HttpTypesHttpHeader + var list_lift_ret_val_val_Headers_F0 string + list_lift_ret_val_val_Headers_F0 = C.GoStringN(lift_ret_val_val_Headers_ptr.f0.ptr, C.int(lift_ret_val_val_Headers_ptr.f0.len)) + list_lift_ret_val_val_Headers.F0 = list_lift_ret_val_val_Headers_F0 + var list_lift_ret_val_val_Headers_F1 string + list_lift_ret_val_val_Headers_F1 = C.GoStringN(lift_ret_val_val_Headers_ptr.f1.ptr, C.int(lift_ret_val_val_Headers_ptr.f1.len)) + list_lift_ret_val_val_Headers.F1 = list_lift_ret_val_val_Headers_F1 + lift_ret_val_val_Headers[lift_ret_val_val_Headers_i] = list_lift_ret_val_val_Headers + } + } + lift_ret_val_val.Headers = lift_ret_val_val_Headers + var lift_ret_val_val_Status uint16 + var lift_ret_val_val_Status_val uint16 + lift_ret_val_val_Status_val = uint16(lift_ret_ptr.status) + lift_ret_val_val_Status = lift_ret_val_val_Status_val + lift_ret_val_val.Status = lift_ret_val_val_Status + lift_ret_val = lift_ret_val_val + lift_ret.Set(lift_ret_val) + } + return lift_ret +} + diff --git a/kits/go/worker/bindings/bindings.h b/kits/go/worker/bindings/bindings.h new file mode 100644 index 00000000..4952b434 --- /dev/null +++ b/kits/go/worker/bindings/bindings.h @@ -0,0 +1,131 @@ +// Generated by `wit-bindgen` 0.6.0. DO NOT EDIT! +#ifndef __BINDINGS_BINDINGS_H +#define __BINDINGS_BINDINGS_H +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +typedef struct { + char*ptr; + size_t len; +} bindings_string_t; + +typedef bindings_string_t http_types_uri_t; + +typedef uint16_t http_types_http_status_t; + +typedef struct { + bindings_string_t f0; + bindings_string_t f1; +} http_types_http_param_t; + +typedef struct { + http_types_http_param_t *ptr; + size_t len; +} http_types_http_params_t; + +typedef uint8_t http_types_http_method_t; + +#define HTTP_TYPES_HTTP_METHOD_GET 0 +#define HTTP_TYPES_HTTP_METHOD_POST 1 +#define HTTP_TYPES_HTTP_METHOD_PUT 2 +#define HTTP_TYPES_HTTP_METHOD_PATCH 3 +#define HTTP_TYPES_HTTP_METHOD_DELETE 4 +#define HTTP_TYPES_HTTP_METHOD_OPTIONS 5 +#define HTTP_TYPES_HTTP_METHOD_HEAD 6 + +typedef struct { + bindings_string_t f0; + bindings_string_t f1; +} http_types_http_header_t; + +typedef struct { + http_types_http_header_t *ptr; + size_t len; +} http_types_http_headers_t; + +typedef uint8_t http_types_http_error_t; + +#define HTTP_TYPES_HTTP_ERROR_INVALID_REQUEST 0 +#define HTTP_TYPES_HTTP_ERROR_INVALID_REQUEST_BODY 1 +#define HTTP_TYPES_HTTP_ERROR_INVALID_RESPONSE_BODY 2 +#define HTTP_TYPES_HTTP_ERROR_NOT_ALLOWED 3 +#define HTTP_TYPES_HTTP_ERROR_INTERNAL_ERROR 4 +#define HTTP_TYPES_HTTP_ERROR_TIMEOUT 5 +#define HTTP_TYPES_HTTP_ERROR_REDIRECT_LOOP 6 + +typedef struct { + http_types_http_error_t error; + bindings_string_t message; +} http_types_http_request_error_t; + +typedef struct { + uint8_t *ptr; + size_t len; +} http_types_http_body_t; + +typedef struct { + bool is_some; + http_types_http_body_t val; +} bindings_option_http_body_t; + +typedef struct { + bindings_option_http_body_t body; + http_types_http_headers_t headers; + http_types_http_status_t status; +} http_types_http_response_t; + +typedef struct { + bindings_option_http_body_t body; + http_types_http_headers_t headers; + http_types_http_method_t method; + http_types_http_params_t params; + http_types_uri_t uri; +} http_types_http_request_t; + +typedef http_types_http_request_t http_interface_http_request_t; + +typedef http_types_http_response_t http_interface_http_response_t; + +typedef http_types_http_request_error_t http_interface_http_request_error_t; + +typedef struct { + bool is_err; + union { + http_interface_http_response_t ok; + http_interface_http_request_error_t err; + } val; +} bindings_result_http_response_http_request_error_t; + +// Imported Functions from `http` +void http_send_http_request(http_interface_http_request_t *request, bindings_result_http_response_http_request_error_t *ret); + +// Helper Functions + +void http_types_uri_free(http_types_uri_t *ptr); +void http_types_http_param_free(http_types_http_param_t *ptr); +void http_types_http_params_free(http_types_http_params_t *ptr); +void http_types_http_header_free(http_types_http_header_t *ptr); +void http_types_http_headers_free(http_types_http_headers_t *ptr); +void http_types_http_request_error_free(http_types_http_request_error_t *ptr); +void http_types_http_body_free(http_types_http_body_t *ptr); +void bindings_option_http_body_free(bindings_option_http_body_t *ptr); +void http_types_http_response_free(http_types_http_response_t *ptr); +void http_types_http_request_free(http_types_http_request_t *ptr); +void http_interface_http_request_free(http_interface_http_request_t *ptr); +void http_interface_http_response_free(http_interface_http_response_t *ptr); +void http_interface_http_request_error_free(http_interface_http_request_error_t *ptr); +void bindings_result_http_response_http_request_error_free(bindings_result_http_response_http_request_error_t *ptr); +void bindings_string_set(bindings_string_t *ret, const char*s); +void bindings_string_dup(bindings_string_t *ret, const char*s); +void bindings_string_free(bindings_string_t *ret); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/kits/go/worker/bindings/bindings_component_type.o b/kits/go/worker/bindings/bindings_component_type.o new file mode 100644 index 00000000..4225eb38 Binary files /dev/null and b/kits/go/worker/bindings/bindings_component_type.o differ diff --git a/kits/go/worker/bindings/bindings_types.go b/kits/go/worker/bindings/bindings_types.go new file mode 100644 index 00000000..3d923fc8 --- /dev/null +++ b/kits/go/worker/bindings/bindings_types.go @@ -0,0 +1,108 @@ +package bindings + +// inspired from https://github.com/moznion/go-optional + +type optionKind int + +const ( +none optionKind = iota +some +) + +type Option[T any] struct { + kind optionKind + val T +} + +// IsNone returns true if the option is None. +func (o Option[T]) IsNone() bool { + return o.kind == none +} + +// IsSome returns true if the option is Some. +func (o Option[T]) IsSome() bool { + return o.kind == some +} + +// Unwrap returns the value if the option is Some. +func (o Option[T]) Unwrap() T { + if o.kind != some { + panic("Option is None") + } + return o.val +} + +// Set sets the value and returns it. +func (o *Option[T]) Set(val T) T { + o.kind = some + o.val = val + return val +} + +// Unset sets the value to None. +func (o *Option[T]) Unset() { + o.kind = none +} + +// Some is a constructor for Option[T] which represents Some. +func Some[T any](v T) Option[T] { + return Option[T]{ + kind: some, + val: v, + } +} + +// None is a constructor for Option[T] which represents None. +func None[T any]() Option[T] { + return Option[T]{ + kind: none, + } +} + +type ResultKind int + +const ( +Ok ResultKind = iota +Err +) + +type Result[T any, E any] struct { + Kind ResultKind + Val T + Err E +} + +func (r Result[T, E]) IsOk() bool { + return r.Kind == Ok +} + +func (r Result[T, E]) IsErr() bool { + return r.Kind == Err +} + +func (r Result[T, E]) Unwrap() T { + if r.Kind != Ok { + panic("Result is Err") + } + return r.Val +} + +func (r Result[T, E]) UnwrapErr() E { + if r.Kind != Err { + panic("Result is Ok") + } + return r.Err +} + +func (r *Result[T, E]) Set(val T) T { + r.Kind = Ok + r.Val = val + return val +} + +func (r *Result[T, E]) SetErr(err E) E { + r.Kind = Err + r.Err = err + return err +} + diff --git a/kits/go/worker/worker.go b/kits/go/worker/worker.go index 373f496c..ddb4fcae 100644 --- a/kits/go/worker/worker.go +++ b/kits/go/worker/worker.go @@ -1,15 +1,20 @@ package worker import ( + "bytes" "context" "encoding/base64" + "errors" "fmt" "io" + "io/ioutil" "net/http" "os" "strings" "unicode/utf8" + "github.com/vmware-labs/wasm-workers-server/kits/go/worker/bindings" + "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) @@ -176,3 +181,87 @@ func Serve(handler http.Handler) { func ServeFunc(handler http.HandlerFunc) { handler(getWriterRequest()) } + +func SendHttpRequest(req *http.Request) (*http.Response, error) { + var method bindings.HttpTypesHttpMethod + switch req.Method { + case "GET": + method = bindings.HttpTypesHttpMethodGet() + case "POST": + method = bindings.HttpTypesHttpMethodPost() + case "PUT": + method = bindings.HttpTypesHttpMethodPut() + case "PATCH": + method = bindings.HttpTypesHttpMethodPatch() + case "DELETE": + method = bindings.HttpTypesHttpMethodDelete() + case "OPTIONS": + method = bindings.HttpTypesHttpMethodOptions() + case "HEAD": + method = bindings.HttpTypesHttpMethodHead() + default: + method = bindings.HttpTypesHttpMethodGet() + } + + // Iterate to get the headers + headers := make([]bindings.HttpTypesHttpHeader, 0, len(req.Header)) + for key, values := range req.Header { + for _, value := range values { + header := bindings.HttpTypesHttpHeader{F0: key, F1: value} + headers = append(headers, header) + } + } + + // Read the body request and convert it + body := []uint8{} + + if req.Body != nil { + readBody, err := ioutil.ReadAll(req.Body) + if err != nil { + return nil, err + } + defer req.Body.Close() + + body = readBody + } + + + // Convert body to []uint8 + bodyBytes := []uint8(body) + + bRequest := bindings.HttpTypesHttpRequest { + Body: bindings.Some(bodyBytes), + // Body: bindings.Some([]uint8{}), + Headers: headers, + Method: method, + Params: []bindings.HttpTypesHttpParam{}, + Uri: req.URL.String(), + } + + result := bindings.HttpSendHttpRequest(bRequest) + + if result.IsOk() { + response := result.Unwrap() + + // Create a new http.Response + httpResponse := &http.Response{} + httpResponse.StatusCode = int(response.Status) + + if response.Body.IsSome() { + body := response.Body.Unwrap() + httpResponse.Body = ioutil.NopCloser(bytes.NewReader(body)) + } + + // Set the headers + httpResponse.Header = make(http.Header) + for _, header := range response.Headers { + httpResponse.Header.Add(header.F0, header.F1) + } + + return httpResponse, nil + } else { + err := result.UnwrapErr() + + return nil, errors.New(err.Message) + } +} diff --git a/wit/go-ephemeral/http-types.wit b/wit/go-ephemeral/http-types.wit new file mode 100644 index 00000000..3db3a9af --- /dev/null +++ b/wit/go-ephemeral/http-types.wit @@ -0,0 +1,62 @@ +default interface http-types { + // URI + type uri = string + + // HTTP Status + type http-status = u16 + + // Header + type http-header = tuple + type http-headers = list + + // Methods + enum http-method { + get, + post, + put, + patch, + delete, + options, + head + } + + // URL params + type http-param = tuple + type http-params = list + + // The body content + type http-body = list + + // A complete HTTP request + record http-request { + body: option, + headers: http-headers, + method: http-method, + params: http-params, + uri: uri, + } + + // Return information about a failed request + record http-request-error { + error: http-error, + message: string + } + + // A complete HTTP response + record http-response { + body: option, + headers: http-headers, + status: http-status, + } + + // The list of errors + enum http-error { + invalid-request, + invalid-request-body, + invalid-response-body, + not-allowed, + internal-error, + timeout, + redirect-loop, + } +} diff --git a/wit/go-ephemeral/http.wit b/wit/go-ephemeral/http.wit new file mode 100644 index 00000000..ba8065d8 --- /dev/null +++ b/wit/go-ephemeral/http.wit @@ -0,0 +1,11 @@ +interface http-interface { + use pkg.http-types.{http-request,http-response,http-request-error} + + // Send a HTTP request from inside the worker. + send-http-request: func(request: http-request) -> result +} + +default world bindings { + // Send a HTTP request from inside the worker. + import http: self.http-interface +}