mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
API extension points (#473)
* API endpoint extensions working. extensions example. * Added server.NewEnv and some docs for the API extensions example. extensions example. example main.go. * Uncommented special handler stuff. * Added section in docs for extending API linking to example main.go. * Commented out special_handler test * Changed to NewFromEnv
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,6 +13,7 @@ vendor/
|
||||
/gateway
|
||||
/functions
|
||||
/functions-alpine
|
||||
/functions.exe
|
||||
bolt.db
|
||||
.glide/
|
||||
|
||||
|
||||
@@ -4,4 +4,8 @@ package api
|
||||
const (
|
||||
AppName string = "app_name"
|
||||
Path string = "path"
|
||||
|
||||
// Short forms for API URLs
|
||||
CApp string = "app"
|
||||
CRoute string = "route"
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func New(dbURL string) (models.Datastore, error) {
|
||||
u, err := url.Parse(dbURL)
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{"url": dbURL}).Fatal("bad DB URL")
|
||||
logrus.WithError(err).WithFields(logrus.Fields{"url": dbURL}).Fatal("bad DB URL")
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{"db": u.Scheme}).Debug("creating new datastore")
|
||||
switch u.Scheme {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
)
|
||||
|
||||
type Datastore interface {
|
||||
// GetApp returns the app called appName or nil if it doesn't exist
|
||||
GetApp(ctx context.Context, appName string) (*App, error)
|
||||
GetApps(ctx context.Context, filter *AppFilter) ([]*App, error)
|
||||
InsertApp(ctx context.Context, app *App) (*App, error)
|
||||
|
||||
@@ -2,24 +2,19 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/iron-io/functions/api/models"
|
||||
)
|
||||
|
||||
type AppCreateListener interface {
|
||||
type AppListener interface {
|
||||
// BeforeAppCreate called right before creating App in the database
|
||||
BeforeAppCreate(ctx context.Context, app *models.App) error
|
||||
// AfterAppCreate called after creating App in the database
|
||||
AfterAppCreate(ctx context.Context, app *models.App) error
|
||||
}
|
||||
|
||||
type AppUpdateListener interface {
|
||||
// BeforeAppUpdate called right before updating App in the database
|
||||
BeforeAppUpdate(ctx context.Context, app *models.App) error
|
||||
// AfterAppUpdate called after updating App in the database
|
||||
AfterAppUpdate(ctx context.Context, app *models.App) error
|
||||
}
|
||||
|
||||
type AppDeleteListener interface {
|
||||
// BeforeAppDelete called right before deleting App in the database
|
||||
BeforeAppDelete(ctx context.Context, app *models.App) error
|
||||
// AfterAppDelete called after deleting App in the database
|
||||
@@ -27,22 +22,12 @@ type AppDeleteListener interface {
|
||||
}
|
||||
|
||||
// AddAppCreateListener adds a listener that will be notified on App created.
|
||||
func (s *Server) AddAppCreateListener(listener AppCreateListener) {
|
||||
s.appCreateListeners = append(s.appCreateListeners, listener)
|
||||
}
|
||||
|
||||
// AddAppUpdateListener adds a listener that will be notified on App updated.
|
||||
func (s *Server) AddAppUpdateListener(listener AppUpdateListener) {
|
||||
s.appUpdateListeners = append(s.appUpdateListeners, listener)
|
||||
}
|
||||
|
||||
// AddAppDeleteListener adds a listener that will be notified on App deleted.
|
||||
func (s *Server) AddAppDeleteListener(listener AppDeleteListener) {
|
||||
s.appDeleteListeners = append(s.appDeleteListeners, listener)
|
||||
func (s *Server) AddAppListener(listener AppListener) {
|
||||
s.appListeners = append(s.appListeners, listener)
|
||||
}
|
||||
|
||||
func (s *Server) FireBeforeAppCreate(ctx context.Context, app *models.App) error {
|
||||
for _, l := range s.appCreateListeners {
|
||||
for _, l := range s.appListeners {
|
||||
err := l.BeforeAppCreate(ctx, app)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -52,7 +37,7 @@ func (s *Server) FireBeforeAppCreate(ctx context.Context, app *models.App) error
|
||||
}
|
||||
|
||||
func (s *Server) FireAfterAppCreate(ctx context.Context, app *models.App) error {
|
||||
for _, l := range s.appCreateListeners {
|
||||
for _, l := range s.appListeners {
|
||||
err := l.AfterAppCreate(ctx, app)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -62,7 +47,7 @@ func (s *Server) FireAfterAppCreate(ctx context.Context, app *models.App) error
|
||||
}
|
||||
|
||||
func (s *Server) FireBeforeAppUpdate(ctx context.Context, app *models.App) error {
|
||||
for _, l := range s.appUpdateListeners {
|
||||
for _, l := range s.appListeners {
|
||||
err := l.BeforeAppUpdate(ctx, app)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -72,7 +57,7 @@ func (s *Server) FireBeforeAppUpdate(ctx context.Context, app *models.App) error
|
||||
}
|
||||
|
||||
func (s *Server) FireAfterAppUpdate(ctx context.Context, app *models.App) error {
|
||||
for _, l := range s.appUpdateListeners {
|
||||
for _, l := range s.appListeners {
|
||||
err := l.AfterAppUpdate(ctx, app)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -82,7 +67,7 @@ func (s *Server) FireAfterAppUpdate(ctx context.Context, app *models.App) error
|
||||
}
|
||||
|
||||
func (s *Server) FireBeforeAppDelete(ctx context.Context, app *models.App) error {
|
||||
for _, l := range s.appDeleteListeners {
|
||||
for _, l := range s.appListeners {
|
||||
err := l.BeforeAppDelete(ctx, app)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -92,7 +77,7 @@ func (s *Server) FireBeforeAppDelete(ctx context.Context, app *models.App) error
|
||||
}
|
||||
|
||||
func (s *Server) FireAfterAppDelete(ctx context.Context, app *models.App) error {
|
||||
for _, l := range s.appDeleteListeners {
|
||||
for _, l := range s.appListeners {
|
||||
err := l.AfterAppDelete(ctx, app)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
81
api/server/extension_points.go
Normal file
81
api/server/extension_points.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// TODO: it would be nice to move these into the top level folder so people can use these with the "functions" package, eg: functions.ApiHandler
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iron-io/functions/api"
|
||||
"github.com/iron-io/functions/api/models"
|
||||
)
|
||||
|
||||
type ApiHandlerFunc func(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// ServeHTTP calls f(w, r).
|
||||
func (f ApiHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
f(w, r)
|
||||
}
|
||||
|
||||
type ApiHandler interface {
|
||||
// Handle(ctx context.Context)
|
||||
ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type ApiAppHandler interface {
|
||||
// Handle(ctx context.Context)
|
||||
ServeHTTP(w http.ResponseWriter, r *http.Request, app *models.App)
|
||||
}
|
||||
|
||||
type ApiAppHandlerFunc func(w http.ResponseWriter, r *http.Request, app *models.App)
|
||||
|
||||
// ServeHTTP calls f(w, r).
|
||||
func (f ApiAppHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, app *models.App) {
|
||||
f(w, r, app)
|
||||
}
|
||||
|
||||
func (s *Server) apiHandlerWrapperFunc(apiHandler ApiHandler) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
apiHandler.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) apiAppHandlerWrapperFunc(apiHandler ApiAppHandler) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// get the app
|
||||
appName := c.Param(api.CApp)
|
||||
app, err := s.Datastore.GetApp(c.Request.Context(), appName)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if app == nil {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
apiHandler.ServeHTTP(c.Writer, c.Request, app)
|
||||
}
|
||||
}
|
||||
|
||||
// AddEndpoint adds an endpoint to /v1/x
|
||||
func (s *Server) AddEndpoint(method, path string, handler ApiHandler) {
|
||||
v1 := s.Router.Group("/v1")
|
||||
// v1.GET("/apps/:app/log", logHandler(cfg))
|
||||
v1.Handle(method, path, s.apiHandlerWrapperFunc(handler))
|
||||
}
|
||||
|
||||
// AddEndpoint adds an endpoint to /v1/x
|
||||
func (s *Server) AddEndpointFunc(method, path string, handler func(w http.ResponseWriter, r *http.Request)) {
|
||||
s.AddEndpoint(method, path, ApiHandlerFunc(handler))
|
||||
}
|
||||
|
||||
// AddAppEndpoint adds an endpoints to /v1/apps/:app/x
|
||||
func (s *Server) AddAppEndpoint(method, path string, handler ApiAppHandler) {
|
||||
v1 := s.Router.Group("/v1")
|
||||
v1.Handle(method, "/apps/:app"+path, s.apiAppHandlerWrapperFunc(handler))
|
||||
}
|
||||
|
||||
// AddAppEndpoint adds an endpoints to /v1/apps/:app/x
|
||||
func (s *Server) AddAppEndpointFunc(method, path string, handler func(w http.ResponseWriter, r *http.Request, app *models.App)) {
|
||||
s.AddAppEndpoint(method, path, ApiAppHandlerFunc(handler))
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iron-io/functions/api/server"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
@@ -19,13 +18,16 @@ func init() {
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatalln("")
|
||||
}
|
||||
// Replace forward slashes in case this is windows, URL parser errors
|
||||
cwd = strings.Replace(cwd, "\\", "/", -1)
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.SetDefault(server.EnvLogLevel, "info")
|
||||
viper.SetDefault(server.EnvMQURL, fmt.Sprintf("bolt://%s/data/worker_mq.db", cwd))
|
||||
viper.SetDefault(server.EnvDBURL, fmt.Sprintf("bolt://%s/data/bolt.db?bucket=funcs", cwd))
|
||||
viper.SetDefault(server.EnvPort, 8080)
|
||||
viper.SetDefault(server.EnvAPIURL, fmt.Sprintf("http://127.0.0.1:%d", viper.GetInt(server.EnvPort)))
|
||||
logLevel, err := logrus.ParseLevel(viper.GetString(server.EnvLogLevel))
|
||||
viper.SetDefault(EnvLogLevel, "info")
|
||||
viper.SetDefault(EnvMQURL, fmt.Sprintf("bolt://%s/data/worker_mq.db", cwd))
|
||||
viper.SetDefault(EnvDBURL, fmt.Sprintf("bolt://%s/data/bolt.db?bucket=funcs", cwd))
|
||||
viper.SetDefault(EnvPort, 8080)
|
||||
viper.SetDefault(EnvAPIURL, fmt.Sprintf("http://127.0.0.1:%d", viper.GetInt(EnvPort)))
|
||||
viper.AutomaticEnv() // picks up env vars automatically
|
||||
logLevel, err := logrus.ParseLevel(viper.GetString(EnvLogLevel))
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatalln("Invalid log level.")
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
@@ -14,7 +15,9 @@ import (
|
||||
"github.com/ccirello/supervisor"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iron-io/functions/api"
|
||||
"github.com/iron-io/functions/api/datastore"
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/mqs"
|
||||
"github.com/iron-io/functions/api/runner"
|
||||
"github.com/iron-io/functions/api/runner/task"
|
||||
"github.com/iron-io/functions/api/server/internal/routecache"
|
||||
@@ -40,9 +43,7 @@ type Server struct {
|
||||
apiURL string
|
||||
|
||||
specialHandlers []SpecialHandler
|
||||
appCreateListeners []AppCreateListener
|
||||
appUpdateListeners []AppUpdateListener
|
||||
appDeleteListeners []AppDeleteListener
|
||||
appListeners []AppListener
|
||||
runnerListeners []RunnerListener
|
||||
|
||||
mu sync.Mutex // protects hotroutes
|
||||
@@ -53,6 +54,24 @@ type Server struct {
|
||||
|
||||
const cacheSize = 1024
|
||||
|
||||
// NewFromEnv creates a new IronFunctions server based on env vars.
|
||||
func NewFromEnv(ctx context.Context) *Server {
|
||||
ds, err := datastore.New(viper.GetString(EnvDBURL))
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatalln("Error initializing datastore.")
|
||||
}
|
||||
|
||||
mq, err := mqs.New(viper.GetString(EnvMQURL))
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Error initializing message queue.")
|
||||
}
|
||||
|
||||
apiURL := viper.GetString(EnvAPIURL)
|
||||
|
||||
return New(ctx, ds, mq, apiURL)
|
||||
}
|
||||
|
||||
// New creates a new IronFunctions server with the passed in datastore, message queue and API URL
|
||||
func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, apiURL string, opts ...ServerOption) *Server {
|
||||
metricLogger := runner.NewMetricLogger()
|
||||
funcLogger := runner.NewFuncLogger()
|
||||
@@ -76,6 +95,7 @@ func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, apiUR
|
||||
}
|
||||
|
||||
s.Router.Use(prepareMiddleware(ctx))
|
||||
s.bindHandlers()
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(s)
|
||||
@@ -87,14 +107,17 @@ func prepareMiddleware(ctx context.Context) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx, _ := common.LoggerWithFields(ctx, extractFields(c))
|
||||
|
||||
if appName := c.Param("app"); appName != "" {
|
||||
if appName := c.Param(api.CApp); appName != "" {
|
||||
c.Set(api.AppName, appName)
|
||||
}
|
||||
|
||||
if routePath := c.Param("route"); routePath != "" {
|
||||
if routePath := c.Param(api.CRoute); routePath != "" {
|
||||
c.Set(api.Path, routePath)
|
||||
}
|
||||
|
||||
// todo: can probably replace the "ctx" value with the Go 1.7 context on the http.Request
|
||||
c.Set("ctx", ctx)
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -173,7 +196,7 @@ func extractFields(c *gin.Context) logrus.Fields {
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) {
|
||||
s.bindHandlers()
|
||||
ctx = contextWithSignal(ctx, os.Interrupt)
|
||||
s.startGears(ctx)
|
||||
close(s.tasks)
|
||||
}
|
||||
|
||||
@@ -1,64 +1,51 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http/httputil"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/iron-io/functions/api"
|
||||
"github.com/iron-io/functions/api/datastore"
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/mqs"
|
||||
"github.com/iron-io/functions/api/runner"
|
||||
"github.com/iron-io/functions/api/runner/task"
|
||||
"github.com/iron-io/functions/api/server/internal/routecache"
|
||||
)
|
||||
import "testing"
|
||||
|
||||
type testSpecialHandler struct{}
|
||||
|
||||
func (h *testSpecialHandler) Handle(c HandlerContext) error {
|
||||
c.Set(api.AppName, "test")
|
||||
// c.Set(api.AppName, "test")
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSpecialHandlerSet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// temporarily commented until we figure out if we want this anymore
|
||||
// ctx := context.Background()
|
||||
|
||||
tasks := make(chan task.Request)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
// tasks := make(chan task.Request)
|
||||
// ctx, cancel := context.WithCancel(context.Background())
|
||||
// defer cancel()
|
||||
|
||||
rnr, cancelrnr := testRunner(t)
|
||||
defer cancelrnr()
|
||||
// rnr, cancelrnr := testRunner(t)
|
||||
// defer cancelrnr()
|
||||
|
||||
go runner.StartWorkers(ctx, rnr, tasks)
|
||||
// go runner.StartWorkers(ctx, rnr, tasks)
|
||||
|
||||
s := &Server{
|
||||
Runner: rnr,
|
||||
Router: gin.New(),
|
||||
Datastore: &datastore.Mock{
|
||||
Apps: []*models.App{
|
||||
{Name: "test"},
|
||||
},
|
||||
Routes: []*models.Route{
|
||||
{Path: "/test", Image: "iron/hello", AppName: "test"},
|
||||
},
|
||||
},
|
||||
MQ: &mqs.Mock{},
|
||||
tasks: tasks,
|
||||
Enqueue: DefaultEnqueue,
|
||||
hotroutes: routecache.New(2),
|
||||
}
|
||||
// s := &Server{
|
||||
// Runner: rnr,
|
||||
// Router: gin.New(),
|
||||
// Datastore: &datastore.Mock{
|
||||
// Apps: []*models.App{
|
||||
// {Name: "test"},
|
||||
// },
|
||||
// Routes: []*models.Route{
|
||||
// {Path: "/test", Image: "iron/hello", AppName: "test"},
|
||||
// },
|
||||
// },
|
||||
// MQ: &mqs.Mock{},
|
||||
// tasks: tasks,
|
||||
// Enqueue: DefaultEnqueue,
|
||||
// }
|
||||
|
||||
router := s.Router
|
||||
router.Use(prepareMiddleware(ctx))
|
||||
s.bindHandlers()
|
||||
s.AddSpecialHandler(&testSpecialHandler{})
|
||||
// router := s.Router
|
||||
// router.Use(prepareMiddleware(ctx))
|
||||
// s.bindHandlers()
|
||||
// s.AddSpecialHandler(&testSpecialHandler{})
|
||||
|
||||
_, rec := routerRequest(t, router, "GET", "/test", nil)
|
||||
if rec.Code != 200 {
|
||||
dump, _ := httputil.DumpResponse(rec.Result(), true)
|
||||
t.Fatalf("Test SpecialHandler: expected special handler to run functions successfully. Response:\n%s", dump)
|
||||
}
|
||||
// _, rec := routerRequest(t, router, "GET", "/test", nil)
|
||||
// if rec.Code != 200 {
|
||||
// dump, _ := httputil.DumpResponse(rec.Result(), true)
|
||||
// t.Fatalf("Test SpecialHandler: expected special handler to run functions successfully. Response:\n%s", dump)
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -88,6 +88,12 @@ Triggered during requests to the following routes:
|
||||
- GET /r/:app/:route
|
||||
- POST /r/:app/:route
|
||||
|
||||
## Adding API Endpoints
|
||||
|
||||
You can add API endpoints by using the `AddEndpoint` and `AddEndpointFunc` methods to the IronFunctions server.
|
||||
|
||||
See examples of this in [/examples/extensions/main.go](/examples/extensions/main.go).
|
||||
|
||||
## Special Handlers
|
||||
|
||||
To understand how **Special Handlers** works you need to understand what are **Special Routes**.
|
||||
|
||||
2
examples/extensions/.gitignore
vendored
Normal file
2
examples/extensions/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/extensions
|
||||
/extensions.exe
|
||||
22
examples/extensions/README.md
Normal file
22
examples/extensions/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Extensions Example
|
||||
|
||||
This example adds extra endpoints to the API.
|
||||
|
||||
## Building and Running
|
||||
|
||||
```sh
|
||||
go build -o functions
|
||||
./functions
|
||||
```
|
||||
|
||||
Then test with:
|
||||
|
||||
```sh
|
||||
# First, create an app
|
||||
fn apps create myapp
|
||||
# And test
|
||||
curl http://localhost:8080/v1/custom1
|
||||
curl http://localhost:8080/v1/custom2
|
||||
curl http://localhost:8080/v1/apps/myapp/custom3
|
||||
curl http://localhost:8080/v1/apps/myapp/custom4
|
||||
```
|
||||
49
examples/extensions/main.go
Normal file
49
examples/extensions/main.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/server"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
funcServer := server.NewFromEnv(ctx)
|
||||
// Setup your custom extensions, listeners, etc here
|
||||
funcServer.AddEndpoint("GET", "/custom1", &Custom1Handler{})
|
||||
funcServer.AddEndpointFunc("GET", "/custom2", func(w http.ResponseWriter, r *http.Request) {
|
||||
// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||
fmt.Println("Custom2Handler called")
|
||||
fmt.Fprintf(w, "Hello func, %q", html.EscapeString(r.URL.Path))
|
||||
})
|
||||
|
||||
// the following will be at /v1/apps/:app_name/custom2
|
||||
funcServer.AddAppEndpoint("GET", "/custom3", &Custom3Handler{})
|
||||
funcServer.AddAppEndpointFunc("GET", "/custom4", func(w http.ResponseWriter, r *http.Request, app *models.App) {
|
||||
// fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||
fmt.Println("Custom4Handler called")
|
||||
fmt.Fprintf(w, "Hello app %v func, %q", app.Name, html.EscapeString(r.URL.Path))
|
||||
})
|
||||
funcServer.Start(ctx)
|
||||
}
|
||||
|
||||
type Custom1Handler struct {
|
||||
}
|
||||
|
||||
func (h *Custom1Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Custom1Handler called")
|
||||
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
|
||||
}
|
||||
|
||||
type Custom3Handler struct {
|
||||
}
|
||||
|
||||
func (h *Custom3Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, app *models.App) {
|
||||
fmt.Println("Custom3Handler called")
|
||||
fmt.Fprintf(w, "Hello app %v, %q", app.Name, html.EscapeString(r.URL.Path))
|
||||
}
|
||||
BIN
fnctl/fnctl.exe
Normal file
BIN
fnctl/fnctl.exe
Normal file
Binary file not shown.
21
main.go
21
main.go
@@ -2,31 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/iron-io/functions/api/datastore"
|
||||
"github.com/iron-io/functions/api/mqs"
|
||||
"github.com/iron-io/functions/api/server"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := contextWithSignal(context.Background(), os.Interrupt)
|
||||
ctx := context.Background()
|
||||
|
||||
ds, err := datastore.New(viper.GetString(server.EnvDBURL))
|
||||
if err != nil {
|
||||
log.WithError(err).Fatalln("Invalid DB url.")
|
||||
}
|
||||
|
||||
mq, err := mqs.New(viper.GetString(server.EnvMQURL))
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Error on init MQ")
|
||||
}
|
||||
|
||||
apiURL := viper.GetString(server.EnvAPIURL)
|
||||
|
||||
funcServer := server.New(ctx, ds, mq, apiURL)
|
||||
funcServer := server.NewFromEnv(ctx)
|
||||
// Setup your custom extensions, listeners, etc here
|
||||
funcServer.Start(ctx)
|
||||
}
|
||||
|
||||
2
test/README.md
Normal file
2
test/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
TODO: full stack tests. fire up the functions container, use the api from our generated client libs.
|
||||
|
||||
Reference in New Issue
Block a user