mux.Vars not working - go

I'm running on HTTPS (port 10443) and use subroutes:
mainRoute := mux.NewRouter()
mainRoute.StrictSlash(true)
mainRoute.Handle("/", http.RedirectHandler("/static/", 302))
mainRoute.PathPrefix("/static/").Handler(http.StripPrefix("/static", *fh))
// Bind API Routes
apiRoute := mainRoute.PathPrefix("/api").Subrouter()
apiProductRoute := apiRoute.PathPrefix("/products").Subrouter()
apiProductRoute.Handle("/", handler(listProducts)).Methods("GET")
And the functions:
func listProducts(w http.ResponseWriter, r *http.Request) (interface{}, *handleHTTPError) {
vars := mux.Vars(r)
productType, ok := vars["id"]
log.Println(productType)
log.Println(ok)
}
ok is false and I have no idea why. I'm doing a simple ?type=model after my URL..

When you enter a URL like somedomain.com/products?type=model you're specifying a query string, not a variable.
Query strings in Go are accessed via r.URL.Query - e.g.
vals := r.URL.Query() // Returns a url.Values, which is a map[string][]string
productTypes, ok := vals["type"] // Note type, not ID. ID wasn't specified anywhere.
var pt string
if ok {
if len(productTypes) >= 1 {
pt = productTypes[0] // The first `?type=model`
}
}
As you can see, this can be a little clunky as it has to account for the map value being empty and for the possibility of a URL like somedomain.com/products?type=model&this=that&here=there&type=cat where a key can be specified more than once.
As per the gorilla/mux docs you can use route variables:
// List all products, or the latest
apiProductRoute.Handle("/", handler(listProducts)).Methods("GET")
// List a specific product
apiProductRoute.Handle("/{id}/", handler(showProduct)).Methods("GET")
This is where you would use mux.Vars:
vars := mux.Vars(request)
id := vars["id"]
Hope that helps clarify. I'd recommend the variables approach unless you specifically need to use query strings.

An easier way to solve this is to add query parameters in your route through Queries, like:
apiProductRoute.Handle("/", handler(listProducts)).
Queries("type","{type}").Methods("GET")
You can get it using:
v := mux.Vars(r)
type := v["type"]
NOTE: This might not have been possible when the question was originally posted but I stumbled across this when I encountered a similar problem and the gorilla docs helped.

Related

How to read multiple parameters from a URL

I have a reverse proxy API that reads the parameters of a localhost API call and then sends those parameters to a 3rd party API.
I'm able to get this working correctly if I only use one parameter. Like so:
http://localhost:8080/path?page=1
I want to be able to use more than one parameter however like so:
http://localhost:8080/path?page=1&param=x
Please see my code below:
This function catches an HTTP request and then sends those parameters to another API.
func (s *Server) getReverseProxy(w http.ResponseWriter, r *http.Request) {
// when I try to append another query in the list a long with page, I get an error
keys, ok := r.URL.Query()["page"]
if !ok || len(keys[0]) < 1 {
log.Println("Url Param 'page' is missing")
return
}
// Query()["key"] will return an array of items,
// we only want the single item.
key := keys[0]
log.Println("Url Param 'page' is: " + string(key))
params := url.Values{
"page[size]": []string{"100"},
"page[number]": []string{""},
}
u := &url.URL{
Scheme: "https",
Host: "url.com",
Path: "/path",
RawQuery: params.Encode(),
}
}
Without having to refractor, am I missing something simple here? How can I add another parameter for my function to catch?
The line of code below ...
keys, ok := r.URL.Query()["page"]
it returns the param value of page, but in []string type. To retrieve more params, simply add similar statement with different param name. for example:
keysPage, ok := r.URL.Query()["page"]
keysParamA, ok := r.URL.Query()["ParamA"]
keysParamB, ok := r.URL.Query()["ParamB"]
keysParamC, ok := r.URL.Query()["ParamC"]
Or, you can also use the r.URL.Query().Get(key) to return the param value in string type.
page := r.URL.Query().Get("page")
paramA := r.URL.Query().Get("ParamA")
paramB := r.URL.Query().Get("ParamB")
paramC := r.URL.Query().Get("ParamC")
r.URL.Query() returns a map[string][]string
you can do a
keys, ok := r.URL.Query()
//browse through keys by
keys["params"]
keys["page"]

Query result is memory address

