Skip to content
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

Get matched route in context #748

Closed
nithinmohan opened this issue Nov 27, 2016 · 25 comments
Closed

Get matched route in context #748

nithinmohan opened this issue Nov 27, 2016 · 25 comments

Comments

@nithinmohan
Copy link

nithinmohan commented Nov 27, 2016

I have a use case where the list of exposed apis should be available as an api, where admin user can add some additional parameter for storing to DB.

What I essentially need is to get the matched route in context for processing in middleware or handler function.

router.GET("/get_user_details",func (c *gin.Context){c.JSON(200, gin.H{
           "matched_route":c.MatchedRoute(),
        })})

So it there some like c.MatchedRoute or some other way to achieve the result?

@tsirolnik
Copy link
Contributor

You could use c.Request.URL and extract the data you need from the URL

@Depado
Copy link

Depado commented Oct 5, 2017

So c.Request.URL gives out the actual path that was called and not the path that gin (or httprouter) matched. For example for requests which have params, let's say /user/:id I'd like to be able to retrieve the raw string /user/:id is that possible ?

@tsirolnik
Copy link
Contributor

@Depado could you please give an example?

@Depado
Copy link

Depado commented Oct 6, 2017

Hey there

Let's say I have this code :

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func mymiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// There ?
		c.Next()
	}
}

func main() {
	r := gin.New()
	r.GET("/user/:id", mymiddleware(), func(c *gin.Context) {
		// How can I get the litteral string "/user/:id" here ?
		c.JSON(http.StatusOK, gin.H{"message": "received request"})
	})
}

Is there a way I can retrieve inside the handler the litteral string /user/:id, the pattern that matched the request ? If I use c.Request.Path it will give me the full output of the path so things like /user/123456

@tsirolnik
Copy link
Contributor

Why won't you just keep it in a constant and so you'll have it as a variable. I mean, if the function runs, it means that it matched the pattern

@Depado
Copy link

Depado commented Oct 6, 2017

Sure but that means there's no generic way of getting the matched pattern in the handler and possibly automate stuff. I'd like to add this as a prometheus label. What I came up with is a middleware that takes this pattern as an argument, making it redundant r.GET("/user/:id", p.Instrument("/user/:id"), func(c *gin.Context) {})

@tsirolnik
Copy link
Contributor

Again, I can't really understand your problem.

Routes are pre-defined, they're not dynamically created. Therefore, if a route is matched then it means that you know which router is being used.

I could see it being a problem which is necessary to solve on a larger scale. An easy solution would be to provide the matched route via Context.

Maybe you could create a pull request with this feature?

@Depado
Copy link

Depado commented Oct 15, 2017

I'd love to if I had time ^^ I'll try to give that a look later.

@tsirolnik
Copy link
Contributor

Seems like this would require using node.getValue and node.path.

node.getValue should either return node.path and bind it to the context or something like that. Or maybe bind the node to the context...

@jonaz
Copy link

jonaz commented Oct 19, 2017

I do it like this in my prometehus middleware:

