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.5.5` // Port and Version are printed with the banner banner = `%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(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)) } 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, cBlack+Version, cBlack+"Express on steriods", cGreen+":"+port+cReset, ) } if r.CertKey != "" && r.CertFile != "" { if err := server.ListenAndServeTLS(fmt.Sprintf("%s:%s", addr, port), r.CertFile, r.CertKey); err != nil { panic(err) } } else { if err := server.ListenAndServe(fmt.Sprintf("%s:%s", addr, port)); err != nil { panic(err) } } }