Golang templates for OpenFaaS using HTTP extensions
Clone or download
alexellis Switch mechanism to ignore function SDK
- moves from setting an ignore flag in the Gopkg.toml to adding
a .dockerignore to stop conflicting files being copied into the
build package. This works and was tested with the faas-cli, but
may cause issues in OpenFaaS Cloud. A follow-up item may be
needed to get the --shrinkwrap (copy) behaviour to honour any
.dockerignore files found and stop those files being put into
./build/<fn>/*.

Signed-off-by: Alex Ellis (VMware) <alexellis2@gmail.com>
Latest commit 4783533 Jan 18, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.github Create PULL_REQUEST_TEMPLATE.md Oct 14, 2018
template Switch mechanism to ignore function SDK Jan 18, 2019
.DEREK.yml Create .DEREK.yml Sep 30, 2018
.gitignore Ignore the SDK in vendor config Jan 18, 2019
LICENSE Add LICENSE Feb 28, 2018
README.md Ensure closing of the body request Oct 17, 2018

README.md

OpenFaaS Golang HTTP templates

This repository contains two Golang templates for OpenFaaS which give additional control over the HTTP request and response. They will both handle higher throughput than the classic watchdog due to the process being kept warm.

$ faas template pull https://github.com/openfaas-incubator/golang-http-template
$ faas new --list

Languages available as templates:
- golang-http
- golang-http-armhf
- golang-middleware
- golang-middleware-armhf

To build and deploy a function for your Raspberry Pi or ARMv7 in general, use the language templates ending in -armhf.

The two templates are equivalent with golang-http using a structured request/response object and the alternative implementing a Golang http.HandleFunc from the Golang stdlib. golang-http is more "conventional" for a Golang serverless template but this is a question of style/taste.

1.0 golang-http

This template provides additional context and control over the HTTP response from your function.

Status of the template

This template is pre-release and is likely to change - please provide feedback via https://github.com/openfaas/faas

The template makes use of the OpenFaaS incubator project of-watchdog.

Trying the template

$ faas template pull https://github.com/openfaas-incubator/golang-http-template
$ faas new --lang golang-http <fn-name>

Example usage

Example writing a successful message:

package function

import (
	"fmt"
	"net/http"

	"github.com/openfaas-incubator/go-function-sdk"
)

// Handle a function invocation
func Handle(req handler.Request) (handler.Response, error) {
	var err error

	message := fmt.Sprintf("Hello world, input was: %s", string(req.Body))

	return handler.Response{
		Body:       []byte(message),
    }, err
}

Example writing a custom status code

package function

import (
	"fmt"
	"net/http"

	"github.com/openfaas-incubator/go-function-sdk"
)

// Handle a function invocation
func Handle(req handler.Request) (handler.Response, error) {
	var err error

	return handler.Response{
		Body:       []byte("Your workload was accepted"),
		StatusCode: http.StatusAccepted,
	}, err
}

Example writing an error / failure.

package function

import (
	"fmt"
	"net/http"

	"github.com/openfaas-incubator/go-function-sdk"
)

// Handle a function invocation
func Handle(req handler.Request) (handler.Response, error) {
	var err error

	return handler.Response{
        Body: []byte("the input was invalid")
	}, fmt.Errorf("invalid input")
}

The error will be logged to stderr and the body will be written to the client along with a HTTP 500 status code.

Example reading a header.

package function

import (
	"log"

	"github.com/openfaas-incubator/go-function-sdk"
)

// Handle a function invocation
func Handle(req handler.Request) (handler.Response, error) {
	var err error

	log.Println(req.Header) // Check function logs for the request headers

	return handler.Response{
		Body: []byte("This is the response"),
		Header: map[string][]string{
			"X-Served-By": []string{"My Awesome Function"},
		},
	}, err
}

2.0 golang-middleware

This template uses the http.HandlerFunc as entry point.

Status of the template

The template makes use of the OpenFaaS incubator project of-watchdog.

Trying the template

$ faas template pull https://github.com/openfaas-incubator/golang-http-template
$ faas new --lang golang-middleware <fn-name>

Example usage

Example writing a json response:

package function

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"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 := ioutil.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)
}

Example persistent database connection pool between function calls:

package function

import (
	"database/sql"
	"fmt"
	"io/ioutil"
	"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

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

		// read request payload
		body, err := ioutil.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.Query(query)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()
	
	ids := make([]string, 0)
	for rows.Next() {
		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))
}