github.com/jackgris/buffalo@v0.10.3-0.20171206174715-b1dbfd2402de/app.go (about) 1 package buffalo 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "net/http" 8 "os" 9 "runtime" 10 "sync" 11 "syscall" 12 13 "github.com/gobuffalo/envy" 14 "github.com/gorilla/mux" 15 "github.com/markbates/going/defaults" 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 // Start is deprecated, and will be removed in v0.11.0. Use app.Serve instead. 37 func (a *App) Start(port string) error { 38 warningMsg := "Start is deprecated, and will be removed in v0.11.0. Use app.Serve instead." 39 _, file, no, ok := runtime.Caller(1) 40 if ok { 41 warningMsg = fmt.Sprintf("%s Called from %s:%d", warningMsg, file, no) 42 } 43 44 log.Println(warningMsg) 45 46 a.Addr = defaults.String(a.Addr, fmt.Sprintf("%s:%s", envy.Get("ADDR", "127.0.0.1"), port)) 47 return a.Serve() 48 } 49 50 // Serve the application at the specified address/port and listen for OS 51 // interrupt and kill signals and will attempt to stop the application 52 // gracefully. This will also start the Worker process, unless WorkerOff is enabled. 53 func (a *App) Serve() error { 54 fmt.Printf("Starting application at %s\n", a.Options.Addr) 55 server := http.Server{ 56 Addr: a.Options.Addr, 57 Handler: a, 58 } 59 ctx, cancel := sigtx.WithCancel(a.Context, syscall.SIGTERM, os.Interrupt) 60 defer cancel() 61 62 go func() { 63 // gracefully shut down the application when the context is cancelled 64 <-ctx.Done() 65 fmt.Println("Shutting down application") 66 67 err := a.Stop(ctx.Err()) 68 if err != nil { 69 fmt.Println(err) 70 } 71 72 if !a.WorkerOff { 73 // stop the workers 74 fmt.Println("Shutting down worker") 75 err = a.Worker.Stop() 76 if err != nil { 77 fmt.Println(err) 78 } 79 } 80 81 err = server.Shutdown(ctx) 82 if err != nil { 83 fmt.Println(err) 84 } 85 86 }() 87 88 // if configured to do so, start the workers 89 if !a.WorkerOff { 90 go func() { 91 err := a.Worker.Start(ctx) 92 if err != nil { 93 a.Stop(err) 94 } 95 }() 96 } 97 98 // start the web server 99 err := server.ListenAndServe() 100 if err != nil { 101 return a.Stop(err) 102 } 103 return nil 104 } 105 106 // Stop the application and attempt to gracefully shutdown 107 func (a *App) Stop(err error) error { 108 a.cancel() 109 if err != nil && errors.Cause(err) != context.Canceled { 110 fmt.Println(err) 111 return err 112 } 113 return nil 114 } 115 116 func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { 117 ws := &Response{ 118 ResponseWriter: w, 119 } 120 if a.MethodOverride != nil { 121 a.MethodOverride(w, r) 122 } 123 if ok := a.processPreHandlers(ws, r); !ok { 124 return 125 } 126 127 var h http.Handler 128 h = a.router 129 if a.Env == "development" { 130 h = web.ErrorChecker(h) 131 } 132 h.ServeHTTP(ws, r) 133 } 134 135 // New returns a new instance of App and adds some sane, and useful, defaults. 136 func New(opts Options) *App { 137 opts = optionsWithDefaults(opts) 138 139 a := &App{ 140 Options: opts, 141 Middleware: newMiddlewareStack(), 142 ErrorHandlers: ErrorHandlers{ 143 404: defaultErrorHandler, 144 500: defaultErrorHandler, 145 }, 146 router: mux.NewRouter().StrictSlash(true), 147 moot: &sync.Mutex{}, 148 routes: RouteList{}, 149 children: []*App{}, 150 } 151 a.router.NotFoundHandler = http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { 152 c := a.newContext(RouteInfo{}, res, req) 153 err := errors.Errorf("path not found: %s", req.URL.Path) 154 a.ErrorHandlers.Get(404)(404, err, c) 155 }) 156 157 if a.MethodOverride == nil { 158 a.MethodOverride = MethodOverride 159 } 160 a.Use(a.PanicHandler) 161 a.Use(RequestLogger) 162 a.Use(sessionSaver) 163 164 return a 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 }