mirror of
https://github.com/gofiber/fiber.git
synced 2025-02-15 02:04:34 +00:00
366 lines
9.7 KiB
Go
366 lines
9.7 KiB
Go
package fiber
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
const (
|
|
Version = "v0.2.0"
|
|
banner = ` _____ _ _
|
|
| __|_| |_ ___ ___
|
|
| __| | . | -_| _|
|
|
|__| |_|___|___|_|%s
|
|
|
|
`
|
|
)
|
|
|
|
type route struct {
|
|
method string
|
|
any bool
|
|
path string
|
|
regex *regexp.Regexp
|
|
params []string
|
|
handler func(*Ctx)
|
|
}
|
|
|
|
// Settings :
|
|
type Settings struct {
|
|
Name string
|
|
ClearTerminal bool
|
|
HideBanner bool
|
|
TLSEnable bool
|
|
CertKey string
|
|
CertFile string
|
|
Concurrency int
|
|
DisableKeepAlive bool
|
|
ReadBufferSize int
|
|
WriteBufferSize int
|
|
WriteTimeout time.Duration
|
|
IdleTimeout time.Duration
|
|
MaxConnsPerIP int
|
|
MaxRequestsPerConn int
|
|
TCPKeepalive bool
|
|
TCPKeepalivePeriod time.Duration
|
|
MaxRequestBodySize int
|
|
ReduceMemoryUsage bool
|
|
GetOnly bool
|
|
DisableHeaderNamesNormalizing bool
|
|
SleepWhenConcurrencyLimitsExceeded time.Duration
|
|
NoDefaultServerHeader bool
|
|
NoDefaultContentType bool
|
|
KeepHijackedConns bool
|
|
}
|
|
|
|
// Fiber :
|
|
type Fiber struct {
|
|
routes []*route
|
|
methods []string
|
|
Settings *Settings
|
|
}
|
|
|
|
// New :
|
|
func New() *Fiber {
|
|
return &Fiber{
|
|
Settings: &Settings{
|
|
Name: "",
|
|
ClearTerminal: false,
|
|
HideBanner: false,
|
|
TLSEnable: false,
|
|
CertKey: "",
|
|
CertFile: "",
|
|
Concurrency: 256 * 1024,
|
|
DisableKeepAlive: false,
|
|
ReadBufferSize: 4096,
|
|
WriteBufferSize: 4096,
|
|
WriteTimeout: 0,
|
|
IdleTimeout: 0,
|
|
MaxConnsPerIP: 0,
|
|
MaxRequestsPerConn: 0,
|
|
TCPKeepalive: false,
|
|
TCPKeepalivePeriod: 0,
|
|
MaxRequestBodySize: 4 * 1024 * 1024,
|
|
ReduceMemoryUsage: false,
|
|
GetOnly: false,
|
|
DisableHeaderNamesNormalizing: false,
|
|
SleepWhenConcurrencyLimitsExceeded: 0,
|
|
NoDefaultServerHeader: true,
|
|
NoDefaultContentType: false,
|
|
KeepHijackedConns: false,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Connect :
|
|
func (r *Fiber) Connect(args ...interface{}) {
|
|
r.register("CONNECT", args...)
|
|
}
|
|
|
|
// Put :
|
|
func (r *Fiber) Put(args ...interface{}) {
|
|
r.register("PUT", args...)
|
|
}
|
|
|
|
// Post :
|
|
func (r *Fiber) Post(args ...interface{}) {
|
|
r.register("POST", args...)
|
|
}
|
|
|
|
// Delete :
|
|
func (r *Fiber) Delete(args ...interface{}) {
|
|
r.register("DELETE", args...)
|
|
}
|
|
|
|
// Head :
|
|
func (r *Fiber) Head(args ...interface{}) {
|
|
r.register("HEAD", args...)
|
|
}
|
|
|
|
// Patch :
|
|
func (r *Fiber) Patch(args ...interface{}) {
|
|
r.register("PATCH", args...)
|
|
}
|
|
|
|
// Options :
|
|
func (r *Fiber) Options(args ...interface{}) {
|
|
r.register("OPTIONS", args...)
|
|
}
|
|
|
|
// Trace :
|
|
func (r *Fiber) Trace(args ...interface{}) {
|
|
r.register("TRACE", args...)
|
|
}
|
|
|
|
// Get :
|
|
func (r *Fiber) Get(args ...interface{}) {
|
|
r.register("GET", args...)
|
|
}
|
|
|
|
// Use :
|
|
func (r *Fiber) Use(args ...interface{}) {
|
|
r.register("*", args...)
|
|
}
|
|
|
|
// All :
|
|
func (r *Fiber) All(args ...interface{}) {
|
|
r.register("*", args...)
|
|
}
|
|
|
|
func (r *Fiber) register(method string, args ...interface{}) {
|
|
// Options
|
|
var path string
|
|
var static string
|
|
var handler func(*Ctx)
|
|
// app.Get(handler)
|
|
if len(args) == 1 {
|
|
switch arg := args[0].(type) {
|
|
case string:
|
|
static = arg
|
|
case func(*Ctx):
|
|
handler = arg
|
|
}
|
|
}
|
|
// app.Get(path, handler)
|
|
if len(args) == 2 {
|
|
path = args[0].(string)
|
|
if path[0] != '/' && path[0] != '*' {
|
|
panic("Invalid path, must begin with slash '/' or wildcard '*'")
|
|
}
|
|
switch arg := args[1].(type) {
|
|
case string:
|
|
static = arg
|
|
case func(*Ctx):
|
|
handler = arg
|
|
}
|
|
}
|
|
// Is this a static file handler?
|
|
if static != "" {
|
|
// static file route!!
|
|
r.registerStatic(method, path, static)
|
|
} else if handler != nil {
|
|
// function route!!
|
|
r.registerHandler(method, path, handler)
|
|
} else {
|
|
panic("Every route needs to contain either a dir/file path or callback function")
|
|
}
|
|
}
|
|
func (r *Fiber) registerStatic(method, prefix, root string) {
|
|
var any bool
|
|
if prefix == "*" || prefix == "/*" {
|
|
any = true
|
|
}
|
|
if prefix == "" {
|
|
prefix = "/"
|
|
}
|
|
files, _, err := walk(root)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
mount := filepath.Clean(root)
|
|
for _, file := range files {
|
|
path := filepath.Join(prefix, strings.Replace(file, mount, "", 1))
|
|
filePath := file
|
|
if filepath.Base(filePath) == "index.html" {
|
|
r.routes = append(r.routes, &route{method, any, prefix, nil, nil, func(c *Ctx) {
|
|
c.SendFile(filePath)
|
|
}})
|
|
}
|
|
r.routes = append(r.routes, &route{method, any, path, nil, nil, func(c *Ctx) {
|
|
c.SendFile(filePath)
|
|
}})
|
|
}
|
|
}
|
|
func (r *Fiber) registerHandler(method, path string, handler func(*Ctx)) {
|
|
if path == "" || path == "*" || path == "/*" {
|
|
r.routes = append(r.routes, &route{method, true, path, nil, nil, handler})
|
|
return
|
|
}
|
|
// Get params from path
|
|
params := getParams(path)
|
|
// If path has no params, we dont need regex
|
|
if len(params) == 0 {
|
|
r.routes = append(r.routes, &route{method, false, path, nil, nil, handler})
|
|
return
|
|
}
|
|
|
|
// Compile regix from path
|
|
regex, err := getRegex(path)
|
|
if err != nil {
|
|
panic("Invalid url pattern: " + path)
|
|
}
|
|
r.routes = append(r.routes, &route{method, false, path, regex, params, handler})
|
|
}
|
|
|
|
// handler :
|
|
func (r *Fiber) handler(fctx *fasthttp.RequestCtx) {
|
|
// get custom context from sync pool
|
|
ctx := acquireCtx(fctx)
|
|
// get path and method from main context
|
|
path := ctx.Path()
|
|
method := ctx.Method()
|
|
// loop trough routes
|
|
for _, route := range r.routes {
|
|
// Skip route if method is not allowed
|
|
if route.method != "*" && route.method != method {
|
|
continue
|
|
}
|
|
// First check if we match a static path or wildcard
|
|
if route.any || (route.path == path && route.params == nil) {
|
|
// If * always set the path to the wildcard parameter
|
|
if route.any {
|
|
ctx.params = &[]string{"*"}
|
|
ctx.values = []string{path}
|
|
}
|
|
// 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
|
|
}
|
|
// Skip route if regex does not match
|
|
if route.regex == nil || !route.regex.MatchString(path) {
|
|
continue
|
|
}
|
|
// If we have parameters, lets find the matches
|
|
if route.params != nil && 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 = matches[0][1:len(matches[0])]
|
|
}
|
|
}
|
|
// 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
|
|
}
|
|
// release context back into sync pool
|
|
releaseCtx(ctx)
|
|
}
|
|
|
|
// Listen :
|
|
func (r *Fiber) Listen(args ...interface{}) {
|
|
var port string
|
|
var addr string
|
|
if len(args) == 1 {
|
|
port = strconv.Itoa(args[0].(int))
|
|
}
|
|
if len(args) == 2 {
|
|
addr = args[0].(string)
|
|
port = strconv.Itoa(args[1].(int))
|
|
}
|
|
// Disable server header if server name is not given
|
|
if r.Settings.Name != "" {
|
|
r.Settings.NoDefaultServerHeader = false
|
|
}
|
|
server := &fasthttp.Server{
|
|
// Express custom handler
|
|
Handler: r.handler,
|
|
// Server settings
|
|
Name: r.Settings.Name,
|
|
Concurrency: r.Settings.Concurrency,
|
|
DisableKeepalive: r.Settings.DisableKeepAlive,
|
|
ReadBufferSize: r.Settings.ReadBufferSize,
|
|
WriteBufferSize: r.Settings.WriteBufferSize,
|
|
WriteTimeout: r.Settings.WriteTimeout,
|
|
IdleTimeout: r.Settings.IdleTimeout,
|
|
MaxConnsPerIP: r.Settings.MaxConnsPerIP,
|
|
MaxRequestsPerConn: r.Settings.MaxRequestsPerConn,
|
|
TCPKeepalive: r.Settings.TCPKeepalive,
|
|
TCPKeepalivePeriod: r.Settings.TCPKeepalivePeriod,
|
|
MaxRequestBodySize: r.Settings.MaxRequestBodySize,
|
|
ReduceMemoryUsage: r.Settings.ReduceMemoryUsage,
|
|
GetOnly: r.Settings.GetOnly,
|
|
DisableHeaderNamesNormalizing: r.Settings.DisableHeaderNamesNormalizing,
|
|
SleepWhenConcurrencyLimitsExceeded: r.Settings.SleepWhenConcurrencyLimitsExceeded,
|
|
NoDefaultServerHeader: r.Settings.NoDefaultServerHeader,
|
|
NoDefaultContentType: r.Settings.NoDefaultContentType,
|
|
KeepHijackedConns: r.Settings.KeepHijackedConns,
|
|
}
|
|
if r.Settings.ClearTerminal {
|
|
var cmd *exec.Cmd
|
|
goos := runtime.GOOS
|
|
if goos == "windows" {
|
|
cmd = exec.Command("cmd", "/c", "cls")
|
|
}
|
|
if goos == "linux" {
|
|
cmd = exec.Command("clear")
|
|
}
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Run()
|
|
}
|
|
if !r.Settings.HideBanner {
|
|
fmt.Printf(color.HiCyanString(banner), color.GreenString(":"+port))
|
|
}
|
|
// fmt.Printf(banner, Version)
|
|
if r.Settings.TLSEnable {
|
|
if err := server.ListenAndServeTLS(fmt.Sprintf("%s:%s", addr, port), r.Settings.CertFile, r.Settings.CertKey); err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
if err := server.ListenAndServe(fmt.Sprintf("%s:%s", addr, port)); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|