-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
PR for middleware support #293
Comments
I second this proposal. it will actually be very useful...I'm currently working on an authentication middleware so I wanted to add the ability to allow certain routes without authentication but can't because |
Can I send a pull request with something like this |
Let me think on the design. Custom middleware interfaces can be tricky and
isolate mux. I'd want to see how something like Chi solves this use-case
first.
…On Mon, Sep 11, 2017 at 8:25 AM Abdullah Saleem ***@***.***> wrote:
Can I send a pull request with something like this
master...Abdullah2993:master
<master...Abdullah2993:master>
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcKBluOGYU55F4pq5dqp9y-Rq4JT_ks5shVEAgaJpZM4PRbdT>
.
|
My fork actually looks like this: As I said, I feel that returning the responseWriter and request seems better (to me at least) than adding a third parameter like Negroni does. Other options are, of course, open to discussion. However, please take into account that middleware writers would value the ability to hijack these two objects, so a modification to the http.Handler interface is required. |
What does func (http.Handler) http.Handler) not solve here, as the
"defacto" middleware interface? (I may be missing something)
…On Mon, Sep 11, 2017 at 9:12 AM Roberto Santalla ***@***.***> wrote:
My fork actually looks like this:
master...roobre:middleware
<master...roobre:middleware>
As I said, I feel that returning the responseWriter and request seems
better (to me at least) than adding a third parameter like Negroni does.
Other options are, of course, open to discussion. However, please take into
account that middleware writers would value the ability to hijack these two
objects, so a modification to the http.Handler interface is required.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcAqVzLP7FFVRlZ4uS5jzekfbKmqGks5shVvUgaJpZM4PRbdT>
.
|
Basically your middleware might want to create a new Request or ResponseWriter, and pass them down to the rest of the chain. Think about returning |
@roobre this can be done with the classical |
Now it's me who is probably missing something. How can you create a middleware chain using that interface? Which |
You create closures - which is a fairly common way for writing middleware,
and makes it portable across any sane library - net/http, gorilla/mux, chi,
etc - as it's just http.Handler
func MyMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do things
})
}
Then you can wrap routers or routes - e.g.
r := mux.NewRouter()
// Some routes go here
http.ListenAndServe("localhost:8000", MyMiddleware(r))
Further reading:
- https://justinas.org/writing-http-middleware-in-go/
…On Tue, Sep 12, 2017 at 9:11 AM Roberto Santalla ***@***.***> wrote:
Now it's me who is probably missing something. How can you create a
middleware chain using that interface?
Which http.Handler is passed and what meaning have the returned
http.Handler? The purpose of middleware is to operate on the
ResponseWriter and Request objects, not on handlers themselves.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcN5BUBktowizhez1xZO7aTuHi7JUks5shq1AgaJpZM4PRbdT>
.
|
Thanks for the info :D It certainly looks good, I'll take a look at it and change the implementation if no functionality is lost. |
We won't have to change mux to support this :)
…On Tue, Sep 12, 2017 at 9:59 AM Roberto Santalla ***@***.***> wrote:
Thanks for the info :D
It certainly looks good, I'll take a look at it and change the
implementation if no functionality is lost.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcJ3R1QtkwVkpuevt2lUXnW2ug63aks5shrhYgaJpZM4PRbdT>
.
|
Then that is not the same approach I'm proposing here. There are plenty of tools which can be used to chain middleware before (and after, I guess) mux, but they get executed wether there is a route match or not. The point of adding middleware support inside mux was to circunvent this limitation, allowing middlewares to perform expensive tasks only if the endpoint is correct, or modifying the http status code only if a route is found (particularly useful for authentication middlewares). |
Can you share a design proposal for this? Take a look at how other routers are doing this, as well as the previous PRs/Issues raised here for background. |
I've looked at Negroni and Martini, each having his own approach. I'll look at a couple more and add a few drawings to explain my point better. I'm not sure when I'll be able to do this, this is being a tough week at work. Stay tuned :D |
Key differences bewteen external and embedded middlewareHello again. I finally have some time to explain this with more detail. I've (hand-)drawn a diagram to better illustrate the difference between the external middleware approach, which is what you are mentioning, and mine: As i've said, middleware embedded in Regarding the
|
@roobre Thanks for describing the design in detail. I'll make time to review today/tomorrow and we can decide about moving ahead. |
Sure! Also please note that the particular signatures and implementation are open to discussion. The key feature I'm trying to get with this is putting middleware after a match is found, and before the handler is called. |
That part [the ordering of matching vs. middleware] makes sense to me!
…On Thu, Sep 28, 2017 at 7:21 AM Roberto Santalla ***@***.***> wrote:
Sure!
Also please note that the particular signatures and implementation are
open to discussion. The key feature I'm trying to get with this is putting
middleware after a match is found, and before the handler is called.
—
You are receiving this because you were assigned.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcO7udp_ul9Oy4DdBw7soGstOZmghks5sm6tSgaJpZM4PRbdT>
.
|
@elithrar thats the whole point, the ability to get current route on the |
I'll do some experiments tomorrow and think about how could this be implemented using |
@roobre it is actually much simpler then you think...take a look at this all the middlewares use this pattern and it is actually quite simple and elegant here is a sample of it working
|
Correct me if I'm wrong, but doesn't this method imply the creation of a new |
Ok, I have migrated implementation to the proposed Here's an example implementation package main
import (
"log"
"net/http"
"roob.re/gorilla-mux"
)
func main() {
myMux := mux.NewRouter()
myMux.AddMiddlewareFunc(simpleMw)
mw := &myMw{}
myMux.AddMiddleware(mw)
myMux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("Handled"))
})
http.ListenAndServe("0.0.0.0:8080", myMux)
}
// Simple middleware function
func simpleMw(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Yee-haw")
h.ServeHTTP(w, r)
})
}
// Complex middleware object
type myMw struct {
logger *log.Logger
}
func (mw *myMw) theRealThing(uri string) {
if mw.logger != nil {
mw.logger.Println(uri)
} else {
log.Println(uri)
}
}
func (mw *myMw) Middleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func (rw http.ResponseWriter, r *http.Request) {
mw.theRealThing(r.RequestURI)
handler.ServeHTTP(rw, r)
})
} However, due to how the middleware chain is parsed, middlewares are executed in reverse order. Using for len instead of for range would solve this, but I'd like to hear your opinion on that. |
@roobre you are right about the |
I would make the order apply in the order that myMux.AddMiddleware(mw) was
called, which aligns with route definition (routes defined first take
precedence).
…On Fri, Sep 29, 2017 at 4:23 AM Abdullah Saleem ***@***.***> wrote:
@roobre <https://github.com/roobre> you are right about the routeLogger
being created for each request which is a problem.
as for your implementation can the for range that handles the middlewares
can be moved on to the Router ( Here
<https://github.com/gorilla/mux/blob/3f19343c7d9ce75569b952758bd236af94956061/mux.go#L245>
)?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcA0bWicOblNOMY2Y913rvvfNq-wKks5snNMrgaJpZM4PRbdT>
.
|
@elithrar Yes, I find it more appropiate and intuitive. I'll change it the next time I'll put my hands on the code. @abdullah2993 That would require duplicating the code, for both |
@roobre besides performance gain...it will allow us to apply incremental middlewares i.e the middlewares will only be applied to the routes that are registered after regirstering that middleware...for example in case of some auth middleware you can register allowed path before registering the middleware hence some routes will be executed with middleware and some won't be. In the current situation all the registerd middlewares are applied to all the routes... |
@abdullah2993 I was planning to circunvent that using subrouters, which I find prettier. Otherwise, if you wanted to add the same auth middleware for 10 routes, you'll end up with 10 different instances of it. |
@roobre subrouters can be used to achieve the same thing but this will allow an elegant alternative and will come in handy, not to mention the negligible performance gain. |
@abdullah2993 Can you show a simple example of the benefits of your approach from an user API perspective? Right now, @roobre's approach—scoping to (sub-)routers makes sense to me, and aligns with other libraries like chi. |
@elithrar sure as soon as I get my hands on the computer. This approach will allow not keep you from scoping to sub-routers, if you want to you can but if you don't want to then you can get away with it |
@abdullah2993 Another problem of moving middleware handling to the route is that becomes difficult to add a middleware to the whole router. Either the user copypastes the whole We could also allow both Route and Router middlewares, where routes would have their own My personal opinion is that middlewares should belong to a router, and not to a route. It just seems more coherent that way. In the end, if you have a middleware that you need to apply to only one or two routes, it makes more sense for that code to be in the route handler instead. |
@elithrar @roobre
Output with first approach:
Output with 2nd approach:
which can also allow us to have an api like this
|
I definitely don't like this. Using order of addition to define which middlewares affect which routes is completely intuitive. |
Status update: I moved middleware chain building from This is because |
Thanks Roberto.
I'm pretty convinced that the router-scoped approach (as opposed to
route-scoped) is the best one here, so will start reviewing this during the
week. A good few examples in the README will go a long way too.
…On Mon, Oct 2, 2017 at 5:55 PM Roberto Santalla ***@***.***> wrote:
Status update: I moved middleware chain building from ServeHTTP() to
Match().
This is because ServeHTTP() is called on the parent router, even if the
match is a subrouter. See 9315e1c
<9315e1c>
for details.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcCOH1v1No1wr6XSSDSyQvp2Z8c5jks5soRWFgaJpZM4PRbdT>
.
|
No prob. I'll probably add a few test cases tomorrow, as well as code documentation and README manuals. |
Status update: Test cases added, brief documentation included in |
Thanks Roberto!
As a heads up, I am on vacation at the moment—without a “real” computer—but
will try to get an initial first-pass review done between now and the end
of next week!
…On Tue, Oct 3, 2017 at 5:36 PM Roberto Santalla ***@***.***> wrote:
Status update: Test cases added, brief documentation included in README.md
and doc.go.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcMhmGtstUt4Vlxqo7k8J1PcvgqbGks5sokZhgaJpZM4PRbdT>
.
|
I've discovered an annoying bug on my implementation. Middlewares aren't supposed to be run if no match is found, however, given how This bug makes me think about the way mux handles this event: correct me if I'm wrong, but if a method mismatch occurs, Changing this behaviour as I described would allow an easy check for a not found event, and avoid building the middleware chain if it took place. |
Sorry for the slow reply @roobe - catching up on issues. I'm not the original author for mux, so I can't explain precisely why it behaves that way. I'm OK with:
Notably, there may be valid cases where you still want middleware to execute when there is no match (logging all requests, for example!), so we should provide a way to account for that too. |
No prob, I agree with you in both points. I think it's best if I create a new PR for setting |
Perfect - SGTM.
…On Sun, Oct 29, 2017 at 4:22 PM Roberto Santalla ***@***.***> wrote:
No prob, I agree with you in both points. I think it's best if I create a
new PR for setting ErrMatchNotFound when falling back to NotFoundHandler,
and then continuing on the middleware branch when it is merged. That should
avoid unnecessary conflicts. Are you okay with that?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#293 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABIcI0mw4ty-ZvntAMzizZYmaSpdNhUks5sxQi1gaJpZM4PRbdT>
.
|
#311 created. |
Closed in #294. |
A new dev cycle means it's a good time to update. I had to remember to clear my glide cache before updating due to a long standing bug (see: Masterminds/glide#592). I was definitely motivated, to see if we can benefit from first-class middleware support in `gorilla/mux` (see: gorilla/mux#293).
Hello there,
I've been using
gorilla/mux
for a while on an internal project at my organization, and due to some requirements, I ended up forking the project and adding some extra features, such as internal support for middleware.Yes, I am aware that negroni is a thing, and that it also provides this functionality. However, there are a few key things you can't do just chaining
mux
after your middleware:Acting only if a route match is found: Waiting to see if a route actually matches before acting allows for more flexibility and coherent design. For example, an authentication middleware can happily return 403 if the required credentials are not supplied, and at the same time a normal 404 will be returned by
mux
if the route does not exist. This option does not exist if you need to process authentication headers before the request is matched.Adding middleware for subrouters is simpler if it is embed inside
mux
.It is more efficient. If your site receives heavy traffic and your middleware performs heavy tasks, you'll appreciate this kind of saving.
After pondering a bit, I decided that this simple addon was worth implementing, so I did.
As a middleware needs to be able to stop the handlers chain, I implemented it using a slightly modified
http.Handler
interface:If the middleware implementations return nil as either
http.ResponseWriter
or*http.Request
the chain is stopped. Also,http.ResponseWriter
and*http.Request
can be hijacked if the middleware wants to. This is an approach slightly different to negroni's, where a third parameter of*http.HandlerFunc
is added. I thought this way were better as it simplifies greatly building and iterating the middleware chain. Also, I don't like recursion. Of course, wrapper functions which receivehttp.Handler
andhttp.HandlerFunc
exists, so standard handlers can be chained too.I'm not sure if you think this feature is kind of out-of scope. But if you don't, I'll happily open a PR and discuss any modifications or style changes you'd want to be done.
The text was updated successfully, but these errors were encountered: