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
Related
In auth0 QuickStart tutorial for Golang I had found this piece of code:
router.Handle("/api/private", middleware.EnsureValidToken()(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message":"Hello from a private endpoint! You need to be authenticated to see this."}`))
}),
))
Then I had simplify it to this form:
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message":"Hello from test"}`))
}
func preHandler() func(next http.Handler) http.Handler {
log.Println("in preHandler")
return func(next http.Handler) http.Handler {
return check(next)
}
}
func main() {
http.Handle("/test/", preHandler()(http.HandlerFunc(handler)))
http.ListenAndServe(":80", nil)
}
But I can't figure out how is working and what is meaning of this piece of code preHandler()(http.HandlerFunc(handler))
Thnx for help!
P.s.
I tried to find answer in google but nothing. I just want to find out how it works
The statement
http.Handle("/test/", preHandler()(http.HandlerFunc(handler)))
has three function calls and a type conversion. I'll explain what's going on by splitting the one statement into four statements:
f := preHandler() // f is function with type func(next http.Handler) http.Handler
h := http.HandlerFunc(handler) // type conversion to http.HandlerFunc
hw := f(h) // call f with h, result is an http.Handler
http.Handle("/test/", hw) // register the handler
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 you set up Gorilla Mux r.Use to return errors down the middleware chain? https://godoc.org/github.com/gorilla/mux#Router.Use
Main.go
r := mux.NewRouter()
r.Use(LoggingFunc)
r.Use(AuthFunc)
Basic middleware
Starts with logging middleware which can catch and handle errors from further down the chain
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
func LoggingFunc(next HandlerFunc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Logging middleware
defer func() {
if err, ok := recover().(error); ok {
w.WriteHeader(http.StatusInternalServerError)
}
}()
err := next(w, r)
if err != nil {
// log error
}
})
}
The next middleware handles authentication and returns an error to the logging middleware.
func AuthFunc(next HandlerFunc) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) error {
if r.GET("JWT") == "" {
return fmt.Errorf("No JWT")
}
return next(w, r)
}
}
I keep getting errors like
cannot use AuthFunc (type func(handlers.HandlerFunc) http.Handler) as type mux.MiddlewareFunc in argument to r.Use
Thanks
According to the mux.Use doc its argument type is MiddlewareFunc which return type is http.Handler not error type. You have to define which return type is http.HandlerFunc
type Middleware func(http.HandlerFunc) http.HandlerFunc
func main() {
r := mux.NewRouter()
// execute middleware from right to left of the chain
chain := Chain(SayHello, AuthFunc(), LoggingFunc())
r.HandleFunc("/", chain)
println("server listening : 8000")
http.ListenAndServe(":8000", r)
}
// Chain applies middlewares to a http.HandlerFunc
func Chain(f http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc {
for _, m := range middlewares {
f = m(f)
}
return f
}
func LoggingFunc() Middleware {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Loggin middleware
defer func() {
if _, ok := recover().(error); ok {
w.WriteHeader(http.StatusInternalServerError)
}
}()
// Call next middleware/handler in chain
next(w, r)
}
}
}
func AuthFunc() Middleware {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("JWT") == "" {
fmt.Errorf("No JWT")
return
}
next(w, r)
}
}
}
func SayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello client")
}
It will execute the LogginFunc then AuthFunc and then SayHello method which is your desire method after passing all those middlewares.
I am using gorilla/mux I have this:
router.HandleFunc("/api/v1/register",h.makeRegisterNewUser(v)).Methods("POST")
and h.makeRegisterNewUser looks like:
func (h Handler) makeRegisterNewUser(v interface{}) http.HandlerFunc {
type RespBody = ...;
type ReqBody = ...
return func(w http.ResponseWriter, r *http.Request) {
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&ReqBody)
// ...
json.NewEncoder(w).Encode(RespBody{});
}
}
my question is - is there a way to return multiple http.HandlerFuncs from makeRegisterNewUser? Something like this:
router.HandleFunc("/api/v1/register",h.makeRegisterNewUser(v)).Methods("POST")
func (h Handler) makeRegisterNewUser(v interface{}) (http.HandlerFunc...) {
type RespBody = ...;
type ReqBody = ...
return func(w http.ResponseWriter, r *http.Request) {
// 1
}, func(w http.ResponseWriter, r *http.Request) {
// 2
}, func(w http.ResponseWriter, r *http.Request) {
// 3
}
}
I am not sure if middleware can be chained in Go like it does in Node.js Express.
You can chain multiple http.Handler using wrapper:
type Middleware struct {
next http.Handler
}
func (m Middleware) Wrap(handler http.Handler) http.Handler {
m.next = handler
return m
}
func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// run your handler code here
// write error into w and return if you need to interrupt request execution
// call next handler
m.next.ServeHTTP(w, r)
}
Register chained wrappers and main handler:
h1 := Middleware{}
h2 := OtherMiddleware{}
router.Handle("/some_route", h1.Wrap(h2.Wrap(mainHandler))).Methods(http.MethodPost)
Request will execute h1, h2 and finally mainHandler.
For example, you can log request in h1, authorize in h2 and keep business logic in mainHandler.
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