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
|
2020-02-01 19:42:40 +03:00
|
|
|
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
|
2020-02-01 19:42:40 +03:00
|
|
|
// 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-01 19:42:40 +03:00
|
|
|
|
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-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
|
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)
|
|
|
|
}
|