Golang request middleware library? - go

I'm using gorrilla mux for my mux for my routing setup.
Is there any open source library that will provide some sort of request middleware?
router.HandleFunc("/products", GetProducts).Methods("GET")
So currently I have the GetProducts function that will return the products etc.
But this is a REST api that I am building, so I have to handle things like loading the user, verifying the 'api token' for the request etc.
I don't want to do this for each and every method so I was hoping I there was some request middleware when I can do this before/after execution, along with adding things like User, Permissions to the context in each middleware function.

You can use Go Gin HTTP web framework that supports middlewares as well as you want:
Using middleware:
func main() {
// Creates a router without any middleware by default
r := gin.New()
// Global middleware
// Logger middleware will write the logs to gin.DefaultWriter even you set with GIN_MODE=release.
// By default gin.DefaultWriter = os.Stdout
r.Use(gin.Logger())
// Recovery middleware recovers from any panics and writes a 500 if there was one.
r.Use(gin.Recovery())
// Per route middleware, you can add as many as you desire.
r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
// Authorization group
// authorized := r.Group("/", AuthRequired())
// exactly the same as:
authorized := r.Group("/")
// per group middleware! in this case we use the custom created
// AuthRequired() middleware just in the "authorized" group.
authorized.Use(AuthRequired())
{
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
authorized.POST("/read", readEndpoint)
// nested group
testing := authorized.Group("testing")
testing.GET("/analytics", analyticsEndpoint)
}
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}

Related

Difference between middleware chi.Use vs chi.With

What is the difference between chi.Use and chi.With when setting up a middleware with Chi router.
Use must be declared before all routes under the same group, whereas r.With allows you to "inline" middlewares.
As a matter of fact, the function signatures are different. Use returns nothing, With returns a chi.Router.
Let's say you have a route and want to add a middleware only to one of them, you would use r.With:
r.Route("/myroute", func(r chi.Router) {
r.Use(someMiddleware) // can declare it here
r.Get("/bar", handlerBar)
r.Put("/baz", handlerBaz)
// r.Use(someMiddleware) // can NOT declare it here
}
r.Route("/other-route", func(r chi.Router) {
r.Get("/alpha", handlerBar)
r.Put("/beta", handlerBaz)
r.With(someMiddleware).Get("/gamma", handlerQuux)
}
In the first example, someMiddleware is declared for all sub-routes, whereas in the second example r.With allows you to add a middleware only for the /other-route/gamma route.
According to the documentation of chi.Use and chi.With.
Use appends a middleware handler to the Mux middleware stack.
The middleware stack for any Mux will execute before searching for a matching route to a specific handler, which provides opportunity to respond early, change the course of the request execution, or set request-scoped values for the next http.Handler.
With adds inline middlewares for an endpoint handler.
Let see how chi.Use and chi.With example
The use case is pretty straight forward with chi.Use the registered middleware will run before all the routes handler which are register with the Router
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("welcome"))
})
http.ListenAndServe(":3000", r)
For eg: here Logger middleware will be called before all the register routes handler.
whereas with chi.With you are returned new route on which the middleware would be ran so if any routes is registered on the returned Router the registered middleware will run. Here the use case is very specific suppose if you want to run a specific middleware for a group of routes or want to perform some operation for specific routes then for the case you can use chi.Use
r.Route("/articles", func(r chi.Router) {
r.With(paginate).Get("/", listArticles) // GET /articles
r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017
r.Post("/", createArticle) // POST /articles
r.Get("/search", searchArticles) // GET /articles/search
// Regexp url parameters:
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto
// Subrouters:
r.Route("/{articleID}", func(r chi.Router) {
r.Use(ArticleCtx)
r.Get("/", getArticle) // GET /articles/123
r.Put("/", updateArticle) // PUT /articles/123
r.Delete("/", deleteArticle) // DELETE /articles/123
})
})
In the above example the paginate middleware will only be called for all the articles with /articles/ and /{month}-{day}-{year} day wise route for other routes chi.With won't be called if there any middlware registered with chi.Use over main route then that would be called.

show static image based on users in golang gin

