mirror of https://github.com/gofiber/fiber.git
Add files via upload
parent
230ae73fa2
commit
615806664c
|
@ -0,0 +1,68 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
// Context struct
|
||||
type Context struct {
|
||||
next bool
|
||||
params *[]string
|
||||
values []string
|
||||
Fasthttp *fasthttp.RequestCtx
|
||||
}
|
||||
|
||||
// Context pool
|
||||
var ctxPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(Context)
|
||||
},
|
||||
}
|
||||
|
||||
// Get new Context from pool
|
||||
func acquireCtx(fctx *fasthttp.RequestCtx) *Context {
|
||||
ctx := ctxPool.Get().(*Context)
|
||||
ctx.Fasthttp = fctx
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Return Context to pool
|
||||
func releaseCtx(ctx *Context) {
|
||||
ctx.next = false
|
||||
ctx.params = nil
|
||||
ctx.values = nil
|
||||
ctx.Fasthttp = nil
|
||||
ctxPool.Put(ctx)
|
||||
}
|
||||
|
||||
// Next : Call the next middleware function in the stack.
|
||||
func (ctx *Context) Next() {
|
||||
ctx.next = true
|
||||
ctx.params = nil
|
||||
ctx.values = nil
|
||||
}
|
||||
|
||||
// Params :
|
||||
func (ctx *Context) Params(key string) string {
|
||||
if ctx.params == nil {
|
||||
return ""
|
||||
}
|
||||
for i := 0; i < len(*ctx.params); i++ {
|
||||
if (*ctx.params)[i] == key {
|
||||
return ctx.values[i]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Method https://expressjs.com/en/4x/api.html#req.method
|
||||
func (ctx *Context) Method() string {
|
||||
return b2s(ctx.Fasthttp.Method())
|
||||
}
|
||||
|
||||
// Path https://expressjs.com/en/4x/api.html#req.path
|
||||
func (ctx *Context) Path() string {
|
||||
return b2s(ctx.Fasthttp.Path())
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var replacer = strings.NewReplacer(":", "", "?", "")
|
||||
|
||||
func getParams(path string) (params []string) {
|
||||
segments := strings.Split(path, "/")
|
||||
for _, s := range segments {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(s, ":") {
|
||||
s = replacer.Replace(s)
|
||||
params = append(params, s)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(s, "*") {
|
||||
params = append(params, "*")
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func getRegex(path string) (*regexp.Regexp, error) {
|
||||
pattern := "^"
|
||||
segments := strings.Split(path, "/")
|
||||
for _, s := range segments {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(s, ":") {
|
||||
if strings.Contains(s, "?") {
|
||||
pattern += "(?:/([^/]+?))?"
|
||||
} else {
|
||||
pattern += "/(?:([^/]+?))"
|
||||
}
|
||||
} else if strings.Contains(s, "*") {
|
||||
pattern += "/(.*)"
|
||||
} else {
|
||||
pattern += "/" + s
|
||||
}
|
||||
}
|
||||
pattern += "/?$"
|
||||
regex, err := regexp.Compile(pattern)
|
||||
return regex, err
|
||||
}
|
||||
|
||||
// Credits to @savsgio
|
||||
// https://github.com/savsgio/gotils/blob/master/conv.go
|
||||
|
||||
// b2s converts byte slice to a string without memory allocation.
|
||||
func b2s(b []byte) string {
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&b))
|
||||
bh := reflect.SliceHeader{
|
||||
Data: sh.Data,
|
||||
Len: sh.Len,
|
||||
Cap: sh.Len,
|
||||
}
|
||||
return *(*string)(unsafe.Pointer(&bh))
|
||||
}
|
||||
|
||||
// s2b converts string to a byte slice without memory allocation.
|
||||
func s2b(s string) []byte {
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
bh := reflect.SliceHeader{
|
||||
Data: sh.Data,
|
||||
Len: sh.Len,
|
||||
Cap: sh.Len,
|
||||
}
|
||||
return *(*[]byte)(unsafe.Pointer(&bh))
|
||||
}
|
|
@ -0,0 +1,296 @@
|
|||
package fiber
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type route struct {
|
||||
method string
|
||||
path string
|
||||
anyPath bool
|
||||
regex *regexp.Regexp
|
||||
params []string
|
||||
handler func(*Context)
|
||||
}
|
||||
|
||||
// Settings :
|
||||
type Settings struct {
|
||||
TLSEnable bool
|
||||
CertKey string
|
||||
CertFile string
|
||||
Name 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{
|
||||
methods: []string{"GET", "PUT", "POST", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE", "CONNECT"},
|
||||
Settings: &Settings{
|
||||
TLSEnable: false,
|
||||
CertKey: "",
|
||||
CertFile: "",
|
||||
Name: "",
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get :
|
||||
func (r *Fiber) Get(args ...interface{}) {
|
||||
r.register("GET", 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...)
|
||||
}
|
||||
|
||||
// Connect :
|
||||
func (r *Fiber) Connect(args ...interface{}) {
|
||||
r.register("CONNECT", args...)
|
||||
}
|
||||
|
||||
// All :
|
||||
func (r *Fiber) All(args ...interface{}) {
|
||||
r.register("*", args...)
|
||||
// for _, method := range r.methods {
|
||||
// r.register(method, args...)
|
||||
// }
|
||||
}
|
||||
|
||||
// Use :
|
||||
func (r *Fiber) Use(args ...interface{}) {
|
||||
r.register("*", args...)
|
||||
// for _, method := range r.methods {
|
||||
// r.register(method, args...)
|
||||
// }
|
||||
}
|
||||
|
||||
// register :
|
||||
func (r *Fiber) register(method string, args ...interface{}) {
|
||||
// Pre-set variables for interface assertion
|
||||
var ok bool
|
||||
var path string
|
||||
var handler func(*Context)
|
||||
// Register only handler: app.Get(handler)
|
||||
if len(args) == 1 {
|
||||
// Convert interface to func(*Context)
|
||||
handler, ok = args[0].(func(*Context))
|
||||
if !ok {
|
||||
panic("Invalid handler must be func(*express.Context)")
|
||||
}
|
||||
}
|
||||
// Register path and handler: app.Get(path, handler)
|
||||
if len(args) == 2 {
|
||||
// Convert interface to path string
|
||||
path, ok = args[0].(string)
|
||||
if !ok {
|
||||
panic("Invalid path")
|
||||
}
|
||||
// Panic if first char does not begins with / or *
|
||||
if path[0] != '/' && path[0] != '*' {
|
||||
panic("Invalid path, must begin with slash '/' or wildcard '*'")
|
||||
}
|
||||
// Convert interface to func(*Context)
|
||||
handler, ok = args[1].(func(*Context))
|
||||
if !ok {
|
||||
panic("Invalid handler, must be func(*express.Context)")
|
||||
}
|
||||
}
|
||||
// If its a simple wildcard ( aka match anything )
|
||||
if path == "" || path == "*" || path == "/*" {
|
||||
r.routes = append(r.routes, &route{method, path, true, nil, nil, handler})
|
||||
fmt.Println(r.routes[0])
|
||||
return
|
||||
}
|
||||
// Get params from path
|
||||
params := getParams(path)
|
||||
fmt.Println(params)
|
||||
// If path has no params, we dont need regex
|
||||
if len(params) == 0 {
|
||||
r.routes = append(r.routes, &route{method, path, false, 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, path, false, 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.anyPath || (route.path == path && route.params == nil) {
|
||||
// If * always set the path to the wildcard parameter
|
||||
if route.anyPath {
|
||||
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
|
||||
fmt.Println("We did regex -,-")
|
||||
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(port 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.TLSEnable {
|
||||
if err := server.ListenAndServeTLS(fmt.Sprintf(":%v", port), r.Settings.CertFile, r.Settings.CertKey); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := server.ListenAndServe(fmt.Sprintf(":%v", port)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue