-
Notifications
You must be signed in to change notification settings - Fork 139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add go function invocation runtime as a package #1451
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
//go:build debug | ||
// +build debug | ||
|
||
package runtime | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"os" | ||
"runtime/debug" | ||
) | ||
|
||
func recoverMiddleware(handler http.Handler) http.Handler { | ||
logger := log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds) | ||
f := func(rw http.ResponseWriter, req *http.Request) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
recoverError := fmt.Errorf("user function error: %v", r) | ||
stack := string(debug.Stack()) | ||
logger.Printf("%v\n%v\n", recoverError, stack) | ||
|
||
rw.WriteHeader(http.StatusInternalServerError) | ||
|
||
rw.Header().Add("Content-Type", "text/plain") | ||
fmt.Fprintln(rw, recoverError) | ||
fmt.Fprintln(rw, stack) | ||
} | ||
}() | ||
handler.ServeHTTP(rw, req) | ||
return | ||
} | ||
return http.HandlerFunc(f) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
//go:build !debug | ||
// +build !debug | ||
|
||
package runtime | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"os" | ||
"runtime/debug" | ||
) | ||
|
||
func recoverMiddleware(handler http.Handler) http.Handler { | ||
logger := log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds) | ||
f := func(rw http.ResponseWriter, req *http.Request) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
recoverError := fmt.Errorf("user function error: %v", r) | ||
stack := string(debug.Stack()) | ||
logger.Printf("%v\n%v\n", recoverError, stack) | ||
|
||
rw.WriteHeader(http.StatusInternalServerError) | ||
} | ||
}() | ||
handler.ServeHTTP(rw, req) | ||
} | ||
return http.HandlerFunc(f) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package runtime | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"os/signal" | ||
"strconv" | ||
"syscall" | ||
"time" | ||
|
||
cloudevents "github.com/cloudevents/sdk-go/v2" | ||
|
||
"github.com/gorilla/mux" | ||
) | ||
|
||
var ( | ||
usage = "run\n\nRuns a CloudFunction CloudEvent handler." | ||
verbose = flag.Bool("V", false, "Verbose logging [$VERBOSE]") | ||
port = flag.Int("port", 8080, "Listen on all interfaces at the given port [$PORT]") | ||
) | ||
|
||
func Start(handler interface{}) { | ||
flag.Usage = func() { | ||
fmt.Fprintln(os.Stderr, usage) | ||
flag.PrintDefaults() | ||
} | ||
parseEnv() // override static defaults with environment. | ||
flag.Parse() // override env vars with flags. | ||
if err := run(handler); err != nil { | ||
fmt.Fprintln(os.Stderr, err) | ||
os.Exit(1) | ||
} | ||
|
||
} | ||
|
||
// parseEnv parses environment variables, populating the destination flags | ||
// prior to the builtin flag parsing. Invalid values exit 1. | ||
func parseEnv() { | ||
parseBool := func(key string, dest *bool) { | ||
if val, ok := os.LookupEnv(key); ok { | ||
b, err := strconv.ParseBool(val) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "%v is not a valid boolean\n", key) | ||
os.Exit(1) | ||
} | ||
*dest = b | ||
} | ||
} | ||
parseInt := func(key string, dest *int) { | ||
if val, ok := os.LookupEnv(key); ok { | ||
n, err := strconv.Atoi(val) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "%v is not a valid integer\n", key) | ||
os.Exit(1) | ||
} | ||
*dest = n | ||
} | ||
} | ||
|
||
parseBool("VERBOSE", verbose) | ||
parseInt("PORT", port) | ||
} | ||
|
||
// run a cloudevents client in receive mode which invokes | ||
// the user-defined function.Handler on receipt of an event. | ||
func run(handler interface{}) error { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
|
||
sigs := make(chan os.Signal, 1) | ||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | ||
go func() { | ||
<-sigs | ||
cancel() | ||
}() | ||
|
||
// Use a gorilla mux for handling all HTTP requests | ||
router := mux.NewRouter() | ||
|
||
// Add handlers for readiness and liveness endpoints | ||
router.HandleFunc("/health/{endpoint:readiness|liveness}", func(w http.ResponseWriter, r *http.Request) { | ||
fmt.Fprintf(w, "OK") | ||
}) | ||
|
||
httpHandler := toHttpHandler(handler, ctx) | ||
|
||
if httpHandler == nil { | ||
if *verbose { | ||
fmt.Printf("Initializing CloudEvent function\n") | ||
} | ||
protocol, err := cloudevents.NewHTTP( | ||
cloudevents.WithPort(*port), | ||
cloudevents.WithPath("/"), | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
eventHandler, err := cloudevents.NewHTTPReceiveHandler(ctx, protocol, handler) | ||
if err != nil { | ||
return err | ||
} | ||
router.Handle("/", eventHandler) | ||
} else { | ||
if *verbose { | ||
fmt.Printf("Initializing HTTP function\n") | ||
} | ||
router.Handle("/", httpHandler) | ||
} | ||
|
||
httpServer := &http.Server{ | ||
Addr: fmt.Sprintf(":%d", *port), | ||
Handler: router, | ||
ReadTimeout: 1 * time.Minute, | ||
WriteTimeout: 1 * time.Minute, | ||
MaxHeaderBytes: 1 << 20, | ||
} | ||
|
||
listenAndServeErr := make(chan error, 1) | ||
go func() { | ||
if *verbose { | ||
fmt.Printf("listening on http port %v\n", *port) | ||
} | ||
err := httpServer.ListenAndServe() | ||
cancel() | ||
listenAndServeErr <- err | ||
}() | ||
|
||
<-ctx.Done() | ||
shutdownCtx, shutdownCancelFn := context.WithTimeout(context.Background(), time.Second*5) | ||
defer shutdownCancelFn() | ||
err := httpServer.Shutdown(shutdownCtx) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "error on server shutdown: %v\n", err) | ||
} | ||
|
||
err = <-listenAndServeErr | ||
if http.ErrServerClosed == err { | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
// if the handler signature is compatible with http handler the function returns an instance of `http.Handler`, | ||
// otherwise nil is returned | ||
func toHttpHandler(handler interface{}, ctx context.Context) http.Handler { | ||
|
||
if f, ok := handler.(func(rw http.ResponseWriter, req *http.Request)); ok { | ||
return recoverMiddleware(http.HandlerFunc(f)) | ||
} | ||
|
||
if f, ok := handler.(func(ctx context.Context, rw http.ResponseWriter, req *http.Request)); ok { | ||
ff := func(rw http.ResponseWriter, req *http.Request) { | ||
f(ctx, rw, req) | ||
} | ||
return recoverMiddleware(http.HandlerFunc(ff)) | ||
} | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package main | ||
|
||
import ( | ||
function "function" | ||
|
||
"knative.dev/func/runtime" | ||
) | ||
|
||
func main() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add some comment here pointing users to handle.go. So they don't try to write the function code here. |
||
runtime.Start(function.Handle) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,22 @@ | ||
module function | ||
|
||
go 1.14 | ||
go 1.18 | ||
|
||
require github.com/cloudevents/sdk-go/v2 v2.5.0 | ||
require ( | ||
github.com/cloudevents/sdk-go/v2 v2.12.0 | ||
knative.dev/func v0.35.1 | ||
) | ||
|
||
require ( | ||
github.com/google/uuid v1.3.0 // indirect | ||
github.com/gorilla/mux v1.8.0 // indirect | ||
github.com/json-iterator/go v1.1.12 // indirect | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
github.com/modern-go/reflect2 v1.0.2 // indirect | ||
go.uber.org/atomic v1.10.0 // indirect | ||
go.uber.org/multierr v1.8.0 // indirect | ||
go.uber.org/zap v1.23.0 // indirect | ||
) | ||
|
||
// TODO: Fix this chicken and egg problem. | ||
replace knative.dev/func => github.com/lance/func v1.1.3-0.20221130200745-483914d520ca |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
github.com/cloudevents/sdk-go/v2 v2.12.0 h1:p1k+ysVOZtNiXfijnwB3WqZNA3y2cGOiKQygWkUHCEI= | ||
github.com/cloudevents/sdk-go/v2 v2.12.0/go.mod h1:xDmKfzNjM8gBvjaF8ijFjM1VYOVUEeUfapHMUX1T5To= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | ||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | ||
github.com/lance/func v1.1.3-0.20221130200745-483914d520ca h1:kgfYaTkb3Y0spZh1eQBryCt1ulkpRRDY5ArpyQvw7DQ= | ||
github.com/lance/func v1.1.3-0.20221130200745-483914d520ca/go.mod h1:GFUD/8UjpoG3SKFDl89Lf62eNXcP8rafnklDZQEXSXI= | ||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= | ||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= | ||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= | ||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= | ||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= | ||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= | ||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package main | ||
|
||
import ( | ||
function "function" | ||
|
||
"knative.dev/func/runtime" | ||
) | ||
|
||
func main() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add some comment here pointing users to handle.go. So they don't try to write the function code here. |
||
runtime.Start(function.Handle) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,20 @@ | ||
module function | ||
|
||
go 1.14 | ||
go 1.18 | ||
|
||
require knative.dev/func v0.35.1 | ||
|
||
require ( | ||
github.com/cloudevents/sdk-go/v2 v2.12.0 // indirect | ||
github.com/google/uuid v1.3.0 // indirect | ||
github.com/gorilla/mux v1.8.0 // indirect | ||
github.com/json-iterator/go v1.1.12 // indirect | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
github.com/modern-go/reflect2 v1.0.2 // indirect | ||
go.uber.org/atomic v1.10.0 // indirect | ||
go.uber.org/multierr v1.8.0 // indirect | ||
go.uber.org/zap v1.23.0 // indirect | ||
) | ||
|
||
// TODO: Fix this chicken and egg problem. | ||
replace knative.dev/func => github.com/lance/func v1.1.3-0.20221130200745-483914d520ca |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/gorilla#gorilla-toolkit
not sure if we should depend on this package
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I agree. For the time being, however, I think we can keep it as is until we come up with a suitable alternative.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zroubalik also, note that I've moved this code over to https://github.com/knative-sandbox/func-go which will change this PR to just be modifications to the templates.