I'm using the Gin framework. I have a database that contains some course info. Users can register in the courses and access the contents. The contents are image, video, and audio.
I store the relative location of these contents in my database like this:
Content\Courses\CourseOne\Unit_1\image\1.jpg
and change it to the actual location in gin:
route := gin.Default()
route.Static("/Content","./Media")
Everything works fine, but I am looking for a way to authenticate users before accessing the contents. In the above-mentioned way, all users can access any data by changing the desired pattern's address. But I want if the user is registered in the course, be able to access data, otherwise, get a 404 error.
how can I do that?
Edit
since it was asked to explain the implementation of authentication:
I used JWT for authentication. so each user has a HashID.
I have a table called UserCourses and the user info would be inserted after purchasing a course.
this is my course route:
route.GET("api/v1/courses", handler.GetCourses)
and my handler:
func GetCourses(context *gin.Context) {
hashID, status, err := repository.GetToken(context)
if err != nil {
context.IndentedJSON(status, err)
return
}
courses := make([]model.CourseAPI, 0)
userInfo := model.Users{HashID: hashID}
err = repository.DatabaseInstance.GetCourses(&courses, &userInfo)
if err != nil {
context.IndentedJSON(http.StatusServiceUnavailable, err)
return
}
context.IndentedJSON(http.StatusOK, gin.H{"courses": courses})
}
The JWT token is passed by the client in the header. so I get the token and validate it. The token contains the user HashID and I check for that HashID in the UserCourses table. besides the course info, there is a variable called isRegistered.if the HashID was registered for any course in UserCourses table,the isRegistered become true for that course otherwise false.
You can create group route and apply authentication middleware through it
r = gin.Default()
// public routes
r.GET("public", publicHandler)
// routes group
auth = r.Group("/")
// authentication middleware within group
auth.Use(AuthMiddleware())
// route before which auth middleware will be run
auth.Static("/Content","./Media")

Getting JWT data in Gorilla CustomLoggingHandler

I am using a custom logging handler in my Go web server like this:
func main() {
// ... Set up everything
router = mux.NewRouter()
router.Handle("/apilookup",
raven.Recoverer(
jwtMiddleware.Handler(
http.HandlerFunc(
doApiLookup)))).Methods("GET")
loggedRouter := handlers.CustomLoggingHandler(os.Stdout, router, writeLog)
http.ListenAndServe(listenAddr, loggedRouter)
}
In the writeLog function, I have made my own version of the Gorilla handlers.LoggingHandler, which logs a lot of additional information.
One thing I would like to do is log the user for authenticated requests. Users authenticate to this server using JWT (using the Authorization: Bearer ... header). I am using Auth0's go-jwt-middleware to parse the token and set its value in the Request's context.
I tried to log the user's email address (one of the claims in the JWT) like this, based on the middleware's documentation:
func writeLog(writer io.Writer, params handlers.LogFormatterParams) {
// ... SNIP
// If we can't identify the user
username := "-"
if userJwt := params.Request.Context().Value("user"); userJwt != nil {
claims := userJwt.(*jwt.Token).Claims.(*jwtClaims)
username = claims.Email
}
// ... SNIP
}
The problem is that username is always the initial value - and not the expected value from the JWT.
By adding log.Printf("%+v\n", params.Request.Context()) above the if, I see that the context doesn't actually contain the parsed JWT data here.
As far as I can tell, the reason this is not working is because the middleware creates a new Request with the updated context, so only middleware further down the chain can see it. Because the logging middleware is above the JWT middleware, it does not have that same context.
I know that I can re-parse the JWT in the logging handler (because I do have access to all the headers), but that seems like a lot of overhead for logging.
Is there a better way to do this that will allow me to have access to this data where I want it?

Is there any way to middleware router with group router?

