How to read multiple parameters from a URL - go

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"]

Related

Go - net/http - extract REST API url without path parameters or query string

I am writing one custom roundtripper where I want to capture client metrics i.e. round trip latency. I'll use this custom roundtripper with http client transport for different REST services. I want to group latency metric as per API method so I am looking for ways to extract REST API path without path parameters or query string. I tried to use r.URL.Path and it gives path without query string but its not truncating path parameters
e.g If there is API like
/data-service/api/v1/streams/2664?start=1645920000000&end=1648512000000
I need extracted path as -> "/data-service/api/v1/streams"
e.g If there is API like
/entities-service/api/v1/entities/1234
I need extracted path as -> "/entities-service/api/v1/entities"
Other cases without path param and query param should be as it is
"session-service/api/v1/token"
r.URL.Path gives "/data-service/api/v1/streams/2664" for first case and "/entities-service/api/v1/entities/1234" for 2nd case(removes query string only). How to strip path params and query params both?
type customTransport struct {
rtp http.RoundTripper
reqStart time.Time
reqEnd time.Time
}
func (tr *customTransport) RoundTrip(r *http.Request) (*http.Response, error) {
tr.reqStart = time.Now()
resp, err := tr.rtp.RoundTrip(r)
tr.reqEnd = time.Now()
// Calculate latency and publish it to prometheus
latency := tr.reqEnd.Sub(tr.reqStart)
// Strip path param and query param here
extractedPath := r.URL.Path
return resp, err
}
extractedPath := r.URL.Path
// Strip path param and query param here
//There is nothing to delete for cases when there is no path or query params
if r.URL.Path != "" && r.URL.RawQuery != "" {
extractedPath = path.Dir(r.URL.Path)
}
fmt.Printf("%s\n", extractedPath)

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.

go struct JSON decode always empty return &{}

What I can be doing wrong in my handler?
type Patient struct {
FirstName string
LastName string
}
func createHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
p := new(Patient)
err := json.NewDecoder(r.Body).Decode(&p)
if err != nil && err != io.EOF {
log.Fatal(err)
}
log.Print(p)
}
I always get from curl -X POST $PATH -d '{"firstName": "Julian"}'
2016/01/23 17:33:50 &{ }
Edit
I've added fmt.Println(r) and got this
2016/01/23 18:33:22 &{POST /patients/ HTTP/1.1 1 1 map[Accept:[*/*] Content-Type:[application/json] User-Agent:[curl/7.46.0]] 0x9b2820 0 [] false localhost:8081 map[] map[] <nil> map[] [::1]:35394 /patients/ <nil> <nil>}
after debug the previous line I realized it's a problem with the subrouter of gorilla toolkit
Two things:
Since you're using the JSON lib convention, then your post form data should be FirstName and not firstName. If you really wanted to use firstName, you need to add this struct tag to the field: json:"firstName".
Next, you don't need to put & in &p anymore because new(Patient) already returns a pointer. Just put p.
Although personally, I'd just do p := Patient{} just so I know it's not a pointer and use &p in the decoder.
You have to remove the r.ParseForm. You can read here
https://golang.org/pkg/net/http/#Request.ParseForm
For POST or PUT requests, it also parses the request body
So your request body will be empty after the call of ParseForm and you will receive an empty struct.

mux.Vars not working

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.

If value is 0 then input value will be empty, value otherwise

Problem:
I have created a simple form, where there's an input field "num". After submission I want to show the value of num in the same input field, in other words want to retain the input in that field. If the value was set to 0 then I want to ignore that.
I can do it in several languages but I'm not sure about how to do it in Golang. My current template file has,
<input type="text" placeholder="foo" name="bar" value="{{if gt .N 0 }} {{.N}} {{end}} "/>
Server file contains:
data := &listOfReport {
R: r,
I: i,
N: n
}
listTmpl := template.Must(template.New("list_tmpl").Parse(string(report.Template["xxx.tmpl"])))
if err := listTmpl.Execute(w, data); err != nil {
http.Error(w, fmt.Sprintf("Error rendering template %v", err), 500)
}
Another thought is to make N a string so make it '' or value in the server file. But that actually spoils the variable's name/purpose.
Is there any better way to do it? Is ther any better way to access GET parameters directly from template? Please note that the value of N is originally got from a GET variable.
*This code is not tested
There is no standard/builtin way to get any request parameters from within a template, you'll have to put it into your data. (You could write a function which does this for you, but that will result in an ugly hack.)
I don't see what's wrong with your solution.
I take a similar approach, but use structs.
type SignupForm struct {
Name string
Email string
Etcera bool
}
// Type alias
type M map[string]interface{}
...
// In the handler that accepts your form
err := r.ParseForm()
if err != nil {
// handle error
}
signup := SignupForm{}
err := decoder.Decode(signup, r.PostForm)
if err != nil {
// handle error
}
// Store the 'saved' form contents somewhere temporary -
// e.g.
// - cookies (keep in mind the 4K browser limit)
// - server side sessions (Redis; how I do it)
// - db
// In the handler that renders your form
err := template.ExecuteTemplate(w, "form.html", M{
"form": signup,
"csrfToken": csrfToken,
// and so on...
})
Note that wherever you store the form data, make sure it is temporary. Server side sessions are ideal as you can have them expire (if you don't want to delete them manually).

Resources