From 491e951a9b6fea73d8cf2684c35a24eae498f09f Mon Sep 17 00:00:00 2001 From: Mohammed Nafees Date: Tue, 30 May 2023 15:05:12 +0530 Subject: [PATCH] Add support for Go (#129) * add go stub * add serve methods to go kit * add go-basic example * add go kv example * add go params example * add go envs example * use sjson and add Go docs --- README.md | 1 + docs/docs/features/dynamic-routes.md | 1 + docs/docs/features/environment-variables.md | 1 + docs/docs/features/key-value.md | 1 + docs/docs/get-started/quickstart.md | 1 + docs/docs/languages/go.md | 331 ++++++++++++++++++ docs/src/components/HomepageFeatures/index.js | 2 +- docs/src/css/custom.css | 6 +- docs/static/img/languages/go.svg | 8 + examples/go-basic/main.go | 59 ++++ examples/go-envs/envs.go | 18 + examples/go-envs/envs.toml | 5 + examples/go-kv/counter.go | 34 ++ examples/go-kv/counter.toml | 6 + examples/go-params/main.go | 41 +++ examples/go-params/public/main.css | 28 ++ examples/go-params/public/water.min.css | 30 ++ go.mod | 13 + go.sum | 10 + kits/go/worker/doc.go | 13 + kits/go/worker/worker.go | 178 ++++++++++ 21 files changed, 785 insertions(+), 2 deletions(-) create mode 100644 docs/docs/languages/go.md create mode 100644 docs/static/img/languages/go.svg create mode 100644 examples/go-basic/main.go create mode 100644 examples/go-envs/envs.go create mode 100644 examples/go-envs/envs.toml create mode 100644 examples/go-kv/counter.go create mode 100644 examples/go-kv/counter.toml create mode 100644 examples/go-params/main.go create mode 100644 examples/go-params/public/main.css create mode 100644 examples/go-params/public/water.min.css create mode 100644 go.mod create mode 100644 go.sum create mode 100644 kits/go/worker/doc.go create mode 100644 kits/go/worker/worker.go diff --git a/README.md b/README.md index 0738fbbe..2a0bd674 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ Wasm Workers Server focuses on simplicity. We want you to run workers (written i | --- | --- | --- | | Rust | ✅ | No | | JavaScript | ✅ | No | +| Go | ✅ | No | | Ruby | ✅ | [Yes](https://workers.wasmlabs.dev/docs/languages/ruby#installation) | | Python | ✅ | [Yes](https://workers.wasmlabs.dev/docs/languages/python#installation) | | ... | ... | ... | diff --git a/docs/docs/features/dynamic-routes.md b/docs/docs/features/dynamic-routes.md index 55416621..c4dea997 100644 --- a/docs/docs/features/dynamic-routes.md +++ b/docs/docs/features/dynamic-routes.md @@ -21,6 +21,7 @@ Check these guides to understand how to read parameters in the different support * [Dynamic routes in Rust](../languages/rust.md#dynamic-routes) * [Dynamic routes in Python](../languages/python.md#dynamic-routes) * [Dynamic routes in Ruby](../languages/ruby.md#dynamic-routes) +* [Dynamic routes in Go](../languages/go.md#dynamic-routes) ## Dynamic routes and folders diff --git a/docs/docs/features/environment-variables.md b/docs/docs/features/environment-variables.md index b46d13c6..4df03032 100644 --- a/docs/docs/features/environment-variables.md +++ b/docs/docs/features/environment-variables.md @@ -22,6 +22,7 @@ Then, you can read them in your worker: * [Read environment variables in Rust](../languages/rust.md#read-environment-variables) * [Read environment variables in Python](../languages/python.md#read-environment-variables) * [Read environment variables in Ruby](../languages/ruby.md#read-environment-variables) +* [Read environment variables in Go](../languages/go.md#read-environment-variables) ## Inject existing environment variables diff --git a/docs/docs/features/key-value.md b/docs/docs/features/key-value.md index ac0c8874..f077183d 100644 --- a/docs/docs/features/key-value.md +++ b/docs/docs/features/key-value.md @@ -20,6 +20,7 @@ The worker may access all the data and perform changes over it. Then, a new K/V * [Add a K/V store to Rust workers](../languages/rust.md#add-a-key--value-store) * [Add a K/V store to Python workers](../languages/python.md#add-a-key--value-store) * [Add a K/V store to Ruby workers](../languages/ruby.md#add-a-key--value-store) +* [Add a K/V store to Go workers](../languages/go.md#add-a-key--value-store) ## Limitations diff --git a/docs/docs/get-started/quickstart.md b/docs/docs/get-started/quickstart.md index 77de0384..92ee3e12 100644 --- a/docs/docs/get-started/quickstart.md +++ b/docs/docs/get-started/quickstart.md @@ -63,5 +63,6 @@ Now you got the taste of Wasm Workers, it's time to create your first worker: * [Create your first Rust worker](../languages/rust.md) * [Create your first Python worker](../languages/python.md) * [Create your first Ruby worker](../languages/ruby.md) +* [Create your first Go worker](../languages/go.md) And if you are curious, here you have a guide about [how it works](./how-it-works.md). \ No newline at end of file diff --git a/docs/docs/languages/go.md b/docs/docs/languages/go.md new file mode 100644 index 00000000..9cac573c --- /dev/null +++ b/docs/docs/languages/go.md @@ -0,0 +1,331 @@ +--- +sidebar_position: 5 +--- + +# Go + +Go workers are compiled into a WASI module using [TinyGo](https://tinygo.org/docs/guides/webassembly/). Then, they are loaded by Wasm Workers Server and start processing requests. + +## Your first Go worker + +Workers can be implemented either as an [http.Handler](https://pkg.go.dev/net/http#Handler) or an [http.HandlerFunc](https://pkg.go.dev/net/http#HandlerFunc). + +In this example, the worker will get a request and print all the related information. + +1. Create a new Go mod project + + ``` + go mod init workers-in-go + ``` + +1. Add the Wasm Workers Server Go dependency + + ``` + go get -u github.com/vmware-labs/wasm-workers-server/kits/go/worker + ``` + +1. Create a `worker.go` file with the following contents: + + ```go title="worker.go" + package main + + import ( + "net/http" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" + ) + + func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte("Hello wasm!")) + }) + } + ``` + +1. Additionally, you can now go further add all the information from the received `http.Request`: + + ```go title="worker.go" + package main + + import ( + "fmt" + "io" + "net/http" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" + ) + + func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + var payload string + + reqBody, err := io.ReadAll(r.Body) + if err != nil { + panic(err) + } + r.Body.Close() + + if len(reqBody) == 0 { + payload = "-" + } else { + payload = string(reqBody) + } + + body := fmt.Sprintf(""+ + ""+ + "Wasm Workers Server"+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + "
"+ + "

Hello from Wasm Workers Server 👋

"+ + "
Replying to %s
"+ + "Method: %s
"+ + "User Agent: %s
"+ + "Payload: %s
"+ + "

"+ + "This page was generated by a Go file running in WebAssembly."+ + "

"+ + "
"+ + "", r.URL.String(), r.Method, r.UserAgent(), payload) + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(body)) + }) + } + ``` + +1. In this case, you need to compile the project to Wasm ([WASI](https://wasi.dev/)). To do this, make sure you have installed the TinyGo compiler by following the steps [here](https://tinygo.org/getting-started/install/): + + ```bash + tinygo build -o worker.wasm -target wasi worker.go + ``` + +1. Run your worker with `wws`. If you didn't download the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide. + + ```bash + wws . + + ⚙️ Loading routes from: . + 🗺 Detected routes: + - http://127.0.0.1:8080/worker + => worker.wasm (name: default) + 🚀 Start serving requests at http://127.0.0.1:8080 + ``` + +1. Finally, open in your browser. + +## Add a Key / Value store + +Wasm Workers allows you to add a Key / Value store to your workers. Read more information about this feature in the [Key / Value store](../features/key-value.md) section. + +To add a KV store to your worker, follow these steps: + +1. Create a new Go project: + + ```bash + go mod init worker-kv + ``` + +1. Add the Wasm Workers Server Go dependency + + ``` + go get -u github.com/vmware-labs/wasm-workers-server/kits/go/worker + ``` + +1. Create a `worker-kv.go` file with the following contents: + + ```go title="worker-kv.go" + package main + + import ( + "net/http" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" + ) + + func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte("Hello wasm!")) + }) + } + ``` + +1. Then, let's read a value from the cache and update it: + + ```go title="worker-kv.go" + package main + + import ( + "fmt" + "net/http" + "strconv" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" + ) + + func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + cache, _ := r.Context().Value(worker.CacheKey).(map[string]string) + + var countNum uint32 + + if count, ok := cache["counter"]; ok { + n, _ := strconv.ParseUint(count, 10, 32) + countNum = uint32(n) + } + + body := fmt.Sprintf(""+ + ""+ + "

Key / Value store in Go

"+ + "

Counter: %d

"+ + "

This page was generated by a Wasm module built from Go.

"+ + "", countNum) + + cache["counter"] = fmt.Sprintf("%d", countNum+1) + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(body)) + }) + } + ``` + +1. Compile the project to Wasm ([WASI](https://wasi.dev/)): + + ```bash + tinygo build -o worker-kv.wasm -target wasi worker-kv.go + ``` + +1. Create a `worker-kv.toml` file with the following content. Note the name of the TOML file must match the name of the worker. In this case we have `worker-kv.wasm` and `worker-kv.toml` in the same folder: + + ```toml title="worker-kv.toml" + name = "workerkv" + version = "1" + + [data] + [data.kv] + namespace = "workerkv" + ``` + +1. Run your worker with `wws`. If you didn't download the `wws` server yet, check our [Getting Started](../get-started/quickstart.md) guide. + + ```bash + wws . + + ⚙️ Loading routes from: . + 🗺 Detected routes: + - http://127.0.0.1:8080/worker-kv + => worker-kv.wasm (name: default) + 🚀 Start serving requests at http://127.0.0.1:8080 + ``` + +1. Finally, open in your browser. + +## Dynamic routes + +You can define [dynamic routes by adding route parameters to your worker files](../features/dynamic-routes.md) (like `[id].wasm`). To read them in Go, follow these steps: + +1. Use the `worker.ParamsKey` context value to read in the passed in parameters: + + ```go title="main.go" + package main + + import ( + "fmt" + "net/http" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" + ) + + func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + params, _ := r.Context().Value(worker.ParamsKey).(map[string]string) + ... + }) + } + ``` + +1. Then, you can read the values as follows: + + ```go title="main.go" + package main + + import ( + "fmt" + "net/http" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" + ) + + func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + params, _ := r.Context().Value(worker.ParamsKey).(map[string]string) + id := "the value is not available" + + if val, ok := params["id"]; ok { + id = val + } + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(fmt.Sprintf("Hey! The parameter is: %s", id))) + }) + } + ``` + +## Read environment variables + +Environment variables are configured [via the related TOML configuration file](../features/environment-variables.md). These variables are accessible via `os.Getenv` in your worker. To read them, just use the same name you configured in your TOML file: + +```toml title="envs.toml" +name = "envs" +version = "1" + +[vars] +MESSAGE = "Hello 👋! This message comes from an environment variable" +``` + +Now, you can read the `MESSAGE` variable using the [`os.Getenv`](https://pkg.go.dev/os#Getenv) function: + +```go title="envs.go" +package main + +import ( + "fmt" + "net/http" + "os" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" +) + +func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + body := fmt.Sprintf("The message is: %s", os.Getenv("MESSAGE")) + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(body)) + }) +} + +``` + +If you prefer, you can configure the environment variable value dynamically by following [these instructions](../features/environment-variables.md#inject-existing-environment-variables). + +## Other examples + +* [Basic](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/go-basic) +* [Counter](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/go-kv) + +The Go kit was originally authored by Mohammed Nafees ([@mnafees](https://github.com/mnafees)) diff --git a/docs/src/components/HomepageFeatures/index.js b/docs/src/components/HomepageFeatures/index.js index 0bfce5bc..42b4e46a 100644 --- a/docs/src/components/HomepageFeatures/index.js +++ b/docs/src/components/HomepageFeatures/index.js @@ -17,7 +17,7 @@ const FeatureList = [ emoji: "⚙️", description: ( <> - Create workers in different languages like JavaScript, Ruby, Python and Rust thanks to WebAssembly. + Create workers in different languages like JavaScript, Ruby, Python, Rust and Go thanks to WebAssembly. ), }, diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 4b3844ec..08b1d584 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -99,4 +99,8 @@ a.menu__link[href*="languages/ruby"]::before { a.menu__link[href*="languages/rust"]::before { background-image: url(/img/languages/rust.svg); -} \ No newline at end of file +} + +a.menu__link[href*="languages/go"]::before { + background-image: url(/img/languages/go.svg); +} diff --git a/docs/static/img/languages/go.svg b/docs/static/img/languages/go.svg new file mode 100644 index 00000000..a17ee14f --- /dev/null +++ b/docs/static/img/languages/go.svg @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/examples/go-basic/main.go b/examples/go-basic/main.go new file mode 100644 index 00000000..fbc6673b --- /dev/null +++ b/examples/go-basic/main.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "io" + "net/http" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" +) + +func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + var payload string + + reqBody, err := io.ReadAll(r.Body) + if err != nil { + panic(err) + } + r.Body.Close() + + if len(reqBody) == 0 { + payload = "-" + } else { + payload = string(reqBody) + } + + body := fmt.Sprintf(""+ + ""+ + "Wasm Workers Server"+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + "
"+ + "

Hello from Wasm Workers Server 👋

"+ + "
Replying to %s
"+ + "Method: %s
"+ + "User Agent: %s
"+ + "Payload: %s
"+ + "

"+ + "This page was generated by a Go file running in WebAssembly."+ + "

"+ + "
"+ + "", r.URL.String(), r.Method, r.UserAgent(), payload) + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(body)) + }) +} diff --git a/examples/go-envs/envs.go b/examples/go-envs/envs.go new file mode 100644 index 00000000..dbb3049b --- /dev/null +++ b/examples/go-envs/envs.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "net/http" + "os" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" +) + +func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + body := fmt.Sprintf("The environment variable value is: %s", os.Getenv("MESSAGE")) + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(body)) + }) +} diff --git a/examples/go-envs/envs.toml b/examples/go-envs/envs.toml new file mode 100644 index 00000000..4c90dfc5 --- /dev/null +++ b/examples/go-envs/envs.toml @@ -0,0 +1,5 @@ +name = "envs" +version = "1" + +[vars] +MESSAGE = "Hello! This message comes from an environment variable" \ No newline at end of file diff --git a/examples/go-kv/counter.go b/examples/go-kv/counter.go new file mode 100644 index 00000000..b08c2f67 --- /dev/null +++ b/examples/go-kv/counter.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" +) + +func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + cache, _ := r.Context().Value(worker.CacheKey).(map[string]string) + + var countNum uint32 + + if count, ok := cache["counter"]; ok { + n, _ := strconv.ParseUint(count, 10, 32) + countNum = uint32(n) + } + + body := fmt.Sprintf(""+ + ""+ + "

Key / Value store in Go

"+ + "

Counter: %d

"+ + "

This page was generated by a Wasm module built from Go.

"+ + "", countNum) + + cache["counter"] = fmt.Sprintf("%d", countNum+1) + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(body)) + }) +} diff --git a/examples/go-kv/counter.toml b/examples/go-kv/counter.toml new file mode 100644 index 00000000..aa9f6a95 --- /dev/null +++ b/examples/go-kv/counter.toml @@ -0,0 +1,6 @@ +name = "counter" +version = "1" + +[data] +[data.kv] +namespace = "counter" \ No newline at end of file diff --git a/examples/go-params/main.go b/examples/go-params/main.go new file mode 100644 index 00000000..63215a53 --- /dev/null +++ b/examples/go-params/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "net/http" + + "github.com/vmware-labs/wasm-workers-server/kits/go/worker" +) + +func main() { + worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) { + params, _ := r.Context().Value(worker.ParamsKey).(map[string]string) + id := "the value is not available" + + if val, ok := params["id"]; ok { + id = val + } + + body := fmt.Sprintf(""+ + ""+ + "Wasm Workers Server"+ + ""+ + ""+ + ""+ + ""+ + ""+ + ""+ + "
"+ + "

Hello from Wasm Workers Server 👋

"+ + "

"+ + "This is a dynamic route! The [id].wasm worker, written in Go, is replying this URL."+ + "The id parameter value is: %s"+ + "

"+ + "

Read more about dynamic routes in the documentation

"+ + "
"+ + "", id) + + w.Header().Set("x-generated-by", "wasm-workers-server") + w.Write([]byte(body)) + }) +} diff --git a/examples/go-params/public/main.css b/examples/go-params/public/main.css new file mode 100644 index 00000000..ca178402 --- /dev/null +++ b/examples/go-params/public/main.css @@ -0,0 +1,28 @@ +body { + max-width: 1000px; +} + +main { + margin: 5rem 0; +} + +h1, +p { + text-align: center; +} + +h1 { + margin-bottom: 2rem; +} + +pre { + font-size: .9rem; +} + +pre>code { + padding: 2rem; +} + +p { + margin-top: 2rem; +} \ No newline at end of file diff --git a/examples/go-params/public/water.min.css b/examples/go-params/public/water.min.css new file mode 100644 index 00000000..fddfc43d --- /dev/null +++ b/examples/go-params/public/water.min.css @@ -0,0 +1,30 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2019 Kognise + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the “Software”), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * + * Ref: https://github.com/kognise/water.css + */ +:root{--background-body:#fff;--background:#efefef;--background-alt:#f7f7f7;--selection:#9e9e9e;--text-main:#363636;--text-bright:#000;--text-muted:#70777f;--links:#0076d1;--focus:rgba(0,150,191,0.67);--border:#dbdbdb;--code:#000;--animation-duration:0.1s;--button-base:#d0cfcf;--button-hover:#9b9b9b;--scrollbar-thumb:#aaa;--scrollbar-thumb-hover:var(--button-hover);--form-placeholder:#949494;--form-text:#1d1d1d;--variable:#39a33c;--highlight:#ff0;--select-arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23161f27'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E")}@media (prefers-color-scheme:dark){:root{--background-body:#202b38;--background:#161f27;--background-alt:#1a242f;--selection:#1c76c5;--text-main:#dbdbdb;--text-bright:#fff;--text-muted:#a9b1ba;--links:#41adff;--focus:rgba(0,150,191,0.67);--border:#526980;--code:#ffbe85;--animation-duration:0.1s;--button-base:#0c151c;--button-hover:#040a0f;--scrollbar-thumb:var(--button-hover);--scrollbar-thumb-hover:#000;--form-placeholder:#a9a9a9;--form-text:#fff;--variable:#d941e2;--highlight:#efdb43;--select-arrow:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E")}}html{scrollbar-color:#aaa #fff;scrollbar-color:var(--scrollbar-thumb) var(--background-body);scrollbar-width:thin}@media (prefers-color-scheme:dark){html{scrollbar-color:#040a0f #202b38;scrollbar-color:var(--scrollbar-thumb) var(--background-body)}}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Segoe UI Emoji,Apple Color Emoji,Noto Color Emoji,sans-serif;line-height:1.4;max-width:800px;margin:20px auto;padding:0 10px;word-wrap:break-word;color:#363636;color:var(--text-main);background:#fff;background:var(--background-body);text-rendering:optimizeLegibility}@media (prefers-color-scheme:dark){body{background:#202b38;background:var(--background-body);color:#dbdbdb;color:var(--text-main)}}button{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){button{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}input{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){input{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}@media (prefers-color-scheme:dark){textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease;transition:background-color var(--animation-duration) linear,border-color var(--animation-duration) linear,color var(--animation-duration) linear,box-shadow var(--animation-duration) linear,transform var(--animation-duration) ease}}h1{font-size:2.2em;margin-top:0}h1,h2,h3,h4,h5,h6{margin-bottom:12px;margin-top:24px}h1{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h1{color:#fff;color:var(--text-bright)}}h2{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h2{color:#fff;color:var(--text-bright)}}h3{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h3{color:#fff;color:var(--text-bright)}}h4{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h4{color:#fff;color:var(--text-bright)}}h5{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h5{color:#fff;color:var(--text-bright)}}h6{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){h6{color:#fff;color:var(--text-bright)}}strong{color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){strong{color:#fff;color:var(--text-bright)}}b,h1,h2,h3,h4,h5,h6,strong,th{font-weight:600}q:after,q:before{content:none}blockquote{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus);margin:1.5em 0;padding:.5em 1em;font-style:italic}@media (prefers-color-scheme:dark){blockquote{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus)}}q{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus);margin:1.5em 0;padding:.5em 1em;font-style:italic}@media (prefers-color-scheme:dark){q{border-left:4px solid rgba(0,150,191,.67);border-left:4px solid var(--focus)}}blockquote>footer{font-style:normal;border:0}address,blockquote cite{font-style:normal}a[href^=mailto\:]:before{content:"📧 "}a[href^=tel\:]:before{content:"📞 "}a[href^=sms\:]:before{content:"💬 "}mark{background-color:#ff0;background-color:var(--highlight);border-radius:2px;padding:0 2px;color:#000}@media (prefers-color-scheme:dark){mark{background-color:#efdb43;background-color:var(--highlight)}}a>code,a>strong{color:inherit}button,input[type=button],input[type=checkbox],input[type=radio],input[type=range],input[type=reset],input[type=submit],select{cursor:pointer}input,select{display:block}[type=checkbox],[type=radio]{display:initial}input{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){input{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}button{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){button{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}textarea{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){textarea{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}select{color:#1d1d1d;color:var(--form-text);background-color:#efefef;background-color:var(--background);font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}@media (prefers-color-scheme:dark){select{background-color:#161f27;background-color:var(--background);color:#fff;color:var(--form-text)}}button{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){button{background-color:#0c151c;background-color:var(--button-base)}}input[type=submit]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=submit]{background-color:#0c151c;background-color:var(--button-base)}}input[type=reset]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=reset]{background-color:#0c151c;background-color:var(--button-base)}}input[type=button]{background-color:#d0cfcf;background-color:var(--button-base);padding-right:30px;padding-left:30px}@media (prefers-color-scheme:dark){input[type=button]{background-color:#0c151c;background-color:var(--button-base)}}button:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){button:hover{background:#040a0f;background:var(--button-hover)}}input[type=submit]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=submit]:hover{background:#040a0f;background:var(--button-hover)}}input[type=reset]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=reset]:hover{background:#040a0f;background:var(--button-hover)}}input[type=button]:hover{background:#9b9b9b;background:var(--button-hover)}@media (prefers-color-scheme:dark){input[type=button]:hover{background:#040a0f;background:var(--button-hover)}}input[type=color]{min-height:2rem;padding:8px;cursor:pointer}input[type=checkbox],input[type=radio]{height:1em;width:1em}input[type=radio]{border-radius:100%}input{vertical-align:top}label{vertical-align:middle;margin-bottom:4px;display:inline-block}button,input:not([type=checkbox]):not([type=radio]),input[type=range],select,textarea{-webkit-appearance:none}textarea{display:block;margin-right:0;box-sizing:border-box;resize:vertical}textarea:not([cols]){width:100%}textarea:not([rows]){min-height:40px;height:140px}select{background:#efefef url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23161f27'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat;background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat;padding-right:35px}@media (prefers-color-scheme:dark){select{background:#161f27 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='63' width='117' fill='%23efefef'%3E%3Cpath d='M115 2c-1-2-4-2-5 0L59 53 7 2a4 4 0 00-5 5l54 54 2 2 3-2 54-54c2-1 2-4 0-5z'/%3E%3C/svg%3E") calc(100% - 12px) 50%/12px no-repeat;background:var(--background) var(--select-arrow) calc(100% - 12px) 50%/12px no-repeat}}select::-ms-expand{display:none}select[multiple]{padding-right:10px;background-image:none;overflow-y:auto}input:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){input:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}select:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){select:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}button:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){button:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}@media (prefers-color-scheme:dark){textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67);box-shadow:0 0 0 2px var(--focus)}}button:active,input[type=button]:active,input[type=checkbox]:active,input[type=radio]:active,input[type=range]:active,input[type=reset]:active,input[type=submit]:active{transform:translateY(2px)}button:disabled,input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;opacity:.5}::-moz-placeholder{color:#949494;color:var(--form-placeholder)}:-ms-input-placeholder{color:#949494;color:var(--form-placeholder)}::-ms-input-placeholder{color:#949494;color:var(--form-placeholder)}::placeholder{color:#949494;color:var(--form-placeholder)}@media (prefers-color-scheme:dark){::-moz-placeholder{color:#a9a9a9;color:var(--form-placeholder)}:-ms-input-placeholder{color:#a9a9a9;color:var(--form-placeholder)}::-ms-input-placeholder{color:#a9a9a9;color:var(--form-placeholder)}::placeholder{color:#a9a9a9;color:var(--form-placeholder)}}fieldset{border:1px solid rgba(0,150,191,.67);border:1px solid var(--focus);border-radius:6px;margin:0 0 12px;padding:10px}@media (prefers-color-scheme:dark){fieldset{border:1px solid rgba(0,150,191,.67);border:1px solid var(--focus)}}legend{font-size:.9em;font-weight:600}input[type=range]{margin:10px 0;padding:10px 0;background:transparent}input[type=range]:focus{outline:none}input[type=range]::-webkit-slider-runnable-track{width:100%;height:9.5px;-webkit-transition:.2s;transition:.2s;background:#efefef;background:var(--background);border-radius:3px}@media (prefers-color-scheme:dark){input[type=range]::-webkit-slider-runnable-track{background:#161f27;background:var(--background)}}input[type=range]::-webkit-slider-thumb{box-shadow:0 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border);-webkit-appearance:none;margin-top:-7px}@media (prefers-color-scheme:dark){input[type=range]::-webkit-slider-thumb{background:#526980;background:var(--border)}}input[type=range]:focus::-webkit-slider-runnable-track{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-webkit-slider-runnable-track{background:#161f27;background:var(--background)}}input[type=range]::-moz-range-track{width:100%;height:9.5px;-moz-transition:.2s;transition:.2s;background:#efefef;background:var(--background);border-radius:3px}@media (prefers-color-scheme:dark){input[type=range]::-moz-range-track{background:#161f27;background:var(--background)}}input[type=range]::-moz-range-thumb{box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border)}@media (prefers-color-scheme:dark){input[type=range]::-moz-range-thumb{background:#526980;background:var(--border)}}input[type=range]::-ms-track{width:100%;height:9.5px;background:transparent;border-color:transparent;border-width:16px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#efefef;background:var(--background);border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d}@media (prefers-color-scheme:dark){input[type=range]::-ms-fill-lower{background:#161f27;background:var(--background)}}input[type=range]::-ms-fill-upper{background:#efefef;background:var(--background);border:.2px solid #010101;border-radius:3px;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d}@media (prefers-color-scheme:dark){input[type=range]::-ms-fill-upper{background:#161f27;background:var(--background)}}input[type=range]::-ms-thumb{box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;border:1px solid #000;height:20px;width:20px;border-radius:50%;background:#dbdbdb;background:var(--border)}@media (prefers-color-scheme:dark){input[type=range]::-ms-thumb{background:#526980;background:var(--border)}}input[type=range]:focus::-ms-fill-lower{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-ms-fill-lower{background:#161f27;background:var(--background)}}input[type=range]:focus::-ms-fill-upper{background:#efefef;background:var(--background)}@media (prefers-color-scheme:dark){input[type=range]:focus::-ms-fill-upper{background:#161f27;background:var(--background)}}a{text-decoration:none;color:#0076d1;color:var(--links)}@media (prefers-color-scheme:dark){a{color:#41adff;color:var(--links)}}a:hover{text-decoration:underline}code{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){code{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}samp{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){samp{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}time{background:#efefef;background:var(--background);color:#000;color:var(--code);padding:2.5px 5px;border-radius:6px;font-size:1em}@media (prefers-color-scheme:dark){time{color:#ffbe85;color:var(--code);background:#161f27;background:var(--background)}}pre>code{padding:10px;display:block;overflow-x:auto}var{color:#39a33c;color:var(--variable);font-style:normal;font-family:monospace}@media (prefers-color-scheme:dark){var{color:#d941e2;color:var(--variable)}}kbd{background:#efefef;background:var(--background);border:1px solid #dbdbdb;border:1px solid var(--border);border-radius:2px;color:#363636;color:var(--text-main);padding:2px 4px}@media (prefers-color-scheme:dark){kbd{color:#dbdbdb;color:var(--text-main);border:1px solid #526980;border:1px solid var(--border);background:#161f27;background:var(--background)}}img,video{max-width:100%;height:auto}hr{border:none;border-top:1px solid #dbdbdb;border-top:1px solid var(--border)}@media (prefers-color-scheme:dark){hr{border-top:1px solid #526980;border-top:1px solid var(--border)}}table{border-collapse:collapse;margin-bottom:10px;width:100%;table-layout:fixed}table caption,td,th{text-align:left}td,th{padding:6px;vertical-align:top;word-wrap:break-word}thead{border-bottom:1px solid #dbdbdb;border-bottom:1px solid var(--border)}@media (prefers-color-scheme:dark){thead{border-bottom:1px solid #526980;border-bottom:1px solid var(--border)}}tfoot{border-top:1px solid #dbdbdb;border-top:1px solid var(--border)}@media (prefers-color-scheme:dark){tfoot{border-top:1px solid #526980;border-top:1px solid var(--border)}}tbody tr:nth-child(2n){background-color:#efefef;background-color:var(--background)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n){background-color:#161f27;background-color:var(--background)}}tbody tr:nth-child(2n) button{background-color:#f7f7f7;background-color:var(--background-alt)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n) button{background-color:#1a242f;background-color:var(--background-alt)}}tbody tr:nth-child(2n) button:hover{background-color:#fff;background-color:var(--background-body)}@media (prefers-color-scheme:dark){tbody tr:nth-child(2n) button:hover{background-color:#202b38;background-color:var(--background-body)}}::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;background:var(--background);border-radius:6px}@media (prefers-color-scheme:dark){::-webkit-scrollbar-track{background:#161f27;background:var(--background)}}::-webkit-scrollbar-thumb{background:#aaa;background:var(--scrollbar-thumb);border-radius:6px}@media (prefers-color-scheme:dark){::-webkit-scrollbar-thumb{background:#040a0f;background:var(--scrollbar-thumb)}}::-webkit-scrollbar-thumb:hover{background:#9b9b9b;background:var(--scrollbar-thumb-hover)}@media (prefers-color-scheme:dark){::-webkit-scrollbar-thumb:hover{background:#000;background:var(--scrollbar-thumb-hover)}}::-moz-selection{background-color:#9e9e9e;background-color:var(--selection);color:#000;color:var(--text-bright)}::selection{background-color:#9e9e9e;background-color:var(--selection);color:#000;color:var(--text-bright)}@media (prefers-color-scheme:dark){::-moz-selection{color:#fff;color:var(--text-bright)}::selection{color:#fff;color:var(--text-bright)}}@media (prefers-color-scheme:dark){::-moz-selection{background-color:#1c76c5;background-color:var(--selection)}::selection{background-color:#1c76c5;background-color:var(--selection)}}details{display:flex;flex-direction:column;align-items:flex-start;background-color:#f7f7f7;background-color:var(--background-alt);padding:10px 10px 0;margin:1em 0;border-radius:6px;overflow:hidden}@media (prefers-color-scheme:dark){details{background-color:#1a242f;background-color:var(--background-alt)}}details[open]{padding:10px}details>:last-child{margin-bottom:0}details[open] summary{margin-bottom:10px}summary{display:list-item;background-color:#efefef;background-color:var(--background);padding:10px;margin:-10px -10px 0;cursor:pointer;outline:none}@media (prefers-color-scheme:dark){summary{background-color:#161f27;background-color:var(--background)}}summary:focus,summary:hover{text-decoration:underline}details>:not(summary){margin-top:0}summary::-webkit-details-marker{color:#363636;color:var(--text-main)}@media (prefers-color-scheme:dark){summary::-webkit-details-marker{color:#dbdbdb;color:var(--text-main)}}dialog{background-color:#f7f7f7;background-color:var(--background-alt);color:#363636;color:var(--text-main);border-radius:6px;border:#dbdbdb;border-color:var(--border);padding:10px 30px}@media (prefers-color-scheme:dark){dialog{border-color:#526980;border-color:var(--border);color:#dbdbdb;color:var(--text-main);background-color:#1a242f;background-color:var(--background-alt)}}dialog>header:first-child{background-color:#efefef;background-color:var(--background);border-radius:6px 6px 0 0;margin:-10px -30px 10px;padding:10px;text-align:center}@media (prefers-color-scheme:dark){dialog>header:first-child{background-color:#161f27;background-color:var(--background)}}dialog::-webkit-backdrop{background:rgba(0,0,0,.61);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}dialog::backdrop{background:rgba(0,0,0,.61);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}footer{border-top:1px solid #dbdbdb;border-top:1px solid var(--border);padding-top:10px;color:#70777f;color:var(--text-muted)}@media (prefers-color-scheme:dark){footer{color:#a9b1ba;color:var(--text-muted);border-top:1px solid #526980;border-top:1px solid var(--border)}}body>footer{margin-top:40px}@media print{body,button,code,details,input,pre,summary,textarea{background-color:#fff}button,input,textarea{border:1px solid #000}body,button,code,footer,h1,h2,h3,h4,h5,h6,input,pre,strong,summary,textarea{color:#000}summary::marker{color:#000}summary::-webkit-details-marker{color:#000}tbody tr:nth-child(2n){background-color:#f2f2f2}a{color:#00f;text-decoration:underline}} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..5d47074a --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/vmware-labs/wasm-workers-server + +go 1.20 + +require ( + github.com/tidwall/gjson v1.14.4 + github.com/tidwall/sjson v1.2.5 +) + +require ( + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..a70a5e0a --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +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= diff --git a/kits/go/worker/doc.go b/kits/go/worker/doc.go new file mode 100644 index 00000000..2f1fbafb --- /dev/null +++ b/kits/go/worker/doc.go @@ -0,0 +1,13 @@ +/** + * + * === Go support for WASM Workers Server === + * + * This package provides a simple way to write WASM workers in Go. It uses the gjson, sjson libraries instead + * of Go's standard encoding/json package due to the following reasons: + * -- as of writing this file, the default Go compiler does not support the WASI backend, + * -- TinyGo (which does support WASI) does not support reflection and hence, we need to rely on a JSON library + * that does not use reflection + * + */ + +package worker diff --git a/kits/go/worker/worker.go b/kits/go/worker/worker.go new file mode 100644 index 00000000..373f496c --- /dev/null +++ b/kits/go/worker/worker.go @@ -0,0 +1,178 @@ +package worker + +import ( + "context" + "encoding/base64" + "fmt" + "io" + "net/http" + "os" + "strings" + "unicode/utf8" + + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +type ContextKey string + +const ( + CacheKey ContextKey = "CACHE" + ParamsKey ContextKey = "PARAMS" +) + +type input struct { + Url string + Method string + Headers map[string]string + Body string +} + +type output struct { + Data string + Headers map[string]string + Status uint16 + Base64 bool + + httpHeader http.Header +} + +var ( + cache map[string]string + params map[string]string +) + +func init() { + cache = make(map[string]string) + params = make(map[string]string) +} + +// output implements the http.ResponseWriter interface + +func (o *output) Header() http.Header { + if o.httpHeader == nil { + o.httpHeader = http.Header{} + } + + return o.httpHeader +} + +func (o *output) Write(data []byte) (int, error) { + if utf8.Valid(data) { + o.Data = string(data) + } else { + o.Base64 = true + o.Data = base64.StdEncoding.EncodeToString(data) + } + + if o.Status == 0 { + o.Status = 200 + } + + for k, v := range o.httpHeader { + o.Headers[k] = v[0] + } + + out, _ := sjson.Set("", "data", o.Data) + out, _ = sjson.Set(out, "status", o.Status) + out, _ = sjson.Set(out, "base64", o.Base64) + out, _ = sjson.SetRaw(out, "headers", "{}") + out, _ = sjson.SetRaw(out, "kv", "{}") + + for k, v := range o.Headers { + out, _ = sjson.Set(out, fmt.Sprintf("headers.%s", k), v) + } + + for k, v := range cache { + out, _ = sjson.Set(out, fmt.Sprintf("kv.%s", k), v) + } + + fmt.Println(out) + + return len(o.Data), nil +} + +func (o *output) WriteHeader(statusCode int) { + o.Status = uint16(statusCode) +} + +func readInput() (*input, error) { + stdin, err := io.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + + in := &input{ + Url: gjson.GetBytes(stdin, "url").String(), + Method: gjson.GetBytes(stdin, "method").String(), + Body: gjson.GetBytes(stdin, "body").String(), + Headers: make(map[string]string), + } + + if gjson.GetBytes(stdin, "headers").Exists() { + gjson.GetBytes(stdin, "headers").ForEach(func(key, value gjson.Result) bool { + in.Headers[key.String()] = value.String() + return true + }) + } + + if gjson.GetBytes(stdin, "kv").Exists() { + gjson.GetBytes(stdin, "kv").ForEach(func(key, value gjson.Result) bool { + cache[key.String()] = value.String() + return true + }) + } + + if gjson.GetBytes(stdin, "params").Exists() { + gjson.GetBytes(stdin, "params").ForEach(func(key, value gjson.Result) bool { + params[key.String()] = value.String() + return true + }) + } + + return in, nil +} + +func createRequest(in *input) (*http.Request, error) { + req, err := http.NewRequest(in.Method, in.Url, strings.NewReader(in.Body)) + if err != nil { + return nil, err + } + + for k, v := range in.Headers { + req.Header.Set(k, v) + } + + req = req.WithContext(context.WithValue(req.Context(), CacheKey, cache)) + req = req.WithContext(context.WithValue(req.Context(), ParamsKey, params)) + + return req, nil +} + +func getWriterRequest() (*output, *http.Request) { + in, err := readInput() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + req, err := createRequest(in) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + w := &output{ + Headers: make(map[string]string), + } + + return w, req +} + +func Serve(handler http.Handler) { + handler.ServeHTTP(getWriterRequest()) +} + +func ServeFunc(handler http.HandlerFunc) { + handler(getWriterRequest()) +}