Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added: Endware functionality #51

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

protometheus
Copy link

@protometheus protometheus commented Mar 25, 2020

For some use cases, the server may want to perform actions after the user's request has been serviced. Examples include:

  • logging the request
  • performing maintenance on resources
  • error handling
  • auditing
  • metrics
  • anything, really

For these purposes, I have added Endware as a solution. Endware is a http.Handler alias that allows alice users to create "after the fact" middleware in a similar way to how they create regular middleware. Endware is executed after all constructors and handlers have been invoked by Then(h)/ThenFunc(h).

Using endware is simple:
chain := alice.New(m1, m2)
becomes
chainWithEndware := alice.New(m1, m2).Finally(e1, e2)

This leads the flow of
chainWithEndware.Then(h)
to be
m1 -> m2 -> h -> e1 -> e2

The Append() functionality has been mirrored in AppendEndware(), as well as Extend being augmented to also extend endware. AfterFuncs() and AppendEndwareFuncs() are convenience method for those not dealing with http.Handlers directly.

This is still a WIP, just wanted to open it so you could take a look at your leisure.

Sorry in advance if this is not the way you are supposed to contribute code

@justinas
Copy link
Owner

Hi @protometheus and thank you for contributing.

Have you considered that any middleware as-is can run actions after the handler has served? Consider this:

func log(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h.ServeHTTP(w, r)
        log.Println("A request has been served")
    })
}

fn main() {
    chain := alice.New(log).Then(myHandler)
}

The message will be printed out after myHandler has served.

@protometheus
Copy link
Author

protometheus commented Mar 26, 2020

Hey @justinas thanks for getting back so quickly! You are definitely correct that you can use the middleware as-is to represent endware. The only difference is that it is not clear from the Chain what the actual order of execution would be. So given:

func log1(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h.ServeHTTP(w, r)
        log.Println("log1")
    })
}

func log2(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        h.ServeHTTP(w, r)
        log.Println("log2")
    })
}

func log3(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println("log3")
        h.ServeHTTP(w, r)
    })
}
func main() {
    chain := alice.New(log1, log2).Then(myHandler)
}

will do myHandler -> log2 -> log1. But

func main() {
    chain := alice.New(log1, log3).Then(myHandler)
}

will do log3 -> myHandler -> log1.

This determination gets more difficult the more complex the middleware. My intent was to make it more obvious the order of execution without having to actually look into each individual middleware to determine what the order is.

@merveillevaneck
Copy link

Preface: Ignore this if im not adding any value here ;P

@justinas i agree with @protometheus on this. Came here specifically to see if I could use alice to chain middleware on a high level and use middleware composition to distribute business logic across the different routes of an app efficiently (preferably without having to repeat business logic in multiple places). This has to work without having to know the inner execution order of the middleware itself.

I think the main reason why this would be valuable is imo that, in simple HTTP services, I expect middleware to block execution on failed conditions (e.g. custom authz middleware for different nested routers). When using alice in a production codebase, I would highly advise against performing calculations or creating side effects in middleware after next is invoked. It just simply doesnt make sense to introduce side effects into a flow that is quite functional to begin with.

Yes technically people can make their middleware execute next handlers in any order they want, but moving to a declarative middleware makes the experience of using Go's built in routing assets much easier to manipulate in my humble opinion. I believe alice solves this, though this addition would really make it a no brainer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants