1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-19 13:27:53 +00:00
fiber/router.go

439 lines
11 KiB
Go
Raw Normal View History

2019-12-30 07:29:42 -05:00
package fiber
import (
2020-02-12 13:23:47 -05:00
"fmt"
2020-02-05 02:53:07 +01:00
"log"
2020-02-21 03:54:36 +01:00
"path/filepath"
2020-02-21 16:56:32 +01:00
"reflect"
2019-12-30 07:29:42 -05:00
"regexp"
2020-01-21 00:57:10 +01:00
"strings"
2019-12-30 07:29:42 -05:00
2020-02-21 16:56:32 +01:00
websocket "github.com/fasthttp/websocket"
2020-02-16 18:00:14 +01:00
fasthttp "github.com/valyala/fasthttp"
2019-12-30 07:29:42 -05:00
)
2020-02-11 01:26:09 +01:00
// Route struct
type Route struct {
2020-02-21 16:56:32 +01:00
// HTTP method in uppercase, can be a * for "Use" & "All" routes
2020-01-28 14:28:09 -05:00
Method string
// Stores the original path
2020-01-28 14:28:09 -05:00
Path string
2020-02-21 16:56:32 +01:00
// Prefix is for ending wildcards or middlewares
Prefix string
// Stores regex for :params & :optionals?
2020-01-28 14:28:09 -05:00
Regex *regexp.Regexp
2020-02-21 16:56:32 +01:00
// Stores params keys for :params & :optionals?
2020-01-28 14:28:09 -05:00
Params []string
2020-02-21 16:56:32 +01:00
// Callback function for context
HandlerCtx func(*Ctx)
// Callback function for websockets
HandlerConn func(*Conn)
2020-02-21 16:54:14 +01:00
}
2020-02-21 03:54:36 +01:00
2020-02-21 16:56:32 +01:00
func (app *App) registerStatic(grpPrefix string, args ...string) {
var prefix = "/"
var root = "./"
// enable / disable gzipping somewhere?
// todo v2.0.0
gzip := true
2020-02-21 04:16:07 +01:00
2020-02-21 16:54:14 +01:00
if len(args) == 1 {
2020-02-21 16:56:32 +01:00
root = args[0]
2020-02-21 03:54:36 +01:00
}
2020-02-21 16:56:32 +01:00
if len(args) == 2 {
prefix = args[0]
root = args[1]
2020-01-21 00:57:10 +01:00
}
2020-02-21 16:54:14 +01:00
2020-02-21 16:56:32 +01:00
// A non wildcard path must start with a '/'
if prefix != "*" && len(prefix) > 0 && prefix[0] != '/' {
prefix = "/" + prefix
}
// Prepend group prefix
if len(grpPrefix) > 0 {
// `/v1`+`/` => `/v1`+``
if prefix == "/" {
prefix = grpPrefix
} else {
prefix = grpPrefix + prefix
2020-01-16 02:31:44 +01:00
}
2020-02-21 16:56:32 +01:00
// Remove duplicate slashes `//`
prefix = strings.Replace(prefix, "//", "/", -1)
}
// Empty or '/*' path equals "match anything"
// TODO fix * for paths with grpprefix
if prefix == "/*" {
prefix = "*"
2020-01-16 02:31:44 +01:00
}
2020-02-21 16:56:32 +01:00
// Lets get all files from root
files, _, err := getFiles(root)
if err != nil {
log.Fatal("Static: ", err)
}
// ./static/compiled => static/compiled
mount := filepath.Clean(root)
2020-02-21 16:54:14 +01:00
2020-02-21 16:56:32 +01:00
if !app.Settings.CaseSensitive {
prefix = strings.ToLower(prefix)
}
if !app.Settings.StrictRouting && len(prefix) > 1 {
prefix = strings.TrimRight(prefix, "/")
2020-02-21 01:54:50 +01:00
}
2020-02-21 16:54:14 +01:00
2020-02-21 16:56:32 +01:00
// Loop over all files
for _, file := range files {
// Ignore the .gzipped files by fasthttp
if strings.Contains(file, ".fasthttp.gz") {
continue
}
// Time to create a fake path for the route match
// static/index.html => /index.html
path := filepath.Join(prefix, strings.Replace(file, mount, "", 1))
// for windows: static\index.html => /index.html
path = filepath.ToSlash(path)
// Store file path to use in ctx handler
filePath := file
if len(prefix) > 1 && strings.Contains(prefix, "*") {
app.routes = append(app.routes, &Route{
Method: "GET",
Path: path,
Prefix: strings.Split(prefix, "*")[0],
HandlerCtx: func(c *Ctx) {
c.SendFile(filePath, gzip)
},
})
return
}
// If the file is an index.html, bind the prefix to index.html directly
if filepath.Base(filePath) == "index.html" || filepath.Base(filePath) == "index.htm" {
app.routes = append(app.routes, &Route{
Method: "GET",
Path: prefix,
HandlerCtx: func(c *Ctx) {
c.SendFile(filePath, gzip)
},
})
}
if !app.Settings.CaseSensitive {
path = strings.ToLower(path)
}
if !app.Settings.StrictRouting && len(prefix) > 1 {
path = strings.TrimRight(path, "/")
}
// Add the route + SendFile(filepath) to routes
app.routes = append(app.routes, &Route{
Method: "GET",
Path: path,
HandlerCtx: func(c *Ctx) {
c.SendFile(filePath, gzip)
},
})
}
}
func (app *App) register(method, grpPrefix string, args ...interface{}) {
// Set variables
var path = "*"
var prefix string
var middleware = method == "USE"
var handlersCtx []func(*Ctx)
var handlersConn []func(*Conn)
for i := 0; i < len(args); i++ {
switch arg := args[i].(type) {
case string:
path = arg
case func(*Ctx):
handlersCtx = append(handlersCtx, arg)
case func(*Conn):
handlersConn = append(handlersConn, arg)
default:
log.Fatalf("Invalid argument type: %v", reflect.TypeOf(arg))
}
}
// A non wildcard path must start with a '/'
if path != "*" && len(path) > 0 && path[0] != '/' {
path = "/" + path
}
// Prepend group prefix
if len(grpPrefix) > 0 {
// `/v1`+`/` => `/v1`+``
if path == "/" {
path = grpPrefix
} else {
path = grpPrefix + path
}
// Remove duplicate slashes `//`
path = strings.Replace(path, "//", "/", -1)
}
// Empty or '/*' path equals "match anything"
// TODO fix * for paths with grpprefix
if path == "" || path == "/*" {
2020-02-21 16:54:14 +01:00
path = "*"
2020-02-21 01:54:50 +01:00
}
2020-02-21 16:56:32 +01:00
if method == "ALL" || middleware {
method = "*"
}
// Routes are case insensitive by default
if !app.Settings.CaseSensitive {
path = strings.ToLower(path)
}
if !app.Settings.StrictRouting && len(path) > 1 {
path = strings.TrimRight(path, "/")
}
// If the route can match anything
if path == "*" {
for i := range handlersCtx {
app.routes = append(app.routes, &Route{
Method: method, Path: path, HandlerCtx: handlersCtx[i],
})
}
for i := range handlersConn {
app.routes = append(app.routes, &Route{
Method: method, Path: path, HandlerConn: handlersConn[i],
})
}
2019-12-30 13:33:50 +01:00
return
}
2020-02-21 16:56:32 +01:00
// Get ':param' & ':optional?' & '*' from path
2019-12-30 13:33:50 +01:00
params := getParams(path)
2020-02-21 16:56:32 +01:00
// Enable prefix for midware
if len(params) == 0 && middleware {
prefix = path
}
2020-02-21 16:56:32 +01:00
// If path has no params (simple path)
if len(params) == 0 {
for i := range handlersCtx {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Prefix: prefix, HandlerCtx: handlersCtx[i],
})
}
for i := range handlersConn {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Prefix: prefix, HandlerConn: handlersConn[i],
})
}
2019-12-30 13:33:50 +01:00
return
}
2020-02-21 16:56:32 +01:00
// If path only contains 1 wildcard, we can create a prefix
// If its a middleware, we also create a prefix
if len(params) == 1 && params[0] == "*" {
prefix = strings.Split(path, "*")[0]
for i := range handlersCtx {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Prefix: prefix,
Params: params, HandlerCtx: handlersCtx[i],
})
}
for i := range handlersConn {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Prefix: prefix,
Params: params, HandlerConn: handlersConn[i],
})
}
return
}
// We have an :param or :optional? and need to compile a regex struct
2019-12-30 13:33:50 +01:00
regex, err := getRegex(path)
if err != nil {
2020-02-21 16:56:32 +01:00
log.Fatal("Router: Invalid path pattern: " + path)
}
// Add route with regex
for i := range handlersCtx {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Regex: regex,
Params: params, HandlerCtx: handlersCtx[i],
})
}
for i := range handlersConn {
app.routes = append(app.routes, &Route{
Method: method, Path: path, Regex: regex,
Params: params, HandlerConn: handlersConn[i],
})
2019-12-30 13:33:50 +01:00
}
}
2020-02-21 16:56:32 +01:00
func (app *App) handler(fctx *fasthttp.RequestCtx) {
// Use this boolean to perform 404 not found at the end
var match = false
2019-12-30 13:33:50 +01:00
// get custom context from sync pool
ctx := acquireCtx(fctx)
2020-02-21 16:56:32 +01:00
if ctx.app == nil {
ctx.app = app
}
// get path and method
2019-12-30 13:33:50 +01:00
path := ctx.Path()
2020-02-21 16:56:32 +01:00
if !app.Settings.CaseSensitive {
path = strings.ToLower(path)
}
if !app.Settings.StrictRouting && len(path) > 1 {
path = strings.TrimRight(path, "/")
}
2019-12-30 13:33:50 +01:00
method := ctx.Method()
2020-02-21 16:56:32 +01:00
// enable recovery
2020-02-12 13:23:47 -05:00
if app.recover != nil {
defer func() {
if r := recover(); r != nil {
ctx.error = fmt.Errorf("panic: %v", r)
app.recover(ctx)
}
}()
}
2019-12-30 13:33:50 +01:00
// loop trough routes
2020-02-11 01:26:09 +01:00
for _, route := range app.routes {
2020-02-21 16:56:32 +01:00
// Skip route if method does not match
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-21 16:56:32 +01:00
// Set route pointer if user wants to call .Route()
ctx.route = route
// wilcard or exact same path
// TODO v2: enable or disable case insensitive match
if route.Path == "*" || route.Path == path {
// if * always set the path to the wildcard parameter
if route.Path == "*" {
2019-12-30 13:33:50 +01:00
ctx.params = &[]string{"*"}
2020-02-21 16:56:32 +01:00
ctx.values = []string{path}
2020-02-21 01:54:50 +01:00
}
2020-02-21 16:56:32 +01:00
// ctx.Fasthttp.Request.Header.ConnectionUpgrade()
// Websocket request
if route.HandlerConn != nil && websocket.FastHTTPIsWebSocketUpgrade(fctx) {
// Try to upgrade
err := socketUpgrade.Upgrade(ctx.Fasthttp, func(fconn *websocket.Conn) {
conn := acquireConn(fconn)
defer releaseConn(conn)
conn.params = ctx.params
conn.values = ctx.values
releaseCtx(ctx)
route.HandlerConn(conn)
})
// Upgrading failed
if err != nil {
panic(err)
}
return
}
// No handler for HTTP nor websocket
if route.HandlerCtx == nil {
continue
}
// Match found, 404 not needed
match = true
route.HandlerCtx(ctx)
2019-12-30 13:33:50 +01:00
// if next is not set, leave loop and release ctx
if !ctx.next {
break
2020-02-21 16:56:32 +01:00
} else {
// reset match to false
match = false
2019-12-30 13:33:50 +01:00
}
// set next to false for next iteration
ctx.next = false
// continue to go to the next route
continue
}
2020-02-21 16:56:32 +01:00
if route.Prefix != "" && strings.HasPrefix(path, route.Prefix) {
2020-01-21 00:57:10 +01:00
ctx.route = route
2020-02-21 16:56:32 +01:00
if strings.Contains(route.Path, "*") {
ctx.params = &[]string{"*"}
// ctx.values = matches[0][1:len(matches[0])]
// parse query source
ctx.values = []string{strings.Replace(path, route.Prefix, "", 1)}
}
// Websocket request
if route.HandlerConn != nil {
// Try to upgrade
err := socketUpgrade.Upgrade(ctx.Fasthttp, func(fconn *websocket.Conn) {
conn := acquireConn(fconn)
defer releaseConn(conn)
conn.params = ctx.params
conn.values = ctx.values
releaseCtx(ctx)
route.HandlerConn(conn)
})
// Upgrading failed
if err != nil {
panic(err)
}
return
}
// No handler for HTTP nor websocket
if route.HandlerCtx == nil {
continue
}
// Match found, 404 not needed
match = true
route.HandlerCtx(ctx)
// if next is not set, leave loop and release ctx
2020-01-21 00:57:10 +01:00
if !ctx.next {
break
2020-02-21 16:56:32 +01:00
} else {
// reset match to false
match = false
2020-01-21 00:57:10 +01:00
}
2020-02-21 16:56:32 +01:00
// set next to false for next iteration
2020-01-21 00:57:10 +01:00
ctx.next = false
2020-02-21 16:56:32 +01:00
// continue to go to the next route
2020-01-21 00:57:10 +01:00
continue
}
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
}
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
}
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
2019-12-30 07:29:42 -05:00
ctx.values = matches[0][1:len(matches[0])]
}
}
2020-02-21 16:56:32 +01:00
// Websocket route
if route.HandlerConn != nil {
// Try to upgrade
err := socketUpgrade.Upgrade(ctx.Fasthttp, func(fconn *websocket.Conn) {
conn := acquireConn(fconn)
conn.params = ctx.params
conn.values = ctx.values
releaseCtx(ctx)
defer releaseConn(conn)
route.HandlerConn(conn)
})
// Upgrading failed
if err != nil {
panic(err)
}
return
}
// No handler for HTTP nor websocket
if route.HandlerCtx == nil {
continue
}
// Match found, 404 not needed
match = true
route.HandlerCtx(ctx)
2019-12-30 07:29:42 -05:00
// if next is not set, leave loop and release ctx
if !ctx.next {
break
2020-02-21 16:56:32 +01:00
} else {
// reset match to false
match = false
2019-12-30 07:29:42 -05:00
}
// set next to false for next iteration
ctx.next = false
}
2020-02-21 16:56:32 +01:00
// No match, send default 404
if !match {
2020-02-12 13:23:47 -05:00
ctx.SendStatus(404)
2020-01-10 03:09:43 +01:00
}
2019-12-30 07:29:42 -05:00
releaseCtx(ctx)
}