First time chaining functions - go

Suppose I want to run a logger before I run my custom mux for http server.
Part way there I can chain loggers and add a custom mux like this: https://play.golang.org/p/Edurl-Rhqb9
package main
import (
"fmt"
"net/http"
"time"
"github.com/julienschmidt/httprouter"
)
type Middleware func(http.HandlerFunc) http.HandlerFunc
func ServeHTTPIterator(h http.HandlerFunc, m ...Middleware) http.HandlerFunc {
if len(m) < 1 {
return h
}
wrapped := h
// loop in reverse to preserve middleware order
for i := len(m) - 1; i >= 0; i-- {
wrapped = m[i](wrapped)
}
return wrapped
}
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Index!")
}
func LogFirst(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`First`)
h.ServeHTTP(w, r)
})
}
func LogSecond(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`Second`)
h.ServeHTTP(w, r)
})
}
func main() {
httpServer := &http.Server{
Addr: `my.local:8080`,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 5 * time.Second,
}
var Router *httprouter.Router
preMiddle := []Middleware{
LogFirst,
LogSecond,
}
http.HandleFunc("/", ServeHTTPIterator(IndexHandler, preMiddle...))
httpServer.Handler = Router
}
But now let suppose I wanted to run my mux after the loggers, in that case I figured I would do something like:
preMiddle := []Middleware{
LogFirst,
LogSecond,
Router,
}
But I'm not sure how to wrap Router to make this work or if this is even a legitimate approach. Could someone clue me in on what I am missing here?

Modify the code to work with http.Handler instead of http.HandlerFunc.
type Middleware func(http.Handler) http.Handler
func ServeHTTPIterator(h http.Handler, m ...Middleware) http.Handler {
if len(m) < 1 {
return h
}
wrapped := h
// loop in reverse to preserve middleware order
for i := len(m) - 1; i >= 0; i-- {
wrapped = m[i](wrapped)
}
return wrapped
}
func LogFirst(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`First`)
h.ServeHTTP(w, r)
})
}
func LogSecond(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`Second`)
h.ServeHTTP(w, r)
})
}
Create the router and register the handler with the router. Add parameters argument to IndexHandler so that the function is compatible with httprouter.
router := httprouter.New()
router.GET("/", IndexHandler)
Wrap the router using the new version of ServeHTTPIterator.
httpServer.Handler = ServeHTTPIterator(router, preMiddle...)
With these changes, the logs are written before the router is called.

You can achieve it by, instead of spreading the middlewares in your serveIterator func expect an slice of middlewares to be applied like: go playground
package main
import (
"fmt"
"net/http"
"time"
"github.com/julienschmidt/httprouter"
)
type Middleware func(http.HandlerFunc) http.HandlerFunc
func ServeHTTPIterator(h http.HandlerFunc, m []Middleware) http.HandlerFunc {
if len(m) < 1 {
return h
}
wrapped := h
// loop in reverse to preserve middleware order
for i := len(m) - 1; i >= 0; i-- {
wrapped = m[i](wrapped)
}
return wrapped
}
func IndexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello Index!")
}
func LogFirst(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`First`)
h.ServeHTTP(w, r)
})
}
func LogSecond(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Print(`Second`)
h.ServeHTTP(w, r)
})
}
func main() {
httpServer := &http.Server{
Addr: `my.local:8080`,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 5 * time.Second,
}
var Router *httprouter.Router
preMiddle := []Middleware{
LogFirst,
LogSecond,
}
http.HandleFunc("/", ServeHTTPIterator(IndexHandler, preMiddle))
httpServer.Handler = Router
}

Related

How to get url param in middleware go-chi

