Skip to main content

Go

Go workers are compiled into a WASI module using TinyGo. 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 or an 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
  2. Add the Wasm Workers Server Go dependency

    go get -u github.com/vmware-labs/wasm-workers-server/kits/go/[email protected]
  3. Create a worker.go file with the following contents:

    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!"))
    })
    }
  4. Additionally, you can now go further add all the information from the received http.Request:

    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))
    })
    }
  5. In this case, you need to compile the project to Wasm (WASI). To do this, make sure you have installed the TinyGo compiler by following the steps here:

    tinygo build -o worker.wasm -target wasi worker.go
  6. Run your worker with wws. If you didn't download the wws server yet, check our Getting Started guide.

    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
  7. 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 section.

To add a KV store to your worker, follow these steps:

  1. Create a new Go project:

    go mod init worker-kv
  2. Add the Wasm Workers Server Go dependency

    go get -u github.com/vmware-labs/wasm-workers-server/kits/go/[email protected]
  3. Create a worker-kv.go file with the following contents:

    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!"))
    })
    }
  4. Then, let's read a value from the cache and update it:

    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))
    })
    }
  5. Compile the project to Wasm (WASI):

    tinygo build -o worker-kv.wasm -target wasi worker-kv.go
  6. 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:

    worker-kv.toml
    name = "workerkv"
    version = "1"

    [data]
    [data.kv]
    namespace = "workerkv"
  7. Run your worker with wws. If you didn't download the wws server yet, check our Getting Started guide.

    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
  8. Finally, open http://127.0.0.1:8080/worker-kv in your browser.

Send an HTTP request

Wasm Workers allows you to send HTTP requests from your workers. Read more information about this feature in the HTTP Requests section.

To perform HTTP requests from your worker, follow these steps:

  1. Create a new Go project:

    go mod init fetch
  2. Add the project dependencies:

    go get -u github.com/vmware-labs/wasm-workers-server/kits/go/[email protected] \
    github.com/tidwall/gjson
  3. Create a fetch.go file with the following contents:

    fetch.go
    package main

    import (
    "io"
    "fmt"
    "net/http"

    "github.com/vmware-labs/wasm-workers-server/kits/go/worker"

    "github.com/tidwall/gjson"
    )

    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!"))
    })
    }
  4. Then, let's create the http.Request instance and pass it to the worker.SendHttpRequest method. In this example, we will call the {JSON} Placeholder API to retrieve a Post. You will read the content of the response using the gjson API:

    fetch.go
    package main

    import (
    "io"
    "fmt"
    "net/http"

    "github.com/vmware-labs/wasm-workers-server/kits/go/worker"

    "github.com/tidwall/gjson"
    )

    func main() {
    worker.ServeFunc(func(w http.ResponseWriter, r *http.Request) {
    url := "https://jsonplaceholder.typicode.com/posts/1"

    // Create the request
    req, err := http.NewRequest(http.MethodGet, url, nil)
    if err != nil {
    panic(err)
    }

    // Send the request
    res, err := worker.SendHttpRequest(req)
    if err != nil {
    msg := fmt.Sprintf("Error when calling the API: %s", err);

    // Send the reponse
    w.Write([]byte(msg))

    return
    }

    // Read the response and parse the title
    resBody, err := io.ReadAll(res.Body)
    if err != nil {
    msg := fmt.Sprintf("Error when reading the response body: %s", err);

    // Send the reponse
    w.Write([]byte(msg))

    return
    }
    res.Body.Close()

    title := gjson.Get(string(resBody), "title")

    w.Header().Set("x-generated-by", "wasm-workers-server")
    w.Write([]byte(fmt.Sprintf("Title: %s", title.String())))
    })
    }
  5. Compile the project to Wasm (WASI):

    tinygo build -o fetch.wasm -target wasi fetch.go
  6. Create a fetch.toml file with the following content. It enables the worker to perform HTTP requests to that host given that, by default, HTTP requests are forbidden.

    Note the name of the TOML file must match the name of the worker. In this case we have fetch.wasm and fetch.toml in the same folder:

    fetch.toml
    name = "fetch"
    version = "1"

    [features]
    [features.http_requests]
    allowed_methods = ["GET"]
    allowed_hosts = ["jsonplaceholder.typicode.com"]
  7. Run your worker with wws. If you didn't download the wws server yet, check our Getting Started guide.

    wws .

    ⚙️ Preparing the project from: .
    ⚙️ Loading routes from: .
    ⏳ Loading workers from 1 routes...
    ✅ Workers loaded in 135.717667ms.
    - http://127.0.0.1:8080/fetch
    => ./fetch.wasm
    🚀 Start serving requests at http://127.0.0.1:8080
  8. Finally, open http://127.0.0.1:8080/fetch in your browser.

Dynamic routes

You can define dynamic routes by adding route parameters to your worker files (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:

    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)
    ...
    })
    }
  2. Then, you can read the values as follows:

    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. These variables are accessible via os.Getenv in your worker. To read them, just use the same name you configured in your TOML file:

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 function:

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.

Other examples

Contributors

The Go kit was originally authored by Mohammed Nafees (@mnafees)

Feature compatibility

Workers' features that are available in Go:

K/V StoreEnvironment VariablesDynamic RoutesFoldersHTTP Requests