How to use an optional query parameter in Go gorilla/mux? - rest

Probably there is already a solution here to my problem, but I couldn't find it anywhere. I tried a bunch of stuff, but nothing worked so far.
I have something like this:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func HealthCheck(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Healthy")
// Also print the value of 'foo'
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/health-check", HealthCheck).Methods("GET").Queries("foo", "{foo}").Name("HealthCheck")
r.HandleFunc("/health-check", HealthCheck).Methods("GET")
http.ListenAndServe(":8080", r)
}
What I'm trying to achieve:
curl http://localhost:8080/health-check
Should respond with: Healthy <foo> ( -> the default value of foo)
And also the following:
curl http://localhost:8080/health-check?foo=bar
Should respond with: Healthy bar

One solution if to simply handle the query params in your handler:
package main
import (
"net/http"
"github.com/gorilla/mux"
)
func HealthCheckHandler(w http.ResponseWriter, req *http.Request) {
values := req.URL.Query()
foo := values.Get("foo")
if foo != "" {
w.Write([]byte("Healthy " + foo))
} else {
w.Write([]byte("Healthy <foo>"))
}
w.WriteHeader(http.StatusOK)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/health-check", HealthCheckHandler)
http.ListenAndServe(":8080", r)
}
according to the gorilla/mux documentation, the Queries method is meant to match your handler to specific functions, akin to a regular expression.

Related

Clean way for same endpoints but with different API versions

