github.com/res-am/buffalo@v0.11.1/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  }