mirror of
https://github.com/gofiber/fiber.git
synced 2025-02-22 06:33:09 +00:00
328 lines
7.5 KiB
Go
328 lines
7.5 KiB
Go
// 🚀 Fiber is an Express inspired web framework written in Go with 💖
|
|
// 📌 API Documentation: https://fiber.wiki
|
|
// 📝 Github Repository: https://github.com/gofiber/fiber
|
|
|
|
package fiber
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
websocket "github.com/fasthttp/websocket"
|
|
fasthttp "github.com/valyala/fasthttp"
|
|
)
|
|
|
|
// Route struct
|
|
type Route struct {
|
|
isMiddleware bool // is middleware route
|
|
isWebSocket bool // is websocket route
|
|
|
|
isStar bool // path == '*'
|
|
isSlash bool // path == '/'
|
|
isRegex bool // needs regex parsing
|
|
|
|
Method string // http method
|
|
Path string // orginal path
|
|
Params []string // path params
|
|
Regexp *regexp.Regexp // regexp matcher
|
|
|
|
HandleCtx func(*Ctx) // ctx handler
|
|
HandleConn func(*Conn) // conn handler
|
|
|
|
}
|
|
|
|
func (app *App) nextRoute(ctx *Ctx) {
|
|
lenr := len(app.routes) - 1
|
|
for ctx.index < lenr {
|
|
ctx.index++
|
|
route := app.routes[ctx.index]
|
|
match, values := route.matchRoute(ctx.method, ctx.path)
|
|
if match {
|
|
ctx.route = route
|
|
if !ctx.matched {
|
|
ctx.matched = true
|
|
}
|
|
if len(values) > 0 {
|
|
ctx.values = values
|
|
}
|
|
if route.isWebSocket {
|
|
if err := websocketUpgrader.Upgrade(ctx.Fasthttp, func(fconn *websocket.Conn) {
|
|
conn := acquireConn(fconn)
|
|
defer releaseConn(conn)
|
|
route.HandleConn(conn)
|
|
}); err != nil { // Upgrading failed
|
|
ctx.SendStatus(400)
|
|
}
|
|
} else {
|
|
route.HandleCtx(ctx)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
if !ctx.matched {
|
|
ctx.SendStatus(404)
|
|
}
|
|
}
|
|
|
|
func (r *Route) matchRoute(method, path string) (match bool, values []string) {
|
|
// is route middleware? matches all http methods
|
|
if r.isMiddleware {
|
|
// '*' or '/' means its a valid match
|
|
if r.isStar || r.isSlash {
|
|
return true, nil
|
|
}
|
|
// if midware path starts with req.path
|
|
if strings.HasPrefix(path, r.Path) {
|
|
return true, nil
|
|
}
|
|
// middlewares dont support regex so bye!
|
|
return false, nil
|
|
}
|
|
// non-middleware route, http method must match!
|
|
// the wildcard method is for .All() method
|
|
if r.Method != method && r.Method[0] != '*' {
|
|
return false, nil
|
|
}
|
|
// '*' means we match anything
|
|
if r.isStar {
|
|
return true, nil
|
|
}
|
|
// simple '/' bool, so you avoid unnecessary comparison for long paths
|
|
if r.isSlash && path == "/" {
|
|
return true, nil
|
|
}
|
|
// does this route need regex matching?
|
|
if r.isRegex {
|
|
// req.path match regex pattern
|
|
if r.Regexp.MatchString(path) {
|
|
// do we have parameters
|
|
if len(r.Params) > 0 {
|
|
// get values for parameters
|
|
matches := r.Regexp.FindAllStringSubmatch(path, -1)
|
|
// did we get the values?
|
|
if len(matches) > 0 && len(matches[0]) > 1 {
|
|
values = matches[0][1:len(matches[0])]
|
|
return true, values
|
|
}
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
// last thing to do is to check for a simple path match
|
|
if r.Path == path {
|
|
return true, nil
|
|
}
|
|
// Nothing match
|
|
return false, nil
|
|
}
|
|
|
|
func (app *App) handler(fctx *fasthttp.RequestCtx) {
|
|
// get fiber context from sync pool
|
|
ctx := acquireCtx(fctx)
|
|
defer releaseCtx(ctx)
|
|
|
|
// attach app poiner and compress settings
|
|
ctx.app = app
|
|
ctx.compress = app.Settings.Compression
|
|
|
|
// Case sensitive routing
|
|
if !app.Settings.CaseSensitive {
|
|
ctx.path = strings.ToLower(ctx.path)
|
|
}
|
|
// Strict routing
|
|
if !app.Settings.StrictRouting && len(ctx.path) > 1 {
|
|
ctx.path = strings.TrimRight(ctx.path, "/")
|
|
}
|
|
|
|
app.nextRoute(ctx)
|
|
|
|
if ctx.compress {
|
|
compressResponse(fctx)
|
|
}
|
|
}
|
|
|
|
func (app *App) registerMethod(method, path string, handlers ...func(*Ctx)) {
|
|
// Route requires atleast one handler
|
|
if len(handlers) == 0 {
|
|
log.Fatalf("Missing handler in route")
|
|
}
|
|
// Cannot have an empty path
|
|
if path == "" {
|
|
path = "/"
|
|
}
|
|
// Path always start with a '/' or '*'
|
|
if path[0] != '/' && path[0] != '*' {
|
|
path = "/" + path
|
|
}
|
|
|
|
// Case sensitive routing, all to lowercase
|
|
if !app.Settings.CaseSensitive {
|
|
path = strings.ToLower(path)
|
|
}
|
|
// Strict routing, remove last `/`
|
|
if !app.Settings.StrictRouting && len(path) > 1 {
|
|
path = strings.TrimRight(path, "/")
|
|
}
|
|
|
|
// Set route booleans
|
|
var isMiddleware = method == "USE"
|
|
// Middleware / All allows all HTTP methods
|
|
if isMiddleware || method == "ALL" {
|
|
method = "*"
|
|
}
|
|
var isStar = path == "*" || path == "/*"
|
|
// Middleware containing only a `/` equals wildcard
|
|
if isMiddleware && path == "/" {
|
|
isStar = true
|
|
}
|
|
var isSlash = path == "/"
|
|
var isRegex = false
|
|
// Route properties
|
|
var Params = getParams(path)
|
|
var Regexp *regexp.Regexp
|
|
// Params requires regex pattern
|
|
if len(Params) > 0 {
|
|
regex, err := getRegex(path)
|
|
if err != nil {
|
|
log.Fatal("Router: Invalid path pattern: " + path)
|
|
}
|
|
isRegex = true
|
|
Regexp = regex
|
|
}
|
|
for i := range handlers {
|
|
app.routes = append(app.routes, &Route{
|
|
isMiddleware: isMiddleware,
|
|
isStar: isStar,
|
|
isSlash: isSlash,
|
|
isRegex: isRegex,
|
|
Method: method,
|
|
Path: path,
|
|
Params: Params,
|
|
Regexp: Regexp,
|
|
HandleCtx: handlers[i],
|
|
})
|
|
}
|
|
}
|
|
|
|
func (app *App) registerWebSocket(method, path string, handle func(*Conn)) {
|
|
// Cannot have an empty path
|
|
if path == "" {
|
|
path = "/"
|
|
}
|
|
// Path always start with a '/' or '*'
|
|
if path[0] != '/' && path[0] != '*' {
|
|
path = "/" + path
|
|
}
|
|
|
|
// Case sensitive routing, all to lowercase
|
|
if !app.Settings.CaseSensitive {
|
|
path = strings.ToLower(path)
|
|
}
|
|
// Strict routing, remove last `/`
|
|
if !app.Settings.StrictRouting && len(path) > 1 {
|
|
path = strings.TrimRight(path, "/")
|
|
}
|
|
|
|
var isWebSocket = true
|
|
|
|
var isStar = path == "*" || path == "/*"
|
|
var isSlash = path == "/"
|
|
var isRegex = false
|
|
// Route properties
|
|
var Params = getParams(path)
|
|
var Regexp *regexp.Regexp
|
|
// Params requires regex pattern
|
|
if len(Params) > 0 {
|
|
regex, err := getRegex(path)
|
|
if err != nil {
|
|
log.Fatal("Router: Invalid path pattern: " + path)
|
|
}
|
|
isRegex = true
|
|
Regexp = regex
|
|
}
|
|
app.routes = append(app.routes, &Route{
|
|
isWebSocket: isWebSocket,
|
|
isStar: isStar,
|
|
isSlash: isSlash,
|
|
isRegex: isRegex,
|
|
|
|
Method: method,
|
|
Path: path,
|
|
Params: Params,
|
|
Regexp: Regexp,
|
|
HandleConn: handle,
|
|
})
|
|
}
|
|
|
|
func (app *App) registerStatic(prefix, root string) {
|
|
// Cannot have an empty prefix
|
|
if prefix == "" {
|
|
prefix = "/"
|
|
}
|
|
// prefix always start with a '/' or '*'
|
|
if prefix[0] != '/' && prefix[0] != '*' {
|
|
prefix = "/" + prefix
|
|
}
|
|
// Case sensitive routing, all to lowercase
|
|
if !app.Settings.CaseSensitive {
|
|
prefix = strings.ToLower(prefix)
|
|
}
|
|
|
|
var isStar = prefix == "*" || prefix == "/*"
|
|
|
|
files := map[string]string{}
|
|
// Clean root path
|
|
root = filepath.Clean(root)
|
|
// Check if root exist and is accessible
|
|
if _, err := os.Stat(root); err != nil {
|
|
log.Fatalf("%s", err)
|
|
}
|
|
// Store path url and file paths in map
|
|
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if !info.IsDir() {
|
|
url := "*"
|
|
if !isStar {
|
|
// /css/style.css: static/css/style.css
|
|
url = filepath.Join(prefix, strings.Replace(path, root, "", 1))
|
|
}
|
|
// \static\css: /static/css
|
|
url = filepath.ToSlash(url)
|
|
files[url] = path
|
|
if filepath.Base(path) == "index.html" {
|
|
files[url] = path
|
|
}
|
|
}
|
|
return err
|
|
}); err != nil {
|
|
log.Fatalf("%s", err)
|
|
}
|
|
compress := app.Settings.Compression
|
|
app.routes = append(app.routes, &Route{
|
|
isMiddleware: true,
|
|
isStar: isStar,
|
|
Method: "*",
|
|
Path: prefix,
|
|
HandleCtx: func(c *Ctx) {
|
|
// Only allow GET & HEAD methods
|
|
if c.method == "GET" || c.method == "HEAD" {
|
|
path := "*"
|
|
if !isStar {
|
|
path = c.path
|
|
}
|
|
file := files[path]
|
|
if file != "" {
|
|
c.SendFile(file, compress)
|
|
return
|
|
}
|
|
}
|
|
c.matched = false
|
|
c.Next()
|
|
},
|
|
})
|
|
}
|