Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Go #129

Merged
merged 8 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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);
}