I'm looking for a clean way to maintain two versions of an API that have the same endpoints.
Right now, the easiest way but seems excessive is to have something like
r := chi.NewRouter()
r.Get("/test", func(w http.ResponseWriter, r *http.Request) {
version := r.Header.Get("Accept-version")
if version == "v1" {
w.Write([]byte("version 1 of api"))
} else {
w.Write([]byte("other version of api"))
}
})
but when you have a couple dozen+ or so endpoints... can get messy
The way that I would like to have it is have a middleware that will fallthrough to the next defined route. So something like
r := chi.NewRouter()
r.With(UseVersion1).Get("/test", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("version 1 of api"))
}
})
r.With(UseVersion2).Get("/test", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("other version of api"))
}
})
Is this even possible? Or can someone suggest a better route (pun not intended)
EDIT: I know that prefixing the path is a viable option. I would like to avoid that
alexmac is right Usually we uses prefix, also, you can uses midleware if you do't want to add prefix(ps:Not recommended)
your code have a problem, one url can't use many midleware
follow is a example in gin, in chi , you can write yourself code:
package main
import (
"github.com/gin-gonic/gin"
)
func Version() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.RequestURI[:2] != "/v" {
version := c.GetHeader("Accept-version")
if version == ""{
version = "v2"
}
c.Redirect(302, "/" + version + c.Request.RequestURI)
c.Abort()
}else{
c.Next()
}
}
}
func main() {
r := gin.New()
r.Use(Version())
v1 := r.Group("v1")
v2 := r.Group("v2")
v1.GET("/test", func (c *gin.Context){
c.String(200, "v1 test")
})
v2.GET("/test", func (c *gin.Context){
c.String(200, "v2 test")
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
if you don't want to chagne the url you can:
package main
import (
"github.com/gin-gonic/gin"
)
var(
r *gin.Engine
)
func Version() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.URL.Path[:2] != "/v" {
version := c.GetHeader("Accept-version")
if version == ""{
version = "v2"
}
c.Request.URL.Path = "/" + version + c.Request.RequestURI
r.HandleContext(c)
c.Abort()
}
c.Next()
}
}
func main() {
r = gin.New()
r.Use(Version())
v1 := r.Group("v1")
v2 := r.Group("v2")
v1.GET("/test", func (c *gin.Context){
c.String(200, "v1/test")
})
v2.GET("/test", func (c *gin.Context){
c.String(200, "v2/test")
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}

go lang net/http request pattern

I'm writing a simple rest api server and I cannot get route dynamic url using net/http
http://localhost:8080/book/name
where name can be any string.
this was my attempt:
func viewIndex(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, 'html')
}
http.HandleFunc("/book/{name}", view)
this does not work, there is some cryptic note in the documentation of HandleFunc:
The documentation for ServeMux explains how patterns are matched.
This is a working solution.
The only interesting part it that patterns ending with / will be treated as prefix matcher for all urls.
So pattern /book/ will match:
/book/
/book/a
/book/bb
/book/a/b
sample simplified code:
package main
import (
"fmt"
"strings"
"net/http"
)
func book(name string) {
fmt.Printf("requesting book '%s'\n",name);
}
func view(w http.ResponseWriter, r *http.Request) {
url := r.URL.String()
if strings.HasPrefix(url, "/book/"){
name := url[6:]
book(name)
fmt.Fprint(w, name)
}
}
func main() {
http.HandleFunc("/book/", view)
http.ListenAndServe("localhost:8080", nil)
}

http.Request r.FormValue returns nothing/map[]

I have the following Go code:
package main
import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"github.com/gorilla/handlers"
"log"
"net/http"
"io/ioutil"
)
type rLog struct {
Method string
URI string
FormParam string
}
func commonMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
formBs, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatalf("Failed to decode postFormByteSlice: %v", err)
}
rl := rLog{Method: r.Method, URI: r.RequestURI, FormParam: string(formBs)}
log.Printf("%+v", rl)
next.ServeHTTP(w, r)
})
}
func main() {
port := ":3000"
var router = mux.NewRouter()
router.Use(commonMiddleware)
router.HandleFunc("/m/{msg}", handleMessage).Methods("GET")
router.HandleFunc("/n/", handleNumber).Methods("POST")
headersOk := handlers.AllowedHeaders([]string{"Authorization"})
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "POST", "OPTIONS"})
fmt.Printf("Server is running at http://localhost%s\n", port)
log.Fatal(http.ListenAndServe(port, handlers.CORS(originsOk, headersOk, methodsOk)(router)))
}
func handleMessage(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
message := vars["msg"]
response := map[string]string{"message": message}
json.NewEncoder(w).Encode(response)
}
func handleNumber(w http.ResponseWriter, r *http.Request) {
log.Println(r.FormValue("name")) // this returns nothing
response := map[string]string{"name": "1"} // dummy response
json.NewEncoder(w).Encode(response)
}
What I try to do here is very simple.
I want to log all POST form data (it's done inside the commonMiddleware)
Access the form data inside the handleNumber i.e. name (and many more later) and do some logic with it.
The problem that I have right now is, the log.Println(r.FormValue("name")) returns nothing and I really wonder why that happened.
I've tried adding r.ParseForm() before log.Println(r.FormValue("name")). But it didn't work.
And, when I add log.Println(r.Form) line, it returns map[].
What did I missed here?
You truing to read r.Body twice, first in commonMiddleware with ioutil.ReadAll(r.Body) and then in handleNumber with r.ParseForm(). You can't. It's io.Reader, you can't read it two times. You can instead, for example, r.ParseForm() in middlewear and then use parsed form data. In middlewear r.PosrForm.Encode() to log, and in handler r.FormValue() or r.Form.Get() to extract. I think something like this should do
func commonMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
err:=r.ParseForm() //parse in middleware, data will be contained in r.PostForm
if err != nil {
log.Fatalf("Failed to decode postFormByteSlice: %v", err)
}
rl := rLog{Method: r.Method, URI: r.RequestURI, FormParam: r.PostForm.Encode()} //url.Values.Encode() stringifys form data
log.Printf("%+v", rl)
next.ServeHTTP(w, r)
})
}
func main() {
port := ":3000"
var router = mux.NewRouter()
router.Use(commonMiddleware)
router.HandleFunc("/m/{msg}", handleMessage).Methods("GET")
router.HandleFunc("/n/", handleNumber).Methods("POST")
}
func handleNumber(w http.ResponseWriter, r *http.Request) {
log.Println(r.PostForm.Get("name")) // or just r.Form.Get("name") or r.FormValue("name")
response := map[string]string{"name": "1"} // dummy response
json.NewEncoder(w).Encode(response)
}

