How can I use HttpRouter with MuxChain? - go

I'd like to use httprouter with muxchain while keeping route parameters like /:user/.
Take the following example:
func log(res http.ResponseWriter, req *http.Request) {
fmt.Println("some logger")
}
func index(res http.ResponseWriter, req *http.Request) {
fmt.Fprintf(res, "Hi there, I love %s!", req.URL.Path[1:])
}
func main() {
logHandler := http.HandlerFunc(log)
indexHandler := http.HandlerFunc(index)
chain := muxchain.ChainHandlers(logHandler, indexHandler)
router := httprouter.New()
router.Handler("GET", "/:user", chain)
http.ListenAndServe(":8080", router)
}
When I visit http://localhost:8080/john I obviously don't have access to ps httprouter.Params
That's because httprouter needs to see type httprouter.Handle but the function is called with type http.Handler.
Is there any way to use both packages together? The HttpRouter GitHub repo says
The only disadvantage is, that no parameter values can be retrieved when a http.Handler or http.HandlerFunc is used, since there is no efficient way to pass the values with the existing function parameters.

If you strongly want to use that packages, you can try to do something like that:
package main
import (
"fmt"
"github.com/gorilla/context"
"github.com/julienschmidt/httprouter"
"github.com/stephens2424/muxchain"
"net/http"
)
func log(res http.ResponseWriter, req *http.Request) {
fmt.Printf("some logger")
}
func index(res http.ResponseWriter, req *http.Request) {
p := context.Get(req, "params").(httprouter.Params)
fmt.Fprintf(res, "Hi there, I love %s!", p.ByName("user"))
}
func MyContextHandler(h http.Handler) httprouter.Handle {
return func(res http.ResponseWriter, req *http.Request, p httprouter.Params) {
context.Set(req, "params", p)
h.ServeHTTP(res, req)
}
}
func main() {
logHandler := http.HandlerFunc(log)
indexHandler := http.HandlerFunc(index)
chain := muxchain.ChainHandlers(logHandler, indexHandler)
router := httprouter.New()
router.GET("/:user", MyContextHandler(chain))
http.ListenAndServe(":8080", router)
}

You would have to patch muxchain to accept httprouter.Handle, but it's rather simple to create your own chain handler, for example:
func chain(funcs ...interface{}) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
for _, h := range funcs {
switch h := h.(type) {
case httprouter.Handle:
h(w, r, p)
case http.Handler:
h.ServeHTTP(w, r)
case func(http.ResponseWriter, *http.Request):
h(w, r)
default:
panic("wth")
}
}
}
}
playground

Related

HTTP Middleware and Google Cloud Functions

What’s the equivalent to middleware handlers in Google Cloud Functions?
In standard approach, normally I do:
router.Handle("/receive", middlewares.ErrorHandler(MyReceiveHandler))
And then, in the middleware:
type ErrorHandler func(http.ResponseWriter, *http.Request) error
func (fn ErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := fn(w, r)
if err == nil {
return
}
log.Printf("An error accured: %v", err)
clientError, ok := err.(errors.BaseError)
if !ok {
w.WriteHeader(500)
return
}
w.WriteHeader(clientError.GetStatusCode())
w.Write([]byte(clientError.Error()))
}
In AWS Lambda, I can achieve the same thing using, for example:
func main() {
lambda.Start(
middlewares.Authentication(Handler),
)
}
But I could not find a way to do this in GCP Functions.
How would it work?
Can you help me?
Let's say you start with the following server code in your development environment:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.Handle("/", MiddlewareFinalMsg(" Goodbye!", http.HandlerFunc(HelloWorld)))
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
func MiddlewareFinalMsg(msg string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
fmt.Fprint(w, msg)
})
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "Hello, World!")
}
As far as I can tell, GCF requires its entry point to be an exported identifier of type func(http.ResponseWriter, *http.Request) (not http.HandlerFunc, not http.Handler); therefore, if you have a http.Handler, you'll need to select its ServeHTTP method explicitly to obtain a function of the expected type. However, that identifier can be a package-level function, a method, or a variable.
Here is how you can adapt the code above for GCF:
package p
import (
"fmt"
"net/http"
)
// use F as your GCF's entry point
var F = MiddlewareFinalMsg(" Goodbye!", http.HandlerFunc(HelloWorld)).ServeHTTP
func MiddlewareFinalMsg(msg string, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
fmt.Fprint(w, msg)
})
}
func HelloWorld(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprint(w, "Hello, World!")
}

