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