gin
gin copied to clipboard
How to access the context from a template function?
Is it possible to access the gin.Context object from a template function? Specifically, I would like to read the content of an HTTP header.
Or: is it possible to have variables that are global to an entire request/goroutine?
I think this is coming from a concept in Java where we can set values in a thread and access them as globals in that thread. This is not possible to be done in Golang where if you want to achieve such a thing, you will have to pass the Gin context in the goroutine created or the function called as a parameter. Also, check the concept of context in Golang, where you can set key-value pairs and share it between goroutines.
Hey, I am having a similar question.
Would it be possible to pass the context to a funcMap?
router.SetFuncMap(template.FuncMap{
"isLoggedIn": isLoggedIn,
"getUser": getUser,
})
And then use those functions to check if the user is logged in, but I need access to the current context that the function is being called in...
func isLoggedIn(c *gin.Context) bool {
_, err := c.Cookie("session_token")
if err == http.ErrNoCookie {
// user NOT logged in
return false
}
return true
}
func getUser(c *gin.Context) database.User {
token, yes := TryGetSessionToken(c)
if !yes {
log.Println("router: failed to get session token")
}
return database.GetUserWithToken(token)
}
I think it would be totally possible to optionally get access to the context from a function in the FuncMap
why not use the middleware to implement this?
@jincheng9 yeah, I looked at that, and I guess I could've put more effort into trying to see if it would work.
Just read https://github.com/gin-gonic/gin#using-middleware
which shows this code
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 if 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")
}
which shows that you can add middleware to specific routes! which is pretty cool, but I still have the problem of wanted to call "IsLoggedIn" inside of a template (globally for all templates) which is why middlewares didn't work for template-wide based "IsUserLoggedIn".
I ended up just going the (less wanted) route of just passing an "IsLoggedIn" value to the templates... for every single route 😬.
Basically, by simply allowing a c *gin.Context
for the functions in FuncMap, it would provide a super elegant way of solving the above problem of having template wide "IsUserLoggedIn" (or any other func in the FuncMap that could make user of the current context). It is a super simple thing that would really give templates in gin epic world fliggering superpowers (lol)... but seriously it would be awesome.
Is it possible to access the gin.Context object from a template function? Specifically, I would like to read the content of an HTTP header.
I mean the original issue is perfectly showing how much the global FuncMap having access to the current context would result in highly elegant solutions. Like knowing that in every template you will be able to check "IsUserLoggedIn" by simply calling one of the functions in the FuncMap... it would be a great alternative to middlewares.
You could also try to use middlewares to pass a function to the eventual (possible) template that may be called by a call to c.HTML
... and I even looked at trying that, but there isnt a func that lets you do...
// inside of middleware
c.AddToMap(gin.H{"IsLoggedIn": isLoggedIn})
c.Next()
// inside of handler
c.HTML(
http.StatusOK,
"template.html",
gin.H{},
)
then inside of ANY template you could do...
{{ if .IsLoggedIn }}
<div> logged in </div>
{{ else }}
<div> not logged in </div>
{{ end }}
But I don't think that is currently possible.... but I am now realizing that allowing a method of putting stuff into templates through middleware may be a better method than making a breaking change and trying to get a gin.Context
into the FuncMap.
Those are just my thoughts and 15 minute dive into the problem, I hope gin becomes better for it.
@wodim
I feel like the FuncMap os pretty much useless without access to a gin.Context
Great thread. I've been looking for an answer for this, is there any?
possible to pass the context to a funcMap?
@barats No. I have just manually pasted IsLoggedIn into each call to c.HTML like this
c.HTML(
http.StatusOK,
"template.html",
gin.H{
"IsLoggedIn": IsLoggedIn(c),
}
)
I've been doing it this way as well. But apparently it's not the best practice.
Alternatively , I tried to expose gin.Context
to template via function, failed.
I also need this. It is quite limiting to not be able to access the request URL and other context-related data within a template without having to stuff it into the model being passed to the template. Especially for global, infrastructural stuff.
@PaperPrototype @barats The solution I've been using to avoid manually passing my user data at the end of every request is using the context.Keys
map to hold the equivalent of the gin.H
map.
So I have middleware that checks for the user in my session cache at the start of every request:
func SessionCheck(c *gin.Context) {
s := sessions.Default(c)
if user := s.Get("User"); user != nil {
c.Keys["User"] = user
}
}
Then my route handlers just pass c.Keys
instead of defining a new gin.H{}
:
func Home(c *gin.Context) {
var err error
c.Keys["TotalPosts"], err = m.PostRepo.Count()
if err != nil {
panic(err)
}
c.HTML(http.StatusOK, "home", c.Keys)
}
Which means my base template has access to the user from every endpoint:
{{ with .User }}
<span>{{ .Name }}</span>
{{ else }}
<a href="/login">Login</a>
<a href="/register">Register</a>
{{ end }}
Not sure if it's the best solution to the problem, but it at least prevents having to copy-paste keys at the end of your routes.
@PaperPrototype @barats The solution I've been using to avoid manually passing my user data at the end of every request is using the
context.Keys
map to hold the equivalent of thegin.H
map. So I have middleware that checks for the user in my session cache at the start of every request:func SessionCheck(c *gin.Context) { s := sessions.Default(c) if user := s.Get("User"); user != nil { c.Keys["User"] = user } }
Then my route handlers just pass
c.Keys
instead of defining a newgin.H{}
:func Home(c *gin.Context) { var err error c.Keys["TotalPosts"], err = m.PostRepo.Count() if err != nil { panic(err) } c.HTML(http.StatusOK, "home", c.Keys) }
Which means my base template has access to the user from every endpoint:
{{ with .User }} <span>{{ .Name }}</span> {{ else }} <a href="/login">Login</a> <a href="/register">Register</a> {{ end }}
Not sure if it's the best solution to the problem, but it at least prevents having to copy-paste keys at the end of your routes.
Seems good enough for me, thanks.
I need context access inside FuncMaps or an ability to edit FuncMap from middlewares too. Hope it happens.