How do I compose /api/v1/users/id/{id} in net/http?

For example, I want to do /api/v1/users/id/{id}.
At the moment, I have this:
mux := http.NewServeMux()
mux.Handle("/api/v1/users", HandleUsersV1{db: db, mux: mux})
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s%d", ":", portNumber), mux))
I want:
mux := http.NewServeMux()
mux.Handle("/api/v1", HandleV1{})
And then in HandleV1:
mux.HandleFunc("/users/{id}", handler)
I know Gorilla Mux can do it for me with PathPrefix, but I prefer net/http.
The standard net/http does not support dynamic path segments, so /{id} is not gonna work the way you might imagine. As for the prefix thing, you can use this https://golang.org/pkg/net/http/#StripPrefix.
v1mux := http.NewServeMux()
v1mux.HandleFunc("/users/", handler)
mux := http.NewServeMux()
mux.Handle("/api/v1/", http.StripPrefix("/api/v1", v1mux))
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s%d", ":", portNumber), mux))
I suggest using https://github.com/julienschmidt/httprouter. But if you insist.
package main
import (
"log"
"net/http"
"strconv"
"strings"
)
func main() {
res := resourceone{}
http.HandleFunc("/api/v1/users/", res.router)
log.Fatal(http.ListenAndServe(":8080", nil))
}
type resourceone struct {
}
func (res *resourceone) router(w http.ResponseWriter, r *http.Request) {
args := strings.Split(r.URL.String()[len(`/api/v1/users/`):], `/`)
switch args[0] {
case `id`:
id, _ := strconv.Atoi(args[1])
res.one(w, r, id)
case `name`:
res.two(w, r, args[1])
}
}
func (res *resourceone) one(w http.ResponseWriter, r *http.Request, id int) {
w.Write([]byte(strconv.Itoa(id)))
}
func (res *resourceone) two(w http.ResponseWriter, r *http.Request, name string) {
w.Write([]byte(name))
}
Test:
curl 127.0.0.1:8080/api/v1/users/name/david
curl 127.0.0.1:8080/api/v1/users/id/1234

Run middleware after Gorilla mux handling

I'm trying to use Golang middleware to run after handling Gorilla Mux routing, in order to change the response of each request.
Code sample see below. Currently it returns 'run before, run test', goal is to return 'run before, run test, run after' using the runsafter middleware.
Is something like this possible with Gorilla Mux?
package main
import (
"fmt"
"github.com/gorilla/mux"
"net/http"
)
func runsbefore(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("run before, "))
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func runsafter(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("run after, "))
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func runtest(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("run test, "))
}
func main() {
fmt.Println("Server starting on port 8000")
r := mux.NewRouter()
r.HandleFunc("/", runtest).Methods("GET")
http.ListenAndServe(":8000", runsbefore(r))
// something like: http.ListenAndServe(":8000", runsbefore(r(runsafter)))
}
Use
http.ListenAndServe(":8000", runsAfter(runsbefore(r)))
and fix the error in runsAfter:
func runsafter(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
h.ServeHTTP(w, r)
w.Write([]byte("run after, "))
}
return http.HandlerFunc(fn)
}

How to use middlewares when using julienschmidt/httprouter in goLang?

