gin icon indicating copy to clipboard operation
gin copied to clipboard

How to access the context from a template function?

Open wodim opened this issue 4 years ago • 17 comments

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.

wodim avatar Aug 20 '19 21:08 wodim

Or: is it possible to have variables that are global to an entire request/goroutine?

wodim avatar Aug 21 '19 16:08 wodim

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.

sinhashubham95 avatar Sep 01 '19 09:09 sinhashubham95

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)
}

PaperPrototype avatar Dec 22 '21 20:12 PaperPrototype

I think it would be totally possible to optionally get access to the context from a function in the FuncMap

PaperPrototype avatar Dec 22 '21 21:12 PaperPrototype

why not use the middleware to implement this?

jincheng9 avatar Dec 23 '21 02:12 jincheng9

@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 😬.

PaperPrototype avatar Dec 26 '21 23:12 PaperPrototype

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.

PaperPrototype avatar Dec 26 '21 23:12 PaperPrototype

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.

PaperPrototype avatar Dec 26 '21 23:12 PaperPrototype

@wodim

PaperPrototype avatar Dec 26 '21 23:12 PaperPrototype

I feel like the FuncMap os pretty much useless without access to a gin.Context

PaperPrototype avatar Dec 29 '21 14:12 PaperPrototype

Great thread. I've been looking for an answer for this, is there any?

barats avatar Feb 07 '22 14:02 barats

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),
    }
)

PaperPrototype avatar Feb 08 '22 00:02 PaperPrototype

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.

barats avatar Feb 08 '22 02:02 barats

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.

asbjornu avatar Jul 27 '22 16:07 asbjornu

@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.

glasket avatar Oct 02 '22 06:10 glasket

@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.

Seems good enough for me, thanks.

barats avatar Oct 08 '22 10:10 barats

I need context access inside FuncMaps or an ability to edit FuncMap from middlewares too. Hope it happens.

maktoobgar avatar Oct 27 '22 09:10 maktoobgar