func(c *gin.Context) {
		if c.Request.URL.String() == p.MetricsPath {
			c.Next()
			return
		}
		routes := p.parent.Routes()
		url := ""
		for _, r := range routes {
			if r.Handler == c.HandlerName() {
				url = r.Path
				break
			}
		}
.........
......

@Depado
Copy link

Depado commented Oct 19, 2017

What's p.parent.Routes() ?

@jonaz
Copy link

jonaz commented Oct 19, 2017 via email

@Depado
Copy link

Depado commented Oct 21, 2017

I see ! I'll give that a try thanks ! 👍

@Depado
Copy link

Depado commented Oct 23, 2017

That works fine @jonaz 👍
Although cycling other all your routes each time there is a request could be optimized so I used a map[string]string like this :

func (p *Prometheus) Instrument() gin.HandlerFunc {
	return func(c *gin.Context) {
		var path string
		start := time.Now()
		reqSz := computeApproximateRequestSize(c.Request)

		if c.Request.URL.String() == p.MetricsPath {
			c.Next()
			return
		}

		if in, ok := p.PathMap[c.HandlerName()]; ok {
			path = in
		} else {
			// We miss some routes so let's parse that again
			for _, ri := range p.Engine.Routes() {
				p.PathMap[ri.Handler] = ri.Path
			}
			if in, ok := p.PathMap[c.HandlerName()]; ok {
				path = in
			} // If we don't know the path here, then we'll never have it
		}

		c.Next()

		status := strconv.Itoa(c.Writer.Status())
		elapsed := float64(time.Since(start)) / float64(time.Second)
		resSz := float64(c.Writer.Size())

		p.reqDur.Observe(elapsed)
		p.reqCnt.WithLabelValues(status, c.Request.Method, c.HandlerName(), c.Request.Host, path).Inc()
		p.reqSz.Observe(float64(reqSz))
		p.resSz.Observe(resSz)
	}
}

This way I have a chance to initialize the map when r.Use(mymiddleware) is used, and if a route isn't found while responding to a request, I update the map. So only the first request can potentially be a bit slower.

@ikenchina
Copy link

ikenchina commented May 15, 2018

i came across same problem, and if do it like you said, must guarantee every route match different handler.

@Depado
Copy link

Depado commented May 15, 2018

I ended up re-creating a gin middleware for Prometheus there. I had to use a protected map (that includes a mutex).
And yes indeed you must use different handler for the different paths.

@nithinmohan
Copy link
Author

I also had done the same method(redundant function argument) to solve this last year.

@mrhack
Copy link

mrhack commented Nov 16, 2018

try with gin middleware

       r.Use(func(c *gin.Context) {
		url := c.Request.URL.String()
		for _, p := range c.Params {
			url = strings.Replace(url, p.Value, ":"+p.Key, 1)
		}
		c.Set("matched_path", url)
	})

then you can get matched_path from gin.Context

    func(c *gin.Context) {
            if path, exist := c.Get("matched_path"); exist {
		   ...
	    }
    }

@gaffo
Copy link

gaffo commented Mar 13, 2019

@tsirolnik the reason for somthing like this is for adding per-api metrics for instance. It's nice to be able to have metrics counts by rest path such as a dimensional metric for:

POST /users/:userid/stories/:storyid

The current alternative is that you just have a metric per user / storyid combo which isn't super useful.

The info needed is in the router and we currently have to either deep inspect into the router to figure that out (some patches on related), or would have to have the middleware know what all the route mappings are and understand that.

The Prometheus middleware is a very good example of this. https://github.com/Depado/ginprom/blob/master/prom.go

@dejanvasic85
Copy link

dejanvasic85 commented Sep 10, 2019

Just came across this and wanted to mention another way of achieving this is to declare the "metricName" whilst declaring the endpoint name. This is how we did it prior.

So our routes looked like this:

r.GET("/users/:userId", metrics("users"), getUserById)

Where the metrics middleware was capturing the latency and sending it to DataDog.

@matheus-meneses
Copy link

Is it available?

@Dominik-K
Copy link

Dominik-K commented Oct 18, 2019

PR #1826 adds gin.Context.FullPath(). It's merged to master but not released yet.

@matheus-meneses
Copy link

matheus-meneses commented Oct 18, 2019

When next version will be released?

@rounakdatta
Copy link

If you're already using a route like router.GET("/:serviceEnv/*routePath", controllers.ProxyToAnotherServer),

then you can well access your matched route through *routePath no?

func ProxyToAnotherServer(c *gin.Context) {
	relativeRoutePath := c.Param("routePath")
}

@bosconi
Copy link

bosconi commented Apr 27, 2023

gin.Context.FullPath() is the culmination of this discussion and likely what you are looking for if you want to obtain the matched route full path in a generic manner. Thanks @Dominik-K for pointing that out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests