github.com/segakazzz/buffalo@v0.16.22-0.20210119082501-1f52048d3feb/server.go (about)

     1  package buffalo
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"os"
     7  	"strings"
     8  	"syscall"
     9  
    10  	"github.com/gobuffalo/buffalo/servers"
    11  	"github.com/gobuffalo/events"
    12  	"github.com/gobuffalo/packd"
    13  	"github.com/markbates/refresh/refresh/web"
    14  	"github.com/markbates/sigtx"
    15  	"github.com/segakazzz/buffalo/internal/takeon/github.com/markbates/errx"
    16  )
    17  
    18  // Serve the application at the specified address/port and listen for OS
    19  // interrupt and kill signals and will attempt to stop the application
    20  // gracefully. This will also start the Worker process, unless WorkerOff is enabled.
    21  func (a *App) Serve(srvs ...servers.Server) error {
    22  	a.Logger.Infof("Starting application at http://%s", a.Options.Addr)
    23  
    24  	payload := events.Payload{
    25  		"app": a,
    26  	}
    27  	if err := events.EmitPayload(EvtAppStart, payload); err != nil {
    28  		return err
    29  	}
    30  
    31  	if len(srvs) == 0 {
    32  		if strings.HasPrefix(a.Options.Addr, "unix:") {
    33  			tcp, err := servers.UnixSocket(a.Options.Addr[5:])
    34  			if err != nil {
    35  				return err
    36  			}
    37  			srvs = append(srvs, tcp)
    38  		} else {
    39  			srvs = append(srvs, servers.New())
    40  		}
    41  	}
    42  
    43  	ctx, cancel := sigtx.WithCancel(a.Context, syscall.SIGTERM, os.Interrupt)
    44  	defer cancel()
    45  
    46  	go func() {
    47  		// gracefully shut down the application when the context is cancelled
    48  		<-ctx.Done()
    49  		a.Logger.Info("Shutting down application")
    50  
    51  		events.EmitError(EvtAppStop, ctx.Err(), payload)
    52  
    53  		if err := a.Stop(ctx.Err()); err != nil {
    54  			events.EmitError(EvtAppStopErr, err, payload)
    55  			a.Logger.Error(err)
    56  		}
    57  
    58  		if !a.WorkerOff {
    59  			// stop the workers
    60  			a.Logger.Info("Shutting down worker")
    61  			events.EmitPayload(EvtWorkerStop, payload)
    62  			if err := a.Worker.Stop(); err != nil {
    63  				events.EmitError(EvtWorkerStopErr, err, payload)
    64  				a.Logger.Error(err)
    65  			}
    66  		}
    67  
    68  		for _, s := range srvs {
    69  			if err := s.Shutdown(ctx); err != nil {
    70  				a.Logger.Error(err)
    71  			}
    72  		}
    73  
    74  	}()
    75  
    76  	// if configured to do so, start the workers
    77  	if !a.WorkerOff {
    78  		go func() {
    79  			events.EmitPayload(EvtWorkerStart, payload)
    80  			if err := a.Worker.Start(ctx); err != nil {
    81  				a.Stop(err)
    82  			}
    83  		}()
    84  	}
    85  
    86  	for _, s := range srvs {
    87  		s.SetAddr(a.Addr)
    88  		go func(s servers.Server) {
    89  			if err := s.Start(ctx, a); err != nil {
    90  				a.Stop(err)
    91  			}
    92  		}(s)
    93  	}
    94  
    95  	<-ctx.Done()
    96  
    97  	return a.Context.Err()
    98  }
    99  
   100  // Stop the application and attempt to gracefully shutdown
   101  func (a *App) Stop(err error) error {
   102  	a.cancel()
   103  	if err != nil && errx.Unwrap(err) != context.Canceled {
   104  		a.Logger.Error(err)
   105  		return err
   106  	}
   107  	return nil
   108  }
   109  
   110  func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   111  	if r.Body != nil {
   112  		// convert the request's body to a packd.File which can be read N times
   113  		f, err := packd.NewFile("", r.Body)
   114  		if err == nil {
   115  			r.Body = f
   116  		}
   117  	}
   118  	ws := &Response{
   119  		ResponseWriter: w,
   120  	}
   121  	if a.MethodOverride != nil {
   122  		a.MethodOverride(w, r)
   123  	}
   124  	if ok := a.processPreHandlers(ws, r); !ok {
   125  		return
   126  	}
   127  
   128  	r.URL.Path = a.normalizePath(r.URL.Path)
   129  
   130  	var h http.Handler = a.router
   131  	if a.Env == "development" {
   132  		h = web.ErrorChecker(h)
   133  	}
   134  	h.ServeHTTP(ws, r)
   135  }
   136  
   137  func (a *App) processPreHandlers(res http.ResponseWriter, req *http.Request) bool {
   138  	sh := func(h http.Handler) bool {
   139  		h.ServeHTTP(res, req)
   140  		if br, ok := res.(*Response); ok {
   141  			if br.Status > 0 || br.Size > 0 {
   142  				return false
   143  			}
   144  		}
   145  		return true
   146  	}
   147  
   148  	for _, ph := range a.PreHandlers {
   149  		if ok := sh(ph); !ok {
   150  			return false
   151  		}
   152  	}
   153  
   154  	last := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {}))
   155  	for _, ph := range a.PreWares {
   156  		last = ph(last)
   157  		if ok := sh(last); !ok {
   158  			return false
   159  		}
   160  	}
   161  	return true
   162  }
   163  
   164  func (a *App) normalizePath(path string) string {
   165  	if strings.HasSuffix(path, "/") {
   166  		return path
   167  	}
   168  	for _, p := range a.filepaths {
   169  		if p == "/" {
   170  			continue
   171  		}
   172  		if strings.HasPrefix(path, p) {
   173  			return path
   174  		}
   175  	}
   176  	return path + "/"
   177  }