I am beginner in beego framework, I have completed few R&D inside on it.
But I need few helps related routers.
I have created few route with middleware and group router but I need few suggestions from expert.
Let me share example which I did.
Router.go
func init() {
ns := beego.NewNamespace("/api/v1",
beego.NSNamespace("/front",
beego.NSBefore(AuthFilter),
beego.NSRouter("/user",&controllers.ObjectController{},"*:GetValueByAdmin"),
beego.NSRouter("/test",&controllers.ObjectController{},"*:GetValueByAdmin"),
beego.NSRouter("/test",&controllers.ObjectController{},"*:GetValueByAdmin"),
beego.NSRouter("/test",&controllers.ObjectController{},"*:GetValueByAdmin"),
beego.NSRouter("/product",&controllers.ObjectController{},"*:GetValueByAdmin"),
),
beego.NSNamespace("/a1",
beego.NSRouter("/test1",&controllers.ObjectController{},"*:GetValueByAdmin1"),
beego.NSRouter("/test2",&controllers.ObjectController{},"*:GetValueByAdmin1"),
beego.NSInclude(
&controllers.UserController{},
),
),
)
beego.AddNamespace(ns)
}
var AuthFilter = func(ctx *context.Context) {
// The Authorization header should come in this format: Bearer <jwt>
// The first thing we do is check that the JWT exists
header := strings.Split(ctx.Input.Header("Authorization"), " ")
if header[0] != "Bearer" {
ctx.Abort(401, "Not authorized")
}
}
I have created router using Namespace and It is working fine using this url (http://localhost:8080/api/v1/front/test). But I want to remove "front" keyword from URL.
I tried below options like:
I copied code inside "Front" namespace to put outside but My "NSBefore" Will apply all the method which is defined after that. I need 2 group. Before auth and after auth. In after auth, I want to add beego.NSBefore(AuthFilter).
I tried using policy but it will not work as I needed.
beego.Policy("/api/v1/front/*","*", AuthFilter)
beego.Policy("/api/v1/admin/*","*", AuthFilter)
If I will remove front from policy then it will apply all the URL.
Do we have any option to create group router without URL path and it will cover my concept?

Different middleware for different routes in negroni

I want to have different middleware for different path. My current implementation is from this link
UserRouter := mux.NewRouter().StrictSlash(true)
AdminRouter := mux.NewRouter().StrictSlash(true)
Router.HandleFunc("/apps/{app_name}/xyz", Handler).Methods("GET")
I created three different routers, so that I can assosiate them with different path and middleware
nUserPath := negroni.New(middleware.NewAuthMiddleWare())
nUserPath.UseHandler(UserRouter)
nAdminPath := negroni.New()
nAdminPath.UseHandler(AdminRouter)
I created two different negroni instances and passed them the respective routers. As I wanted all this to run part of the same application on the same port so I created a Wrapper Router and negroni instance and associated them with the existing like below
BaseRouter := mux.NewRouter().StrictSlash(true)
BaseRouter.Handle(UserBasePath,nUserPath) // UserBasePath is `/apps`
BaseRouter.Handle(HealthCheck,nUserPath) // HealthCheck is `/health`
BaseRouter.Handle(AdminBasePath,nAdminPath) // AdminBasePath is `/Admin`
n := negroni.New(middleware.NewLogger()) // attached other common middleware here
n.UseHandler(router.BaseRouter)
n.Run(":8080")
Issues faced in this approach:
When I run /health it runs properly but when I run /apps/{app_name}/something I get a 404: Not Found
Note : I went through other approaches mentioned in below link but they don't satisfy my need.
- Route-specific Middlewares with Negroni
So, the issue with the above implementation is that BaseRouter.Handle() method take a path and not a path_matcher/template so all the url's which has path_length more than one were not working.
I figured out two ways to achieve what I needed:
First approach
// Create a rootRouter
var rootRouter *mux.Router = mux.NewRouter()
// Create as many subRouter you want with some prefix
var appsBasePath string = "/apps"
var adminBasePath string = "/admin"
upRouter := rootRouter.PathPrefix(appsBasePath).Subrouter()
apRouter := rootRouter.PathPrefix(adminBasePath).Subrouter()
// Register all the paths and mention middleware specifically for all of them
// Here middleware is a method with signature as
// func middleware( http.Handler) http.HandlerFunc {}
upRouter.Path("/test").Methods("POST").Handler(middleware(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request){
fmt.Fprintf(w, "Welcome to the home page!")
})))
n := negroni.New(middleware.NewLogger()) // attached other common middleware here
n.UseHandler(rootRouter)
n.Run(":8080")
Second approach
This is extension/solution of the original issue in the question
// Replace BaseRouter.handle() as below
// as PathPrefix takes a template so it won't have issue that we were facing
BaseRouter.PathPrefix(UserBasePath).Handler(nUserPath)
Thing to remember here is that within negroni nUserPath RequestContext of the middleware attached will be different from that of the actual router's HandlerMethod
Note:
By path length I mean something like this -- /abc or /abc/ has path_length=1 and /abc/xyz has path_length=2

Resources