-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathmiddleware.go
279 lines (253 loc) · 7.13 KB
/
middleware.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
package kocha
import (
"bytes"
"fmt"
"net/http"
"strconv"
"time"
"github.com/naoina/kocha/log"
"github.com/naoina/kocha/util"
"github.com/ugorji/go/codec"
)
// Middleware is the interface that middleware.
type Middleware interface {
Process(app *Application, c *Context, next func() error) error
}
// Validator is the interface to validate the middleware.
type Validator interface {
// Validate validates the middleware.
// Validate will be called in boot-time of the application.
Validate() error
}
// PanicRecoverMiddleware is a middleware to recover a panic where occurred in request sequence.
type PanicRecoverMiddleware struct{}
func (m *PanicRecoverMiddleware) Process(app *Application, c *Context, next func() error) (err error) {
defer func() {
defer func() {
if perr := recover(); perr != nil {
app.logStackAndError(perr)
err = fmt.Errorf("%v", perr)
}
}()
if err != nil {
app.Logger.Errorf("%+v", err)
goto ERROR
} else if perr := recover(); perr != nil {
app.logStackAndError(perr)
goto ERROR
}
return
ERROR:
c.Response.reset()
if err = internalServerErrorController.GET(c); err != nil {
app.logStackAndError(err)
}
}()
return next()
}
// FormMiddleware is a middleware to parse a form data from query string and/or request body.
type FormMiddleware struct{}
// Process implements the Middleware interface.
func (m *FormMiddleware) Process(app *Application, c *Context, next func() error) error {
c.Request.Body = http.MaxBytesReader(c.Response, c.Request.Body, app.Config.MaxClientBodySize)
if err := c.Request.ParseMultipartForm(app.Config.MaxClientBodySize); err != nil && err != http.ErrNotMultipart {
return err
}
c.Params = c.newParams()
return next()
}
// SessionMiddleware is a middleware to process a session.
type SessionMiddleware struct {
// Name of cookie (key)
Name string
// Implementation of session store
Store SessionStore
// Expiration of session cookie, in seconds, from now. (not session expiration)
// 0 is for persistent.
CookieExpires time.Duration
// Expiration of session data, in seconds, from now. (not cookie expiration)
// 0 is for persistent.
SessionExpires time.Duration
HttpOnly bool
ExpiresKey string
}
func (m *SessionMiddleware) Process(app *Application, c *Context, next func() error) error {
if err := m.before(app, c); err != nil {
return err
}
if err := next(); err != nil {
return err
}
return m.after(app, c)
}
// Validate validates configuration of the session.
func (m *SessionMiddleware) Validate() error {
if m == nil {
return fmt.Errorf("kocha: session: middleware is nil")
}
if m.Store == nil {
return fmt.Errorf("kocha: session: because Store is nil, session cannot be used")
}
if m.Name == "" {
return fmt.Errorf("kocha: session: Name must be specified")
}
if m.ExpiresKey == "" {
m.ExpiresKey = "_kocha._sess._expires"
}
if v, ok := m.Store.(Validator); ok {
return v.Validate()
}
return nil
}
func (m *SessionMiddleware) before(app *Application, c *Context) (err error) {
defer func() {
switch err.(type) {
case nil:
// do nothing.
case ErrSession:
app.Logger.Info(err)
default:
app.Logger.Errorf("%+v", err)
}
if c.Session == nil {
c.Session = make(Session)
}
err = nil
}()
cookie, err := c.Request.Cookie(m.Name)
if err != nil {
return NewErrSession("new session")
}
sess, err := m.Store.Load(cookie.Value)
if err != nil {
return err
}
expiresStr, ok := sess[m.ExpiresKey]
if !ok {
return fmt.Errorf("expires value not found")
}
expires, err := strconv.ParseInt(expiresStr, 10, 64)
if err != nil {
return err
}
if expires < util.Now().Unix() {
return NewErrSession("session has been expired")
}
c.Session = sess
return nil
}
func (m *SessionMiddleware) after(app *Application, c *Context) (err error) {
expires, _ := m.expiresFromDuration(m.SessionExpires)
c.Session[m.ExpiresKey] = strconv.FormatInt(expires.Unix(), 10)
cookie := m.newSessionCookie(app, c)
cookie.Value, err = m.Store.Save(c.Session)
if err != nil {
return err
}
c.Response.SetCookie(cookie)
return nil
}
func (m *SessionMiddleware) newSessionCookie(app *Application, c *Context) *http.Cookie {
expires, maxAge := m.expiresFromDuration(m.CookieExpires)
return &http.Cookie{
Name: m.Name,
Value: "",
Path: "/",
Expires: expires,
MaxAge: maxAge,
Secure: c.Request.IsSSL(),
HttpOnly: m.HttpOnly,
}
}
func (m *SessionMiddleware) expiresFromDuration(d time.Duration) (expires time.Time, maxAge int) {
switch d {
case -1:
// persistent
expires = util.Now().UTC().AddDate(20, 0, 0)
case 0:
expires = time.Time{}
default:
expires = util.Now().UTC().Add(d)
maxAge = int(d.Seconds())
}
return expires, maxAge
}
// Flash messages processing middleware.
type FlashMiddleware struct{}
func (m *FlashMiddleware) Process(app *Application, c *Context, next func() error) error {
if err := m.before(app, c); err != nil {
return err
}
if err := next(); err != nil {
return err
}
return m.after(app, c)
}
func (m *FlashMiddleware) before(app *Application, c *Context) error {
if c.Session == nil {
app.Logger.Error("kocha: FlashMiddleware hasn't been added after SessionMiddleware; it cannot be used")
return nil
}
c.Flash = Flash{}
if flash := c.Session["_flash"]; flash != "" {
if err := codec.NewDecoderBytes([]byte(flash), codecHandler).Decode(&c.Flash); err != nil {
// make a new Flash instance because there is a possibility that
// garbage data is set to c.Flash by in-place decoding of Decode().
c.Flash = Flash{}
return fmt.Errorf("kocha: flash: unexpected error in decode process: %v", err)
}
}
return nil
}
func (m *FlashMiddleware) after(app *Application, c *Context) error {
if c.Session == nil {
return nil
}
if c.Flash.deleteLoaded(); c.Flash.Len() == 0 {
delete(c.Session, "_flash")
return nil
}
buf := bufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufPool.Put(buf)
}()
if err := codec.NewEncoder(buf, codecHandler).Encode(c.Flash); err != nil {
return fmt.Errorf("kocha: flash: unexpected error in encode process: %v", err)
}
c.Session["_flash"] = buf.String()
return nil
}
// Request logging middleware.
type RequestLoggingMiddleware struct{}
func (m *RequestLoggingMiddleware) Process(app *Application, c *Context, next func() error) error {
defer func() {
app.Logger.With(log.Fields{
"method": c.Request.Method,
"uri": c.Request.RequestURI,
"protocol": c.Request.Proto,
"status": c.Response.StatusCode,
}).Info()
}()
return next()
}
// DispatchMiddleware is a middleware to dispatch handler.
// DispatchMiddleware should be set to last of middlewares because doesn't call other middlewares after DispatchMiddleware.
type DispatchMiddleware struct{}
// Process implements the Middleware interface.
func (m *DispatchMiddleware) Process(app *Application, c *Context, next func() error) error {
name, handler, params, found := app.Router.dispatch(c.Request)
if !found {
handler = (&ErrorController{
StatusCode: http.StatusNotFound,
}).GET
}
c.Name = name
if c.Params == nil {
c.Params = c.newParams()
}
for _, param := range params {
c.Params.Add(param.Name, param.Value)
}
return handler(c)
}