2020-02-08 01:05:13 +01:00
|
|
|
// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖
|
2020-01-22 05:42:37 +01:00
|
|
|
// 📌 Please open an issue if you got suggestions or found a bug!
|
2020-02-08 01:05:13 +01:00
|
|
|
// 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki
|
2020-01-15 03:07:49 +01:00
|
|
|
|
2020-01-16 00:24:58 +01:00
|
|
|
// 🦸 Not all heroes wear capes, thank you to some amazing people
|
2020-02-08 01:05:13 +01:00
|
|
|
// 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr
|
2020-01-15 03:07:49 +01:00
|
|
|
|
2019-12-30 07:29:42 -05:00
|
|
|
package fiber
|
|
|
|
|
|
|
|
import (
|
2020-02-05 02:53:07 +01:00
|
|
|
"log"
|
2020-02-10 05:48:52 +01:00
|
|
|
"path/filepath"
|
2019-12-30 07:29:42 -05:00
|
|
|
"regexp"
|
2020-01-21 00:57:10 +01:00
|
|
|
"strings"
|
2020-02-10 01:24:59 +01:00
|
|
|
"sync"
|
2019-12-30 07:29:42 -05:00
|
|
|
|
|
|
|
"github.com/valyala/fasthttp"
|
|
|
|
)
|
|
|
|
|
2020-02-10 01:24:59 +01:00
|
|
|
// Ctx : struct
|
|
|
|
type Ctx struct {
|
|
|
|
route *Route
|
|
|
|
next bool
|
|
|
|
params *[]string
|
|
|
|
values []string
|
|
|
|
Fasthttp *fasthttp.RequestCtx
|
|
|
|
}
|
|
|
|
|
2020-02-01 19:42:40 +03:00
|
|
|
// Route : struct
|
|
|
|
type Route struct {
|
2020-01-11 04:59:51 +01:00
|
|
|
// HTTP method in uppercase, can be a * for Use() & All()
|
2020-01-28 14:28:09 -05:00
|
|
|
Method string
|
2020-02-01 19:42:40 +03:00
|
|
|
// Stores the original path
|
2020-01-28 14:28:09 -05:00
|
|
|
Path string
|
|
|
|
// Bool that defines if the route is a Use() middleware
|
|
|
|
Midware bool
|
2020-01-15 03:07:49 +01:00
|
|
|
// wildcard bool is for routes without a path, * and /*
|
2020-01-28 14:28:09 -05:00
|
|
|
Wildcard bool
|
2020-01-11 04:59:51 +01:00
|
|
|
// Stores compiled regex special routes :params, *wildcards, optionals?
|
2020-01-28 14:28:09 -05:00
|
|
|
Regex *regexp.Regexp
|
2020-01-11 04:59:51 +01:00
|
|
|
// Store params if special routes :params, *wildcards, optionals?
|
2020-01-28 14:28:09 -05:00
|
|
|
Params []string
|
2020-01-11 04:59:51 +01:00
|
|
|
// Callback function for specific route
|
2020-01-28 14:28:09 -05:00
|
|
|
Handler func(*Ctx)
|
2019-12-30 07:29:42 -05:00
|
|
|
}
|
|
|
|
|
2020-02-10 01:24:59 +01:00
|
|
|
// Ctx pool
|
|
|
|
var poolCtx = sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
return new(Ctx)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get new Ctx from pool
|
|
|
|
func acquireCtx(fctx *fasthttp.RequestCtx) *Ctx {
|
|
|
|
ctx := poolCtx.Get().(*Ctx)
|
|
|
|
ctx.Fasthttp = fctx
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return Context to pool
|
|
|
|
func releaseCtx(ctx *Ctx) {
|
|
|
|
ctx.route = nil
|
|
|
|
ctx.next = false
|
|
|
|
ctx.params = nil
|
|
|
|
ctx.values = nil
|
|
|
|
ctx.Fasthttp = nil
|
|
|
|
poolCtx.Put(ctx)
|
|
|
|
}
|
|
|
|
|
2020-02-10 05:48:52 +01:00
|
|
|
func (g *Group) register(method string, args ...interface{}) {
|
|
|
|
path := g.path
|
|
|
|
var handler func(*Ctx)
|
|
|
|
if len(args) == 1 {
|
|
|
|
handler = args[0].(func(*Ctx))
|
|
|
|
} else if len(args) > 1 {
|
|
|
|
path = path + args[0].(string)
|
|
|
|
handler = args[1].(func(*Ctx))
|
|
|
|
if path[0] != '/' && path[0] != '*' {
|
|
|
|
path = "/" + path
|
|
|
|
}
|
|
|
|
path = strings.Replace(path, "//", "/", -1)
|
|
|
|
path = filepath.Clean(path)
|
|
|
|
}
|
|
|
|
g.fiber.register(method, path, handler)
|
|
|
|
}
|
|
|
|
|
2020-01-16 02:31:44 +01:00
|
|
|
// Function to add a route correctly
|
2020-02-10 05:48:52 +01:00
|
|
|
func (f *Fiber) register(method string, args ...interface{}) {
|
2020-01-28 14:28:09 -05:00
|
|
|
// Set if method is Use() midware
|
2020-02-10 05:48:52 +01:00
|
|
|
var midware = method == "USE"
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-21 00:57:10 +01:00
|
|
|
// Match any method
|
2020-01-28 14:28:09 -05:00
|
|
|
if method == "ALL" || midware {
|
2020-01-21 00:57:10 +01:00
|
|
|
method = "*"
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-16 02:31:44 +01:00
|
|
|
// Prepare possible variables
|
|
|
|
var path string // We could have a path/prefix
|
|
|
|
var handler func(*Ctx) // We could have a ctx handler
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-16 02:31:44 +01:00
|
|
|
// Only 1 argument, so no path/prefix
|
|
|
|
if len(args) == 1 {
|
|
|
|
handler = args[0].(func(*Ctx))
|
|
|
|
} else if len(args) > 1 {
|
|
|
|
path = args[0].(string)
|
|
|
|
handler = args[1].(func(*Ctx))
|
|
|
|
if path[0] != '/' && path[0] != '*' {
|
2020-02-10 05:48:52 +01:00
|
|
|
path = "/" + path
|
2020-01-16 02:31:44 +01:00
|
|
|
}
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-28 14:28:09 -05:00
|
|
|
if midware && strings.Contains(path, "/:") {
|
2020-02-05 02:53:07 +01:00
|
|
|
log.Fatal("Router: You cannot use :params in Use()")
|
2020-01-21 00:57:10 +01:00
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-21 00:57:10 +01:00
|
|
|
// If Use() path == "/", match anything aka *
|
2020-01-28 14:28:09 -05:00
|
|
|
if midware && path == "/" {
|
2020-01-21 00:57:10 +01:00
|
|
|
path = "*"
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-16 00:24:58 +01:00
|
|
|
// If the route needs to match any path
|
2019-12-30 13:33:50 +01:00
|
|
|
if path == "" || path == "*" || path == "/*" {
|
2020-02-10 05:48:52 +01:00
|
|
|
f.routes = append(f.routes, &Route{method, path, midware, true, nil, nil, handler})
|
2019-12-30 13:33:50 +01:00
|
|
|
return
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 13:33:50 +01:00
|
|
|
// Get params from path
|
|
|
|
params := getParams(path)
|
2020-02-01 19:42:40 +03:00
|
|
|
|
|
|
|
// If path has no params (simple path), we don't need regex (also for use())
|
2020-01-28 14:28:09 -05:00
|
|
|
if midware || len(params) == 0 {
|
2020-02-10 05:48:52 +01:00
|
|
|
f.routes = append(f.routes, &Route{method, path, midware, false, nil, nil, handler})
|
2019-12-30 13:33:50 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-02-01 19:42:40 +03:00
|
|
|
// We have parametes, so we need to compile regex from the path
|
2019-12-30 13:33:50 +01:00
|
|
|
regex, err := getRegex(path)
|
|
|
|
if err != nil {
|
2020-02-05 02:53:07 +01:00
|
|
|
log.Fatal("Router: Invalid url pattern: " + path)
|
2019-12-30 13:33:50 +01:00
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-16 00:24:58 +01:00
|
|
|
// Add regex + params to route
|
2020-02-10 05:48:52 +01:00
|
|
|
f.routes = append(f.routes, &Route{method, path, midware, false, regex, params, handler})
|
2019-12-30 13:33:50 +01:00
|
|
|
}
|
|
|
|
|
2020-01-11 04:59:51 +01:00
|
|
|
// then try to match a route as efficient as possible.
|
2020-02-10 05:48:52 +01:00
|
|
|
func (f *Fiber) handler(fctx *fasthttp.RequestCtx) {
|
2020-01-10 03:09:43 +01:00
|
|
|
found := false
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 13:33:50 +01:00
|
|
|
// get custom context from sync pool
|
|
|
|
ctx := acquireCtx(fctx)
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 13:33:50 +01:00
|
|
|
// get path and method from main context
|
|
|
|
path := ctx.Path()
|
|
|
|
method := ctx.Method()
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 13:33:50 +01:00
|
|
|
// loop trough routes
|
2020-02-10 05:48:52 +01:00
|
|
|
for _, route := range f.routes {
|
2019-12-30 13:33:50 +01:00
|
|
|
// Skip route if method is not allowed
|
2020-01-28 14:28:09 -05:00
|
|
|
if route.Method != "*" && route.Method != method {
|
2019-12-30 13:33:50 +01:00
|
|
|
continue
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-21 00:57:10 +01:00
|
|
|
// First check if we match a wildcard or static path
|
2020-01-28 14:28:09 -05:00
|
|
|
if route.Wildcard || route.Path == path {
|
2020-01-21 00:57:10 +01:00
|
|
|
// if route.wildcard || (route.path == path && route.params == nil) {
|
2019-12-30 13:33:50 +01:00
|
|
|
// If * always set the path to the wildcard parameter
|
2020-01-28 14:28:09 -05:00
|
|
|
if route.Wildcard {
|
2019-12-30 13:33:50 +01:00
|
|
|
ctx.params = &[]string{"*"}
|
2020-02-05 11:54:10 +01:00
|
|
|
ctx.values = make([]string, 1)
|
|
|
|
ctx.values[0] = path
|
2019-12-30 13:33:50 +01:00
|
|
|
}
|
2020-01-10 03:09:43 +01:00
|
|
|
found = true
|
2020-01-15 03:07:49 +01:00
|
|
|
// Set route pointer if user wants to call .Route()
|
|
|
|
ctx.route = route
|
2019-12-30 13:33:50 +01:00
|
|
|
// Execute handler with context
|
2020-01-28 14:28:09 -05:00
|
|
|
route.Handler(ctx)
|
2019-12-30 13:33:50 +01:00
|
|
|
// if next is not set, leave loop and release ctx
|
|
|
|
if !ctx.next {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
// set next to false for next iteration
|
|
|
|
ctx.next = false
|
|
|
|
// continue to go to the next route
|
|
|
|
continue
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-21 00:57:10 +01:00
|
|
|
// If route is Use() and path starts with route.path
|
|
|
|
// aka strings.HasPrefix(route.path, path)
|
2020-01-28 14:28:09 -05:00
|
|
|
if route.Midware && strings.HasPrefix(path, route.Path) {
|
2020-01-21 00:57:10 +01:00
|
|
|
found = true
|
|
|
|
ctx.route = route
|
2020-01-28 14:28:09 -05:00
|
|
|
route.Handler(ctx)
|
2020-01-21 00:57:10 +01:00
|
|
|
if !ctx.next {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
ctx.next = false
|
|
|
|
continue
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-11 04:59:51 +01:00
|
|
|
// Skip route if regex does not exist
|
2020-01-28 14:28:09 -05:00
|
|
|
if route.Regex == nil {
|
2020-01-11 04:59:51 +01:00
|
|
|
continue
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 13:33:50 +01:00
|
|
|
// Skip route if regex does not match
|
2020-01-28 14:28:09 -05:00
|
|
|
if !route.Regex.MatchString(path) {
|
2019-12-30 07:29:42 -05:00
|
|
|
continue
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 07:29:42 -05:00
|
|
|
// If we have parameters, lets find the matches
|
2020-01-28 14:28:09 -05:00
|
|
|
if len(route.Params) > 0 {
|
|
|
|
matches := route.Regex.FindAllStringSubmatch(path, -1)
|
2019-12-30 07:29:42 -05:00
|
|
|
// If we have matches, add params and values to context
|
|
|
|
if len(matches) > 0 && len(matches[0]) > 1 {
|
2020-01-28 14:28:09 -05:00
|
|
|
ctx.params = &route.Params
|
2020-02-05 11:54:10 +01:00
|
|
|
// ctx.values = make([]string, len(*ctx.params))
|
2019-12-30 07:29:42 -05:00
|
|
|
ctx.values = matches[0][1:len(matches[0])]
|
|
|
|
}
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-10 03:09:43 +01:00
|
|
|
found = true
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-15 03:07:49 +01:00
|
|
|
// Set route pointer if user wants to call .Route()
|
|
|
|
ctx.route = route
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 07:29:42 -05:00
|
|
|
// Execute handler with context
|
2020-01-28 14:28:09 -05:00
|
|
|
route.Handler(ctx)
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 07:29:42 -05:00
|
|
|
// if next is not set, leave loop and release ctx
|
|
|
|
if !ctx.next {
|
|
|
|
break
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 07:29:42 -05:00
|
|
|
// set next to false for next iteration
|
|
|
|
ctx.next = false
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2020-01-11 04:59:51 +01:00
|
|
|
// No routes found
|
2020-01-10 03:09:43 +01:00
|
|
|
if !found {
|
2020-01-11 04:59:51 +01:00
|
|
|
// Custom 404 handler?
|
2020-01-10 03:09:43 +01:00
|
|
|
ctx.Status(404).Send("Not Found")
|
|
|
|
}
|
2020-02-01 19:42:40 +03:00
|
|
|
|
2019-12-30 07:29:42 -05:00
|
|
|
// release context back into sync pool
|
|
|
|
releaseCtx(ctx)
|
|
|
|
}
|