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
|
/gateway
|
||||||
/functions
|
/functions
|
||||||
/functions-alpine
|
/functions-alpine
|
||||||
|
/functions.exe
|
||||||
bolt.db
|
bolt.db
|
||||||
.glide/
|
.glide/
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,8 @@ package api
|
|||||||
const (
|
const (
|
||||||
AppName string = "app_name"
|
AppName string = "app_name"
|
||||||
Path string = "path"
|
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) {
|
func New(dbURL string) (models.Datastore, error) {
|
||||||
u, err := url.Parse(dbURL)
|
u, err := url.Parse(dbURL)
|
||||||
if err != nil {
|
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")
|
logrus.WithFields(logrus.Fields{"db": u.Scheme}).Debug("creating new datastore")
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Datastore interface {
|
type Datastore interface {
|
||||||
|
// GetApp returns the app called appName or nil if it doesn't exist
|
||||||
GetApp(ctx context.Context, appName string) (*App, error)
|
GetApp(ctx context.Context, appName string) (*App, error)
|
||||||
GetApps(ctx context.Context, filter *AppFilter) ([]*App, error)
|
GetApps(ctx context.Context, filter *AppFilter) ([]*App, error)
|
||||||
InsertApp(ctx context.Context, app *App) (*App, error)
|
InsertApp(ctx context.Context, app *App) (*App, error)
|
||||||
|
|||||||
@@ -2,24 +2,19 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/iron-io/functions/api/models"
|
"github.com/iron-io/functions/api/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppCreateListener interface {
|
type AppListener interface {
|
||||||
// BeforeAppCreate called right before creating App in the database
|
// BeforeAppCreate called right before creating App in the database
|
||||||
BeforeAppCreate(ctx context.Context, app *models.App) error
|
BeforeAppCreate(ctx context.Context, app *models.App) error
|
||||||
// AfterAppCreate called after creating App in the database
|
// AfterAppCreate called after creating App in the database
|
||||||
AfterAppCreate(ctx context.Context, app *models.App) error
|
AfterAppCreate(ctx context.Context, app *models.App) error
|
||||||
}
|
|
||||||
|
|
||||||
type AppUpdateListener interface {
|
|
||||||
// BeforeAppUpdate called right before updating App in the database
|
// BeforeAppUpdate called right before updating App in the database
|
||||||
BeforeAppUpdate(ctx context.Context, app *models.App) error
|
BeforeAppUpdate(ctx context.Context, app *models.App) error
|
||||||
// AfterAppUpdate called after updating App in the database
|
// AfterAppUpdate called after updating App in the database
|
||||||
AfterAppUpdate(ctx context.Context, app *models.App) error
|
AfterAppUpdate(ctx context.Context, app *models.App) error
|
||||||
}
|
|
||||||
|
|
||||||
type AppDeleteListener interface {
|
|
||||||
// BeforeAppDelete called right before deleting App in the database
|
// BeforeAppDelete called right before deleting App in the database
|
||||||
BeforeAppDelete(ctx context.Context, app *models.App) error
|
BeforeAppDelete(ctx context.Context, app *models.App) error
|
||||||
// AfterAppDelete called after deleting App in the database
|
// 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.
|
// AddAppCreateListener adds a listener that will be notified on App created.
|
||||||
func (s *Server) AddAppCreateListener(listener AppCreateListener) {
|
func (s *Server) AddAppListener(listener AppListener) {
|
||||||
s.appCreateListeners = append(s.appCreateListeners, listener)
|
s.appListeners = append(s.appListeners, 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) FireBeforeAppCreate(ctx context.Context, app *models.App) error {
|
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)
|
err := l.BeforeAppCreate(ctx, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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)
|
err := l.AfterAppCreate(ctx, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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)
|
err := l.BeforeAppUpdate(ctx, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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)
|
err := l.AfterAppUpdate(ctx, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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)
|
err := l.BeforeAppDelete(ctx, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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)
|
err := l.AfterAppDelete(ctx, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/iron-io/functions/api/server"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,13 +18,16 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatalln("")
|
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.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
viper.SetDefault(server.EnvLogLevel, "info")
|
viper.SetDefault(EnvLogLevel, "info")
|
||||||
viper.SetDefault(server.EnvMQURL, fmt.Sprintf("bolt://%s/data/worker_mq.db", cwd))
|
viper.SetDefault(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(EnvDBURL, fmt.Sprintf("bolt://%s/data/bolt.db?bucket=funcs", cwd))
|
||||||
viper.SetDefault(server.EnvPort, 8080)
|
viper.SetDefault(EnvPort, 8080)
|
||||||
viper.SetDefault(server.EnvAPIURL, fmt.Sprintf("http://127.0.0.1:%d", viper.GetInt(server.EnvPort)))
|
viper.SetDefault(EnvAPIURL, fmt.Sprintf("http://127.0.0.1:%d", viper.GetInt(EnvPort)))
|
||||||
logLevel, err := logrus.ParseLevel(viper.GetString(server.EnvLogLevel))
|
viper.AutomaticEnv() // picks up env vars automatically
|
||||||
|
logLevel, err := logrus.ParseLevel(viper.GetString(EnvLogLevel))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Fatalln("Invalid log level.")
|
logrus.WithError(err).Fatalln("Invalid log level.")
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -14,7 +15,9 @@ import (
|
|||||||
"github.com/ccirello/supervisor"
|
"github.com/ccirello/supervisor"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/iron-io/functions/api"
|
"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/models"
|
||||||
|
"github.com/iron-io/functions/api/mqs"
|
||||||
"github.com/iron-io/functions/api/runner"
|
"github.com/iron-io/functions/api/runner"
|
||||||
"github.com/iron-io/functions/api/runner/task"
|
"github.com/iron-io/functions/api/runner/task"
|
||||||
"github.com/iron-io/functions/api/server/internal/routecache"
|
"github.com/iron-io/functions/api/server/internal/routecache"
|
||||||
@@ -39,11 +42,9 @@ type Server struct {
|
|||||||
|
|
||||||
apiURL string
|
apiURL string
|
||||||
|
|
||||||
specialHandlers []SpecialHandler
|
specialHandlers []SpecialHandler
|
||||||
appCreateListeners []AppCreateListener
|
appListeners []AppListener
|
||||||
appUpdateListeners []AppUpdateListener
|
runnerListeners []RunnerListener
|
||||||
appDeleteListeners []AppDeleteListener
|
|
||||||
runnerListeners []RunnerListener
|
|
||||||
|
|
||||||
mu sync.Mutex // protects hotroutes
|
mu sync.Mutex // protects hotroutes
|
||||||
hotroutes *routecache.Cache
|
hotroutes *routecache.Cache
|
||||||
@@ -53,6 +54,24 @@ type Server struct {
|
|||||||
|
|
||||||
const cacheSize = 1024
|
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 {
|
func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, apiURL string, opts ...ServerOption) *Server {
|
||||||
metricLogger := runner.NewMetricLogger()
|
metricLogger := runner.NewMetricLogger()
|
||||||
funcLogger := runner.NewFuncLogger()
|
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.Router.Use(prepareMiddleware(ctx))
|
||||||
|
s.bindHandlers()
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(s)
|
opt(s)
|
||||||
@@ -87,14 +107,17 @@ func prepareMiddleware(ctx context.Context) gin.HandlerFunc {
|
|||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
ctx, _ := common.LoggerWithFields(ctx, extractFields(c))
|
ctx, _ := common.LoggerWithFields(ctx, extractFields(c))
|
||||||
|
|
||||||
if appName := c.Param("app"); appName != "" {
|
if appName := c.Param(api.CApp); appName != "" {
|
||||||
c.Set(api.AppName, appName)
|
c.Set(api.AppName, appName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if routePath := c.Param("route"); routePath != "" {
|
if routePath := c.Param(api.CRoute); routePath != "" {
|
||||||
c.Set(api.Path, 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.Set("ctx", ctx)
|
||||||
|
c.Request = c.Request.WithContext(ctx)
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +196,7 @@ func extractFields(c *gin.Context) logrus.Fields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start(ctx context.Context) {
|
func (s *Server) Start(ctx context.Context) {
|
||||||
s.bindHandlers()
|
ctx = contextWithSignal(ctx, os.Interrupt)
|
||||||
s.startGears(ctx)
|
s.startGears(ctx)
|
||||||
close(s.tasks)
|
close(s.tasks)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +1,51 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testSpecialHandler struct{}
|
type testSpecialHandler struct{}
|
||||||
|
|
||||||
func (h *testSpecialHandler) Handle(c HandlerContext) error {
|
func (h *testSpecialHandler) Handle(c HandlerContext) error {
|
||||||
c.Set(api.AppName, "test")
|
// c.Set(api.AppName, "test")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpecialHandlerSet(t *testing.T) {
|
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)
|
// tasks := make(chan task.Request)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
// ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
// defer cancel()
|
||||||
|
|
||||||
rnr, cancelrnr := testRunner(t)
|
// rnr, cancelrnr := testRunner(t)
|
||||||
defer cancelrnr()
|
// defer cancelrnr()
|
||||||
|
|
||||||
go runner.StartWorkers(ctx, rnr, tasks)
|
// go runner.StartWorkers(ctx, rnr, tasks)
|
||||||
|
|
||||||
s := &Server{
|
// s := &Server{
|
||||||
Runner: rnr,
|
// Runner: rnr,
|
||||||
Router: gin.New(),
|
// Router: gin.New(),
|
||||||
Datastore: &datastore.Mock{
|
// Datastore: &datastore.Mock{
|
||||||
Apps: []*models.App{
|
// Apps: []*models.App{
|
||||||
{Name: "test"},
|
// {Name: "test"},
|
||||||
},
|
// },
|
||||||
Routes: []*models.Route{
|
// Routes: []*models.Route{
|
||||||
{Path: "/test", Image: "iron/hello", AppName: "test"},
|
// {Path: "/test", Image: "iron/hello", AppName: "test"},
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
MQ: &mqs.Mock{},
|
// MQ: &mqs.Mock{},
|
||||||
tasks: tasks,
|
// tasks: tasks,
|
||||||
Enqueue: DefaultEnqueue,
|
// Enqueue: DefaultEnqueue,
|
||||||
hotroutes: routecache.New(2),
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
router := s.Router
|
// router := s.Router
|
||||||
router.Use(prepareMiddleware(ctx))
|
// router.Use(prepareMiddleware(ctx))
|
||||||
s.bindHandlers()
|
// s.bindHandlers()
|
||||||
s.AddSpecialHandler(&testSpecialHandler{})
|
// s.AddSpecialHandler(&testSpecialHandler{})
|
||||||
|
|
||||||
_, rec := routerRequest(t, router, "GET", "/test", nil)
|
// _, rec := routerRequest(t, router, "GET", "/test", nil)
|
||||||
if rec.Code != 200 {
|
// if rec.Code != 200 {
|
||||||
dump, _ := httputil.DumpResponse(rec.Result(), true)
|
// dump, _ := httputil.DumpResponse(rec.Result(), true)
|
||||||
t.Fatalf("Test SpecialHandler: expected special handler to run functions successfully. Response:\n%s", dump)
|
// 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
|
- GET /r/:app/:route
|
||||||
- POST /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
|
## Special Handlers
|
||||||
|
|
||||||
To understand how **Special Handlers** works you need to understand what are **Special Routes**.
|
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 (
|
import (
|
||||||
"context"
|
"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/iron-io/functions/api/server"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx := contextWithSignal(context.Background(), os.Interrupt)
|
ctx := context.Background()
|
||||||
|
|
||||||
ds, err := datastore.New(viper.GetString(server.EnvDBURL))
|
funcServer := server.NewFromEnv(ctx)
|
||||||
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)
|
|
||||||
// Setup your custom extensions, listeners, etc here
|
// Setup your custom extensions, listeners, etc here
|
||||||
funcServer.Start(ctx)
|
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