mirror of https://github.com/gofiber/fiber.git
420 lines
12 KiB
Go
420 lines
12 KiB
Go
package fiber
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
// This json parsing lib is awesome *.*
|
|
// "github.com/tidwall/gjson"
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
const (
|
|
// Version for debugging
|
|
Version = `0.6.0`
|
|
// Port and Version are printed with the banner
|
|
banner = `%s _____ _ _
|
|
%s| __|_| |_ ___ ___
|
|
%s| __| | . | -_| _|
|
|
%s|__| |_|___|___|_|%s
|
|
%s%s
|
|
|
|
`
|
|
// https://play.golang.org/p/r6GNeV1gbH
|
|
cReset = "\x1b[0000m"
|
|
cBlack = "\x1b[1;30m"
|
|
cRed = "\x1b[1;31m"
|
|
cGreen = "\x1b[1;32m"
|
|
cYellow = "\x1b[1;33m"
|
|
cBlue = "\x1b[1;34m"
|
|
cMagenta = "\x1b[1;35m"
|
|
cCyan = "\x1b[1;36m"
|
|
cWhite = "\x1b[1;37m"
|
|
)
|
|
|
|
// Fiber structure
|
|
type Fiber struct {
|
|
// Stores all routes
|
|
routes []*route
|
|
// Fasthttp server settings
|
|
Fasthttp *Fasthttp
|
|
// Server name header
|
|
Server string
|
|
// Provide certificate files to enable TLS
|
|
CertKey string
|
|
CertFile string
|
|
// Disable the fiber banner on launch
|
|
NoBanner bool
|
|
// Clears terminal on launch
|
|
ClearTerminal bool
|
|
}
|
|
|
|
type route struct {
|
|
// HTTP method in uppercase, can be a * for Use() & All()
|
|
method string
|
|
// Any bool is for routes without a path or * and /*
|
|
any bool
|
|
// Stores the orignal path
|
|
path string
|
|
// Stores compiled regex special routes :params, *wildcards, optionals?
|
|
regex *regexp.Regexp
|
|
// Store params if special routes :params, *wildcards, optionals?
|
|
params []string
|
|
// Callback function for specific route
|
|
handler func(*Ctx)
|
|
}
|
|
|
|
// Fasthttp settings
|
|
// https://github.com/valyala/fasthttp/blob/master/server.go#L150
|
|
type Fasthttp struct {
|
|
Concurrency int
|
|
DisableKeepAlive bool
|
|
ReadBufferSize int
|
|
WriteBufferSize int
|
|
ReadTimeout time.Duration
|
|
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
|
|
NoDefaultContentType bool
|
|
KeepHijackedConns bool
|
|
}
|
|
|
|
// New creates a Fiber instance
|
|
func New() *Fiber {
|
|
return &Fiber{
|
|
// No server header is sent when set empty ""
|
|
Server: "",
|
|
// TLS is disabled by default, unless files are provided
|
|
CertKey: "",
|
|
CertFile: "",
|
|
// Fiber banner is printed by default
|
|
NoBanner: false,
|
|
// Terminal is not cleared by default
|
|
ClearTerminal: false,
|
|
Fasthttp: &Fasthttp{
|
|
// Default fasthttp settings
|
|
// https://github.com/valyala/fasthttp/blob/master/server.go#L150
|
|
Concurrency: 256 * 1024,
|
|
DisableKeepAlive: false,
|
|
ReadBufferSize: 4096,
|
|
WriteBufferSize: 4096,
|
|
WriteTimeout: 0,
|
|
ReadTimeout: 0,
|
|
IdleTimeout: 0,
|
|
MaxConnsPerIP: 0,
|
|
MaxRequestsPerConn: 0,
|
|
TCPKeepalive: false,
|
|
TCPKeepalivePeriod: 0,
|
|
MaxRequestBodySize: 4 * 1024 * 1024,
|
|
ReduceMemoryUsage: false,
|
|
GetOnly: false,
|
|
DisableHeaderNamesNormalizing: false,
|
|
SleepWhenConcurrencyLimitsExceeded: 0,
|
|
NoDefaultContentType: false,
|
|
KeepHijackedConns: false,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Connect establishes a tunnel to the server
|
|
// identified by the target resource.
|
|
func (r *Fiber) Connect(args ...interface{}) {
|
|
r.register("CONNECT", args...)
|
|
}
|
|
|
|
// Put replaces all current representations
|
|
// of the target resource with the request payload.
|
|
func (r *Fiber) Put(args ...interface{}) {
|
|
r.register("PUT", args...)
|
|
}
|
|
|
|
// Post is used to submit an entity to the specified resource,
|
|
// often causing a change in state or side effects on the server.
|
|
func (r *Fiber) Post(args ...interface{}) {
|
|
r.register("POST", args...)
|
|
}
|
|
|
|
// Delete deletes the specified resource.
|
|
func (r *Fiber) Delete(args ...interface{}) {
|
|
r.register("DELETE", args...)
|
|
}
|
|
|
|
// Head asks for a response identical to that of a GET request,
|
|
// but without the response body.
|
|
func (r *Fiber) Head(args ...interface{}) {
|
|
r.register("HEAD", args...)
|
|
}
|
|
|
|
// Patch is used to apply partial modifications to a resource.
|
|
func (r *Fiber) Patch(args ...interface{}) {
|
|
r.register("PATCH", args...)
|
|
}
|
|
|
|
// Options is used to describe the communication options
|
|
// for the target resource.
|
|
func (r *Fiber) Options(args ...interface{}) {
|
|
r.register("OPTIONS", args...)
|
|
}
|
|
|
|
// Trace performs a message loop-back test
|
|
// along the path to the target resource.
|
|
func (r *Fiber) Trace(args ...interface{}) {
|
|
r.register("TRACE", args...)
|
|
}
|
|
|
|
// Get requests a representation of the specified resource.
|
|
// Requests using GET should only retrieve data.
|
|
func (r *Fiber) Get(args ...interface{}) {
|
|
r.register("GET", args...)
|
|
}
|
|
|
|
// All matches any HTTP method
|
|
func (r *Fiber) All(args ...interface{}) {
|
|
r.register("*", args...)
|
|
}
|
|
|
|
// Use is another name for All()
|
|
// People using Expressjs are used to this
|
|
func (r *Fiber) Use(args ...interface{}) {
|
|
r.All(args...)
|
|
}
|
|
|
|
// Function to add a route correctly
|
|
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 := walkDir(root)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
mount := filepath.Clean(root)
|
|
for _, file := range files {
|
|
if strings.Contains(file, ".fasthttp.gz") {
|
|
continue
|
|
}
|
|
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 create a new context struct from the pool
|
|
// then try to match a route as efficient as possible.
|
|
// 1 > loop trough all routes
|
|
// 2 > if method != * or method != method SKIP
|
|
// 3 > if any == true or (path == path && params == nil): MATCH
|
|
// 4 > if regex == nil: SKIP
|
|
// 5 > if regex.match(path) != true: SKIP
|
|
// 6 > if params != nil && len(params) > 0 REGEXPARAMS
|
|
func (r *Fiber) handler(fctx *fasthttp.RequestCtx) {
|
|
found := false
|
|
// 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}
|
|
}
|
|
found = true
|
|
// 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 exist
|
|
if route.regex == nil {
|
|
continue
|
|
}
|
|
// Skip route if regex does not match
|
|
if !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])]
|
|
}
|
|
}
|
|
found = true
|
|
// 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
|
|
}
|
|
// No routes found
|
|
if !found {
|
|
// Custom 404 handler?
|
|
ctx.Status(404).Send("Not Found")
|
|
}
|
|
// release context back into sync pool
|
|
releaseCtx(ctx)
|
|
}
|
|
|
|
// Listen starts the server with the correct settings
|
|
func (r *Fiber) Listen(port int, addr ...string) {
|
|
portStr := strconv.Itoa(port)
|
|
var address string
|
|
if len(addr) > 0 {
|
|
address = addr[0]
|
|
}
|
|
server := &fasthttp.Server{
|
|
Handler: r.handler,
|
|
Name: r.Server,
|
|
Concurrency: r.Fasthttp.Concurrency,
|
|
DisableKeepalive: r.Fasthttp.DisableKeepAlive,
|
|
ReadBufferSize: r.Fasthttp.ReadBufferSize,
|
|
WriteBufferSize: r.Fasthttp.WriteBufferSize,
|
|
ReadTimeout: r.Fasthttp.ReadTimeout,
|
|
WriteTimeout: r.Fasthttp.WriteTimeout,
|
|
IdleTimeout: r.Fasthttp.IdleTimeout,
|
|
MaxConnsPerIP: r.Fasthttp.MaxConnsPerIP,
|
|
MaxRequestsPerConn: r.Fasthttp.MaxRequestsPerConn,
|
|
TCPKeepalive: r.Fasthttp.TCPKeepalive,
|
|
TCPKeepalivePeriod: r.Fasthttp.TCPKeepalivePeriod,
|
|
MaxRequestBodySize: r.Fasthttp.MaxRequestBodySize,
|
|
ReduceMemoryUsage: r.Fasthttp.ReduceMemoryUsage,
|
|
GetOnly: r.Fasthttp.GetOnly,
|
|
DisableHeaderNamesNormalizing: r.Fasthttp.DisableHeaderNamesNormalizing,
|
|
SleepWhenConcurrencyLimitsExceeded: r.Fasthttp.SleepWhenConcurrencyLimitsExceeded,
|
|
NoDefaultServerHeader: r.Server == "",
|
|
NoDefaultContentType: r.Fasthttp.NoDefaultContentType,
|
|
KeepHijackedConns: r.Fasthttp.KeepHijackedConns,
|
|
}
|
|
if r.ClearTerminal {
|
|
if runtime.GOOS == "linux" {
|
|
cmd := exec.Command("clear")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Run()
|
|
} else if runtime.GOOS == "windows" {
|
|
cmd := exec.Command("cmd", "/c", "cls")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Run()
|
|
}
|
|
}
|
|
if !r.NoBanner {
|
|
fmt.Printf(banner, cGreen, cGreen, cGreen, cGreen,
|
|
cBlack+Version,
|
|
cBlack+"Express on steriods",
|
|
cGreen+":"+portStr+cReset,
|
|
)
|
|
}
|
|
if r.CertKey != "" && r.CertFile != "" {
|
|
if err := server.ListenAndServeTLS(fmt.Sprintf("%s:%s", address, portStr), r.CertFile, r.CertKey); err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
if err := server.ListenAndServe(fmt.Sprintf("%s:%s", address, portStr)); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|