Gorilla/mux middleware not being hit - go

I am on go version go1.10.4 linux/amd64
I am registering middleware, but they don't seem to be hit.
package main
import (
"encoding/json"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gorilla/context"
"github.com/mitchellh/mapstructure"
"huru/migrations"
"huru/models"
"huru/models/person"
"huru/routes"
"net/http"
"os"
"github.com/gorilla/mux"
_ "github.com/lib/pq"
log "github.com/sirupsen/logrus"
)
func loggingMiddleware(next http.Handler) http.Handler {
log.Println("logging middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println("Here is the request URI:",r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
type Exception struct {
Message string `json:"message"`
}
func authMiddleware(next http.Handler) http.Handler {
log.Println("auth middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
fmt.Println("the params are:", params);
token, _ := jwt.Parse(params["token"][0], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("there was an error")
}
return []byte("secret"), nil
})
claims, ok := token.Claims.(jwt.MapClaims)
if ! (ok && token.Valid) {
json.NewEncoder(w).Encode(Exception{Message: "Invalid authorization token"})
return;
}
var user person.Model
mapstructure.Decode(claims, &user)
context.Set(r, "logged_in_user", user)
next.ServeHTTP(w, r)
})
}
func errorMiddleware(next http.Handler) http.Handler {
log.Println("error handling middleware registered");
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Error("Caught error in defer/recover middleware: ", err)
originalError := err.(struct{ OriginalError error }).OriginalError
if originalError != nil {
log.Error("Original error in defer/recover middleware: ", originalError)
}
statusCode := err.(struct{ StatusCode int }).StatusCode
if statusCode != 0 {
w.WriteHeader(statusCode)
} else {
w.WriteHeader(http.StatusInternalServerError)
}
message := err.(struct{ Message string }).Message
if message == "" {
message = "Unknown error message."
}
json.NewEncoder(w).Encode(struct {
ID string
}{
message,
})
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
routerParent := mux.NewRouter()
routerParent.Use(loggingMiddleware)
routerParent.Use(errorMiddleware)
routerParent.Use(authMiddleware)
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
// register and login
{
handler := routes.LoginHandler{}
subRouter := router.PathPrefix("/").Subrouter()
handler.Mount(subRouter, struct{}{});
}
{
handler := routes.RegisterHandler{}
subRouter := router.PathPrefix("/").Subrouter()
handler.Mount(subRouter, struct{}{})
}
{
// people
handler := routes.PersonHandler{}
subRouter := router.PathPrefix("/").Subrouter()
subRouter.Use(authMiddleware)
handler.Mount(subRouter, routes.PersonInjection{People: models.PersonInit()})
}
// ...
}
none of these get logged:
log.Println("error handling middleware registered");
log.Println("auth middleware registered");
log.Println("logging middleware registered");
and at runtime none of middleware routes seem to get hit, nothing is logged there. Anyone know why that may be?
Note that I don't expect to need all these:
routerParent := mux.NewRouter()
routerParent.Use(loggingMiddleware)
routerParent.Use(errorMiddleware)
routerParent.Use(authMiddleware)
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
in reality I probably just want:
routerParent := mux.NewRouter()
router := routerParent.PathPrefix("/api/v1").Subrouter();
router.Use(loggingMiddleware)
router.Use(errorMiddleware)
router.Use(authMiddleware)
but it's just there to prove that something is off. At the end of the main func, I have this to start the server:
host := os.Getenv("huru_api_host")
port := os.Getenv("huru_api_port")
if host == "" {
host = "localhost"
}
if port == "" {
port = "80"
}
log.Info(fmt.Sprintf("Huru API server listening on port %s", port))
path := fmt.Sprintf("%s:%s", host, port)
log.Fatal(http.ListenAndServe(path, routerParent))

Related

Reverse Proxy using Go to Cloud Run Instance

