// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖 // 📌 Please open an issue if you got suggestions or found a bug! // 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki // 🦸 Not all heroes wear capes, thank you to some amazing people // 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr package fiber import ( "flag" "fmt" "log" "net" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "time" "github.com/mattn/go-colorable" "github.com/valyala/fasthttp" "github.com/valyala/fasthttp/reuseport" ) const ( // Version : Fiber version Version = "1.4.0" website = "https://fiber.wiki" // https://play.golang.org/p/r6GNeV1gbH banner = "" + " \x1b[1;32m _____ _ _\n" + " \x1b[1;32m| __|_| |_ ___ ___\n" + " \x1b[1;32m| __| | . | -_| _|\n" + " \x1b[1;32m|__| |_|___|___|_|\x1b[1;30m%s\x1b[1;32m%s\n" + " \x1b[1;30m%s\x1b[1;32m%v\x1b[0000m\n\n" ) var ( prefork = flag.Bool("prefork", false, "use prefork") child = flag.Bool("child", false, "is child process") ) // Fiber structure type Fiber struct { // Server name header Server string httpServer *fasthttp.Server // Show fiber banner Banner bool // https://github.com/valyala/fasthttp/blob/master/server.go#L150 Engine *engine // https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ Prefork bool child bool // Stores all routes routes []*Route } // Fasthttp settings // https://github.com/valyala/fasthttp/blob/master/server.go#L150 type engine 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 https://fiber.wiki/application#new func New() *Fiber { flag.Parse() return &Fiber{ Server: "", httpServer: nil, Banner: true, Prefork: *prefork, child: *child, Engine: &engine{ 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("ALL", args...) } // Use only matches the starting path func (r *Fiber) Use(args ...interface{}) { r.register("MIDWARE", args...) } // Static https://fiber.wiki/application#static func (r *Fiber) Static(args ...string) { prefix := "/" root := "./" wildcard := false // enable / disable gzipping somewhere? // todo v2.0.0 gzip := true if len(args) == 1 { root = args[0] } else if len(args) == 2 { prefix = args[0] root = args[1] if prefix[0] != '/' { prefix = "/" + prefix } } // Check if wildcard for single files // app.Static("*", "./public/index.html") // app.Static("/*", "./public/index.html") if prefix == "*" || prefix == "/*" { wildcard = true } // Check if root exists if _, err := os.Lstat(root); err != nil { log.Fatal("Static: ", err) } // Lets get all files from root files, _, err := getFiles(root) if err != nil { log.Fatal("Static: ", err) } // ./static/compiled => static/compiled mount := filepath.Clean(root) // Loop over all files for _, file := range files { // Ignore the .gzipped files by fasthttp if strings.Contains(file, ".fasthttp.gz") { continue } // Time to create a fake path for the route match // static/index.html => /index.html path := filepath.Join(prefix, strings.Replace(file, mount, "", 1)) // Store original file path to use in ctx handler filePath := file // If the file is an index.html, bind the prefix to index.html directly if filepath.Base(filePath) == "index.html" || filepath.Base(filePath) == "index.htm" { r.routes = append(r.routes, &Route{"GET", prefix, wildcard, false, nil, nil, func(c *Ctx) { c.SendFile(filePath, gzip) }}) } // Add the route + SendFile(filepath) to routes r.routes = append(r.routes, &Route{"GET", path, wildcard, false, nil, nil, func(c *Ctx) { c.SendFile(filePath, gzip) }}) } } // Listen : https://fiber.wiki/application#listen func (r *Fiber) Listen(address interface{}, tls ...string) { host := "" switch val := address.(type) { case int: host = ":" + strconv.Itoa(val) // 8080 => ":8080" case string: if !strings.Contains(val, ":") { val = ":" + val // "8080" => ":8080" } host = val default: log.Fatal("Listen: Host must be an INT port or STRING address") } // Create fasthttp server r.httpServer = r.setupServer() out := colorable.NewColorableStdout() // Prefork enabled if r.Prefork && runtime.NumCPU() > 1 { if r.Banner && !r.child { cores := fmt.Sprintf("%s\x1b[1;30m %v cores", host, runtime.NumCPU()) fmt.Fprintf(out, banner, Version, " prefork", "Express on steroids", cores) } r.prefork(host, tls...) } // Prefork disabled if r.Banner { fmt.Fprintf(out, banner, Version, "", "Express on steroids", host) } ln, err := net.Listen("tcp4", host) if err != nil { log.Fatal("Listen: ", err) } // enable TLS/HTTPS if len(tls) > 1 { if err := r.httpServer.ServeTLS(ln, tls[0], tls[1]); err != nil { log.Fatal("Listen: ", err) } } if err := r.httpServer.Serve(ln); err != nil { log.Fatal("Listen: ", err) } } // Shutdown server gracefully func (r *Fiber) Shutdown() error { if r.httpServer == nil { return fmt.Errorf("Server is not running") } return r.httpServer.Shutdown() } // https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ func (r *Fiber) prefork(host string, tls ...string) { // Master proc if !r.child { // Create babies childs := make([]*exec.Cmd, runtime.NumCPU()) // #nosec G204 for i := range childs { childs[i] = exec.Command(os.Args[0], "-prefork", "-child") childs[i].Stdout = os.Stdout childs[i].Stderr = os.Stderr if err := childs[i].Start(); err != nil { log.Fatal("Listen-prefork: ", err) } } for _, child := range childs { if err := child.Wait(); err != nil { log.Fatal("Listen-prefork: ", err) } } os.Exit(0) } // Child proc runtime.GOMAXPROCS(1) ln, err := reuseport.Listen("tcp4", host) if err != nil { log.Fatal("Listen-prefork: ", err) } // enable TLS/HTTPS if len(tls) > 1 { if err := r.httpServer.ServeTLS(ln, tls[0], tls[1]); err != nil { log.Fatal("Listen-prefork: ", err) } } if err := r.httpServer.Serve(ln); err != nil { log.Fatal("Listen-prefork: ", err) } } func (r *Fiber) setupServer() *fasthttp.Server { return &fasthttp.Server{ Handler: r.handler, Name: r.Server, Concurrency: r.Engine.Concurrency, DisableKeepalive: r.Engine.DisableKeepAlive, ReadBufferSize: r.Engine.ReadBufferSize, WriteBufferSize: r.Engine.WriteBufferSize, ReadTimeout: r.Engine.ReadTimeout, WriteTimeout: r.Engine.WriteTimeout, IdleTimeout: r.Engine.IdleTimeout, MaxConnsPerIP: r.Engine.MaxConnsPerIP, MaxRequestsPerConn: r.Engine.MaxRequestsPerConn, TCPKeepalive: r.Engine.TCPKeepalive, TCPKeepalivePeriod: r.Engine.TCPKeepalivePeriod, MaxRequestBodySize: r.Engine.MaxRequestBodySize, ReduceMemoryUsage: r.Engine.ReduceMemoryUsage, GetOnly: r.Engine.GetOnly, DisableHeaderNamesNormalizing: r.Engine.DisableHeaderNamesNormalizing, SleepWhenConcurrencyLimitsExceeded: r.Engine.SleepWhenConcurrencyLimitsExceeded, NoDefaultServerHeader: r.Server == "", NoDefaultContentType: r.Engine.NoDefaultContentType, KeepHijackedConns: r.Engine.KeepHijackedConns, } }