github.com/naoina/kocha@v0.7.1-0.20171129072645-78c7a531f799/kocha.go (about)

     1  package kocha
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"reflect"
     9  	"runtime"
    10  	"sync"
    11  
    12  	"github.com/joho/godotenv"
    13  	"github.com/naoina/kocha/log"
    14  	"github.com/naoina/miyabi"
    15  )
    16  
    17  const (
    18  	// DefaultHttpAddr is the default listen address.
    19  	DefaultHttpAddr = "127.0.0.1:9100"
    20  
    21  	// DefaultMaxClientBodySize is the maximum size of HTTP request body.
    22  	// This can be overridden by setting Config.MaxClientBodySize.
    23  	DefaultMaxClientBodySize = 1024 * 1024 * 10 // 10MB
    24  
    25  	// StaticDir is the directory of the static files.
    26  	StaticDir = "public"
    27  )
    28  
    29  var (
    30  	nullMiddlewareNext = func() error {
    31  		return nil
    32  	}
    33  
    34  	bufPool = &sync.Pool{
    35  		New: func() interface{} {
    36  			return new(bytes.Buffer)
    37  		},
    38  	}
    39  )
    40  
    41  // Run starts Kocha app.
    42  // This will launch the HTTP server by using github.com/naoina/miyabi.
    43  // If you want to use other HTTP server that compatible with net/http such as
    44  // http.ListenAndServe, you can use New.
    45  func Run(config *Config) error {
    46  	app, err := New(config)
    47  	if err != nil {
    48  		return err
    49  	}
    50  	pid := os.Getpid()
    51  	miyabi.ServerState = func(state miyabi.State) {
    52  		switch state {
    53  		case miyabi.StateStart:
    54  			fmt.Printf("Listening on %s\n", app.Config.Addr)
    55  			fmt.Printf("Server PID: %d\n", pid)
    56  		case miyabi.StateRestart:
    57  			app.Logger.Warn("kocha: graceful restarted")
    58  		case miyabi.StateShutdown:
    59  			app.Logger.Warn("kocha: graceful shutdown")
    60  		}
    61  	}
    62  	server := &miyabi.Server{
    63  		Addr:    config.Addr,
    64  		Handler: app,
    65  	}
    66  	app.Event.start()
    67  	defer app.Event.stop()
    68  	return server.ListenAndServe()
    69  }
    70  
    71  // Application represents a Kocha app.
    72  // This implements the http.Handler interface.
    73  type Application struct {
    74  	// Config is a configuration of an application.
    75  	Config *Config
    76  
    77  	// Router is an HTTP request router of an application.
    78  	Router *Router
    79  
    80  	// Template is template sets of an application.
    81  	Template *Template
    82  
    83  	// Logger is an application logger.
    84  	Logger log.Logger
    85  
    86  	// Event is an interface of the event system.
    87  	Event *Event
    88  
    89  	// ResourceSet is set of resource of an application.
    90  	ResourceSet ResourceSet
    91  
    92  	failedUnits map[string]struct{}
    93  	mu          sync.RWMutex
    94  }
    95  
    96  // New returns a new Application that configured by config.
    97  func New(config *Config) (*Application, error) {
    98  	app := &Application{
    99  		Config:      config,
   100  		failedUnits: make(map[string]struct{}),
   101  	}
   102  	if app.Config.Addr == "" {
   103  		config.Addr = DefaultHttpAddr
   104  	}
   105  	if app.Config.MaxClientBodySize < 1 {
   106  		config.MaxClientBodySize = DefaultMaxClientBodySize
   107  	}
   108  	if err := app.validateMiddlewares(); err != nil {
   109  		return nil, err
   110  	}
   111  	if err := app.buildResourceSet(); err != nil {
   112  		return nil, err
   113  	}
   114  	if err := app.buildTemplate(); err != nil {
   115  		return nil, err
   116  	}
   117  	if err := app.buildRouter(); err != nil {
   118  		return nil, err
   119  	}
   120  	if err := app.buildLogger(); err != nil {
   121  		return nil, err
   122  	}
   123  	if err := app.buildEvent(); err != nil {
   124  		return nil, err
   125  	}
   126  	return app, nil
   127  }
   128  
   129  // ServeHTTP implements the http.Handler.ServeHTTP.
   130  func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   131  	c := newContext()
   132  	c.Layout = app.Config.DefaultLayout
   133  	c.Request = newRequest(r)
   134  	c.Response = newResponse()
   135  	c.App = app
   136  	c.Errors = make(map[string][]*ParamError)
   137  	defer c.reuse()
   138  	defer func() {
   139  		if err := c.Response.writeTo(w); err != nil {
   140  			app.Logger.Error(err)
   141  		}
   142  	}()
   143  	if err := app.wrapMiddlewares(c)(); err != nil {
   144  		app.Logger.Error(err)
   145  		c.Response.reset()
   146  		http.Error(c.Response, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   147  	}
   148  }
   149  
   150  // Invoke invokes newFunc.
   151  // It invokes newFunc but will behave to fallback.
   152  // When unit.ActiveIf returns false or any errors occurred in invoking, it invoke the defaultFunc if defaultFunc isn't nil.
   153  // Also if any errors occurred at least once, next invoking will always invoke the defaultFunc.
   154  func (app *Application) Invoke(unit Unit, newFunc func(), defaultFunc func()) {
   155  	name := reflect.TypeOf(unit).String()
   156  	defer func() {
   157  		if err := recover(); err != nil {
   158  			if err != ErrInvokeDefault {
   159  				app.logStackAndError(err)
   160  				app.mu.Lock()
   161  				app.failedUnits[name] = struct{}{}
   162  				app.mu.Unlock()
   163  			}
   164  			if defaultFunc != nil {
   165  				defaultFunc()
   166  			}
   167  		}
   168  	}()
   169  	app.mu.RLock()
   170  	_, failed := app.failedUnits[name]
   171  	app.mu.RUnlock()
   172  	if failed || !unit.ActiveIf() {
   173  		panic(ErrInvokeDefault)
   174  	}
   175  	newFunc()
   176  }
   177  
   178  func (app *Application) buildRouter() (err error) {
   179  	app.Router, err = app.Config.RouteTable.buildRouter()
   180  	return err
   181  }
   182  
   183  func (app *Application) buildResourceSet() error {
   184  	app.ResourceSet = app.Config.ResourceSet
   185  	return nil
   186  }
   187  
   188  func (app *Application) buildTemplate() (err error) {
   189  	app.Template, err = app.Config.Template.build(app)
   190  	return err
   191  }
   192  
   193  func (app *Application) buildLogger() error {
   194  	if app.Config.Logger == nil {
   195  		app.Config.Logger = &LoggerConfig{}
   196  	}
   197  	if app.Config.Logger.Writer == nil {
   198  		app.Config.Logger.Writer = os.Stdout
   199  	}
   200  	if app.Config.Logger.Formatter == nil {
   201  		app.Config.Logger.Formatter = &log.LTSVFormatter{}
   202  	}
   203  	app.Logger = log.New(app.Config.Logger.Writer, app.Config.Logger.Formatter, app.Config.Logger.Level)
   204  	return nil
   205  }
   206  
   207  func (app *Application) buildEvent() (err error) {
   208  	app.Event, err = app.Config.Event.build(app)
   209  	return err
   210  }
   211  
   212  func (app *Application) validateMiddlewares() error {
   213  	for _, m := range app.Config.Middlewares {
   214  		if v, ok := m.(Validator); ok {
   215  			if err := v.Validate(); err != nil {
   216  				return err
   217  			}
   218  		}
   219  	}
   220  	return nil
   221  }
   222  
   223  func (app *Application) wrapMiddlewares(c *Context) func() error {
   224  	wrapped := nullMiddlewareNext
   225  	for i := len(app.Config.Middlewares) - 1; i >= 0; i-- {
   226  		f, next := app.Config.Middlewares[i].Process, wrapped
   227  		wrapped = func() error {
   228  			return f(app, c, next)
   229  		}
   230  	}
   231  	return wrapped
   232  }
   233  
   234  func (app *Application) logStackAndError(err interface{}) {
   235  	buf := make([]byte, 4096)
   236  	n := runtime.Stack(buf, false)
   237  	app.Logger.Errorf("%v\n%s", err, buf[:n])
   238  }
   239  
   240  // Config represents a application-scope configuration.
   241  type Config struct {
   242  	Addr              string        // listen address, DefaultHttpAddr if empty.
   243  	AppPath           string        // root path of the application.
   244  	AppName           string        // name of the application.
   245  	DefaultLayout     string        // name of the default layout.
   246  	Template          *Template     // template config.
   247  	RouteTable        RouteTable    // routing config.
   248  	Middlewares       []Middleware  // middlewares.
   249  	Logger            *LoggerConfig // logger config.
   250  	Event             *Event        // event config.
   251  	MaxClientBodySize int64         // maximum size of request body, DefaultMaxClientBodySize if 0
   252  
   253  	ResourceSet ResourceSet
   254  }
   255  
   256  // Getenv is similar to os.Getenv.
   257  // However, Getenv returns def value if the variable is not present, and
   258  // sets def to environment variable.
   259  func Getenv(key, def string) string {
   260  	env := os.Getenv(key)
   261  	if env != "" {
   262  		return env
   263  	}
   264  	os.Setenv(key, def)
   265  	return def
   266  }
   267  
   268  func init() {
   269  	_ = godotenv.Load()
   270  }