I feel like I'm close to having this working but so far I"m running into an issue building a small reverse proxy in Go to a GCP Cloud Run instance. The request 'goes through' but the response from the request is the default GCP Cloud Run 404. It appears when making the request back to Cloud Run the Host header is being ignored and therefore the request is not being routed correction.
What might I be missing here?
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
)
const apiUrl = "MY_CLOUD_RUN.a.run.app"
func main() {
http.HandleFunc("/", proxy)
log.Fatal(http.ListenAndServe(":8081", nil))
}
func proxy(res http.ResponseWriter, req *http.Request) {
// gets past CORS checks
if req.Method == http.MethodOptions {
headers := res.Header()
headers.Add("Access-Control-Allow-Origin", "*")
headers.Add("Vary", "Origin")
headers.Add("Vary", "Access-Control-Request-Method")
headers.Add("Vary", "Access-Control-Request-Headers")
headers.Add("Access-Control-Allow-Headers", "*")
headers.Add("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
res.WriteHeader(http.StatusOK)
return
}
p := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host: apiUrl,
})
p.Director = func(req *http.Request) {
req.Header.Add("X-Forwarded-Host", req.Host)
req.Header.Add("X-Origin-Host", apiUrl)
req.Header.Add("Host", apiUrl)
req.Header.Add("Access-Control-Allow-Origin", "*")
req.URL.Scheme = "https"
req.URL.Host = apiUrl
}
p.ModifyResponse = func(res *http.Response) error {
res.Header.Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
res.Header.Set("Access-Control-Allow-Credentials", "true")
res.Header.Set("Access-Control-Allow-Origin", "*")
res.Header.Set("Access-Control-Allow-Headers", "*")
return nil
}
p.ServeHTTP(res, req)
}
This is a bit more elaborate than the original initial write-up but what we wound up with was as follows.
package main
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/signal"
"time"
"golang.org/x/oauth2"
"google.golang.org/api/idtoken"
)
var port = ":8080"
var backend = "[CLOUD_RUN_INSTANCE_TO_PROXY].a.run.app"
func main() {
logger := log.New(os.Stdout, "proxy: ", log.LstdFlags)
logger.Println(fmt.Sprintf("Proxy server is starting for: %s on port: %s", backend, port))
router := http.NewServeMux()
router.Handle("/", proxyHandler())
server := &http.Server{
Addr: port,
Handler: logging(logger)(router),
ErrorLog: logger,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 15 * time.Second,
}
done := make(chan bool)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
go func() {
<-quit
logger.Println("Proxy server is shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
}
close(done)
}()
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Fatalf("Could not listen on %s: %v\n", port, err)
}
<-done
logger.Println("Server stopped")
}
func proxyHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions {
headers := w.Header()
headers.Add("Access-Control-Allow-Origin", "*")
headers.Add("Access-Control-Allow-Headers", "*")
headers.Add("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
w.WriteHeader(http.StatusOK)
return
}
path := fmt.Sprintf("https://%s%s", backend, r.RequestURI)
at, _ := idTokenTokenSource(path)
p := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "https",
Host: backend,
})
p.Director = func(r *http.Request) {
if at != nil {
at.SetAuthHeader(r)
}
}
p.ModifyResponse = func(res *http.Response) error {
res.Header.Set("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE")
res.Header.Set("Access-Control-Allow-Credentials", "true")
res.Header.Set("Access-Control-Allow-Origin", "*")
res.Header.Set("Access-Control-Allow-Headers", "*")
return nil
}
r.URL.Scheme = "https"
r.URL.Host = backend
r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
r.Host = backend
if at != nil {
at.SetAuthHeader(r)
}
p.ServeHTTP(w, r)
})
}
func logging(l *log.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
requestId := r.Header.Get("X-Request-Id")
if requestId == "" {
requestId = fmt.Sprintf("%d", time.Now().UnixNano())
}
w.Header().Set("X-Request-Id", requestId)
l.Println(requestId, r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent())
}()
next.ServeHTTP(w, r)
})
}
}
func idTokenTokenSource(audience string) (*oauth2.Token, error) {
ts, err := idtoken.NewTokenSource(context.Background(), audience)
if err != nil {
return nil, err
}
t, err := ts.Token()
if err != nil {
return nil, err
}
return t, nil
}
A good chunk of some of the graceful shutdown, http setup, and logging came from: https://gist.github.com/enricofoltran/10b4a980cd07cb02836f70a4ab3e72d7