I use a specific middleware for specific set of routes
r.Route("/platform", func(r chi.Router) {
r.Use(authService.AuthMiddleware)
r.Get("/{id}/latest", RequestPlatformVersion)
})
Now how can I access id url param inside this AuthMiddleware middleware
func (s *Service) AuthMiddleware(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
fmt.Println(chi.URLParam(r, "id"))
id := chi.URLParam(r, "id")
if id > 100 {
http.Error(w, errors.New("Error").Error(), http.StatusUnauthorized)
return
}
}
return http.HandlerFunc(fn)
}
However, the id param prints as an empty string even though the middleware is being ran and a specific route is being called
You put your chi.URLParam before the path param {id} and you forgot to put .ServeHTTP(w, r) at the middleware. If you don't put that thing, your request will not go inside the path inside the route.
this is the working example:
package main
import (
"fmt"
"net/http"
"github.com/go-chi/chi"
)
func AuthMiddleware(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
fmt.Println(chi.URLParam(r, "id"))
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func main() {
r := chi.NewRouter()
r.Route("/platform/{id}", func(r chi.Router) {
r.Use(AuthMiddleware)
r.Get("/latest", func(rw http.ResponseWriter, r *http.Request) {
fmt.Println("here ", chi.URLParam(r, "id")) // <- here
})
})
http.ListenAndServe(":8080", r)
}
I move the {id} to platform/{id} so the middleware got the id path value, and add h.ServeHTTP(w, r) inside the middleware.
try to access http://localhost:8080/platform/1/latest
the output will be:
1
here 1
UPDATE
It is not good to run the validation after the code, you must fix the way you define the path, and move the .ServeHTTP after the validation.
This is the example:
package main
import (
"errors"
"fmt"
"net/http"
"strconv"
"github.com/go-chi/chi"
)
func AuthMiddleware(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Middleware First, id: %+v\n", chi.URLParam(r, "id"))
id, _ := strconv.Atoi(chi.URLParam(r, "id"))
if id > 100 {
http.Error(w, errors.New("Error").Error(), http.StatusUnauthorized)
return
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func main() {
r := chi.NewRouter()
// This works too ()
// r.Route("/platform/{id}", func(r chi.Router) {
// r.Use(AuthMiddleware)
// r.Get("/latest", func(rw http.ResponseWriter, r *http.Request) {
// fmt.Println("second: ", chi.URLParam(r, "id")) // <- here
// })
// })
// Other Solution (Wrapping Middleware)
r.Route("/platform", func(r chi.Router) {
r.Get("/{id}/latest", AuthMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
fmt.Println("second: ", chi.URLParam(r, "id")) // <- here
})).ServeHTTP)
})
http.ListenAndServe(":8080", r)
}

How to use gorilla middleware handlers for all requests?

I want to use the handlers specified here for logging everything.
This is what I have:
r := mux.NewRouter()
s := r.PathPrefix("/api/v1").Subrouter()
s.HandleFunc("/abc", handler.GetAbc).Methods("GET")
s.HandleFunc("/xyz", handler.GetXyz).Methods("GET")
I want to use the logging middleware but I don't want to repeat it in every single line, as they show in github:
r.Handle("/admin", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(ShowAdminDashboard)))
r.HandleFunc("/", ShowIndex)
Is there a way to just pass the general logging middleware to r, and everything that passes the r router will pass by the middleware first?
Use a middleware:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println(r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
r.Use(loggingMiddleware)
Here's the doc: https://github.com/gorilla/mux#middleware
I wrapped the LoggingHandler with a middleware function
func loggingMiddleware(next http.Handler) http.Handler {
return handlers.LoggingHandler(os.Stdout, next)
}
r.Use(loggingMiddleware)
This is the approach I took and this worked best for me.
type Route struct {
Name string
Method string
Pattern string
Secure bool
HandlerFunc http.HandlerFunc
}
type Routes []Route
var routes = Routes{
Route{
Name: "Docs",
Method: "GET",
Pattern: "/v2/docs",
HandlerFunc: Docs,
},
Route{
Name: "GetUserByName",
Method: "GET",
Pattern: "/v2/user/{username}",
HandlerFunc: user.GetUserByName,
Secure: true,
},
}
func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
router.NotFoundHandler = http.HandlerFunc(notFound)
router.MethodNotAllowedHandler = http.HandlerFunc(notAllowed)
for _, route := range routes {
var handler http.Handler
if route.Secure {
handler = AuthMiddleware(route.HandlerFunc)
} else {
handler = route.HandlerFunc
}
handler = Logger(os.Stderr, handler)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}
return router
}
func ApplicationRecovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
fmt.Fprintln(os.Stderr, "Recovered from application error occurred")
_, _ = fmt.Fprintln(os.Stderr, err)
w.WriteHeader(http.StatusInternalServerError)
}))
}
}()
next.ServeHTTP(w, r)
})
}
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//TODO: Add authentication
log.Println("Authentication required")
next.ServeHTTP(w, r)
})
}
func Logger(inner http.Handler, name string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf(
"%s %s %s %s",
r.Method,
r.RequestURI,
name,
time.Since(start),
)
inner.ServeHTTP(w, r)
})
}
func notFound(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}
func notAllowed(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
}
func main() {
srv := http.Server{
Addr: "0.0.0.0:8080",
Handler: ApplicationRecovery(Middleware(NewRouter())),
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
This way I'm covering my bases:
panic during the request execution
a common middleware that set the response headers for all request
a logging middleware for all request logging
an authentication middleware for secure resources
Console logs of the handler in action
2020/06/23 22:28:48 Server started
2020/06/23 22:28:51 Authentication required
2020/06/23 22:28:51 Begin x-api-key validation
2020/06/23 22:28:51 x-api-key matched user: 1
2020/06/23 22:28:51 User 1 successfully accessed secure resourecs
::1 - - [23/Jun/2020:22:28:51 +0100] "DELETE /v2/user/john?permanent=true HTTP/1.1" 403 85
More compact solution
r.Use(func(next http.Handler) http.Handler { return handlers.LoggingHandler(os.Stdout, next) })

Go Gorilla Mux MiddlewareFunc with r.Use and returning errors

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.

Return multiple middleware funcs from router.HandleFunc call

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.

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

Resources