forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
policy.go
563 lines (505 loc) · 20.5 KB
/
policy.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
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
package iris
import (
"io"
"log"
"net/http"
"strings"
"github.com/kataras/go-errors"
)
type (
// Policy is an interface which should be implemented by all
// modules that can adapt a policy to the Framework.
// With a Policy you can change the behavior of almost each of the existing Iris' features.
Policy interface {
// Adapt receives the main *Policies which the Policy should be attached on.
Adapt(frame *Policies)
}
// Policies is the main policies list, the rest of the objects that implement the Policy
// are adapted to the object which contains a field of type *Policies.
//
// Policies can have nested policies behaviors too.
// See iris.go field: 'policies' and function 'Adapt' for more.
Policies struct {
LoggerPolicy
EventPolicy
RouterReversionPolicy
RouterBuilderPolicy
RouterWrapperPolicy
RenderPolicy
TemplateFuncsPolicy
SessionsPolicy
}
)
// Adapt implements the behavior in order to be valid to pass Policies as one
// useful for third-party libraries which can provide more tools in one registration.
func (p Policies) Adapt(frame *Policies) {
// Adapt the logger (optionally, it defaults to a log.New(...).Printf)
if p.LoggerPolicy != nil {
p.LoggerPolicy.Adapt(frame)
}
// Adapt the flow callbacks (optionally)
p.EventPolicy.Adapt(frame)
// Adapt the reverse routing behaviors and policy
p.RouterReversionPolicy.Adapt(frame)
// Adapt the router builder
if p.RouterBuilderPolicy != nil {
p.RouterBuilderPolicy.Adapt(frame)
}
// Adapt any Router's wrapper (optionally)
if p.RouterWrapperPolicy != nil {
p.RouterWrapperPolicy.Adapt(frame)
}
// Adapt the render policy (both templates and rich content)
if p.RenderPolicy != nil {
p.RenderPolicy.Adapt(frame)
}
// Adapt the template funcs which can be used to register template funcs
// from community's packages, it doesn't matters what template/view engine the user
// uses, and if uses at all.
if p.TemplateFuncsPolicy != nil {
p.TemplateFuncsPolicy.Adapt(frame)
}
p.SessionsPolicy.Adapt(frame)
}
// LogMode is the type for the LoggerPolicy write mode.
// Two modes available:
// - ProdMode (production level mode)
// - DevMode (development level mode)
//
// The ProdMode should output only fatal errors
// The DevMode ouputs the rest of the errors
//
// Iris logs ONLY errors at both cases.
// By-default ONLY ProdMode level messages are printed to the os.Stdout.
type LogMode uint8
const (
// ProdMode the production level logger write mode,
// responsible to fatal errors, errors that happen which
// your app can't continue running.
ProdMode LogMode = iota
// DevMode is the development level logger write mode,
// responsible to the rest of the errors, for example
// if you set a app.Favicon("myfav.ico"..) and that fav doesn't exists
// in your system, then it printed by DevMode and app.Favicon simple doesn't works.
// But the rest of the app can continue running, so it's not 'Fatal error'
DevMode
)
// LoggerPolicy is a simple interface which is used to log mostly system panics
// exception for general debugging messages is when the `Framework.Config.IsDevelopment = true`.
// It should prints to the logger.
// Arguments should be handled in the manner of fmt.Printf.
type LoggerPolicy func(mode LogMode, log string)
// Adapt adapts a Logger to the main policies.
func (l LoggerPolicy) Adapt(frame *Policies) {
if l != nil {
// notes for me: comment these in order to remember
// why I choose not to do that:
// It wraps the loggers, so you can use more than one
// when you have multiple print targets.
// No this is not a good idea for loggers
// the user may not expecting this behavior,
// if the user wants multiple targets she/he
// can wrap their loggers or use one logger to print on all targets.
// COMMENT:
// logger := l
// if frame.LoggerPolicy != nil {
// prevLogger := frame.LoggerPolicy
// nextLogger := l
// logger = func(mode LogMode, log string) {
// prevLogger(mode, log)
// nextLogger(mode, log)
// }
// }
frame.LoggerPolicy = l
}
}
// The write method exists to LoggerPolicy to be able to passed
// as a valid an io.Writer when you need it.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
//
// Note: this Write writes as the DevMode.
func (l LoggerPolicy) Write(p []byte) (n int, err error) {
log := string(p)
l(DevMode, log)
return len(log), nil
}
// ToLogger returns a new *log.Logger
// which prints to the the LoggerPolicy function
// this is used when your packages needs explicit an *log.Logger.
//
// Note: Each time you call it, it returns a new *log.Logger.
func (l LoggerPolicy) ToLogger(flag int) *log.Logger {
return log.New(l, "", flag)
}
type (
// EventListener is the signature for type of func(*Framework),
// which is used to register events inside an EventPolicy.
//
// Keep note that, inside the policy this is a wrapper
// in order to register more than one listener without the need of slice.
EventListener func(*Framework)
// EventPolicy contains the available Framework's flow event callbacks.
// Available events:
// - Boot
// - Build
// - Interrupted
// - Recover
EventPolicy struct {
// Boot with a listener type of EventListener.
// Fires when '.Boot' is called (by .Serve functions or manually),
// before the Build of the components and the Listen,
// after VHost and VSCheme configuration has been setted.
Boot EventListener
// Before Listen, after Boot
Build EventListener
// Interrupted with a listener type of EventListener.
// Fires after the terminal is interrupted manually by Ctrl/Cmd + C
// which should be used to release external resources.
// Iris will close and os.Exit at the end of custom interrupted events.
// If you want to prevent the default behavior just block on the custom Interrupted event.
Interrupted EventListener
// Recover with a listener type of func(*Framework, interface{}).
// Fires when an unexpected error(panic) is happening at runtime,
// while the server's net.Listener accepting requests
// or when a '.Must' call contains a filled error.
// Used to release external resources and '.Close' the server.
// Only one type of this callback is allowed.
//
// If not empty then the Framework will skip its internal
// server's '.Close' and panic to its '.Logger' and execute that callback instaed.
// Differences from Interrupted:
// 1. Fires on unexpected errors
// 2. Only one listener is allowed.
Recover func(*Framework, error)
}
)
var _ Policy = EventPolicy{}
// Adapt adaps an EventPolicy object to the main *Policies.
func (e EventPolicy) Adapt(frame *Policies) {
// Boot event listener, before the build (old: PreBuild)
frame.EventPolicy.Boot =
wrapEvtListeners(frame.EventPolicy.Boot, e.Boot)
// Build event listener, after Boot and before Listen(old: PostBuild & PreListen)
frame.EventPolicy.Build =
wrapEvtListeners(frame.EventPolicy.Build, e.Build)
// Interrupted event listener, when control+C or manually interrupt by os signal
frame.EventPolicy.Interrupted =
wrapEvtListeners(frame.EventPolicy.Interrupted, e.Interrupted)
// Recover event listener, when panic on .Must and inside .Listen/ListenTLS/ListenUNIX/ListenLETSENCRYPT/Serve
// only one allowed, no wrapper is used.
if e.Recover != nil {
frame.EventPolicy.Recover = e.Recover
}
}
// Fire fires an EventListener with its Framework when listener is not nil.
// Returns true when fired, otherwise false.
func (e EventPolicy) Fire(ln EventListener, s *Framework) bool {
if ln != nil {
ln(s)
return true
}
return false
}
func wrapEvtListeners(prev EventListener, next EventListener) EventListener {
if next == nil {
return prev
}
listener := next
if prev != nil {
listener = func(s *Framework) {
prev(s)
next(s)
}
}
return listener
}
type (
// RouterReversionPolicy is used for the reverse routing feature on
// which custom routers should create and adapt to the Policies.
RouterReversionPolicy struct {
// StaticPath should return the static part of the route path
// for example, with the httprouter(: and *):
// /api/user/:userid should return /api/user
// /api/user/:userid/messages/:messageid should return /api/user
// /dynamicpath/*path should return /dynamicpath
// /my/path should return /my/path
StaticPath func(path string) string
// WildcardPath should return a path converted to a 'dynamic' path
// for example, with the httprouter(wildcard symbol: '*'):
// ("/static", "path") should return /static/*path
// ("/myfiles/assets", "anything") should return /myfiles/assets/*anything
WildcardPath func(path string, paramName string) string
// Param should return a named parameter as each router defines named path parameters.
// For example, with the httprouter(: as named param symbol):
// userid should return :userid.
// with gorillamux, userid should return {userid}
// or userid[1-9]+ should return {userid[1-9]+}.
// so basically we just wrap the raw parameter name
// with the start (and end) dynamic symbols of each router implementing the RouterReversionPolicy.
// It's an optional functionality but it can be used to create adaptors without even know the router
// that the user uses (which can be taken by app.Config.Other[iris.RouterNameConfigKey].
//
// Note: we don't need a function like WildcardParam because the developer
// can use the Param with a combination with WildcardPath.
Param func(paramName string) string
// URLPath used for reverse routing on templates with {{ url }} and {{ path }} funcs.
// Receives the route name and arguments and returns its http path
URLPath func(r RouteInfo, args ...string) string
}
// RouterBuilderPolicy is the most useful Policy for custom routers.
// A custom router should adapt this policy which is a func
// accepting a route repository (contains all necessary routes information)
// and a context pool which should be used inside router's handlers.
RouterBuilderPolicy func(repo RouteRepository, cPool ContextPool) http.Handler
// RouterWrapperPolicy is the Policy which enables a wrapper on the top of
// the builded Router. Usually it's useful for third-party middleware
// when need to wrap the entire application with a middleware like CORS.
//
// Developers can Adapt more than one RouterWrapper
// those wrappers' execution comes from last to first.
// That means that the second wrapper will wrap the first, and so on.
RouterWrapperPolicy func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
)
func normalizePath(path string) string {
// some users can't understand the difference between
// request path and operating system's directory path
// they think that "./" is the index, that's wrong, "/" is the index
// so fix that here...
if path[0] == '.' {
path = path[1:]
}
path = strings.Replace(path, "//", "/", -1)
if len(path) > 1 && strings.IndexByte(path, '/') == len(path)-1 {
// if it's not "/" and ending with slash remove that slash
path = path[0 : len(path)-2]
}
return path
}
// Adapt adaps a RouterReversionPolicy object to the main *Policies.
func (r RouterReversionPolicy) Adapt(frame *Policies) {
if r.StaticPath != nil {
staticPathFn := r.StaticPath
frame.RouterReversionPolicy.StaticPath = func(path string) string {
return staticPathFn(normalizePath(path))
}
}
if r.WildcardPath != nil {
wildcardPathFn := r.WildcardPath
frame.RouterReversionPolicy.WildcardPath = func(path string, paramName string) string {
return wildcardPathFn(normalizePath(path), paramName)
}
}
if r.Param != nil {
frame.RouterReversionPolicy.Param = r.Param
}
if r.URLPath != nil {
frame.RouterReversionPolicy.URLPath = r.URLPath
}
}
// Adapt adaps a RouterBuilderPolicy object to the main *Policies.
func (r RouterBuilderPolicy) Adapt(frame *Policies) {
// What is this kataras?
// The whole design of this file is brilliant = go's power + my ideas and experience on software architecture.
//
// When the router decides to compile/build this behavior
// then this overload will check for a wrapper too
// if a wrapper exists it will wrap the result of the RouterBuilder (which is http.Handler, the Router.)
// and return that instead.
// I moved the logic here so we don't need a 'compile/build' method inside the routerAdaptor.
frame.RouterBuilderPolicy = RouterBuilderPolicy(func(repo RouteRepository, cPool ContextPool) http.Handler {
handler := r(repo, cPool)
wrapper := frame.RouterWrapperPolicy
if wrapper != nil {
originalHandler := handler.ServeHTTP
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wrapper(w, r, originalHandler)
})
}
return handler
})
}
// Adapt adaps a RouterWrapperPolicy object to the main *Policies.
func (rw RouterWrapperPolicy) Adapt(frame *Policies) {
if rw != nil {
wrapper := rw
prevWrapper := frame.RouterWrapperPolicy
if prevWrapper != nil {
nextWrapper := rw
wrapper = func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if next != nil {
nexthttpFunc := http.HandlerFunc(func(_w http.ResponseWriter, _r *http.Request) {
prevWrapper(_w, _r, next)
})
nextWrapper(w, r, nexthttpFunc)
}
}
}
frame.RouterWrapperPolicy = wrapper
}
}
// RenderPolicy is the type which you can adapt custom renderers
// based on the 'name', simple as that.
// Note that the whole template view system and
// content negotiation works by setting this function via other adaptors.
//
// The functions are wrapped, like any other policy func, the only difference is that
// here the developer has a priority over the defaults:
// - the last registered is trying to be executed first
// - the first registered is executing last.
// So a custom adaptor that the community can create and share with each other
// can override the existing one with just a simple registration.
type RenderPolicy func(out io.Writer, name string, bind interface{}, options ...map[string]interface{}) (bool, error)
// Adapt adaps a RenderPolicy object to the main *Policies.
func (r RenderPolicy) Adapt(frame *Policies) {
if r != nil {
renderer := r
prevRenderer := frame.RenderPolicy
if prevRenderer != nil {
nextRenderer := r
renderer = func(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (bool, error) {
// Remember: RenderPolicy works in the opossite order of declaration,
// the last registered is trying to be executed first,
// the first registered is executing last.
ok, err := nextRenderer(out, name, binding, options...)
if !ok {
prevOk, prevErr := prevRenderer(out, name, binding, options...)
if err != nil {
if prevErr != nil {
err = errors.New(prevErr.Error()).Append(err.Error())
}
}
if prevOk {
ok = true
}
}
// this renderer is responsible for this name
// but it has an error, so don't continue to the next
return ok, err
}
}
frame.RenderPolicy = renderer
}
}
// TemplateFuncsPolicy sets or overrides template func map.
// Defaults are the iris.URL and iris.Path, all the template engines supports the following:
// {{ url "mynamedroute" "pathParameter_ifneeded"} }
// {{ urlpath "mynamedroute" "pathParameter_ifneeded" }}
// {{ render "header.html" }}
// {{ render_r "header.html" }} // partial relative path to current page
// {{ yield }}
// {{ current }}
//
// Developers can already set the template's func map from the view adaptors, example: view.HTML(...).Funcs(...)),
// this type exists in order to be possible from third-party developers to create packages that bind template functions
// to the Iris without the need of knowing what template engine is used by the user or
// what order of declaration the user should follow.
type TemplateFuncsPolicy map[string]interface{} // interface can be: func(arguments ...string) string {}
// Adapt adaps a TemplateFuncsPolicy object to the main *Policies.
func (t TemplateFuncsPolicy) Adapt(frame *Policies) {
if len(t) > 0 {
if frame.TemplateFuncsPolicy == nil {
frame.TemplateFuncsPolicy = t
return
}
if frame.TemplateFuncsPolicy != nil {
for k, v := range t {
// set or replace the existing
frame.TemplateFuncsPolicy[k] = v
}
}
}
}
type (
// Author's notes:
// session manager can work as a middleware too
// but we want an easy-api for the user
// as we did before with: context.Session().Set/Get...
// these things cannot be done with middleware and sessions is a critical part of an application
// which needs attention, so far we used the kataras/go-sessions which I spent many weeks to create
// and that time has not any known bugs or any other issues, it's fully featured.
// BUT user may want to use other session library and in the same time users should be able to use
// iris' api for sessions from context, so a policy is that we need, the policy will contains
// the Start(responsewriter, request) and the Destroy(responsewriter, request)
// (keep note that this Destroy is not called at the end of a handler, Start does its job without need to end something
// sessions are setting in real time, when the user calls .Set ),
// the Start(responsewriter, request) will return a 'Session' which will contain the API for context.Session() , it should be
// rich, as before, so the interface will be a clone of the kataras/go-sessions/Session.
// If the user wants to use other library and that library missing features that kataras/go-sesisons has
// then the user should make an empty implementation of these calls in order to work.
// That's no problem, before they couldn't adapt any session manager, now they will can.
//
// The databases or stores registration will be in the session manager's responsibility,
// as well the DestroyByID and DestroyAll (I'm calling these with these names because
// I take as base the kataras/go-sessions,
// I have no idea if other session managers
// supports these things, if not then no problem,
// these funcs will be not required by the sessions policy)
//
// ok let's begin.
// Session should expose the SessionsPolicy's end-user API.
// This will be returned at the sess := context.Session().
Session interface {
ID() string
Get(string) interface{}
HasFlash() bool
GetFlash(string) interface{}
GetString(key string) string
GetFlashString(string) string
GetInt(key string) (int, error)
GetInt64(key string) (int64, error)
GetFloat32(key string) (float32, error)
GetFloat64(key string) (float64, error)
GetBoolean(key string) (bool, error)
GetAll() map[string]interface{}
GetFlashes() map[string]interface{}
VisitAll(cb func(k string, v interface{}))
Set(string, interface{})
SetFlash(string, interface{})
Delete(string)
DeleteFlash(string)
Clear()
ClearFlashes()
}
// SessionsPolicy is the policy for a session manager.
//
// A SessionsPolicy should be responsible to Start a sesion based
// on raw http.ResponseWriter and http.Request, which should return
// a compatible iris.Session interface, type. If the external session manager
// doesn't qualifies, then the user should code the rest of the functions with empty implementation.
//
// A SessionsPolicy should be responsible to Destroy a session based
// on the http.ResponseWriter and http.Request, this function should works individually.
//
// No iris.Context required from users. In order to be able to adapt any external session manager.
//
// The SessionsPolicy should be adapted once.
SessionsPolicy struct {
// Start should starts the session for the particular net/http request
Start func(http.ResponseWriter, *http.Request) Session
// Destroy should kills the net/http session and remove the associated cookie
// Keep note that: Destroy should not called at the end of any handler, it's an independent func.
// Start should set
// the values at realtime and if manager doesn't supports these
// then the user manually have to call its 'done' func inside the handler.
Destroy func(http.ResponseWriter, *http.Request)
}
)
// Adapt adaps a SessionsPolicy object to the main *Policies.
//
// Remember: Each policy is an adaptor.
// An adaptor should contains one or more policies too.
func (s SessionsPolicy) Adapt(frame *Policies) {
if s.Start != nil {
frame.SessionsPolicy.Start = s.Start
}
if s.Destroy != nil {
frame.SessionsPolicy.Destroy = s.Destroy
}
}