1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-22 22:43:54 +00:00
fiber/router.go
2020-02-16 18:00:14 +01:00

262 lines
6.2 KiB
Go

// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖
// 📌 Please open an issue if you got suggestions or found a bug!
// 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki
// 🦸 Not all heroes wear capes, thank you to some amazing people
// 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr
package fiber
import (
"fmt"
"log"
"path/filepath"
"regexp"
"strings"
"sync"
fasthttp "github.com/valyala/fasthttp"
)
// Ctx is the context that contains everything
type Ctx struct {
route *Route
next bool
error error
params *[]string
values []string
Fasthttp *fasthttp.RequestCtx
}
// Route struct
type Route struct {
// HTTP method in uppercase, can be a * for Use() & All()
Method string
// Stores the original path
Path string
// Bool that defines if the route is a Use() middleware
Midware bool
// wildcard bool is for routes without a path, * and /*
Wildcard bool
// Stores compiled regex special routes :params, *wildcards, optionals?
Regex *regexp.Regexp
// Store params if special routes :params, *wildcards, optionals?
Params []string
// Callback function for specific route
Handler func(*Ctx)
}
// 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.error = nil
ctx.params = nil
ctx.values = nil
ctx.Fasthttp = nil
poolCtx.Put(ctx)
}
func (grp *Group) register(method string, args ...interface{}) {
path := grp.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)
path = filepath.ToSlash(path)
}
grp.app.register(method, path, handler)
}
// Function to add a route correctly
func (app *Application) register(method string, args ...interface{}) {
// Set if method is Use() midware
var midware = method == "USE"
// Match any method
if method == "ALL" || midware {
method = "*"
}
// Prepare possible variables
var path string // We could have a path/prefix
var handler func(*Ctx) // We could have a ctx handler
// 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 == "" || path[0] != '/' && path[0] != '*' {
path = "/" + path
}
}
if midware && strings.Contains(path, "/:") {
log.Fatal("Router: You cannot use :params in Use()")
}
// If Use() path == "/", match anything aka *
if midware && path == "/" {
path = "*"
}
// If the route needs to match any path
if path == "" || path == "*" || path == "/*" {
app.routes = append(app.routes, &Route{method, path, midware, true, nil, nil, handler})
return
}
// Get params from path
params := getParams(path)
// If path has no params (simple path), we don't need regex (also for use())
if midware || len(params) == 0 {
app.routes = append(app.routes, &Route{method, path, midware, false, nil, nil, handler})
return
}
// We have parametes, so we need to compile regex from the path
regex, err := getRegex(path)
if err != nil {
log.Fatal("Router: Invalid url pattern: " + path)
}
// Add regex + params to route
app.routes = append(app.routes, &Route{method, path, midware, false, regex, params, handler})
}
// then try to match a route as efficient as possible.
func (app *Application) handler(fctx *fasthttp.RequestCtx) {
found := false
// get custom context from sync pool
ctx := acquireCtx(fctx)
// get path and method from main context
path := ctx.Path()
method := ctx.Method()
if app.recover != nil {
defer func() {
if r := recover(); r != nil {
ctx.error = fmt.Errorf("panic: %v", r)
app.recover(ctx)
}
}()
}
// loop trough routes
for _, route := range app.routes {
// Skip route if method is not allowed
if route.Method != "*" && route.Method != method {
continue
}
// First check if we match a wildcard or static path
if route.Wildcard || route.Path == path {
// if route.wildcard || (route.path == path && route.params == nil) {
// If * always set the path to the wildcard parameter
if route.Wildcard {
ctx.params = &[]string{"*"}
ctx.values = make([]string, 1)
ctx.values[0] = path
}
found = true
// Set route pointer if user wants to call .Route()
ctx.route = route
// Execute handler with context
route.Handler(ctx)
// 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
}
// If route is Use() and path starts with route.path
// aka strings.HasPrefix(route.path, path)
if route.Midware && strings.HasPrefix(path, route.Path) {
found = true
ctx.route = route
route.Handler(ctx)
if !ctx.next {
break
}
ctx.next = false
continue
}
// Skip route if regex does not exist
if route.Regex == nil {
continue
}
// Skip route if regex does not match
if !route.Regex.MatchString(path) {
continue
}
// If we have parameters, lets find the matches
if len(route.Params) > 0 {
matches := route.Regex.FindAllStringSubmatch(path, -1)
// If we have matches, add params and values to context
if len(matches) > 0 && len(matches[0]) > 1 {
ctx.params = &route.Params
// ctx.values = make([]string, len(*ctx.params))
ctx.values = matches[0][1:len(matches[0])]
}
}
found = true
// Set route pointer if user wants to call .Route()
ctx.route = route
// Execute handler with context
route.Handler(ctx)
// if next is not set, leave loop and release ctx
if !ctx.next {
break
}
// set next to false for next iteration
ctx.next = false
}
// No routes found
if !found {
// Custom 404 handler?
ctx.SendStatus(404)
}
// release context back into sync pool
releaseCtx(ctx)
}