Skip to content

Go

Go (Golang)

Go, aka Golang is a popular open-source programming language created and maintained by a team at Google.

Official Go template

The official and recommended template for Go for OpenFaaS is golang-middleware.

It implements handler for HTTP requests using http.HandleFunc from Go's stdlib. It has support for Go modules, vendoring, unit testing, and adding static files.

You can pull this template by running:

faas-cli template store pull golang-middleware

The source for the template is: https://github.com/openfaas/golang-http-template

The most comprehensive guide and set of examples for this template are in the Premium and Team Edition of Alex Ellis' book: Everyday Golang.

This is an official template maintained by OpenFaaS Ltd.

Create a new function

Create a new function using the template:

faas-cli new --lang golang-middleware echo

The handler will be created as ./echo/handler.go, along with a go.mod for managing dependencies.

handler.go:

package function

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

func Handle(w http.ResponseWriter, r *http.Request) {
    var input []byte

    if r.Body != nil {
        defer r.Body.Close()

        body, _ := io.ReadAll(r.Body)

        input = body
    }

    w.WriteHeader(http.StatusOK)
    w.Write([]byte(fmt.Sprintf("Body: %s", string(input))))
}

Just like any other HTTP server written with the Go standard library, the incoming HTTP request is available via the http.Request object. The response is written to the http.ResponseWriter object. Any body passed in from the client should be closed to avoid any resource leaks.

Add a dependency

To add a dependency such as a bcrypt library, you can use the go get command:

faas-cli new --lang golang-middleware bcrypt

cd bcrypt

go get golang.org/x/crypto/bcrypt

Then write a bcrypt/handler.go:

package function

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

    "golang.org/x/crypto/bcrypt"
)

func Handle(w http.ResponseWriter, r *http.Request) {
    var input []byte

    if r.Body != nil {
        defer r.Body.Close()
        body, _ := io.ReadAll(r.Body)
        input = body
    }

    res, err := bcrypt.GenerateFromPassword(input, bcrypt.DefaultCost)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, "%s", res)
}

You'll see that bcrypt/go.mod has been updated with the new dependency:

module handler/function

go 1.18

require golang.org/x/crypto v0.13.0 // indirect

Working with JSON

Example writing a JSON response from your function.

package function

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
)

func Handle(w http.ResponseWriter, r *http.Request) {
    var input []byte

    if r.Body != nil {
        defer r.Body.Close()

        // read request payload
        reqBody, err := io.ReadAll(r.Body)

        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return

        input = reqBody
        }
    }

    // log to stdout
    fmt.Printf("request body: %s", string(input))

    response := struct {
        Payload     string              `json:"payload"`
        Headers     map[string][]string `json:"headers"`
        Environment []string            `json:"environment"`
    }{
        Payload:     string(input),
        Headers:     r.Header,
        Environment: os.Environ(),
    }

    resBody, err := json.Marshal(response)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // write result
    w.WriteHeader(http.StatusOK)
    w.Write(resBody)
}

Use private code

If you have private code to include in your function from another repository, then the easiest way to do this is to use vendoring.

faas-cli new --lang golang-middleware secret-fn

Create a new function just like in the previous example, when you run go get, prefix it with GOPRIVATE, and make sure that any go commands that you run are performed inside the function's source code directory.

cd secret-fn
GOPRIVATE=github.com/acmecorp go get
GOPRIVATE=github.com/acmecorp go mod vendor

Then you can commit the vendor directory to your repository.

You shouldn't need to set GO111MODULE=on, as the template already has this set, but you can do so explicitly if you wish.

functions:
  secret-fn:
    lang: golang-middleware
    handler: ./secret-fn
    image: ttl.sh/test/secret-fn:latest
    build_args:
      GO111MODULE: on

Add static files to your function

A common use-case for static files is when you want to serve HTML, lookup information from a JSON manifest or render some kind of templates.

If a folder named static is found in the root of your function's source code, it will be copied into the final image published for your function.

To read this back at runtime, you can do the following:

package function

import (
    "net/http"
    "os"
)

func Handle(w http.ResponseWriter, r *http.Request) {

    data, err := os.ReadFile("./static/file.txt")

    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }

    w.Write(data)
}

Add your own sub-modules

If you would like to include sub-modules, a certain replace statement is required in your go.mod file: replace handler/function => ./. This replace statement allows Go to see and use all sub-modules you create with-in your handler, for example

Create your sub-package i.e. handlers and run cd handlers ; go mod init

Here's handlers/handlers.go:

package handlers

import (
    "fmt"

    execute "github.com/alexellis/go-execute/pkg/v1"
)

func Handle() {
    ls := execute.ExecTask{
        Command: "exit 1",
        Shell:   true,
    }
    res, err := ls.Execute()
    if err != nil {
        panic(err)
    }

    fmt.Printf("stdout: %q, stderr: %q, exit-code: %d\n", res.Stdout, res.Stderr, res.ExitCode)
}

Within your handler.go:

package function

import (
    "fmt"

    "handler/function/handlers"
)

// Handle a serverless request
func Handle(req []byte) string {

    handlers.Handle()

    return fmt.Sprintf("Hello, Go. You said: %s", string(req))
}

Now add the following replace statement to your go.mod

replace handler/function => ./

This can also be affected using

go mod edit -replace handler/function=./

Now you can build with --build-arg GO111MODULE=on or with a build_arg map entry for the function in its stack.yml.

Access a database

Example persistent database connection pool between function calls:

package function

import (
    "database/sql"
    "fmt"
    "io"
    "net/http"
    "strings"
    _ "github.com/go-sql-driver/mysql"
)

// db pool shared between function calls
var db *sql.DB

func init() {
    var err error
    db, err = sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        panic(err.Error())
    }

    err = db.Ping()
    if err != nil {
        panic(err.Error())
    }
}

func Handle(w http.ResponseWriter, r *http.Request) {
    var query string
    ctx := r.Context()

    if r.Body != nil {
        defer r.Body.Close()

        // read request payload
        body, err := io.ReadAll(r.Body)

        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        query = string(body)
    }

    // log to stdout
    fmt.Printf("Executing query: %s", query)

    rows, err := db.QueryContext(ctx, query)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    ids := make([]string, 0)
    for rows.Next() {
        if e := ctx.Err(); e != nil {
            http.Error(w, e, http.StatusBadRequest)
            return
        }
        var id int
        if err := rows.Scan(&id); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        ids = append(ids, string(id))
    }
    if err := rows.Err(); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    result := fmt.Sprintf("ids %s", strings.Join(ids, ", "))

    // write result
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(result))
}

Query string example

Example retrieving request query strings

package function
import (
    "fmt"
    "net/http"
)
func Handle(w http.ResponseWriter, r *http.Request) {
    // Parses RawQuery and returns the corresponding
    // values as a map[string][]string
    // for more info https://golang.org/pkg/net/url/#URL.Query
    query := r.URL.Query()
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(fmt.Sprintf("id: %s", query.Get("id"))))
}