// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. func New(log *log.Logger, h http.Handler) http.Handler { return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { c := karambie.Context(res) defer func() { if err := recover(); err != nil { if c.Status() != 0 { // something has been written as response // unrecoverable, re-panic panic(err) } else { stack := stack(3) c.Set(errInstance, err) c.Set(stackInstance, stack) logPrintf(log, "PANIC: %s\n%s", err, stack) if h == nil { res.Header().Set("Content-Type", "text/html") res.WriteHeader(http.StatusInternalServerError) res.Write([]byte(fmt.Sprintf( panicHtml, err, template.HTMLEscapeString(string(stack))))) } else { h.ServeHTTP(res, req) } } } }() c.Next() }) }
func main() { list := karambie.List() route := mux.NewRouter() // use gorilla as router martini := martinihelper.New() // and also martini as middleware, see below currentDir, _ := os.Getwd() log := log.New(os.Stdout, "[Karambie] ", 0) logger := logger.New(log, false) // log every request recovery := recovery.New(log, nil) // recover if panic notfoundhandler := notfoundhandler.New(true, nil) // show 404, add trailing slash to url if necessary static := static.New(filepath.Join(currentDir, "public"), log) // serve static file in folder "public" // register logger service for martini martini.Map(log) // the list is immutable, every calling to Add or AddFunc will create new list list = list.Add(logger, recovery) list = list.Add(karambie.Later(notfoundhandler)) list = list.Add(karambie.Later(static)) // or you can use karambie/middleware.Common() to build those list // list is [logger, recovery, notfoundhandler, static] // but the order of execution is [logger, recovery, static, notfoundhandler] // karambie.Later will create new handler that will be executed after succeeding handler // list processing will stop if one of them respond the request (http response status != 0) secureList := list.Add(martini.Conv(auth.Basic("user", "pass"))) // execution of secureList is [logger, recovery, auth, static, notfoundhandler] list = list.Add(martini.Conv(render.Renderer())) // execution of list is [logger, recovery, render, static, notfoundhandler] // list != secureList, because it is immutable, every calling to Add or AddFunc will create new list // using http.HandlerFunc style route.Handle("/helloworld", list.AddFunc(hello)) // [logger, recovery, render, hello] // 'static' and 'notfoundhandler' will be ignored because 'hello' response the request // we can user list as NotFoundHandler (handle static file, and show error 404 if necessary) route.NotFoundHandler = list // [logger, recovery, static, notfoundhandler] // 'notfoundhandler' will be ignored if 'static' response the request // using martini.Handler style and gorilla routing route.Handle("/sayhi/{name}", list.Add(martini.Conv(sayhi))) // [logger, recovery, render, sayhi] // use secureList for sensitive resource route.Handle("/secret", secureList.AddFunc(secret)) // [logger, recovery, auth, secret] // add filterheader to list apiList := list.AddFunc(filterheader) // execution of apiList is [logger, recovery, filterheader, static, notfoundhandler] route.Handle("/api", apiList.AddFunc(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { // this handler will not be called if 'filterheader' is not passed // get apikey ctx := karambie.Context(rw) key := ctx.Get("apikey").(string) fmt.Fprintln(rw, "Your api key : "+key) }))) http.ListenAndServe(":3000", route) }
// Logger returns a middleware handler that logs the request as it goes in and the response as it goes out. // return http.Handler func New(log *log.Logger, excludeHttpOk bool) http.Handler { return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { c := karambie.Context(res) start := time.Now() addr := req.Header.Get("X-Real-IP") if addr == "" { addr = req.Header.Get("X-Forwarded-For") if addr == "" { addr = req.RemoteAddr } } defer func() { if err := recover(); err != nil { logPrintf(log, "UNHANDLED PANIC") panic(err) } }() c.Next() if excludeHttpOk && c.Status() == http.StatusOK { return } logPrintf(log, "%s %s for %s -> %v %s (written %d bytes) in %v\n", req.Method, req.URL.Path, addr, c.Status(), http.StatusText(c.Status()), c.Written(), time.Since(start), ) }) }
func New() (http.Handler, *sync.WaitGroup) { var waiter sync.WaitGroup return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { waiter.Add(1) defer waiter.Done() karambie.Context(rw).Next() }), &waiter }
func filterheader(rw http.ResponseWriter, r *http.Request) { if key := r.Header.Get("X-Api-Key"); len(key) == 0 { rw.WriteHeader(http.StatusBadRequest) fmt.Fprintln(rw, "X-Api-Key header not defined") } else { ctx := karambie.Context(rw) ctx.Set("apikey", key) // dont write anything, if you do that, the chain list will be stop } }
// convert martini middleware into http.Handler func (this *MartiniHelper) Conv(h martini.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rwc := karambie.Context(rw) c := this.context(rwc, r) vals, err := c.Invoke(h) if err != nil { panic(err) } if rwc.Status() == 0 { // if the handler returned something, write it to the http response if len(vals) > 0 { ev := c.Get(reflect.TypeOf(martini.ReturnHandler(nil))) handleReturn := ev.Interface().(martini.ReturnHandler) handleReturn(c, vals) } } }) }
// if redirect is true, it will try to add trailing slash to the url. // if h is nil, default handler will be use func New(redirect bool, h http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { c := karambie.Context(rw) if c.Status() == 0 { if redirect && !strings.HasSuffix(r.URL.Path, "/") { u, _ := url.Parse(r.URL.String()) u.Path += "/" http.Redirect(rw, r, u.String(), http.StatusTemporaryRedirect) } else { if h == nil { c.Header().Set("Content-Type", "text/html") c.WriteHeader(http.StatusNotFound) c.Write([]byte(fmt.Sprintf(template, r.URL))) } else { h.ServeHTTP(c, r) } } } }) }