How to implement http.Hijacker in middleware

I tried to add websocket within my restful web server, but (maybe) because of the middleware block the ws response Hijacker, it cannot build the connection succeed.
// main.go
api := http.Server{
...
Handler: handlers.API(middleware...), // pass the needed middleware.
}
// handler.go
func API(middleware...) http.Handler {
app := web.NewApp(middleware...)
app.Handle(http.MethodGet, "/ws", wsHandler)
}
func wsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
upgrader.CheckOrigin = func(r *http.Request) bool {
return true
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return errors.Wrapf(err, "")
}
defer conn.Close()
for {
messageType, p, err := conn.ReadMessage()
if err != nil {
...
return web.NewRequestError(err, http.StatusInternalServerError)
}
...
}
}
// web.go
type Handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) error
type Middleware func(Handler) Handler
func (a *App) Handle(method, path string, handler Handler, mw ...Middleware) {
handler = wrapMiddleware(mw, handler)
...
}
func wrapMiddleware(mw []Middleware, handler Handler) Handler {
for i := len(mw) - 1; i >= 0; i-- {
h := mw[i]
if h != nil {
handler = h(handler)
}
}
return handler
}

Unable to protect gorilla/mux Subroute with basic auth

I am trying to create routes using gorilla/mux, some of which should be protected by basic auth and others shouldn't. Specifically, every route under /v2 should require basic auth, but the routes under /health should be publicly accessible.
As you can see below, I can wrap each of my /v2 route handlers with BasicAuth(), but that's against the DRY principle, and also error prone, not to mention the security implications of forgetting to wrap one of those handlers.
I have the following output from curl. All but the last one is as I expect. One should not be able to GET /smallcat without authentication.
$ curl localhost:3000/health/ping
"PONG"
$ curl localhost:3000/health/ping/
404 page not found
$ curl localhost:3000/v2/bigcat
Unauthorised.
$ curl apiuser:apipass#localhost:3000/v2/bigcat
"Big MEOW"
$ curl localhost:3000/v2/smallcat
"Small Meow"
Here's the complete code. I believe I need to fix the v2Router definition somehow, but fail to see how.
package main
import (
"crypto/subtle"
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
)
func endAPICall(w http.ResponseWriter, httpStatus int, anyStruct interface{}) {
result, err := json.MarshalIndent(anyStruct, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(httpStatus)
w.Write(result)
}
func BasicAuth(handler http.HandlerFunc, username, password, realm string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("Unauthorised.\n"))
return
}
handler(w, r)
}
}
func routers() *mux.Router {
username := "apiuser"
password := "apipass"
noopHandler := func(http.ResponseWriter, *http.Request) {}
topRouter := mux.NewRouter().StrictSlash(false)
healthRouter := topRouter.PathPrefix("/health/").Subrouter()
v2Router := topRouter.PathPrefix("/v2").HandlerFunc(BasicAuth(noopHandler, username, password, "Provide username and password")).Subrouter()
healthRouter.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "PONG")
})
v2Router.HandleFunc("/smallcat", func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "Small Meow")
})
bigMeowFn := func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "Big MEOW")
}
v2Router.HandleFunc("/bigcat", BasicAuth(bigMeowFn, username, password, "Provide username and password"))
return topRouter
}
func main() {
if r := routers(); r != nil {
log.Fatal("Server exited:", http.ListenAndServe(":3000", r))
}
}
I achieved the expected behavior by using negroni. If the BasicAuth() call fails, none of the route handlers under /v2 are invoked.
The working code is in a Gist (with revisions, for those interested) here: https://gist.github.com/gurjeet/13b2f69af6ac80c0357ab20ee24fa575
Per SO convention, though, here's the complete code:
package main
import (
"crypto/subtle"
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/urfave/negroni"
)
func endAPICall(w http.ResponseWriter, httpStatus int, anyStruct interface{}) {
result, err := json.MarshalIndent(anyStruct, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(httpStatus)
w.Write(result)
}
func BasicAuth(w http.ResponseWriter, r *http.Request, username, password, realm string) bool {
user, pass, ok := r.BasicAuth()
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
w.WriteHeader(401)
w.Write([]byte("Unauthorised.\n"))
return false
}
return true
}
func routers() *mux.Router {
username := "apiuser"
password := "apipass"
v2Path := "/v2"
healthPath := "/health"
topRouter := mux.NewRouter().StrictSlash(true)
healthRouter := mux.NewRouter().PathPrefix(healthPath).Subrouter().StrictSlash(true)
v2Router := mux.NewRouter().PathPrefix(v2Path).Subrouter().StrictSlash(true)
healthRouter.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "PONG")
})
v2Router.HandleFunc("/smallcat", func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "Small Meow")
})
bigMeowFn := func(w http.ResponseWriter, r *http.Request) {
endAPICall(w, 200, "Big MEOW")
}
v2Router.HandleFunc("/bigcat", bigMeowFn)
topRouter.PathPrefix(healthPath).Handler(negroni.New(
/* Health-check routes are unprotected */
negroni.Wrap(healthRouter),
))
topRouter.PathPrefix(v2Path).Handler(negroni.New(
negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if BasicAuth(w, r, username, password, "Provide user name and password") {
/* Call the next handler iff Basic-Auth succeeded */
next(w, r)
}
}),
negroni.Wrap(v2Router),
))
return topRouter
}
func main() {
if r := routers(); r != nil {
log.Fatal("Server exited:", http.ListenAndServe(":3000", r))
}
}

