Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

Allow buffalo to accept preconfigured http.Server(s) fixes #1015 #1039

Merged
merged 3 commits into from
Apr 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 0 additions & 130 deletions app.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
package buffalo

import (
"context"
"net"
"net/http"
"os"
"strings"
"sync"
"syscall"

"github.com/sirupsen/logrus"

"github.com/gobuffalo/envy"
"github.com/gorilla/mux"
"github.com/markbates/refresh/refresh/web"
"github.com/markbates/sigtx"
"github.com/pkg/errors"
)

Expand All @@ -33,100 +24,6 @@ type App struct {
children []*App
}

// Serve the application at the specified address/port and listen for OS
// interrupt and kill signals and will attempt to stop the application
// gracefully. This will also start the Worker process, unless WorkerOff is enabled.
func (a *App) Serve() error {
logrus.Infof("Starting application at %s", a.Options.Host)
server := http.Server{
Handler: a,
}
ctx, cancel := sigtx.WithCancel(a.Context, syscall.SIGTERM, os.Interrupt)
defer cancel()

go func() {
// gracefully shut down the application when the context is cancelled
<-ctx.Done()
logrus.Info("Shutting down application")

if err := a.Stop(ctx.Err()); err != nil {
logrus.Error(err)
}

if !a.WorkerOff {
// stop the workers
logrus.Info("Shutting down worker")
if err := a.Worker.Stop(); err != nil {
logrus.Error(err)
}
}

if err := server.Shutdown(ctx); err != nil {
logrus.Error(err)
}

}()

// if configured to do so, start the workers
if !a.WorkerOff {
go func() {
if err := a.Worker.Start(ctx); err != nil {
a.Stop(err)
}
}()
}

if strings.HasPrefix(a.Options.Addr, "unix:") {
// Use an UNIX socket
listener, err := net.Listen("unix", a.Options.Addr[5:])
if err != nil {
return a.Stop(err)
}
// start the web server
if err = server.Serve(listener); err != nil {
return a.Stop(err)
}
return nil
}
// Use a TCP socket
server.Addr = a.Options.Addr

// start the web server
if err := server.ListenAndServe(); err != nil {
return a.Stop(err)
}

return nil
}

// Stop the application and attempt to gracefully shutdown
func (a *App) Stop(err error) error {
a.cancel()
if err != nil && errors.Cause(err) != context.Canceled {
logrus.Error(err)
return err
}
return nil
}

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ws := &Response{
ResponseWriter: w,
}
if a.MethodOverride != nil {
a.MethodOverride(w, r)
}
if ok := a.processPreHandlers(ws, r); !ok {
return
}

var h http.Handler = a.router
if a.Env == "development" {
h = web.ErrorChecker(h)
}
h.ServeHTTP(ws, r)
}

// New returns a new instance of App and adds some sane, and useful, defaults.
func New(opts Options) *App {
envy.Load()
Expand Down Expand Up @@ -165,30 +62,3 @@ func New(opts Options) *App {

return a
}

func (a *App) processPreHandlers(res http.ResponseWriter, req *http.Request) bool {
sh := func(h http.Handler) bool {
h.ServeHTTP(res, req)
if br, ok := res.(*Response); ok {
if br.Status > 0 || br.Size > 0 {
return false
}
}
return true
}

for _, ph := range a.PreHandlers {
if ok := sh(ph); !ok {
return false
}
}

last := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {}))
for _, ph := range a.PreWares {
last = ph(last)
if ok := sh(last); !ok {
return false
}
}
return true
}
138 changes: 138 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package buffalo

