-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathbaa.go
391 lines (337 loc) · 8.95 KB
/
baa.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
package baa
import (
"errors"
"log"
"net/http"
"os"
"strings"
"sync"
)
const (
// DEV mode
DEV = "development"
// PROD mode
PROD = "production"
// TEST mode
TEST = "test"
)
// Env default application runtime environment
var Env string
// Baa provlider an application
type Baa struct {
debug bool
name string
di DIer
router Router
pool sync.Pool
errorHandler ErrorHandleFunc
notFoundHandler HandlerFunc
middleware []HandlerFunc
}
// Middleware middleware handler
type Middleware interface{}
// Handler context handler
type Handler interface{}
// HandlerFunc context handler func
type HandlerFunc func(*Context)
// ErrorHandleFunc HTTP error handleFunc
type ErrorHandleFunc func(error, *Context)
// appInstances storage application instances
var appInstances map[string]*Baa
// defaultAppName default application name
const defaultAppName = "_default_"
// New create a baa application without any config.
func New() *Baa {
b := new(Baa)
b.middleware = make([]HandlerFunc, 0)
b.pool = sync.Pool{
New: func() interface{} {
return NewContext(nil, nil, b)
},
}
if Env != PROD {
b.debug = true
}
b.SetDIer(NewDI())
b.SetDI("router", NewTree(b))
b.SetDI("logger", log.New(os.Stderr, "[Baa] ", log.LstdFlags))
b.SetDI("render", newRender())
b.SetNotFound(b.DefaultNotFoundHandler)
return b
}
// Instance register or returns named application
func Instance(name string) *Baa {
if name == "" {
name = defaultAppName
}
if appInstances[name] == nil {
appInstances[name] = New()
appInstances[name].name = defaultAppName
}
return appInstances[name]
}
// Default initial a default app then returns
func Default() *Baa {
return Instance(defaultAppName)
}
// Server returns the internal *http.Server.
func (b *Baa) Server(addr string) *http.Server {
s := &http.Server{Addr: addr}
return s
}
// Run runs a server.
func (b *Baa) Run(addr string) {
b.run(b.Server(addr))
}
// RunTLS runs a server with TLS configuration.
func (b *Baa) RunTLS(addr, certfile, keyfile string) {
b.run(b.Server(addr), certfile, keyfile)
}
// RunServer runs a custom server.
func (b *Baa) RunServer(s *http.Server) {
b.run(s)
}
// RunTLSServer runs a custom server with TLS configuration.
func (b *Baa) RunTLSServer(s *http.Server, crtFile, keyFile string) {
b.run(s, crtFile, keyFile)
}
func (b *Baa) run(s *http.Server, files ...string) {
s.Handler = b
b.Logger().Printf("Run mode: %s", Env)
if len(files) == 0 {
b.Logger().Printf("Listen %s", s.Addr)
b.Logger().Fatal(s.ListenAndServe())
} else if len(files) == 2 {
b.Logger().Printf("Listen %s with TLS", s.Addr)
b.Logger().Fatal(s.ListenAndServeTLS(files[0], files[1]))
} else {
panic("invalid TLS configuration")
}
}
func (b *Baa) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c := b.pool.Get().(*Context)
c.Reset(w, r)
// build handler chain
route := b.Router().Match(r.Method, r.URL.Path, c)
// notFound
if route == nil || route.Handlers() == nil {
c.handlers = append(c.handlers, b.notFoundHandler)
} else {
c.handlers = append(c.handlers, route.Handlers()...)
}
c.Next()
b.pool.Put(c)
}
// SetDIer set baa di
func (b *Baa) SetDIer(v DIer) {
b.di = v
}
// SetDebug set baa debug
func (b *Baa) SetDebug(v bool) {
b.debug = v
}
// Debug returns baa debug state
func (b *Baa) Debug() bool {
return b.debug
}
// Logger return baa logger
func (b *Baa) Logger() Logger {
return b.GetDI("logger").(Logger)
}
// Render return baa render
func (b *Baa) Render() Renderer {
return b.GetDI("render").(Renderer)
}
// Router return baa router
func (b *Baa) Router() Router {
if b.router == nil {
b.router = b.GetDI("router").(Router)
}
return b.router
}
// Use registers a middleware
func (b *Baa) Use(m ...Middleware) {
for i := range m {
if m[i] != nil {
b.middleware = append(b.middleware, wrapMiddleware(m[i]))
}
}
}
// SetDI registers a dependency injection
func (b *Baa) SetDI(name string, h interface{}) {
switch name {
case "logger":
if _, ok := h.(Logger); !ok {
panic("DI logger must be implement interface baa.Logger")
}
case "render":
if _, ok := h.(Renderer); !ok {
panic("DI render must be implement interface baa.Renderer")
}
case "router":
if _, ok := h.(Router); !ok {
panic("DI router must be implement interface baa.Router")
}
}
b.di.Set(name, h)
}
// GetDI fetch a registered dependency injection
func (b *Baa) GetDI(name string) interface{} {
return b.di.Get(name)
}
// Static set static file route
// h used for set Expries ...
func (b *Baa) Static(prefix string, dir string, index bool, h HandlerFunc) {
if prefix == "" {
panic("baa.Static prefix can not be empty")
}
if dir == "" {
panic("baa.Static dir can not be empty")
}
staticHandler := newStatic(prefix, dir, index, h)
b.Get(prefix, staticHandler)
b.Get(prefix+":file", staticHandler)
}
// SetAutoHead sets the value who determines whether add HEAD method automatically
// when GET method is added. Combo router will not be affected by this value.
func (b *Baa) SetAutoHead(v bool) {
b.Router().SetAutoHead(v)
}
// SetAutoTrailingSlash optional trailing slash.
func (b *Baa) SetAutoTrailingSlash(v bool) {
b.Router().SetAutoTrailingSlash(v)
}
// Route is a shortcut for same handlers but different HTTP methods.
//
// Example:
// baa.Route("/", "GET,POST", h)
func (b *Baa) Route(pattern, methods string, h ...HandlerFunc) RouteNode {
var ru RouteNode
var ms []string
if methods == "*" {
for m := range RouterMethods {
ms = append(ms, m)
}
} else {
ms = strings.Split(methods, ",")
}
for _, m := range ms {
ru = b.Router().Add(strings.TrimSpace(m), pattern, h)
}
return ru
}
// Group registers a list of same prefix route
func (b *Baa) Group(pattern string, f func(), h ...HandlerFunc) {
b.Router().GroupAdd(pattern, f, h)
}
// Any is a shortcut for b.Router().handle("*", pattern, handlers)
func (b *Baa) Any(pattern string, h ...HandlerFunc) RouteNode {
var ru RouteNode
for m := range RouterMethods {
ru = b.Router().Add(m, pattern, h)
}
return ru
}
// Delete is a shortcut for b.Route(pattern, "DELETE", handlers)
func (b *Baa) Delete(pattern string, h ...HandlerFunc) RouteNode {
return b.Router().Add("DELETE", pattern, h)
}
// Get is a shortcut for b.Route(pattern, "GET", handlers)
func (b *Baa) Get(pattern string, h ...HandlerFunc) RouteNode {
return b.Router().Add("GET", pattern, h)
}
// Head is a shortcut forb.Route(pattern, "Head", handlers)
func (b *Baa) Head(pattern string, h ...HandlerFunc) RouteNode {
return b.Router().Add("HEAD", pattern, h)
}
// Options is a shortcut for b.Route(pattern, "Options", handlers)
func (b *Baa) Options(pattern string, h ...HandlerFunc) RouteNode {
return b.Router().Add("OPTIONS", pattern, h)
}
// Patch is a shortcut for b.Route(pattern, "PATCH", handlers)
func (b *Baa) Patch(pattern string, h ...HandlerFunc) RouteNode {
return b.Router().Add("PATCH", pattern, h)
}
// Post is a shortcut for b.Route(pattern, "POST", handlers)
func (b *Baa) Post(pattern string, h ...HandlerFunc) RouteNode {
return b.Router().Add("POST", pattern, h)
}
// Put is a shortcut for b.Route(pattern, "Put", handlers)
func (b *Baa) Put(pattern string, h ...HandlerFunc) RouteNode {
return b.Router().Add("PUT", pattern, h)
}
// SetNotFound set not found route handler
func (b *Baa) SetNotFound(h HandlerFunc) {
b.notFoundHandler = h
}
// NotFound execute not found handler
func (b *Baa) NotFound(c *Context) {
if b.notFoundHandler != nil {
b.notFoundHandler(c)
return
}
http.NotFound(c.Resp, c.Req)
}
// SetError set error handler
func (b *Baa) SetError(h ErrorHandleFunc) {
b.errorHandler = h
}
// Error execute internal error handler
func (b *Baa) Error(err error, c *Context) {
if err == nil {
err = errors.New("Internal Server Error")
}
if b.errorHandler != nil {
b.errorHandler(err, c)
return
}
code := http.StatusInternalServerError
msg := http.StatusText(code)
if b.debug {
msg = err.Error()
}
http.Error(c.Resp, msg, code)
}
// DefaultNotFoundHandler invokes the default HTTP error handler.
func (b *Baa) DefaultNotFoundHandler(c *Context) {
code := http.StatusNotFound
msg := http.StatusText(code)
http.Error(c.Resp, msg, code)
}
// URLFor use named route return format url
func (b *Baa) URLFor(name string, args ...interface{}) string {
return b.Router().URLFor(name, args...)
}
// wrapMiddleware wraps middleware.
func wrapMiddleware(m Middleware) HandlerFunc {
switch m := m.(type) {
case HandlerFunc:
return m
case func(*Context):
return m
case http.Handler, http.HandlerFunc:
return WrapHandlerFunc(func(c *Context) {
m.(http.Handler).ServeHTTP(c.Resp, c.Req)
})
case func(http.ResponseWriter, *http.Request):
return WrapHandlerFunc(func(c *Context) {
m(c.Resp, c.Req)
})
default:
panic("unknown middleware")
}
}
// WrapHandlerFunc wrap for context handler chain
func WrapHandlerFunc(h HandlerFunc) HandlerFunc {
return func(c *Context) {
h(c)
c.Next()
}
}
func init() {
appInstances = make(map[string]*Baa)
Env = os.Getenv("BAA_ENV")
if Env == "" {
Env = DEV
}
}