Skip to content

Commit

Permalink
Add support for Go (#129)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mnafees committed May 30, 2023
1 parent ec2fc6f commit 491e951
Show file tree
Hide file tree
Showing 21 changed files with 785 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
| ... | ... | ... |
Expand Down
1 change: 1 addition & 0 deletions docs/docs/features/dynamic-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/docs/features/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/docs/features/key-value.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/docs/get-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
331 changes: 331 additions & 0 deletions docs/docs/languages/go.md
Original file line number Diff line number Diff line change
@@ -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("<!DOCTYPE html>"+
"<head>"+
"<title>Wasm Workers Server</title>"+
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"+
"<meta charset=\"UTF-8\">"+
"<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/water.css@2/out/water.css\">"+
"<style>"+
"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; }"+
"</style>"+
"</head>"+
"<body>"+
"<main>"+
"<h1>Hello from Wasm Workers Server 👋</h1>"+
"<pre><code>Replying to %s<br>"+
"Method: %s<br>"+
"User Agent: %s<br>"+
"Payload: %s</code></pre>"+
"<p>"+
"This page was generated by a Go file running in WebAssembly."+
"</p>"+
"</main>"+
"</body>", 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 <http://127.0.0.1:8080/worker> 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("<!DOCTYPE html>"+
"<body>"+
"<h1>Key / Value store in Go</h1>"+
"<p>Counter: %d</p>"+
"<p>This page was generated by a Wasm module built from Go.</p>"+
"</body>", 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 <http://127.0.0.1:8080/worker-kv> 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))
2 changes: 1 addition & 1 deletion docs/src/components/HomepageFeatures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</>
),
},
Expand Down
6 changes: 5 additions & 1 deletion docs/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

a.menu__link[href*="languages/go"]::before {
background-image: url(/img/languages/go.svg);
}

0 comments on commit 491e951

Please sign in to comment.