How to pass two or more parameters using julienschmidt/httprouter in golang?

I am new to golang and using julienschmidt/httprouter for routing.
based on below snippet, able to send one parameter.
but I am little confused to send multiple parameters, cloud anyone helps me.
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
"log"
)
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}
func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe(":8080", router))
}
Just add in another parameter:
router.GET("/hello/:first_name/:last_name", Hello)
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s %s!\n", ps.ByName("first_name"), ps.ByName("last_name"))
}

Go Gorilla Mux not routing as expected

I am having issues getting the Gorilla Mux library for Go to work. From the documentation I have read and all the debugging I've done, I cannot seem to figure out what the problem is. Here's what I've got for routing:
Folder structure:
project_root
|-- main.go
|-- routes
|-- routes.go
|-- user.go
main.go:
package main
import (
"fmt"
"net/http"
"./routes"
)
func main() {
r := routes.CreateRoutes(http.Dir("./content"))
http.Handle("/", r)
err := http.ListenAndServe(fmt.Sprintf("%s:%d", "localhost", 8000), nil)
if err != nil {
fmt.Println("Error: ", err)
}
}
routes/routes.go
package routes
import (
"net/http"
"github.com/gorilla/mux"
)
func CreateRoutes(staticDir http.FileSystem) *mux.Router {
r := mux.NewRouter()
// Serve static pages (i.e. web app)
r.PathPrefix("/").Handler(http.FileServer(staticDir))
// Serve User Pages
createUserRoutes(r)
return r
}
routes/user.go
package routes
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func createUserRoutes(r *mux.Router) {
user := r.PathPrefix("/user/").Subrouter()
// Create a new user
user.Path("/new").Methods("PUT").HandlerFunc(newUserHandler)
// Remove a user
user.Path("/remove/{username:[a-z][a-z0-9]+}").Methods("DELETE").HandlerFunc(removeUserHandler)
// Update a user
user.Path("update/{username:[a-z][a-z0-9]+").Methods("POST").HandlerFunc(updateUserHandler)
// Get a user (Get user information)
user.Path("/{username:[a-z][a-z0-9]+").Methods("GET").HandlerFunc(getUserHandler)
}
func newUserHandler(resp http.ResponseWriter, req *http.Request) {
// Do something that might cause an error
if err != nil {
fmt.Println(err)
resp.WriteHeader(409)
resp.Write([]byte(err.Error()))
} else {
fmt.Println("Created new user")
resp.WriteHeader(201)
resp.Write([]byte("Created new user"))
}
}
func removeUserHandler(resp http.ResponseWriter, req *http.Request) {
}
func updateUserHandler(resp http.ResponseWriter, req *http.Request) {
}
func getUserHandler(resp http.ResponseWriter, req *http.Request) {
}
Whenever I make a request to root path of the server (i.e. the path that serves the static content), the server responds as intended, with the main page. However, any other calls result in a 404 response (I test requests using cURL). For example, a malformed request to http://localhost:8000/user/new should return a 409, but instead returns a 404. Same if I expect a 201 response.
Everything looks right and I've triple checked it, but I cannot figure out what the issue here is.
Turns out the solution was simple (like it usually is). This line in routes.go
r.PathPrefix("/").Handler(http.FileServer(staticDir))
was causing the unintended routing. When PathPrefix is used, it seems to route all URLs to the first matching prefix (in this case this prefix). This explains why static files were being served, but nothing else works.
The fix is to use the Path function instead. There's a subtle difference as explained in the docs; PathPrefix "matches if the given template is a prefix of the full URL path", whereas Path does not. Hence the line above now looks like this to solve the issue I was having:
r.Path("/").Handler(http.FileServer(staticDir))