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) })
Related
I'm trying to implement two routes using the Chi router. One should be invoked only if the "host" header is set to example.com. But only the lastly added route is invoked.
r := chi.NewRouter()
r.Use(middleware.Logger)
middlewareHeader := middleware.RouteHeaders().Route("Host", "example.com", middleware.New(r)).Handler
r.With(middlewareHeader).MethodFunc("get", "/", func(w http.ResponseWriter, r *http.Request) {
log.Println("Host: example.com")
echo(w, r)
})
middlewareNone := middleware.RouteHeaders().Handler
r.With(middlewareNone).MethodFunc("get", "/", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8080",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
As mentioned on comment: you cannot assign multiple handlers to one route.
RouteHeaders is a neat little header-based router that allows you to direct the flow of a request through a middleware stack based on a request header.
RouteHeaders is used to put your request through specific middleware stack, not to change routes. You can still use it but middleware has to redirect to other route.
Options:
1. Create second route and redirect in middleware.
func redirectOnHeader(header, value string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(header) == value {
http.Redirect(w, r, "/with-header", http.StatusMovedPermanently)
return
}
next.ServeHTTP(w, r)
})
}
}
then call the middleware on desired route
r := chi.NewRouter()
r.With(redirectOnHeader("Host", "example.com")).Get("/", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
r.Get("/with-headers", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
2. Use second router and RouteHeaders
As in example in docs.
Note that you need 2 routers on order to have 2 "/" routes.
r := chi.NewRouter()
anotherR := chi.NewRouter()
r.Use(middleware.RouteHeaders().
Route("Host", "example.com", middleware.New(r)).
RouteDefault(middleware.New(anotherR)).
Handler)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
anotherR.Get("/", func(w http.ResponseWriter, r *http.Request) {
echo(w, r)
})
3. Implement two logic in one HandlerFunc
func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(header) == value {
// do something
return
}
// do something else
})
I'm using a valuable rootHandler to deal with error generated. I'd like to know how I could use this handler for every endpoint my api may serve.
r := mux.NewRouter()
r.Handle("/api/endpoint0", rootHandler(function0)).Methods("POST")
r.HandleFunc("/api/endpoint1", function1).Methods("POST")
r.HandleFunc("/api/endpoint2", function2).Methods("POST")
s := &http.Server{
Handler: r,
Addr: "127.0.0.1:8080",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
My error handler is working as follow :
type rootHandler func(http.ResponseWriter, *http.Request) error
func (fn rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := fn(w, r) // Call handler function
if err == nil {
return
}
//log error or w/e
}
func NewHTTPError(err error, status int, detail string) error {
return &HTTPError{
Cause: err,
Detail: detail,
Status: status,
}
}
And one of the exemple function I was using :
func function0(w http.ResponseWriter, r *http.Request) (err error) {
if err = notLogged(); err != nil {
return NewHTTPError(err, 400, "not authorized")
}
}
Do I have to wrap every route functions with rootHanlder(functionName) or is there a way to apply it to every routes ?
Depending on the exact structure of your rootHandler, you might be able to set it as a middleware:
func GetRootHandlerMW(authUser SomeType) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do what needs to be done?
next.ServeHTTP(w, r)
})
}
}
...
r.Use(GetRootHandlerMW(authUser))
A simple logging middleware looks like this:
func LogMW(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logRequest(...)
next.ServeHTTP(w, r)
})
}
...
r.Use(LogMW)
From your added code, it looks like you want to change the HTTP handler function to return error. That changes the signature of the function and you cannot use it as a handler. You can either:
Use context to pass errors back to the middleware. That is, set some error value in the request context in your handler so the logger middleware can log it after the handler returns, or
Wrap every handler with the error logger like you are apparently doing now.
Adding values to a context will create a new context, so if you use context to pass values back from the handler, you have to set a placeholder first. Something like this:
type RequestError struct {
Err error
}
type requestErrorKeyType int
const requestErrorKey requestErrorKeyType=iota
func SetRequestError(req *http.Request, err error) {
if r:=req.Context().Value(requestErrorKey); r!=nil {
r.(*RequestError).Err=err
}
}
// In the handler...
requestError:=RequestError{}
newReq:=request.WithContext(request.Context().WithValue(requestErrorKey,&requestError))
next.ServeHTTP(w,newReq)
if requestError.Err!=nil {
...
}
AM trying generate and appy the client credential accesstoken to wrap simple API endpoint. I got code that work well when all codes are in the main function. However, i woild like to have the API endpoint (/protected) outside the main function in a different directory together with the middleware.
How do I do this, so that middleware apply to every request to the server?
my main function in the parent directory
func main() {
log.Printf("Server started")
router := sw.NewRouter()
manager := manage.NewDefaultManager()
manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
manager.MustTokenStorage(store.NewMemoryTokenStore())
clientStore := store.NewClientStore()
manager.MapClientStorage(clientStore)
srv := server.NewDefaultServer(manager)
srv.SetAllowGetAccessRequest(true)
srv.SetClientInfoHandler(server.ClientFormHandler)
manager.SetRefreshTokenCfg(manage.DefaultRefreshTokenCfg)
srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
log.Println("Internal Error:", err.Error())
return
})
srv.SetResponseErrorHandler(func(re *errors.Response) {
log.Println("Response Error:", re.Error.Error())
})
router.HandleFunc("/oauth2/token", func(w http.ResponseWriter, r *http.Request) {
srv.HandleTokenRequest(w, r)
})
router.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) {
clientId := uuid.New().String()[:8]
clientSecret := uuid.New().String()[:8]
err := clientStore.Set(clientId, &models.Client{
ID: clientId,
Secret: clientSecret,
Domain: "http://localhost:9094",
})
if err != nil {
fmt.Println(err.Error())
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"CLIENT_ID": clientId, "CLIENT_SECRET": clientSecret})
})
log.Fatal(http.ListenAndServe(":8000", router))
}
middleware API endpoint and router in a sub directory
func protecteduri(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}
func validateToken(f http.HandlerFunc, srv *server.Server) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := srv.ValidationBearerToken(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
f.ServeHTTP(w, r)
})
}
router
type Route struct {
Name string
Method string
Pattern string
HandlerFunc http.HandlerFunc
}
type Routes []Route
func NewRouter() *mux.Router {
router := mux.NewRouter().StrictSlash(true)
for _, route := range routes {
var handler http.Handler
handler = route.HandlerFunc
handler = Logger(handler, route.Name)
router.
Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(handler)
}
return router
}
func Index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
var routes = Routes{
{
"Index",
"GET",
"/",
Index,
},
{
"protecteduri",
strings.ToUpper("Get"),
"/protected",
protecteduri,
},
{
"AccessTokenRequest",
strings.ToUpper("Post"),
"/oauth2/token",
AccessTokenRequest,
},
}
Normally i add middleware in the router file as
hadler = middleware(handler)
and it apply to all the incoming request but in this case it does not
work.
The full working code is this
func main() {
manager := manage.NewDefaultManager()
manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
// token memory store
manager.MustTokenStorage(store.NewMemoryTokenStore())
// client memory store
clientStore := store.NewClientStore()
manager.MapClientStorage(clientStore)
srv := server.NewDefaultServer(manager)
srv.SetAllowGetAccessRequest(true)
srv.SetClientInfoHandler(server.ClientFormHandler)
manager.SetRefreshTokenCfg(manage.DefaultRefreshTokenCfg)
srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
log.Println("Internal Error:", err.Error())
return
})
srv.SetResponseErrorHandler(func(re *errors.Response) {
log.Println("Response Error:", re.Error.Error())
})
http.HandleFunc("/oauth2/token", func(w http.ResponseWriter, r *http.Request) {
srv.HandleTokenRequest(w, r)
})
http.HandleFunc("/credentials", func(w http.ResponseWriter, r *http.Request) {
clientId := uuid.New().String()[:8]
clientSecret := uuid.New().String()[:8]
err := clientStore.Set(clientId, &models.Client{
ID: clientId,
Secret: clientSecret,
Domain: "http://localhost:9094",
})
if err != nil {
fmt.Println(err.Error())
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"CLIENT_ID": clientId, "CLIENT_SECRET": clientSecret})
})
http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}, srv))
log.Fatal(http.ListenAndServe(":9096", nil))
}
//main function end here
//middleware function below
func validateToken(f http.HandlerFunc, srv *server.Server) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := srv.ValidationBearerToken(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
f.ServeHTTP(w, r)
})
}
As you can see the accesstoken to request this handle function http.HandleFunc("/protected"... below is validated using the middleware function "validateToken".
http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}, srv))
What i want to do is to rewrite the main function by moving the route handle function (http.HandleFunc("/protected"...) outside the main function and still the the accesstoken get validated when i send request to the (http.HandleFunc("/protected", validateToken(...) function using the routes defined above. In the ealier code, it works but the token to the handlefunction
func protecteduri(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}
is not validated.
i will be adding more handle function and do not want to put all of them in the main function.
http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, I'm protected"))
}, srv))
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 would like to ask if we can create 'middleware' functions for Go http client? Example I want to add a log function, so every sent request will be logged, or add setAuthToken so the token will be added to each request's header.
You can use the Transport parameter in HTTP client to that effect, with a composition pattern, using the fact that:
http.Client.Transport defines the function that will handle all HTTP requests;
http.Client.Transport has interface type http.RoundTripper, and can thus be replaced with your own implementation;
For example:
package main
import (
"fmt"
"net/http"
)
// This type implements the http.RoundTripper interface
type LoggingRoundTripper struct {
Proxied http.RoundTripper
}
func (lrt LoggingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) {
// Do "before sending requests" actions here.
fmt.Printf("Sending request to %v\n", req.URL)
// Send the request, get the response (or the error)
res, e = lrt.Proxied.RoundTrip(req)
// Handle the result.
if (e != nil) {
fmt.Printf("Error: %v", e)
} else {
fmt.Printf("Received %v response\n", res.Status)
}
return
}
func main() {
httpClient := &http.Client{
Transport: LoggingRoundTripper{http.DefaultTransport},
}
httpClient.Get("https://example.com/")
}
Feel free to alter names as you wish, I did not think on them for very long.
I worked on a project that had similar requirement so I built a middleware pipeline library that allows setting multiple middleware to the http client. You can check it out here.
Using the library, you would solve this in the following way
type LoggingMiddleware struct{}
func (s LoggingMiddleware) Intercept(pipeline pipeline.Pipeline, req *http.Request) (*http.Response, error) {
body, _ := httputil.DumpRequest(req, true)
log.Println(fmt.Sprintf("%s", string(body)))
/*
If you want to perform an action based on the response, do the following
resp, err = pipeline.Next
// perform some action
return resp, err
*/
return pipeline.Next(req)
}
transport := pipeline.NewCustomTransport(&LoggingMiddleware{})
client := &http.Client{Transport: transport}
resp, err := client.Get("https://example.com")
if err != nil {
// handle err
}
fmt.Println(resp.Status)
I wrote a small tutorial/library to do just that https://github.com/HereMobilityDevelopers/mediary
Here is some basic usage example:
client := mediary.Init().AddInterceptors(dumpInterceptor).Build()
client.Get("https://golang.org")
func dumpInterceptor(req *http.Request, handler mediary.Handler) (*http.Response, error) {
if bytes, err := httputil.DumpRequestOut(req, true); err == nil {
fmt.Printf("%s", bytes)
//GET / HTTP/1.1
//Host: golang.org
//User-Agent: Go-http-client/1.1
//Accept-Encoding: gzip
}
return handler(req)
}
There is also an explanation here https://github.com/HereMobilityDevelopers/mediary/wiki/Reasoning
Good idea! Here is a simple implementation of HTTP service middleware in Go.
Usually a simple http service framework is to register a bunch of routes, and then call different logics to process them according to the routes.
But in fact, there may be some unified processing involving almost all routes, such as logs, permissions, and so on.
So it is a good idea to engage in intermediate preprocessing at this time.
Define a middleware unit:
package main
import (
"net/http"
)
// AdaptorHandle middleware func type
type AdaptorHandle func(w http.ResponseWriter, r *http.Request) (next bool, err error)
// MiddleWareAdaptor router middlewares mapped by url
type MiddleWareAdaptor struct {
URLs map[string][]AdaptorHandle
}
// MakeMiddleWareAdaptor make a middleware adaptor
func MakeMiddleWareAdaptor() *MiddleWareAdaptor {
mwa := &MiddleWareAdaptor{
URLs: make(map[string][]AdaptorHandle),
}
return mwa
}
// Regist regist a adaptor
func (mw *MiddleWareAdaptor) Regist(url string, Adaptor ...AdaptorHandle) {
for _, adp := range Adaptor {
mw.URLs[url] = append(mw.URLs[url], adp)
// mw.URLs[url] = adp
}
}
// Exec exec middleware adaptor funcs...
func (mw *MiddleWareAdaptor) Exec(url string, w http.ResponseWriter, r *http.Request) (bool, error) {
if adps, ok := mw.URLs[url]; ok {
for _, adp := range adps {
if next, err := adp(w, r); !next || (err != nil) {
return next, err
}
}
}
return true, nil
}
Then wrap the route processing function with a middleware entry:
func middlewareHandler(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// before call handler
start := time.Now()
do, _ := mwa.Exec(r.URL.Path, w, r) // exec middleware
// call next handler
if do {
log.Println("middleware done. next...")
next.ServeHTTP(w, r)
} else {
log.Println("middleware done.break...")
}
// after call handle
log.Printf("Comleted %s in %v", r.URL.Path, time.Since(start))
})
}
mux.Handle("/", middlewareHandler(&uPlusRouterHandler{}))
type uPlusRouterHandler struct {
}
func (rh *uPlusRouterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...
}
Finally, register the middleware you need:
mwa = MakeMiddleWareAdaptor() // init middleware
mwa.Regist("/", testMWAfunc, testMWAfunc2) // regist middleware
...
func testMWAfunc(w http.ResponseWriter, r *http.Request) (bool, error) {
log.Println("I am Alice Middleware...")
log.Printf("Started %s %s", r.Method, r.URL.Path)
return true, nil
}
func testMWAfunc2(w http.ResponseWriter, r *http.Request) (bool, error) {
log.Println("I am Ben Middleware...")
return false, nil // return false,break follow-up actions.
}
This can be achieved using closure functions. It's probably more clear with an example:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/hello", logged(hello))
http.ListenAndServe(":3000", nil)
}
func logged(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Println("logging something")
f(w, r)
fmt.Println("finished handling request")
}
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "<h1>Hello!</h1>")
}
credit goes to: http://www.calhoun.io/5-useful-ways-to-use-closures-in-go/