From 9a75785cbf15784827b27f356870a9cc119b3eb5 Mon Sep 17 00:00:00 2001 From: Nigel Deakin Date: Wed, 29 Nov 2017 12:03:23 +0000 Subject: [PATCH] Per route api extensions (#542) * Extend extension mechanism to support per-route API extensions * Tidy up comment * Remove print statement * Minor improvement to README * Avoid calling c.Request.Context() twice --- api/server/extension_points.go | 59 ++++++++++++++++++++++++++++++++++ api/server/routes_get.go | 3 +- api/server/server.go | 2 +- examples/extensions/README.md | 7 ++-- examples/extensions/main.go | 16 +++++++++ 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/api/server/extension_points.go b/api/server/extension_points.go index 9ed12c378..428aa076e 100644 --- a/api/server/extension_points.go +++ b/api/server/extension_points.go @@ -59,6 +59,54 @@ func (s *Server) apiAppHandlerWrapperFunc(apiHandler ApiAppHandler) gin.HandlerF } } +// per Route + +type ApiRouteHandler interface { + // Handle(ctx context.Context) + ServeHTTP(w http.ResponseWriter, r *http.Request, app *models.App, route *models.Route) +} + +type ApiRouteHandlerFunc func(w http.ResponseWriter, r *http.Request, app *models.App, route *models.Route) + +// ServeHTTP calls f(w, r). +func (f ApiRouteHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request, app *models.App, route *models.Route) { + f(w, r, app, route) +} + +func (s *Server) apiRouteHandlerWrapperFunc(apiHandler ApiRouteHandler) gin.HandlerFunc { + return func(c *gin.Context) { + context := c.Request.Context() + // get the app + appName := c.Param(api.CApp) + app, err := s.Datastore.GetApp(context, appName) + if err != nil { + handleErrorResponse(c, err) + c.Abort() + return + } + if app == nil { + handleErrorResponse(c, models.ErrAppsNotFound) + c.Abort() + return + } + // get the route TODO + routePath := "/" + c.Param(api.CRoute) + route, err := s.Datastore.GetRoute(context, appName, routePath) + if err != nil { + handleErrorResponse(c, err) + c.Abort() + return + } + if route == nil { + handleErrorResponse(c, models.ErrRoutesNotFound) + c.Abort() + return + } + + apiHandler.ServeHTTP(c.Writer, c.Request, app, route) + } +} + // AddEndpoint adds an endpoint to /v1/x func (s *Server) AddEndpoint(method, path string, handler ApiHandler) { v1 := s.Router.Group("/v1") @@ -81,3 +129,14 @@ func (s *Server) AddAppEndpoint(method, path string, handler ApiAppHandler) { func (s *Server) AddAppEndpointFunc(method, path string, handler func(w http.ResponseWriter, r *http.Request, app *models.App)) { s.AddAppEndpoint(method, path, ApiAppHandlerFunc(handler)) } + +// AddRouteEndpoint adds an endpoints to /v1/apps/:app/routes/:route/x +func (s *Server) AddRouteEndpoint(method, path string, handler ApiRouteHandler) { + v1 := s.Router.Group("/v1") + v1.Handle(method, "/apps/:app/routes/:route"+path, s.apiRouteHandlerWrapperFunc(handler)) // conflicts with existing wildcard +} + +// AddRouteEndpoint adds an endpoints to /v1/apps/:app/routes/:route/x +func (s *Server) AddRouteEndpointFunc(method, path string, handler func(w http.ResponseWriter, r *http.Request, app *models.App, route *models.Route)) { + s.AddRouteEndpoint(method, path, ApiRouteHandlerFunc(handler)) +} diff --git a/api/server/routes_get.go b/api/server/routes_get.go index 87235fc58..12fb64ce8 100644 --- a/api/server/routes_get.go +++ b/api/server/routes_get.go @@ -12,8 +12,7 @@ func (s *Server) handleRouteGet(c *gin.Context) { ctx := c.Request.Context() appName := c.MustGet(api.AppName).(string) - routePath := path.Clean(c.MustGet(api.Path).(string)) - + routePath := path.Clean("/" + c.MustGet(api.Path).(string)) route, err := s.Datastore.GetRoute(ctx, appName, routePath) if err != nil { handleErrorResponse(c, err) diff --git a/api/server/server.go b/api/server/server.go index 018720bb5..605683ab3 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -363,7 +363,7 @@ func (s *Server) bindHandlers(ctx context.Context) { apps.GET("/routes", s.handleRouteList) apps.POST("/routes", s.handleRoutesPostPutPatch) - apps.GET("/routes/*route", s.handleRouteGet) + apps.GET("/routes/:route", s.handleRouteGet) apps.PATCH("/routes/*route", s.handleRoutesPostPutPatch) apps.PUT("/routes/*route", s.handleRoutesPostPutPatch) apps.DELETE("/routes/*route", s.handleRouteDelete) diff --git a/examples/extensions/README.md b/examples/extensions/README.md index ced9b26db..a982553f8 100644 --- a/examples/extensions/README.md +++ b/examples/extensions/README.md @@ -9,14 +9,13 @@ go build ./extensions ``` -Then test with: +First create an app `myapp` and a function `myroute`. 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 +curl http://localhost:8080/v1/apps/myapp/routes/myroute/custom5 +curl http://localhost:8080/v1/apps/myapp/routes/myroute/custom5 ``` diff --git a/examples/extensions/main.go b/examples/extensions/main.go index 335434479..c300348c8 100644 --- a/examples/extensions/main.go +++ b/examples/extensions/main.go @@ -29,6 +29,14 @@ func main() { fmt.Println("Custom4Handler called") fmt.Fprintf(w, "Hello app %v func, %q", app.Name, html.EscapeString(r.URL.Path)) }) + // the following will be at /v1/apps/:app_name/routes/:route_name/custom5 + // and /v1/apps/:app_name/routes/:route_name/custom6 + funcServer.AddRouteEndpoint("GET", "/custom5", &Custom5Handler{}) + funcServer.AddRouteEndpointFunc("GET", "/custom6", func(w http.ResponseWriter, r *http.Request, app *models.App, route *models.Route) { + // fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) + fmt.Println("Custom6Handler called") + fmt.Fprintf(w, "Hello app %v, route %v, request %q", app.Name, route.Path, html.EscapeString(r.URL.Path)) + }) funcServer.Start(ctx) } @@ -47,3 +55,11 @@ func (h *Custom3Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, app * fmt.Println("Custom3Handler called") fmt.Fprintf(w, "Hello app %v, %q", app.Name, html.EscapeString(r.URL.Path)) } + +type Custom5Handler struct { +} + +func (h *Custom5Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, app *models.App, route *models.Route) { + fmt.Println("Custom5Handler called") + fmt.Fprintf(w, "Hello! app %v, route %v, request %q", app.Name, route.Path, html.EscapeString(r.URL.Path)) +}