github.com/bketelsen/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  }