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  }