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 }