What's the best way to chain middlewares while using julienschmidt/httprouter?
As far as I have googled, http.HandlerFunc accepts functions only in the form func (w ResponseWriter, r *Request) whereas httprouter.Handle functions are of the form func (w http.ResponseWriter, r *http.Request, ps httprouter.Params).
How to I chain middlewares without converting the httprouter.Handle function into http.HandlerFunc?
For example:
My routes.go is of the form,
router := httprouter.New()
router.POST("/api/user/create", middlewares.EscapeStringsMiddleware(User.CreateUser))
log.Fatal(http.ListenAndServe(":8000", router))
How do I write middleware functions for the above mentioned route?
Already tried methods:
1.
func EscapeStringsMiddleware(next http.Handler) httprouter.Handle {
return func (response http.ResponseWriter, request *http.Request, ps httprouter.Params) {
err := request.ParseForm()
if err != nil {
panic(err)
}
for key, values := range request.Form {
for i, value := range values {
value = template.HTMLEscapeString(value)
value = template.JSEscapeString(value)
request.Form[key][i] = value
}
}
next.ServeHTTP(response, request)
}
}
Error obtained:
cannot use User.CreateUser (type func(http.ResponseWriter, *http.Request, httprouter.Params)) as type http.Handler in argument to middlewares.EscapeStringsMiddleware:
func(http.ResponseWriter, *http.Request, httprouter.Params) does not implement http.Handler (missing ServeHTTP method)
2.
func EscapeStringsMiddleware(next httprouter.Handle) httprouter.Handle {
return func (response http.ResponseWriter, request *http.Request, ps httprouter.Params) {
err := request.ParseForm()
if err != nil {
panic(err)
}
for key, values := range request.Form {
for i, value := range values {
value = template.HTMLEscapeString(value)
value = template.JSEscapeString(value)
request.Form[key][i] = value
}
}
next.ServeHTTP(response, request)
}
}
Error obtained:
next.ServeHTTP undefined (type httprouter.Handle has no field or method ServeHTTP)
Also how do I chain multiple middleware?
For example,
router.POST("/api/user/create", middlewares.VerifyCSRF(middlewares.EscapeStringsMiddleware(User.CreateUser)))
This issue is not with your middleware handler. You are getting errs because User.CreateUser is not of type http.Handler.
Try this pattern :
The important bit is to return a http.Handler and wrap func(w http.ResponseWriter, r *http.Request) with http.HandlerFunc.
func Handler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do stuff
})
}
go source :
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
Based on your feedback:
httprouter.Handle does not implement ServeHTTP. It is called directly. For example : next(w, r, ps)
Below you will find examples of middleware handlers.
// Middleware without "github.com/julienschmidt/httprouter"
func StdToStdMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do stuff
next.ServeHTTP(w, r)
})
}
// Middleware for a standard handler returning a "github.com/julienschmidt/httprouter" Handle
func StdToJulienMiddleware(next http.Handler) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// do stuff
next.ServeHTTP(w, r)
}
}
// Pure "github.com/julienschmidt/httprouter" middleware
func JulienToJulienMiddleware(next httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// do stuff
next(w, r, ps)
}
}
func JulienHandler() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// do stuff
}
}
func StdHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do stuff
})
}
func main() {
router := httprouter.New()
router.POST("/api/user/create", StdToJulienMiddleware(StdHandler()))
router.GET("/api/user/create", JulienToJulienMiddleware(JulienHandler()))
log.Fatal(http.ListenAndServe(":8000", router))
}
From https://github.com/cnvrtly/adaptr lib where adapters are also added to chain request middleware functions.
func compatibleHandler(h http.Handler, httprParamsCtxKey interface{}) httprouter.Handle {
return toHttpRouterHandle(h, httprParamsCtxKey)
}
func toHttpRouterHandle(h http.Handler, httprParamsCtxKey interface{}) func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
if httprParamsCtxKey != nil {
r = SetCtxValue(r, httprParamsCtxKey, p)
}
h.ServeHTTP(w, r)
}
}
If you are looking for example/idea how to define routes: https://github.com/cnvrtly/golang-appengine-seed

How to call a route by its name from inside a handler?

How do I properly refer to route names from inside handlers?
Should mux.NewRouter() be assigned globally instead of standing inside a function?
func AnotherHandler(writer http.ResponseWriter, req *http.Request) {
url, _ := r.Get("home") // I suppose this 'r' should refer to the router
http.Redirect(writer, req, url, 302)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler).Name("home")
r.HandleFunc("/nothome/", AnotherHandler).Name("another")
http.Handle("/", r)
http.ListenAndServe(":8000", nil)
}
You have the method mux.CurrentRoute() that returns the route for a given request. From that request, you can create a subrouter and call Get("home")
Example: (play: http://play.golang.org/p/Lz10YUyP6e)
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func HomeHandler(writer http.ResponseWriter, req *http.Request) {
writer.WriteHeader(200)
fmt.Fprintf(writer, "Home!!!\n")
}
func AnotherHandler(writer http.ResponseWriter, req *http.Request) {
url, err := mux.CurrentRoute(req).Subrouter().Get("home").URL()
if err != nil {
panic(err)
}
http.Redirect(writer, req, url.String(), 302)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/home", HomeHandler).Name("home")
r.HandleFunc("/nothome/", AnotherHandler).Name("another")
http.Handle("/", r)
http.ListenAndServe(":8000", nil)
}

Resources