Fixes to recent extension changes. (#568)

* Fixes to recent extension changes.

* Fixes issue where gin will continue calling the handler even if next() isn't called.

* Updated docs.
This commit is contained in:
Travis Reeder
2017-12-06 10:12:55 -08:00
committed by GitHub
parent 3096900d52
commit 6b8627d1c5
6 changed files with 52 additions and 22 deletions

View File

@@ -43,6 +43,12 @@ func HandleErrorResponse(ctx context.Context, w http.ResponseWriter, err error)
statuscode = http.StatusInternalServerError statuscode = http.StatusInternalServerError
err = ErrInternalServerError err = ErrInternalServerError
} }
WriteError(ctx, w, statuscode, err)
}
// WriteError easy way to do standard error response, but can set statuscode and error message easier than HandleErrorResponse
func WriteError(ctx context.Context, w http.ResponseWriter, statuscode int, err error) {
log := common.Logger(ctx)
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(statuscode) w.WriteHeader(statuscode)
err = json.NewEncoder(w).Encode(simpleError(err)) err = json.NewEncoder(w).Encode(simpleError(err))

View File

@@ -1,7 +1,6 @@
package server package server
import ( import (
"fmt"
"log" "log"
"github.com/fnproject/fn/fnext" "github.com/fnproject/fn/fnext"
@@ -10,15 +9,15 @@ import (
// TODO: Move this into `github.com/fnproject/fn` package after main is moved out of root dir. // TODO: Move this into `github.com/fnproject/fn` package after main is moved out of root dir.
var extensions = map[string]fnext.Extension{} var extensions = map[string]fnext.Extension{}
// RegisterExtension registers the extension so it's available, but does not initialize it or anything // RegisterExtension registers the extension so it's available, but does not initialize it.
// This is generally what third party extensions will use in their init() method.
func RegisterExtension(ext fnext.Extension) { func RegisterExtension(ext fnext.Extension) {
extensions[ext.Name()] = ext extensions[ext.Name()] = ext
} }
// AddExtensionByName This essentially just makes sure the extensions are ordered properly. // AddExtensionByName This essentially just makes sure the extensions are ordered properly and is
// It could do some initialization if required too. // what the CLI uses for the `fn build-server` command. Probably not used by much else.
func (s *Server) AddExtensionByName(name string) { func (s *Server) AddExtensionByName(name string) {
fmt.Printf("extensions: %+v\n", extensions)
e, ok := extensions[name] e, ok := extensions[name]
if !ok { if !ok {
log.Fatalf("Extension %v not registered.\n", name) log.Fatalf("Extension %v not registered.\n", name)
@@ -28,3 +27,10 @@ func (s *Server) AddExtensionByName(name string) {
log.Fatalf("Failed to add extension %v: %v\n", name, err) log.Fatalf("Failed to add extension %v: %v\n", name, err)
} }
} }
// AddExtension both registers an extension and adds it. This is useful during extension development
// or if you want to build a custom server without using `fn build-server`.
func (s *Server) AddExtension(ext fnext.Extension) {
RegisterExtension(ext)
s.AddExtensionByName(ext.Name())
}

View File

@@ -2,7 +2,6 @@ package server
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"github.com/fnproject/fn/api/common" "github.com/fnproject/fn/api/common"
@@ -37,12 +36,14 @@ func (c *middlewareController) FunctionCalled() bool {
func (s *Server) apiMiddlewareWrapper() gin.HandlerFunc { func (s *Server) apiMiddlewareWrapper() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
// fmt.Println("api middleware")
s.runMiddleware(c, s.apiMiddlewares) s.runMiddleware(c, s.apiMiddlewares)
} }
} }
func (s *Server) rootMiddlewareWrapper() gin.HandlerFunc { func (s *Server) rootMiddlewareWrapper() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
// fmt.Println("ROOT MIDDLE")
s.runMiddleware(c, s.rootMiddlewares) s.runMiddleware(c, s.rootMiddlewares)
} }
} }
@@ -50,7 +51,9 @@ func (s *Server) rootMiddlewareWrapper() gin.HandlerFunc {
// This is basically a single gin middleware that runs a bunch of fn middleware. // This is basically a single gin middleware that runs a bunch of fn middleware.
// The final handler will pass it back to gin for further processing. // The final handler will pass it back to gin for further processing.
func (s *Server) runMiddleware(c *gin.Context, ms []fnext.Middleware) { func (s *Server) runMiddleware(c *gin.Context, ms []fnext.Middleware) {
// fmt.Println("runMiddleware")
if len(ms) == 0 { if len(ms) == 0 {
// fmt.Println("ZERO MIDDLEWARE")
c.Next() c.Next()
return return
} }
@@ -65,11 +68,11 @@ func (s *Server) runMiddleware(c *gin.Context, ms []fnext.Middleware) {
ctx := context.WithValue(c.Request.Context(), fnext.MiddlewareControllerKey, s.newMiddlewareController(c)) ctx := context.WithValue(c.Request.Context(), fnext.MiddlewareControllerKey, s.newMiddlewareController(c))
last := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { last := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("final function called") // fmt.Println("final handler called")
// check for bypass // check for bypass
mctx := fnext.GetMiddlewareController(r.Context()) mctx := fnext.GetMiddlewareController(r.Context())
if mctx.FunctionCalled() { if mctx.FunctionCalled() {
fmt.Println("function already called, skipping") // fmt.Println("func already called, skipping")
c.Abort() c.Abort()
return return
} }
@@ -77,6 +80,8 @@ func (s *Server) runMiddleware(c *gin.Context, ms []fnext.Middleware) {
}) })
chainAndServe(ms, c.Writer, c.Request.WithContext(ctx), last) chainAndServe(ms, c.Writer, c.Request.WithContext(ctx), last)
c.Abort() // we always abort here because the middleware decides to call next or not. If the `last` handler gets called, it will continue, otherwise we abort.
} }
func (s *Server) newMiddlewareController(c *gin.Context) *middlewareController { func (s *Server) newMiddlewareController(c *gin.Context) *middlewareController {
@@ -86,6 +91,14 @@ func (s *Server) newMiddlewareController(c *gin.Context) *middlewareController {
} }
} }
// TODO: I will remove this and other debug commented lines once I'm sure it's all right.
func debugH(i int, m fnext.Middleware, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// fmt.Println("handling", i, "m:", reflect.TypeOf(m), "h:", reflect.TypeOf(h))
h.ServeHTTP(w, r) // call original
})
}
// chainAndServe essentially makes a chain of middleware wrapped around each other, then calls ServerHTTP on the end result. // chainAndServe essentially makes a chain of middleware wrapped around each other, then calls ServerHTTP on the end result.
// then each middleware also calls ServeHTTP within it // then each middleware also calls ServeHTTP within it
func chainAndServe(ms []fnext.Middleware, w http.ResponseWriter, r *http.Request, last http.Handler) { func chainAndServe(ms []fnext.Middleware, w http.ResponseWriter, r *http.Request, last http.Handler) {
@@ -93,7 +106,7 @@ func chainAndServe(ms []fnext.Middleware, w http.ResponseWriter, r *http.Request
// These get chained in reverse order so they play out in the right order. Don't ask. // These get chained in reverse order so they play out in the right order. Don't ask.
for i := len(ms) - 1; i >= 0; i-- { for i := len(ms) - 1; i >= 0; i-- {
m := ms[i] m := ms[i]
h = m.Handle(h) h = m.Handle(debugH(i, m, h))
} }
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
} }

View File

@@ -5,7 +5,8 @@
If you are a developer using Fn through the API, this section is for you. If you are a developer using Fn through the API, this section is for you.
### Getting Started ### Getting Started
* [Quickstart](https://github.com/fnproject/fn)
* [Quickstart](https://github.com/fnproject/fn#quickstart)
* [API Reference](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/fnproject/fn/master/docs/swagger.yml) * [API Reference](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/fnproject/fn/master/docs/swagger.yml)
* [FAQ](faq.md) * [FAQ](faq.md)
* [Object Model](developers/model.md) * [Object Model](developers/model.md)
@@ -17,17 +18,25 @@ If you are a developer using Fn through the API, this section is for you.
* [Writing functions](writing.md) * [Writing functions](writing.md)
### Advanced ### Advanced
* [Open Function Format](function-format.md) * [Open Function Format](function-format.md)
* [Packaging functions](packaging.md) * [Packaging functions](packaging.md)
* [CLI Source](https://github.com/fnproject/cli/) * [CLI Source](https://github.com/fnproject/cli/)
* [Async functions](async.md) * [Async functions](async.md)
* [Organizing functions into an application](developers/apps.md) * [Organizing functions into an application](developers/apps.md)
### Learn More
* [Examples](../examples)
* [Getting Started Series](../examples/tutorial)
* [Tutorials](https://github.com/fnproject/tutorials)
## For Operators ## For Operators
If you are operating Fn, this section is for you. If you are operating Fn, this section is for you.
### Getting Started ### Getting Started
* [Running in Production](operating/production.md) * [Running in Production](operating/production.md)
* [Logging](operating/logging.md) * [Logging](operating/logging.md)
* [Message Queues](operating/mqs/README.md) * [Message Queues](operating/mqs/README.md)
@@ -37,18 +46,15 @@ If you are operating Fn, this section is for you.
* [User Interface](operating/ui.md) * [User Interface](operating/ui.md)
### Advanced ### Advanced
* [Extending Fn](operating/extending.md) * [Extending Fn](operating/extending.md)
* [Running Fn on Kubernetes](operating/kubernetes/README.md) * [Running Fn on Kubernetes](operating/kubernetes/README.md)
* [Setting up development environment with Docker compose](./operating/compose.md) * [Setting up development environment with Docker compose](./operating/compose.md)
* [OpenStack Triggers](operating/triggers.md) * [OpenStack Triggers](operating/triggers.md)
* [Docker Configuration](operating/docker.md) * [Docker Configuration](operating/docker.md)
## For Contributors
If you are working on the Fn Project, want to work on it or are creating extensions, this section is for you.
## Learn More * [Writing Extensions](contributors/extensions.md)
* [Examples](../examples)
* [Getting Started Series](../examples/tutorial)
* [Tutorials](https://github.com/fnproject/tutorials)

View File

@@ -1,3 +0,0 @@
# Docs for Contributors
* [Writing Extensions](extending.md)

View File

@@ -20,17 +20,19 @@ to be called during setup:
```go ```go
func init() { func init() {
server.RegisterExtension(&fnext.Extension{ server.RegisterExtension(&fnext.Extension{
Name: "logspam", Name: "github.com/treeder/fn-ext-example/logspam", // Should be the import name
Setup: setup, // Fn will call this during startup Setup: setup, // Fn will call this during startup
}) })
} }
func setup(s *fnext.ExtServer) error { func setup(s *fnext.ExtServer) error {
// Add all the hooks you extension needs here // Add all the hooks your extension needs here
s.AddCallListener(&LogSpam{}) s.AddCallListener(&LogSpam{})
} }
``` ```
See https://github.com/treeder/fn-ext-example for full example.
## Listeners ## Listeners
Listeners are the main way to extend Fn. Listeners are the main way to extend Fn.