I'm building an application using the Micro Service Architecture.
On the gateway, I do want to route requests to the correct endpoints.
But, the endpoint are now known at runtime and needs to be configured in the DB.
Below if the code to get the router.
func getRouter() *mux.Router {
r := mux.NewRouter()
r.Use(dynamicRouteMiddleware)
return r
}
The middleware itself is this:
func dynamicRouteMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Error")
})
}
However, the "Error" is never printed.
It's only printed when I put a handler for '/'
How can I create middleware without handlers?
It's called "middleware" because it's supposed to put your Handler in the "middle". It receives the input before your Handler, and receives the output of your Handler.
Inherently, to have your middleware work, you are required to have at least one Handler. Preferably you may just use this functionality that you need in the Handler rather than middleware.
On the middleware, you need call the next handler so all incoming requests will proceed to the destination route.
func dynamicRouteMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Error")
next.ServeHTTP(w, r) // <------- this one
})
}
You can register any routes as you want, but in the very end make sure the r object used as handler of / route.
r.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test"))
})
r.HandleFunc("/test/12", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test 12"))
})
r.HandleFunc("/about-us", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("about us"))
})
http.Handle("/", r)
http.ListenAndServe(":8080", nil)
When you access /test, /test/12, or /about-us; the Error will still be printed.
Previously it's not printed because you don't proceed to the next handler. The code next.ServeHTTP(w, r) is mandatory in your case.
Related
Hi
there is a need to pass a json object to check authentication in each request
for this purpose i am using gorilla package
func middlewareFirstAuthCheck(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("middlewareFirstAuthCheck - Before Handler")
next.ServeHTTP(w, r)
})
}
func StandartHandler(w http.ResponseWriter, r *http.Request) {
}
in standard handler i accept json object, and in middlewareFirstAuthCheck i accept different json object
middlewareFirstAuthCheck is executed first
there is an understanding of what needs to be done, but how to implement it correctly?
Keep in mind that the code running inside this function is not safe for a mux server:
func middlewareFirstAuthCheck(next http.Handler) http.Handler {
// don't manage something related to request if you have a mux server
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("middlewareFirstAuthCheck - Before Handler")
// you could get the r.Body or r.Headers here to get the user authenticated
next.ServeHTTP(w, r)
})
}
Ideally if you wanna handle permissions you should be setting them on the middleware depending the scope of your endpoint. Something like:
// please don't mind the terrible function/variable names
router.Get("/url", authMiddlewareForScope(s.handleTask(), "scope:to:handle:task"))
If you want this roles to be configurable by your application you will need a more elegant solution.
Please let me know if
I have a requirement of informing user the allowed methods for specific endpoint. This information will be shown in case there is 405 response from server (I'm using gorilla/mux).
I've trying using custom handler by mux for 405, but I can't find any info in the Request object and ResponseWriter.
After reading the docs and SO, I can't find any. May I know if anyone has been doing same thing before?
Code is below. I only allow GET apparently.
router.HandleFunc("/users/{id}",).Methods(http.MethodGet)
In my handler for 405, the response header is empty apparently. There is no info on allowed methods for this endpoint.
func MethodNotAllowedHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logrus.Debugln("Header Writer: ", w.Header())
})
Thanks!
You can modify your MethodNotAllowed handler such below;
It simply walks through the routes and applies routematch function. If path is matched and method is not, it returns mux.ErrMethodMismatch error. Then you can obtain allowed methods for the route and send it in the headers
func notAllowedHandler(x *mux.Router) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//next.ServeHTTP(w, r)
x.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
var routeMatch mux.RouteMatch
if route.Match(r, &routeMatch) || routeMatch.MatchErr == mux.ErrMethodMismatch {
m, _ := route.GetMethods()
w.Header().Set("Access-Control-Allow-Methods", strings.Join(m, ", "))
}
return nil
})
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
})
}
Edit: I forgot to mention. You need to pass router parameter to your custom method not allowed handler;
You can find the complete example below;
func main() {
r := mux.NewRouter()
r.HandleFunc("/test", func(rw http.ResponseWriter, r *http.Request) {}).Methods(http.MethodPost)
r.HandleFunc("/test2", func(rw http.ResponseWriter, r *http.Request) {}).Methods(http.MethodPut)
r.MethodNotAllowedHandler = notAllowedHandler(r)
http.ListenAndServe(":8089", r)
}
func notAllowedHandler(x *mux.Router) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
x.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
var routeMatch mux.RouteMatch
if route.Match(r, &routeMatch) || routeMatch.MatchErr == mux.ErrMethodMismatch {
m, _ := route.GetMethods()
w.Header().Set("Access-Control-Allow-Methods", strings.Join(m, ", "))
}
return nil
})
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
})
}
I need to wrap the GetAssetsCompute function inside a middleware
r.Handle("/api/v1/assets/ComputeBlade", GetAssetsCompute(assetService)).Methods("GET")
func GetAssetsCompute(assetService ServiceType) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// stuff here
})
}
but because middlewares take HTTP handlers as an argument and my function is not a handler, I can't.
I was thinking of doing something like this.
func GetAssetsCompute(assetService ServiceType) http.Handler {
return MyMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// stuff here
}))
}
func MyMiddleware(next http.Handler) http.Handler {
}
Is this correct? Or is there a better way to do this.
Also inside the middleware, I need to access the URL endpoint, do some processing and store this processed value and then again access that in the main handler. How can I do that?
EDIT: I want to apply this middleware to only a subset(>1) of endpoints I have. Not all
I also require the assetService variable used in GetAssetsCompute(assetService ServiceType) function in the handler. So, I need this closure too.
It seems you are trying to do 2 things. 1 - Apply a middleware to only some of your request handlers. 2 - Pass data from your middleware to your request handlers.
For the first one, I can think of three options. The first is what you are doing now, having a Middleware function in which you wrap your handler functions when you pass them to r.Handle. Pseudocode:
r.Handle("/path1", Mware(Handler1())).Methods("GET")
r.Handle("/path2", Mware(Handler2())).Methods("GET")
r.Handle("/path3-nomiddleware", Handler3()).Methods("GET")
The second thing you could do is to add code to your middleware to filter based on URI path and then register your middleware using r.Use. Pseudocode:
const mwarePaths []string = ...
func Mware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.RequestURI is in mwarePaths {
// do the middleware
}
}
}
r.Use(Mware)
Thirdly, you could put the code in a function which you call directly in your handlers and not register it like a middleware. Pseudocode:
func myUtil(w http.ResponseWriter, r *http.Request){ ... }
func GetAssetsCompute(assetService ServiceType) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
myUtil(w, r)
// stuff here
})
}
For the second thing - passing data from middleware to request handlers - here are some ideas.
First, if you go with the regular-function, no-middleware setup above, this problem disappears because anything you need in your handler can simply be a return value from your function.
If you do use a middleware, you can use the context library (also from gorilla) to tie variables to an http.Request instance for passing to your handler: http://www.gorillatoolkit.org/pkg/context . Using that looks like this:
import "github.com/gorilla/context"
func middleware(...) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
context.Set(r, "myKey", "bar")
}
}
func handler(w http.ResponseWriter, r *http.Request) {
val, ok := context.GetOk(r, "myKey") // returns "bar", true
}
Which of these options you choose to use is up to you (you know your needs). But, as mentioned in the comments, a good rule of thumb would be that code which handles unrelated concerns to what your request handlers do can be middleware. Code which handles concerns that are directly related to what your request handlers are doing can go directly in the handlers.
I need to implement case insensitive URL matching in gorilla mux as it is done here for built in mux
I tried to achieve the same using middle-ware like this
router := mux.NewRouter()
router.Use(srv.GetCaseMiddleware())
//GetCaseMiddleware middleware to make match URL case insensitive
func (srv *Server) GetCaseMiddleware() (w mux.MiddlewareFunc) {
var middleware mux.MiddlewareFunc = func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = strings.ToLower(r.URL.Path)
next.ServeHTTP(w, r)
})
}
return middleware
}
but still it throws 404 if URL case is changed,is there any way to implement it using gorilla-mux
Unfortunately, as of this writing, middleware functions are invoked after URL matching in gorilla/mux.
Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters.
I would suggest going with the example in the link you provided.
e.g.
func CaselessMatcher(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = strings.ToLower(r.URL.Path)
next.ServeHTTP(w, r)
})
}
Then, just wrap your multiplexer.
r := mux.NewRouter()
//...
handler := CaselessMatcher(r)
It's actually not bad IMO.
I have created middle-wares using Adapter pattern. One of my middle-ware is for authentication. So if the user is not authorized then I have to send back response to the user and the next middle-ware/s should not be called.
// Adapter type
type Adapter func(http.Handler) http.Handler
// Adapt func
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
// Call all middleware
for _, adapter := range adapters {
h = adapter(h)
}
return h
}
// CheckAuth middleware
func CheckAuth() Adapter {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get Authorization token from the header
// Validate the token
// if valid token then call h.ServeHTTP(w, r)
// else send response 401 to the user,
if(validUser){
h.ServeHTTP(w, r)
}else{
fmt.Fprintf(w, "Unauthorized")
}
return h
}
}
}
http.Handle("/", Adapt(indexHandler, AddHeader(),
CheckAuth(),
CopyMgoSession(db),
Notify(logger),
)
in the CheckAuth middleware I'm calling h.ServeHTTP(w, r) only if the user is authorized, so for the else condtition we also need to break the for loop of the Adapt function or else it will call next middleware even after sending the response.
let me know if there is any other way to handle such situation.
The next middleware in the chain only runs if you explicitly call it.
That next middleware is passed to your closure as h, and you are calling it by invoking h.ServeHTTP(). If you do not call this, no other middleware runs, so you must supply the complete HTTP response.
The Adapt function is not relevant for serving requests. It is executed once (and only once) before the HTTP server even starts. Note that it returns an http.Handler but it isn't an http.Handler itself.
That handler that Adapt returns in this case behaves like this:
var indexHandler http.Handler
func handlerWithMiddleWare(w http.ResponseWriter, r *http.Request) {
notify := func(w http.ResponseWriter, r *http.Request) {
copyMgoSession := func(w http.ResponseWriter, r *http.Request) {
checkAuth := func(w http.ResponseWriter, r *http.Request) {
addHeader := func(w http.ResponseWriter, r *http.Request) {
indexHandler.ServeHTTP(w, r)
}
addHeader(w, r)
}
checkAuth(w, r)
}
copyMgoSession(w, r)
}
notify(w, r)
}
So if you let CheckAuth return without calling the next middleware, you can send whatever response you like; just as you would in any other handler.
By the way, you way want to let Adapt iterate in reverse order. I'm not sure that you're aware that Notify executes first, then CopyMgoSession, then CheckAuth, and then AddHeader.
Middleware is typically chained. There are frameworks that can do it for you. A sleek example is Alice.
chain := alice.New(th.Throttle, timeoutHandler, nosurf.NewPure).Then(myHandler)
If you want to do it yourself you can use recursion to avoid a for loop. For example (from this link):
// buildChain builds the middlware chain recursively, functions are first class
func buildChain(f http.HandlerFunc, m ...middleware) http.HandlerFunc {
// if our chain is done, use the original handlerfunc
if len(m) == 0 {
return f
}
// otherwise nest the handlerfuncs
return m[0](buildChain(f, m[1:cap(m)]...))
}
Each middleware receives the next as parameter. As such the next has to be manually called by the previous handler otherwise the chain stops. So in your auth middleware you don't have to call the next one if auth fails and the chain stops with your error status being the last thing returned. So in your code you need to accept a parameter of http.Handler and that is the next handler (a middleware function has to have the signature of func(http.Handler) http.Handler). See this blog for more details.
You may want to set the correct http status codes as well. Include something like this:
http.Error(w, "Forbidden: xyz", http.StatusForbidden)