I'm new to go and still confused about pointers but I have followed the instructions for querying multiple rows but the result I get back is series of memory addresses instead of actual values.
This same structure, minus the rows.Next() works just fine for a single user so I'm confused as to the origin of the problem here.
Ultimately I'm trying to use the results of the function in a template but I'm trying to figure out the structure of it so I can range it in my HTML.
For example, if I try to run the code below, I get something like: &{[0xc... 0xc... 0xc...]}
type User struct {
Id int `json:"int"`
Name string `json:"name"`
Role string `json:"role"`
}
type Users struct {
Users []*User
}
func getUsers(company string) *Users {
users := Users{}
rows, err := db.Query("SELECT Id, Name, Role...")
// Check err
defer rows.Close()
for rows.Next() {
user := &User{}
err = rows.Scan(&user.Id, &user.Name, &user.Role)
// Check err
users.Users = append(users.Users, user)
}
err = rows.Err()
// Check err
return &users
}
This is how I'm attempting to use the function
func userView(w http.ResponseWriter, r *http.Request) {
res := getUsers("test") // Should return 3 results
fmt.Println(res.Users)
}
The problem isn't in your fetching of the data, it's in your display of the data. fmt.Println() prints memory addresses when given pointers--so it's behaving exactly as expected.
If you instead do:
fmt.Printf("%+v", res.Users)
you'll get a different result, probably closer to what you expect.
If you're planning to use a template, then you should do so--your template should be able to access the fields of each User just fine.
But the short answer is: Your testing method is invalid.
Type Users is a slice of pointers. If you print the return value of getUsers it looks like a bunch of memory addresses. This is OK.
If you want to print something more meaningful, write a String() method for Users in which you dereference each pointer and build a string containing struct fields.

Gorilla mux optional query values

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

Nesting subrouters in Gorilla Mux

I've been using gorilla/mux for my routing needs. But I noticed one problem, when I nest multiple Subrouters it doesn't work.
Here is the example:
func main() {
r := mux.NewRouter().StrictSlash(true)
api := r.Path("/api").Subrouter()
u := api.Path("/user").Subrouter()
u.Methods("GET").HandleFunc(UserHandler)
http.ListenAndServe(":8080", r)
}
I wanted to use this approach so I can delegate populating the router to some other package, for example user.Populate(api)
However this doesn't seem to work. It works only if I use single Subrouter in the chain.
Any ideas?
I figured it out, so I'll just post it here in case someone is as stupid as I was. :D
When creating path-based subrouter, you have to obtain it with PathPrefix instead of Path.
r.PathPrefix("/api").Subrouter()
Use r.Path("/api") only when attaching handlers to that endpoint.
For those who are struggling to split between auth and noauth routes, the following works fine for me:
r := mux.NewRouter()
noAuthRouter := r.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
return r.Header.Get("Authorization") == ""
}).Subrouter()
authRouter := r.MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
return true
}).Subrouter()
Then you can apply middleware for authRouter only
If you need to Separate out the UI and API routers, you can simply do what the OP suggested:
appRouter := r.PathPrefix("/").Subrouter()
appRouter.Use(myAppRouter)
apiRouter := r.PathPrefix("/api").Subrouter()
apiRouter.Use(myAPIRouter)
Many thanks for the OP for providing the answer. Hopefully having it all in one place for my use case will help someone.

Key into map with undefined integer

I'm using Gorilla mux for my handlers and using mux.Vars. I'm trying to write a test for one of the handlers that uses mux.Vars so what I do is
var vars = map[string]string{
"id": user.ID,
}
context.Set(req, 0, vars)
In mux the key (an integer) is undefined so by default 0. I've logged the key when mux.Vars gets called and it prints 0. I should be able to key into this map
map[0:map[id:522d14f5b1b92235d6000002]]
by doing map[key] but that returns nil. However, I get the correct value back if I hardcode map[0]. Any thoughts?
I'm not entirely sure I understand the question, but it looks like you might be confusing mux.Vars with mux.context. The two are separate entities. The former returns route variables that are parsed from the URL path. For instance, you could do:
r := mux.NewRouter()
r.HandleFunc("/blah/{foo}/", MyHandler)
...
func MyHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
...
}
The latter contains context variables you set yourself. For instance:
func MyHandler(w http.ResponseWriter, r *http.Request) {
context.Set(r, 0, map[string]string{"id": "myid"})
myMap := context.Get(r, 0)
...
}
You might check out some usage examples of how others use both to see what is most appropriate for your use case:
mux.Vars: https://sourcegraph.com/github.com/gorilla/mux/symbols/go/github.com/gorilla/mux/Vars
mux.context: https://sourcegraph.com/github.com/gorilla/context/symbols/go/github.com/gorilla/context

Resources