Gorilla mux optional query values
Asked Answered
R

3

34

I've been working on a Go project where gorilla/mux is used as the router.

I need to be able to have query values associated with a route, but these values should be optional. That means that I'd like to catch both /articles/123 and /articles/123?key=456 in the same handler.

To accomplish so I tried using the r.Queries method that accepts key/value pairs: router.

  Path("/articles/{id:[0-9]+}").Queries("key", "{[0-9]*?}")

but this makes only the value (456) optional, but not the key. So both /articles/123?key=456 and /articles/123?key= are valid, but not /articles/123.

Edit: another requirement is that, after registering the route, I'd like to build them programatically, and I can't seem to work out how to use r.Queries even though the docs specifically state that it's possible (https://github.com/gorilla/mux#registered-urls).

@jmaloney answer works, but doesn't allow to build URLs from names.

Routinize answered 28/7, 2017 at 16:42 Comment(2)
One approach: #43380442Flit
@smarx I've seen that question, but there are 2 reasons why it doesn't work for me: 1. it prevents me to use mux.Vars(req)["tab"] in my Handler 2. it doesn't allow me to build registered URLs by name (I've updated the question)Routinize
D
35

I would just register your handler twice.

router.Path("/articles/{id:[0-9]+}").
    Queries("key", "{[0-9]*?}").
    HandlerFunc(YourHandler).
    Name("YourHandler")

router.Path("/articles/{id:[0-9]+}").HandlerFunc(YourHandler)

Here is a working program to demonstrate. Notice that I am using r.FormValue to get the query parameter.

Note: make sure you have an up to date version go get -u github.com/gorilla/mux since a bug of query params not getting added the built URLs was fixed recently.

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

var router = mux.NewRouter()

func main() {
    router.Path("/articles/{id:[0-9]+}").Queries("key", "{key}").HandlerFunc(YourHandler).Name("YourHandler")
    router.Path("/articles/{id:[0-9]+}").HandlerFunc(YourHandler)

    if err := http.ListenAndServe(":9000", router); err != nil {
        log.Fatal(err)
    }
}

func YourHandler(w http.ResponseWriter, r *http.Request) {
    id := mux.Vars(r)["id"]
    key := r.FormValue("key")

    u, err := router.Get("YourHandler").URL("id", id, "key", key)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    // Output:
    // /articles/10?key=[key]
    w.Write([]byte(u.String()))
}
Delfeena answered 28/7, 2017 at 16:47 Comment(10)
I tried to do that, and worked, even though I don't know if it's a hack or not. Only problem is I'd like to use the registered URLs to reverse build URLs by name and handling query values using them and it doesn't seem to work. I'll update the question.Routinize
@Routinize I think my recent edit should address your issues.Delfeena
Thank you @jmaloney, I was able to get it to work. One question though: is there a reason why you use r.FormValue instead of mux.Vars? I seem to be able to get it to workRoutinize
Also, to be able to build URLs both with params and without, I resorted to use two different names for the routes, as with your method one name is lost, and "overloading" the routes with two identical names messes with mux and doesn't seem to work.Routinize
@Routinize I use r.FormValue since it will return an empty string if the key is not found, key would not be found using mux.Vars if the request went to the second handler, handling that case also makes you have to check if the map has that value. I only "Named" the route with the query params since building a url with blank queries is still valid and results in less code and logic. For instance, in the above code if the user hit the second route the output would just be /articles/10?key= which is perfectly fine.Delfeena
@Routinize this question is almost 3 years old. Is there a better way to add optional parameter without registering a handle twice nowadays?Elnaelnar
@Elnaelnar I have to say I haven't looked into it, this solution worked at the time and we didn't change it. If you find any new tips, please share here or in a new answer!Routinize
So if I have 10 optional parameters i have to declare a route for each possible combination? seems tedious..Hobbema
@Hobbema You could just define all 10 of your arguments in the first handler. Personally I would not use this feature of mux if I expected a bunch of optional query args. I would define the handler without any arguments and use the url package to construct the url query if needed. The Queries feature seems most useful when you want to have multiple handlers for the same route but separate them by query arguments.Delfeena
@Delfeena Yeah, by nature query string are commonly used as optional parameters. Required parameters should feet into the URI. Would be a great add to the library if it could handle that.Hobbema
P
18

If you register query parameters they are required doc:

All variables defined in the route are required, and their values must conform to the corresponding patterns.

Because those parameters are optional you just need to check for them inside of a handler function: id, found := mux.Vars(r)["id"]. Where found will show if the parameter in the query or not.

Packaging answered 28/7, 2017 at 18:25 Comment(1)
This is the real answer.Udale
H
11

Seems like the best way to handle optional URL parameters is to define your router as normal without them, then parse the optional params out like this:

urlParams := request.URL.Query()

This returns a map that contains the URL parameters as Key/Value pairs.

Hued answered 1/12, 2020 at 2:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.