Developing Golang functions
Golang function template structure
When you create a Golang function using the kn func
CLI, the project directory looks like a typical Go project, with the exception of an additional func.yaml
configuration file. Both http
and event
trigger functions have the same template structure:
fn
├── README.md
├── func.yaml (1)
├── go.mod
├── go.sum
├── handle.go
└── handle_test.go
1 | The func.yaml configuration file is used to determine the image name and registry. |
Golang functions have very few restrictions. The only requirement is that your project must be defined in a function
module and exports the function Handle()
.
Dependencies
You can add any required dependencies to the go.mod
file, which can include additional local Golang files. When the project is built for deployment, these dependencies will be included in the resulting runtime container image.
go get gopkg.in/yaml.v2@v2.4.0
Invoking a function
When using the kn func
CLI to create a function project you can generate a project that responds to CloudEvents, or one that responds to simple HTTP requests. CloudEvents in Knative are transported over HTTP as a POST request, so both function types will listen and respond to incoming HTTP events.
Function triggered by HTTP request
When an incoming HTTP request is received, your function will be invoked with a standard Golang Context as the first parameter, followed by two more parameters:
You can use standard Golang techniques to access the request, and set a proper HTTP response of your function.
func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) {
// Read body
body, err := ioutil.ReadAll(req.Body)
defer req.Body.Close()
if err != nil {
http.Error(res, err.Error(), 500)
return
}
// Process body and function logic
// ...
}
Function triggered by CloudEvent
When an incoming CloudEvent is received, the event is invoked by the CloudEvents Golang SDK and the Event
type as a parameter.
You can leverage the Golang Context as an optional parameter in the function contract, as shown in the list of supported function signatures:
Handle()
Handle() error
Handle(context.Context)
Handle(context.Context) error
Handle(cloudevents.Event)
Handle(cloudevents.Event) error
Handle(context.Context, cloudevents.Event)
Handle(context.Context, cloudevents.Event) error
Handle(cloudevents.Event) *cloudevents.Event
Handle(cloudevents.Event) (*cloudevents.Event, error)
Handle(context.Context, cloudevents.Event) *cloudevents.Event
Handle(context.Context, cloudevents.Event) (*cloudevents.Event, error)
-
A CloudEvent is received which contains a JSON string in its data property:
{ "customerId": "0123456", "productId": "6543210" }
-
To access this data, you must define a structure which maps properties in the CloudEvent data, and retrieves the data from the incoming event. This example uses the
Purchase
structure:type Purchase struct { CustomerId string `json:"customerId"` ProductId string `json:"productId"` } func Handle(ctx context.Context, event cloudevents.Event) err error { purchase := &Purchase{} if err = cloudevents.DataAs(purchase); err != nil { fmt.Fprintf(os.Stderr, "failed to parse incoming CloudEvent %s\n", err) return } // ... }
-
Optional: Alternatively, you can use the Golang
encoding/json
package to access the CloudEvent directly as JSON in the form of a bytes array:func Handle(ctx context.Context, event cloudevents.Event) { bytes, err := json.Marshal(event) // ... }
Return values
HTTP triggered functions can set the response directly by using the Golang http.ResponseWriter.
func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) {
// Set response
res.Header().Add("Content-Type", "text/plain")
res.Header().Add("Content-Length", "3")
res.WriteHeader(200)
_, err := fmt.Fprintf(res, "OK\n")
if err != nil {
fmt.Fprintf(os.Stderr, "error or response write: %v", err)
}
}
Functions triggered by a CloudEvent may return nothing, error
, or CloudEvent
in order to push events into the Knative eventing system. In this case, you must set a unique ID
, proper Source
and a Type
for the CloudEvent. The data can be populated from a defined structure, or from a map
.
func Handle(ctx context.Context, event cloudevents.Event) (resp *cloudevents.Event, err error) {
// ...
response := cloudevents.NewEvent()
response.SetID("example-uuid-32943bac6fea")
response.SetSource("purchase/getter")
response.SetType("purchase")
// Set the data from Purchase type
response.SetData(cloudevents.ApplicationJSON, Purchase{
CustomerId: custId,
ProductId: prodId,
})
// OR set the data directly from map
response.SetData(cloudevents.ApplicationJSON, map[string]string{"customerId": custId, "productId": prodId})
// Validate the response
resp = &response
if err = resp.Validate(); err != nil {
fmt.Printf("invalid event created. %v", err)
}
return
}
Testing a Golang function locally
Golang functions can be tested locally on your computer. In the default project that is created when you create a function using kn func create
, there is a handle_test.go
file which contains some basic tests. These tests can be extended as needed.
-
Run the test:
$ go test