import (
"context"
"net/http"
"os"
"strings"
"syscall"

"github.com/gobuffalo/buffalo/servers"
"github.com/markbates/refresh/refresh/web"
"github.com/markbates/sigtx"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

// Serve the application at the specified address/port and listen for OS
// interrupt and kill signals and will attempt to stop the application
// gracefully. This will also start the Worker process, unless WorkerOff is enabled.
func (a *App) Serve(srvs ...servers.Server) error {
logrus.Infof("Starting application at %s", a.Options.Host)

if len(srvs) == 0 {
if strings.HasPrefix(a.Options.Addr, "unix:") {
tcp, err := servers.UnixSocket(a.Options.Addr[5:])
if err != nil {
return errors.WithStack(err)
}
srvs = append(srvs, tcp)
} else {
srvs = append(srvs, servers.New())
}
}

ctx, cancel := sigtx.WithCancel(a.Context, syscall.SIGTERM, os.Interrupt)
defer cancel()

go func() {
// gracefully shut down the application when the context is cancelled
<-ctx.Done()
logrus.Info("Shutting down application")

if err := a.Stop(ctx.Err()); err != nil {
logrus.Error(err)
}

if !a.WorkerOff {
// stop the workers
logrus.Info("Shutting down worker")
if err := a.Worker.Stop(); err != nil {
logrus.Error(err)
}
}

for _, s := range srvs {
if err := s.Shutdown(ctx); err != nil {
logrus.Error(err)
}
}

}()

// if configured to do so, start the workers
if !a.WorkerOff {
go func() {
if err := a.Worker.Start(ctx); err != nil {
a.Stop(err)
}
}()
}

for _, s := range srvs {
s.SetAddr(a.Addr)
go func(s servers.Server) {
if err := s.Start(ctx, a); err != nil {
a.Stop(err)
}
}(s)
}

<-ctx.Done()
return a.Context.Err()
}

// Stop the application and attempt to gracefully shutdown
func (a *App) Stop(err error) error {
a.cancel()
if err != nil && errors.Cause(err) != context.Canceled {
logrus.Error(err)
return err
}
return nil
}

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ws := &Response{
ResponseWriter: w,
}
if a.MethodOverride != nil {
a.MethodOverride(w, r)
}
if ok := a.processPreHandlers(ws, r); !ok {
return
}

var h http.Handler = a.router
if a.Env == "development" {
h = web.ErrorChecker(h)
}
h.ServeHTTP(ws, r)
}

func (a *App) processPreHandlers(res http.ResponseWriter, req *http.Request) bool {
sh := func(h http.Handler) bool {
h.ServeHTTP(res, req)
if br, ok := res.(*Response); ok {
if br.Status > 0 || br.Size > 0 {
return false
}
}
return true
}

for _, ph := range a.PreHandlers {
if ok := sh(ph); !ok {
return false
}
}

last := http.Handler(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {}))
for _, ph := range a.PreWares {
last = ph(last)
if ok := sh(last); !ok {
return false
}
}
return true
}
40 changes: 40 additions & 0 deletions servers/listener.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package servers

import (
"context"
"net"
"net/http"

"github.com/pkg/errors"
)

// Listener server for using a pre-defined net.Listener
type Listener struct {
*http.Server
Listener net.Listener
}

// SetAddr sets the servers address, if it hasn't already been set
func (s *Listener) SetAddr(addr string) {
if s.Server.Addr == "" {
s.Server.Addr = addr
}
}

// Start the server
func (s *Listener) Start(c context.Context, h http.Handler) error {
s.Handler = h
return s.Serve(s.Listener)
}

// UnixSocket returns a new Listener on that address
func UnixSocket(addr string) (*Listener, error) {
listener, err := net.Listen("unix", addr)
if err != nil {
return nil, errors.WithStack(err)
}
return &Listener{
Server: &http.Server{},
Listener: listener,
}, nil
}
37 changes: 37 additions & 0 deletions servers/servers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package servers

import (
"context"
"net"
"net/http"
)

// Server allows for custom server implementations
type Server interface {
Shutdown(context.Context) error
Start(context.Context, http.Handler) error
SetAddr(string)
}

// Wrap converts a standard *http.Server to a buffalo.Server
func Wrap(s *http.Server) Server {
return &Simple{Server: s}
}

// WrapTLS Server converts a standard *http.Server to a buffalo.Server
// but makes sure it is run with TLS.
func WrapTLS(s *http.Server, certFile string, keyFile string) Server {
return &TLS{
Server: s,
CertFile: certFile,
KeyFile: keyFile,
}
}

// WrapListener wraps an *http.Server and a net.Listener
func WrapListener(s *http.Server, l net.Listener) Server {
return &Listener{
Server: s,
Listener: l,
}
}
Loading