Is there 'middleware' for Go http client?

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/

martini recover for any panics

I want to wire RecoverWrap to all handlers of martini routes to make any panic be finished by code inside RecoverWrap.
I tried to do it like m.Use(RecoverWrap) but do not know how to do it exactly, it fails on compile.
package main
import (
"errors"
"github.com/go-martini/martini"
"net/http"
)
func main() {
m := martini.Classic()
//m.Use(RecoverWrap)
m.Get("/", func() {
panic("some panic")
})
m.Run()
}
func RecoverWrap(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var err error
defer func() {
r := recover()
if r != nil {
switch t := r.(type) {
case string:
err = errors.New(t)
case error:
err = t
default:
err = errors.New("Unknown error")
}
http.Error(w, "Something goes wrong", http.StatusInternalServerError)
}
}()
h.ServeHTTP(w, req)
})
}
Middleware handlers in Martini do not get to "wrap" other handler calls, so http.Handler is not found by the injector.
What you can do is use context.Next():
package main
import (
"errors"
"github.com/go-martini/martini"
"net/http"
)
func main() {
m := martini.Classic()
m.Use(RecoverWrap)
m.Get("/", func() {
panic("some panic")
})
m.Run()
}
func RecoverWrap(c martini.Context, w http.ResponseWriter) {
var err error
defer func(w http.ResponseWriter) {
r := recover()
if r != nil {
switch t := r.(type) {
case string:
err = errors.New(t)
case error:
err = t
default:
err = errors.New("Unknown error")
}
http.Error(w, "Something goes wrong", http.StatusInternalServerError)
}
}(w)
c.Next()
}
You will have to make sure that your error handler is the first middleware registered, or those handlers running before will not be caught.
Actually, the same method is implemented in martini.Recovery:
https://github.com/go-martini/martini/blob/6241001738f6e1b1ea7c4a4089195e1b0681609a/recovery.go#L115

Resources