github.com/fasmat/buffalo@v0.11.0/app.go (about) 1 package buffalo 2 3 import ( 4 "context" 5 "net" 6 "net/http" 7 "os" 8 "strings" 9 "sync" 10 "syscall" 11 12 "github.com/sirupsen/logrus" 13 14 "github.com/gobuffalo/envy" 15 "github.com/gorilla/mux" 16 "github.com/markbates/refresh/refresh/web" 17 "github.com/markbates/sigtx" 18 "github.com/pkg/errors" 19 ) 20 21 // App is where it all happens! It holds on to options, 22 // the underlying router, the middleware, and more. 23 // Without an App you can't do much! 24 type App struct { 25 Options 26 // Middleware returns the current MiddlewareStack for the App/Group. 27 Middleware *MiddlewareStack 28 ErrorHandlers ErrorHandlers 29 router *mux.Router 30 moot *sync.Mutex 31 routes RouteList 32 root *App 33 children []*App 34 } 35 36 // Serve the application at the specified address/port and listen for OS 37 // interrupt and kill signals and will attempt to stop the application 38 // gracefully. This will also start the Worker process, unless WorkerOff is enabled. 39 func (a *App) Serve() error { 40 logrus.Infof("Starting application at %s", a.Options.Addr) 41 server := http.Server{ 42 Handler: a, 43 } 44 ctx, cancel := sigtx.WithCancel(a.Context, syscall.SIGTERM, os.Interrupt) 45 defer cancel() 46 47 go func() { 48 // gracefully shut down the application when the context is cancelled 49 <-ctx.Done() 50 logrus.Info("Shutting down application") 51 52 err := a.Stop(ctx.Err()) 53 if err != nil { 54 logrus.Error(err) 55 } 56 57 if !a.WorkerOff { 58 // stop the workers 59 logrus.Info("Shutting down worker") 60 err = a.Worker.Stop() 61 if err != nil { 62 logrus.Error(err) 63 } 64 } 65 66 err = server.Shutdown(ctx) 67 if err != nil { 68 logrus.Error(err) 69 } 70 71 }() 72 73 // if configured to do so, start the workers 74 if !a.WorkerOff { 75 go func() { 76 err := a.Worker.Start(ctx) 77 if err != nil { 78 a.Stop(err) 79 } 80 }() 81 } 82 83 var err error 84 85 if strings.HasPrefix(a.Options.Addr, "unix:") { 86 // Use an UNIX socket 87 listener, err := net.Listen("unix", a.Options.Addr[5:]) 88 if err != nil { 89 return a.Stop(err) 90 } 91 // start the web server 92 err = server.Serve(listener) 93 } else { 94 // Use a TCP socket 95 server.Addr = a.Options.Addr 96 97 // start the web server 98 err = server.ListenAndServe() 99 } 100 101 if err != nil { 102 return a.Stop(err) 103 } 104 105 return nil 106 } 107 108 // Stop the application and attempt to gracefully shutdown 109 func (a *App) Stop(err error) error { 110 a.cancel() 111 if err != nil && errors.Cause(err) != context.Canceled { 112 logrus.Error(err) 113 return err 114 } 115 return nil 116 } 117 118 func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { 119 ws := &Response{ 120 ResponseWriter: w, 121 } 122 if a.MethodOverride != nil { 123 a.MethodOverride(w, r) 124 } 125 if ok := a.processPreHandlers(ws, r); !ok { 126 return 127 } 128 129 var h http.Handler 130 h = a.router 131 if a.Env == "development" { 132 h = web.ErrorChecker(h) 133 } 134 h.ServeHTTP(ws, r) 135 } 136 137 // New returns a new instance of App and adds some sane, and useful, defaults. 138 func New(opts Options) *App { 139 envy.Load() 140 opts = optionsWithDefaults(opts) 141 142 a := &App{ 143 Options: opts, 144 Middleware: newMiddlewareStack(), 145 ErrorHandlers: ErrorHandlers{ 146 404: defaultErrorHandler, 147 500: defaultErrorHandler, 148 }, 149 router: mux.NewRouter().StrictSlash(!opts.LooseSlash), 150 moot: &sync.Mutex{}, 151 routes: RouteList{}, 152 children: []*App{}, 153 } 154 a.router.NotFoundHandler = http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 155 c := a.newContext(RouteInfo{}, res, req) 156 err := errors.Errorf("path not found: %s %s", req.Method, req.URL.Path) 157 a.ErrorHandlers.Get(404)(404, err, c) 158 }) 159 160 if a.MethodOverride == nil { 161 a.MethodOverride = MethodOverride 162 } 163 a.Use(a.PanicHandler) 164 a.Use(RequestLogger) 165 a.Use(sessionSaver) 166 167 return a 168 } 169 170 func (a *App) processPreHandlers(res http.ResponseWriter, req *http.Request) bool { 171 sh := func(h http.Handler) bool { 172 h.ServeHTTP(res, req) 173 if br, ok := res.(*Response); ok { 174 if br.Status > 0 || br.Size > 0 { 175 return false 176 } 177 } 178 return true 179 } 180 181 for _, ph := range a.PreHandlers { 182 if ok := sh(ph); !ok { 183 return false 184 } 185 } 186 187 last := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {})) 188 for _, ph := range a.PreWares { 189 last = ph(last) 190 if ok := sh(last); !ok { 191 return false 192 } 193 } 194 return true 195 }