mirror of https://github.com/knative/func.git
249 lines
7.3 KiB
Markdown
249 lines
7.3 KiB
Markdown
# Golang Function Developer's Guide
|
||
|
||
When creating a Go (Golang) function using the `func` CLI, the project directory
|
||
looks like a typical Go project. Both HTTP and Event functions have the same
|
||
template structure.
|
||
|
||
```
|
||
❯ func create -l go fn
|
||
Project path: /home/developer/projects/fn
|
||
Function name: fn
|
||
Runtime: go
|
||
|
||
❯ tree
|
||
fn
|
||
├── README.md
|
||
├── func.yaml
|
||
├── go.mod
|
||
├── go.sum
|
||
├── handle.go
|
||
└── handle_test.go
|
||
```
|
||
|
||
Aside from the `func.yaml` file, this looks like the beginning of just about
|
||
any Go project. For now, we will ignore the `func.yaml` file, and just
|
||
say that it is a configuration file that is used when building your project.
|
||
If you're really interested, check out the [reference doc](../reference/func_yaml.md).
|
||
To learn more about the CLI and the details for each supported command, see
|
||
the [CLI Commands document](../reference/func.md).
|
||
|
||
## Running the function locally
|
||
|
||
To run a function, you'll first need to build it. This step creates an OCI
|
||
container image that can be run locally on your computer, or on a Kubernetes
|
||
cluster.
|
||
|
||
```
|
||
❯ func build
|
||
```
|
||
|
||
After the function has been built, it can be run locally.
|
||
|
||
```
|
||
❯ func run
|
||
```
|
||
|
||
Functions can be invoked with a simple HTTP request.
|
||
You can test to see if the function is working by using your browser to visit
|
||
http://localhost:8080. You can also access liveness and readiness
|
||
endpoints at http://localhost:8080/health/liveness and
|
||
http://localhost:8080/health/readiness. These two endpoints are used
|
||
by Kubernetes to determine the health of your function. If everything
|
||
is good, both of these will return `{"ok":true}`.
|
||
|
||
## Deploying the function to a cluster
|
||
|
||
To deploy your function to a Kubernetes cluster, use the `deploy` command.
|
||
|
||
```
|
||
❯ func deploy
|
||
```
|
||
|
||
You can get the URL for your deployed function with the `info` command.
|
||
|
||
```
|
||
❯ func info
|
||
```
|
||
|
||
## Testing a function locally
|
||
|
||
|
||
Go functions can be tested locally on your computer. In the project there is
|
||
a `handle_test.go` file which contains simple test which can be extended as needed.
|
||
Yo can run this test locally as you would do with any Go project.
|
||
|
||
```
|
||
❯ go test
|
||
```
|
||
|
||
## Function reference
|
||
|
||
Boson Go functions have very few restrictions. You can add any required dependencies
|
||
in `go.mod` and you may include additional local Go files. The only real requirement are
|
||
that your project is defined in a `function` module and exports the function `Handle()`
|
||
(supported contracts of this function will be discussed more deeply later).
|
||
In this section, we will look in a little more detail at how Boson functions are invoked,
|
||
and what APIs are available to you as a developer.
|
||
|
||
### Invocation parameters
|
||
|
||
When using the `func` CLI to create a function project, you may choose to generate a project
|
||
that responds to a `CloudEvent` or simple HTTP. `CloudEvents` in Knative are transported over
|
||
HTTP as a `POST` request, so in many ways, the two types of functions are very much the same.
|
||
They each will listen and respond to incoming HTTP events.
|
||
|
||
#### Function triggered by HTTP request
|
||
|
||
When an incoming request is received, your function will be invoked with two parameters:
|
||
Golang's [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter) and [http.Request](https://golang.org/pkg/net/http/#Request).
|
||
|
||
Then you can use standard Golang techniques to access the request (eg. read the body)
|
||
and set a proper HTTP response of your function, as you can see on the following example:
|
||
|
||
```go
|
||
func Handle(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 & function logic
|
||
// ...
|
||
}
|
||
```
|
||
|
||
#### Function triggered by CloudEvent
|
||
|
||
If the incoming request is a `CloudEvent`, the event is provided via
|
||
[CloudEvents Golang SDK](https://cloudevents.github.io/sdk-go/) and its `Event` type
|
||
as a parameter. There's possibility to leverage Golang's
|
||
[Context](https://golang.org/pkg/context/) as the optional parameter in the function contract,
|
||
as you can see in the list of supported function signatures:
|
||
|
||
```go
|
||
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)
|
||
```
|
||
|
||
For example, a `CloudEvent` is received which contains a JSON string such as this in its data property,
|
||
|
||
```json
|
||
{
|
||
"customerId": "0123456",
|
||
"productId": "6543210"
|
||
}
|
||
```
|
||
|
||
to access this data, we need to define `Purchase` structure, which maps properties in `CloudEvents`
|
||
data and retrieve it from the incoming event:
|
||
|
||
```go
|
||
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
|
||
}
|
||
|
||
// ...
|
||
}
|
||
```
|
||
|
||
Or we can use Golang's `encoding/json` package to access the `CloudEvent` directly as
|
||
a JSON in form of bytes array:
|
||
|
||
```golang
|
||
func Handle(ctx context.Context, event cloudevents.Event) {
|
||
|
||
bytes, err := json.Marshal(event)
|
||
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### Return Values
|
||
As mentioned above, HTTP triggered functions can set the response directly via
|
||
Golang's [http.ResponseWriter](https://golang.org/pkg/net/http/#ResponseWriter).
|
||
|
||
```go
|
||
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 `CloudEvent` may return nothing, `error` and/or `CloudEvent` in order
|
||
to push events into the Knative eventing system. In this case, the developer is required
|
||
to set a unique `ID`, proper `Source` and a `Type` of the CloudEvent. The data can be populated
|
||
from a defined structure or from a `map`.
|
||
|
||
```go
|
||
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
|
||
}
|
||
```
|
||
|
||
## Dependencies
|
||
Developers are not restricted to the dependencies provided in the template
|
||
`go.mod` file. Additional dependencies can be added as they would be in any
|
||
other Golang project.
|
||
|
||
### Example
|
||
```console
|
||
go get gopkg.in/yaml.v2@v2.4.0
|
||
```
|
||
|
||
When the project is built for deployment, these dependencies will be included
|
||
in the resulting runtime container image.
|