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

Optionally Match trailing slash in the URL and HTTP Method based matching #30

Closed
srinathgs opened this issue Jul 19, 2013 · 21 comments
Closed

Comments

@srinathgs
Copy link

Lets say we have a URL like

http://www.hostname/upload/

Considering that the user types in

http://www.hostname/upload (note no trailing slash)

Possibly we can redirect the user from the no-slash one to the slashed one by registering different handlers.StrictSlash does this. But is there a way to match both of them?

we use mux like this

package main
import (
       "fmt"
       "github.com/gorilla/mux"
       "net/http"
)
func uploadHandler(w http.ResponseWriter, r * Request){
        fmt.Fprintf(w,"Upload page")
}

func main(){
    routes := mux.NewRouter()
    routes.HandleFunc("/upload",uploadHandler) //this doesnot match the trailing slash case
    http.Handle("/",routes)
    http.ListenAndServe("0.0.0.0:8081",nil)
}

How do we write the matching condition so that it accepts both http://www.hostname/upload/ and http://www.hostname/upload ?

Another question. How do we execute different handlers based on the request method?

e.g., the GET request to upload can be handled by different handler and POST can be handled by another function.

I already know that we can check request.Method in the handler and do different things accordingly. But is there a way to do such HTTP method based matching?

@kisielk
Copy link
Contributor

kisielk commented Jul 19, 2013

For the latter you need to make two routes, and use .Methods()

eg:

r.HandleFunc('/foo', getFoo).Methods('GET')
r.HandleFunc('/foo', postFoo).Methods('POST')

If you want a route to match both with and without a trailing slash, but not have any redirection, you just have to make two routes with the same handler.

@kisielk kisielk closed this as completed Jul 19, 2013
@zeebo
Copy link

zeebo commented Jul 19, 2013

@kisielk
Copy link
Contributor

kisielk commented Jul 19, 2013

Also in the future you can ask questions here instead of filing an issue: https://groups.google.com/forum/#!forum/gorilla-web

@fbjork
Copy link

fbjork commented May 22, 2014

What if I don't want the redirect, but just match the trailing slash optionally?

@tnine
Copy link

tnine commented Apr 7, 2016

+1 to the question from @fbjork. I also want to have this behavior

@m90
Copy link

m90 commented Jul 27, 2017

FWIW, you can do this using patterns:

m.Handle("/{route:route\\/?}", handler)

will handle /route as well as /route/

@babolivier
Copy link

babolivier commented Aug 8, 2017

FWIW, you can do this using patterns:

m.Handle("/{route:route\\/?}", handler)

will handle /route as well as /route/

Is there a way to do this when the last part in your path is a variable, for example if I want the same route to match for /{var} and /{var}/? Or does it necessary lead to code duplication?

@elithrar
Copy link
Contributor

elithrar commented Aug 8, 2017 via email

@babolivier
Copy link

babolivier commented Aug 8, 2017

How would you do that in my case? Sorry for asking, I might not be familiar enough with the groups' syntax. I tried a few things but none of them worked.

@elithrar
Copy link
Contributor

elithrar commented Aug 8, 2017

e.g.

"/{route:route(?:\\/)?}"
~ curl http://localhost:8080/route
Hello
➜  ~ curl http://localhost:8080/route/
Hello

If that doesn't make sense, provide an example of what you think the duplicate is.

@babolivier
Copy link

My use case is that the last part of the root path is a variable, while your example shows the case of a static last part. I'd like the route to match both /{myVar} and /{myVar}/ but without capturing the trailing slash in the myVar variable.

@m90
Copy link

m90 commented Aug 8, 2017

The second part of the pattern is not really static, it's a regular expression (route is a very strict one). Consider the following example:

package main

import (
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func main() {
	m := mux.NewRouter()
	m.HandleFunc("/things/{id:[0-9](?:\\/)?}", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("match"))
	})
	log.Fatal(http.ListenAndServe(":1337", m))
}

This will match /things/9 just like /things/7/

@babolivier
Copy link

babolivier commented Aug 8, 2017

I understand that (sorry for not being specific enough between static/strict regular expression). However, a regular expression isn't declared the same way a variable is in a route. eg:

m.HandleFunc("/things/{thing:[a-z]}", handler)

isn't, from what I understand, the same as

m.HandleFunc("/things/{thing}", handler)

my case is the second one. Sorry if I'm not clear enough.

I perfectly understand how to do it with a regular expression, but not how to do it with just the variable name.

@PierBover
Copy link

Wouldn't it be simpler to just have a parameter to ignore trailing slashes?

@benyanke
Copy link

benyanke commented Oct 5, 2018

Any updates on this one? This seems like prime real estate for a simple parameter.

@elithrar
Copy link
Contributor

elithrar commented Oct 5, 2018

What would that parameter look like?

Each new parameter we add is more API surface for users to learn and understand.

This can be achieved via existing pattern support here: #30 (comment)

@jnishiyama
Copy link

jnishiyama commented Nov 15, 2018

@elithrar how would one take care of the situation where the last part of the path is a variable

e.g. "/products/{id:[0-9]+(?:\\/)?}"

This will match both /products/1 and /products/1/, but id will be passed as id == 1 and id == 1/, respectively. Is there a way to elegantly handle this situation?

From the source code, it seems pretty hard to get around as I believe that the relevant part of the regex expression generated would be (?P<id>[0-9]+(?:\/)?) which will have a named group match at <id> == 1/.

@tfbirk
Copy link

tfbirk commented May 7, 2021

FWIW, you can do this using patterns:

m.Handle("/{route:route\\/?}", handler)

will handle /route as well as /route/

Ran into this issue. This solution is great and so underrated! Lots of other blog solutions recommend middleware to handle the trailing slash without a redirect (from using StrictSlash(true)). This simple regex pattern solves the problem without middleware.

Edit to add: you can see options to handle this trailing slash redirect with middleware as an alternative: -

@samifouad
Copy link

What would that parameter look like?

Each new parameter we add is more API surface for users to learn and understand.

This can be achieved via existing pattern support here: #30 (comment)

I honestly think treating the trailing slash as optional should be the default behaviour. And indicating you want to treat the route with a trailing slash different I feel is the much less common use case, and that should be what needs to be turned on. But, if the plan/ideal goal is to stick with current behaviour, what about something like this:

r := mux.NewRouter()

// default = false
r.TrailingSlash(true)

@rubenvannieuwpoort
Copy link

If you want api/hello to behave the same as api/hello/ you can insert a bogus variable that matches the trailing slash:

m.HandleFunc("/api/{message:[a-z]*}{trailingslash:\\/?}", apiHandler)

Both routes will now call the apiHandler with message set to "hello".

@mcgtrt
Copy link

mcgtrt commented Nov 22, 2023

I think I found a solution to the issue with the trailing slash.

Suffix slash is the most annoying thing when the request method is different than "GET" as there is no use of r.StrictSlash(true).

Instead of writing complex or inelegant solutions like multiplying the r.HandlerFunc routes for "/path" and "/path/", a middleware might come useful:

func routingMiddleware(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		url := *r.URL
		url.Path = strings.TrimSuffix(r.URL.Path, "/")
		r.URL = &url

		h.ServeHTTP(w, r)
	})
}

And wherever your mux.Router is do this:

r := mux.NewRouter()
r.Use(routingMiddleware)

Hope it helps!

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