1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-22 05:32:57 +00:00
fiber/router.go
Fenny e719fa00bf
Use param support + optimizations (#361)
* Benchmark workflow

* Update router.go

* Clean root

* Add mutex

* Benchmark workflow

* Benchmark workflow
* Add mutex
* Enable benchmark tests
* Enable race testing

Co-Authored-By: ReneWerner87 <renewerner87@users.noreply.github.com>

* Benchmark Workflow

* Benchmark workflow
* Add mutex
* Enable benchmark tests
* Enable race testing

Co-Authored-By: ReneWerner87 <renewerner87@users.noreply.github.com>

* Update security workflow

* Benchmark workflow
* Add mutex
* Enable benchmark tests
* Enable race testing

Co-Authored-By: ReneWerner87 <renewerner87@users.noreply.github.com>

* Make Ctx pool accessible

- Add ctx benchmarks

* v1.9.6

* v1.9.6

Co-Authored-By: ReneWerner87 <renewerner87@googlemail.com>

* Improve context functions

* Add utils benchmarks

* Update benchmarks & tests

* Add utils tests

* New tests

* update test

* Move fastpath tests

* offer negotiation

* Cleanup

* Update Vary

Co-Authored-By: RW <renewerner87@googlemail.com>

* Optimize Append

Co-Authored-By: RW <renewerner87@googlemail.com>

* Optimize more methods

Co-Authored-By: RW <renewerner87@googlemail.com>
Co-Authored-By: Vic Shóstak <vikkyshostak@gmail.com>

* Add param support to Use

Co-Authored-By: RW <renewerner87@googlemail.com>
Co-Authored-By: Vic Shóstak <vikkyshostak@gmail.com>

* Add use_params tests

Co-Authored-By: RW <renewerner87@googlemail.com>
Co-Authored-By: Vic Shóstak <vikkyshostak@gmail.com>

* v1.9.7

Co-Authored-By: RW <renewerner87@googlemail.com>
Co-Authored-By: Vic Shóstak <vikkyshostak@gmail.com>

* Tests

Co-Authored-By: RW <renewerner87@googlemail.com>
Co-Authored-By: Vic Shóstak <vikkyshostak@gmail.com>

* v1.9.7

Co-Authored-By: RW <renewerner87@googlemail.com>
Co-Authored-By: Vic Shóstak <vikkyshostak@gmail.com>
Co-Authored-By: József Sallai <jozsef@sallai.me>
Co-Authored-By: Thomas van Vugt <thomasvvugt@users.noreply.github.com>

* Update app_test.go

Co-Authored-By: RW <renewerner87@googlemail.com>
Co-Authored-By: Vic Shóstak <vikkyshostak@gmail.com>
Co-Authored-By: József Sallai <jozsef@sallai.me>
Co-Authored-By: Thomas van Vugt <thomasvvugt@users.noreply.github.com>
Co-Authored-By: Nifty255 <nifty255@users.noreply.github.com>

* Rename argument

Co-Authored-By: RW <renewerner87@googlemail.com>

* Add nosec for WriteByte

Co-Authored-By: RW <renewerner87@googlemail.com>

* Add media article

* Update media articles

* Fix typo

Co-Authored-By: Thomas van Vugt <thomasvvugt@users.noreply.github.com>

* Fix typo

Co-authored-by: ReneWerner87 <renewerner87@users.noreply.github.com>
Co-authored-by: ReneWerner87 <renewerner87@googlemail.com>
Co-authored-by: Vic Shóstak <vikkyshostak@gmail.com>
Co-authored-by: József Sallai <jozsef@sallai.me>
Co-authored-by: Thomas van Vugt <thomasvvugt@users.noreply.github.com>
Co-authored-by: Nifty255 <nifty255@users.noreply.github.com>
2020-05-12 19:24:04 +02:00

268 lines
6.4 KiB
Go

// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io
package fiber
import (
"log"
"strings"
"time"
fasthttp "github.com/valyala/fasthttp"
)
// Route metadata
type Route struct {
// Internal fields
use bool // USE matches path prefixes
star bool // Path equals '*' or '/*'
root bool // Path equals '/'
parsed parsedParams // parsed contains parsed params segments
// External fields for ctx.Route() method
Path string // Registered route path
Method string // HTTP method
Params []string // Slice containing the params names
Handler func(*Ctx) // Ctx handler
}
func (app *App) nextRoute(ctx *Ctx) {
mINT := methodINT[ctx.method]
// Get stack length
lenr := len(app.routes[mINT]) - 1
// Loop over stack starting from previous index
for ctx.index < lenr {
// Increment stack index
ctx.index++
// Get *Route
route := app.routes[mINT][ctx.index]
// Check if it matches the request path
match, values := route.matchRoute(ctx.path)
// No match, continue
if !match {
continue
}
// Match! Set route and param values to Ctx
ctx.route = route
ctx.values = values
// Execute handler
route.Handler(ctx)
// Generate ETag if enabled
if app.Settings.ETag {
setETag(ctx, false)
}
return
}
// Send a 404 by default if no route is matched
if len(ctx.Fasthttp.Response.Body()) == 0 {
ctx.SendStatus(404)
}
}
func (r *Route) matchRoute(path string) (match bool, values []string) {
if r.use {
if r.root == true || strings.HasPrefix(path, r.Path) {
return true, values
}
// Check for a simple path match
} else if len(r.Path) == len(path) && r.Path == path {
return true, values
// Middleware routes allow prefix matches
} else if r.root == true && path == "/" {
return true, values
}
// '*' wildcard matches any path
if r.star == true {
return true, []string{path}
}
// Does this route have parameters
if len(r.Params) > 0 {
// Match params
if values, match = r.parsed.getMatch(path, r.use); match {
return
}
}
// No match
return false, values
}
func (app *App) handler(fctx *fasthttp.RequestCtx) {
// get fiber context from sync pool
ctx := AcquireCtx(fctx)
defer ReleaseCtx(ctx)
// Attach app poiner to access the routes
ctx.app = app
// 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, "/")
}
// Find route
app.nextRoute(ctx)
}
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 = "/" + path
}
// Store original path to strip case sensitive params
original := 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 isUse = method == "USE"
// Middleware / All allows all HTTP methods
if isUse || method == "ALL" {
method = "*"
}
var isStar = path == "/*"
// Middleware containing only a `/` equals wildcard
// if isUse && path == "/" {
// isStar = true
// }
var isRoot = path == "/"
// Route properties
var isParsed = getParams(original)
for i := range handlers {
route := &Route{
use: isUse,
star: isStar,
root: isRoot,
parsed: isParsed,
Path: path,
Method: method,
Params: isParsed.params,
Handler: handlers[i],
}
if method == "*" {
// Add handler to all HTTP methods
for m := range methodINT {
app.addRoute(m, route)
}
continue
}
// Add route to stack
app.addRoute(method, route)
// Add route to HEAD method if GET
if method == MethodGet {
app.addRoute(MethodHead, route)
}
}
}
func (app *App) registerStatic(prefix, root string, config ...Static) {
// Cannot have an empty prefix
if prefix == "" {
prefix = "/"
}
// Prefix always start with a '/' or '*'
if prefix[0] != '/' {
prefix = "/" + prefix
}
// Match anything
var wildcard = false
if prefix == "*" || prefix == "/*" {
wildcard = true
prefix = "/"
}
// Case sensitive routing, all to lowercase
if !app.Settings.CaseSensitive {
prefix = strings.ToLower(prefix)
}
// For security we want to restrict to the current work directory.
if len(root) == 0 {
root = "."
}
// Strip trailing slashes from the root path
if len(root) > 0 && root[len(root)-1] == '/' {
root = root[:len(root)-1]
}
// isSlash ?
var isRoot = prefix == "/"
if strings.Contains(prefix, "*") {
wildcard = true
prefix = strings.Split(prefix, "*")[0]
}
var stripper = len(prefix)
if isRoot {
stripper = 0
}
// Fileserver settings
fs := &fasthttp.FS{
Root: root,
GenerateIndexPages: false,
AcceptByteRange: false,
Compress: false,
CompressedFileSuffix: ".fiber.gz",
CacheDuration: 10 * time.Second,
IndexNames: []string{"index.html"},
PathRewrite: fasthttp.NewPathPrefixStripper(stripper),
PathNotFound: func(ctx *fasthttp.RequestCtx) {
ctx.Response.SetStatusCode(404)
ctx.Response.SetBodyString("Not Found")
},
}
// Set config if provided
if len(config) > 0 {
fs.Compress = config[0].Compress
fs.AcceptByteRange = config[0].ByteRange
fs.GenerateIndexPages = config[0].Browse
if config[0].Index != "" {
fs.IndexNames = []string{config[0].Index}
}
}
fileHandler := fs.NewRequestHandler()
route := &Route{
use: true,
root: isRoot,
Method: "*",
Path: prefix,
Handler: func(c *Ctx) {
// Do stuff
if wildcard {
c.Fasthttp.Request.SetRequestURI(prefix)
}
// Serve file
fileHandler(c.Fasthttp)
// Finish request if found and not forbidden
status := c.Fasthttp.Response.StatusCode()
if status != 404 && status != 403 {
return
}
// Reset response
c.Fasthttp.Response.Reset()
// Next middleware
c.Next()
},
}
// Add route to stack
app.addRoute(MethodGet, route)
app.addRoute(MethodHead, route)
}
func (app *App) addRoute(method string, route *Route) {
m := methodINT[method]
app.routes[m] = append(app.routes[m], route)
}