gocraft / web

Go Router + Middleware. Your Contexts.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Integrating Outerware (middleware post handler processing)

gtrevg opened this issue · comments

First off, thanks for building a flexible, yet simple router & middleware system.

I was wondering, though -- Do you have any thoughts on how I might go about implementing "Outerware" (often called output filters)? Essentially, middleware that will get executed after the route handler gets processed? You could imagine that for ever web request you serve, you want to have a standard way of recording stats for that request without having to put the code in each handler. For instance:

type YourContext struct {
  startTime time.Time
}

func (c *YourContext) StartRequest(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) {
  c.startTime = time.Now()
  next(rw, req)
}

func (c *YourContext) EndRequest(rw web.ResponseWriter, req *web.Request, next web.NextOuterwareFunc) {
  fmt.Fprint(rw, "request_time=", time.Since(c.startTime), " request_url=", req.URL.Path)
  next(rw, req)
}

router := web.New(YourContext{})
router.Middleware((*YourContext).StartRequest)

router.Get("/users", (*YourContext).UsersList)
router.Post("/users", (*YourContext).UsersCreate)
router.Put("/users/:id", (*YourContext).UsersUpdate)
router.Delete("/users/:id", (*YourContext).UsersDelete)
router.Patch("/users/:id", (*YourContext).UsersUpdate)
router.Get("/", (*YourContext).Root)

router.Outerware((*YourContext).EndRequest)

You could also imagine this process would be useful for having the handlers prep the context with the output data, and a common outerware takes that data and generates the JSON response. I think the alternative would be to call a common function at the end of every handler. That solution just is not as flexible in terms of attaching different outerware to different routers and subrouters (not demonstrated in the above example).

Thanks!

commented

If you just want to record some statistics after each request, just put it after the call to 'next' in your example.

If you want to mutate the result, don't pass the instance of 'web.ResponseWriter' to next. Create your own class implementing the interface and then do whatever you want when the result is written.

So, just to be clear, the control flow is:

  • All middleware next(rw, req) statements are run
  • When next items are done running, the routing kicks in and calls the handler code (for example, (*YourContext).UsersUpdate )
  • After that returns, any code in middleware after the next(rw, req) statement will get executed

Is that correct?

Thanks

commented

Well your first bullet isn't really true in all circumstances. A piece of middleware could decide not to call next and just render a response itself, or it could decide to panic.

Other than that you have it correct.

Cool. Thanks!