-
Notifications
You must be signed in to change notification settings - Fork 2
/
route.go
300 lines (251 loc) · 7.26 KB
/
route.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
package webfmwk
import (
"context"
"log/slog"
"github.com/fasthttp/router"
"github.com/segmentio/encoding/json"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpadaptor"
"github.com/valyala/fasthttp/pprofhandler"
)
const (
// GET http verbe
GET = "GET"
// POST http verbe
POST = "POST"
// PATCH http verbe
PATCH = "PATCH"
// PUT http verbe
PUT = "PUT"
// DELETE http verbe
DELETE = "DELETE"
ANY = "ANY"
_pingEndpoint = "/ping"
)
type (
// HandlerFunc hold the signature of a Handler.
// You may return an error implementing the ErrorHandled interface
// to reduce the boilerplate. If the returned error doesn't implement
// the interface, a error 500 is by default returned.
//
// HandlerError(c webfmwk.Context) error {
// return webfmwk.NewUnauthorizedError("get me some credential !")
// }
//
// Will produce a http 500 json response.
HandlerFunc func(c Context) error
// Handler hold the function signature for webfmwk Handler chaning (middlware).
//
// import (
// github.com/burgesQ/webfmwk/v6
// github.com/burgesQ/webfmwk/handler/logging
// github.com/burgesQ/webfmwk/handler/security
// )
//
// s := webfmwk.InitServer(
// webfmwk.WithHandler(
// logging.Handler,
// security.Handler,
// ))
Handler func(HandlerFunc) HandlerFunc
// DocHandler hold the required data to expose a swagger documentation handlers.
//
// Example serving a redoc one:
// import (
// github.com/burgesQ/webfmwk/v6
// github.com/burgesQ/webfmwk/handler/redoc
// )
//
// s := webfmwk.InitServer(
// webfmwk.WithDocHandler(redoc.GetHandler(
// redoc.DocURI("/swagger.json")
// ))
//
// s.Get("/swagger.json", func(c webfmwk.Context) error{
// return c.JSONBlob(200, `{"title": "some swagger"`)
// })
DocHandler struct {
// H hold the doc Handler to expose.
H HandlerFunc
// Name is used in debug message.
Name string
// Path hold the URI one which the handler is reachable.
// If a prefix is setup, the path will prefixed.
Path string
}
// Route hold the data for one route.
Route struct {
// Handler hold the exposed Handler method.
Handler HandlerFunc `json:"-"`
// Verbe hold the verbe at which the handler is reachable.
Verbe string `json:"verbe"`
// Path hold the uri at which the handler is reachable.
// If a prefix is setup, the path will be prefixed.
Path string `json:"path"`
// Name is used in message.
Name string `json:"name"`
Middlewares *[]Handler
}
// Routes hold an array of route.
Routes []Route
// RoutesPerPrefix hold the routes and there respectiv prefix.
RoutesPerPrefix map[string]Routes
)
var _pong = json.RawMessage(`{"ping": "pong"}`)
//
// Routes method
//
// AddRoutes add the endpoint to the server.
func (s *Server) AddRoutes(r ...Route) {
s.meta.routes[s.meta.prefix] = append(s.meta.routes[s.meta.prefix], r...)
}
// GET expose a handler to the http verb GET.
func (s *Server) GET(path string, handler HandlerFunc) {
s.AddRoutes(Route{
Path: path,
Verbe: GET,
Handler: handler,
})
}
// DELETE expose a handler to the http verb DELETE.
func (s *Server) DELETE(path string, handler HandlerFunc) {
s.AddRoutes(Route{
Path: path,
Verbe: DELETE,
Handler: handler,
})
}
// POST expose a handler to the http verb POST.
func (s *Server) POST(path string, handler HandlerFunc) {
s.AddRoutes(Route{
Path: path,
Verbe: POST,
Handler: handler,
})
}
// PUT expose a handler to the http verb PUT.
func (s *Server) PUT(path string, handler HandlerFunc) {
s.AddRoutes(Route{
Path: path,
Verbe: PUT,
Handler: handler,
})
}
// PATCH expose a handler to the http verb PATCH.
func (s *Server) PATCH(path string, handler HandlerFunc) {
s.AddRoutes(Route{
Path: path,
Verbe: PATCH,
Handler: handler,
})
}
// PATCH expose a handler to the http verb PATCH.
func (s *Server) ANY(path string, handler HandlerFunc) {
s.AddRoutes(Route{
Path: path,
Verbe: ANY,
Handler: handler,
})
}
// RouteApplier apply the array of RoutePerPrefix.
func (s *Server) RouteApplier(rpps ...RoutesPerPrefix) {
for i := range rpps {
rpp := rpps[i]
for prefix := range rpp {
routes := rpp[prefix]
for i := range routes {
route := routes[i]
route.Path = prefix + route.Path
s.AddRoutes(route)
}
}
}
}
// GetRouter create a fasthttp/router.Router whit:
// - registered handlers (webfmwk/v6/handler)
// - doc handler is registered
// - test handler (/ping) is registered
// - registered fmwk routes
func (s *Server) GetRouter() *router.Router {
r := router.New()
r.HandleMethodNotAllowed, r.HandleOPTIONS = true, true
r.RedirectTrailingSlash, r.RedirectFixedPath = false, false
// IDEA: router.PanicHandler
r.NotFound, r.MethodNotAllowed = s.CustomHandler(handleNotFound), s.CustomHandler(handleNotAllowed)
// register doc handler
if len(s.meta.docHandlers) > 0 {
for i := range s.meta.docHandlers {
h := s.meta.docHandlers[i]
s.slog.Info("load doc handler", slog.String("name", h.Name))
r.ANY(s.meta.prefix+h.Path, s.CustomHandler(h.H))
}
}
// register test handler
if s.meta.checkIsUp {
r.GET(s.meta.prefix+_pingEndpoint, s.CustomHandler(func(c Context) error {
return c.JSONOk(_pong)
}))
}
// register socket.io (goplog) handlers
switch {
case s.meta.socketIOHF:
s.slog.Info("loading socket io handler func", slog.String("path", s.meta.socketIOPath))
r.ANY(s.meta.socketIOPath,
fasthttpadaptor.NewFastHTTPHandlerFunc(s.meta.socketIOHandlerFunc))
case s.meta.socketIOH:
s.slog.Info("loading socket io handler", slog.String("path", s.meta.socketIOPath))
r.ANY(s.meta.socketIOPath,
fasthttpadaptor.NewFastHTTPHandler(s.meta.socketIOHandler))
}
if s.meta.pprof {
s.slog.Info("loading pprof handler", "path", "/debug/pprof/{profile:*}'")
r.GET(s.meta.prefix+s.meta.pprofPath, pprofhandler.PprofHandler)
}
// register routes
for prefix := range s.meta.routes {
routes := s.meta.routes[prefix] // never sure if I should copy
var group *router.Group
if len(prefix) != 0 {
group = r.Group(prefix)
}
for i := range routes {
route := routes[i]
handler := route.Handler
// register internal Handlers
handler = contentIsJSON(handleHandlerError(handler))
// TODO: register group wise / route wise custom Handlers
// if route. != nil {
if route.Middlewares != nil {
for _, mdlw := range *route.Middlewares {
handler = mdlw(handler)
}
}
// register user server wise custom Handlers
if s.meta.handlers != nil {
for _, h := range s.meta.handlers {
handler = h(handleHandlerError(handler))
}
}
if len(prefix) == 0 {
r.Handle(route.Verbe, route.Path, s.CustomHandler(handler))
} else {
group.Handle(route.Verbe, route.Path, s.CustomHandler(handler))
}
}
}
return r
}
// CustomHandler return the webfmwk Handler main logic,
// which return a HandlerFunc wrapper in an fasthttp.Handler.
func (s *Server) CustomHandler(handler HandlerFunc) fasthttp.RequestHandler {
return func(c *fasthttp.RequestCtx) {
ctx, cancel := s.genContext(c)
defer cancel()
// we skip verification as it's done in the useHandler
_ = handler(ctx)
}
}
func (s *Server) genContext(c *fasthttp.RequestCtx) (Context, context.CancelFunc) {
ctx, fn := context.WithCancel(s.ctx)
return &icontext{c, s.slog, ctx}, fn
}