1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-23 15:03:46 +00:00
fiber/application.go

516 lines
14 KiB
Go
Raw Normal View History

2020-02-08 01:05:13 +01:00
// 🚀 Fiber is an Express.js inspired web framework written in Go with 💖
2020-01-22 05:42:37 +01:00
// 📌 Please open an issue if you got suggestions or found a bug!
2020-02-08 01:05:13 +01:00
// 🖥 Links: https://github.com/gofiber/fiber, https://fiber.wiki
2020-01-20 04:33:55 +01:00
// 🦸 Not all heroes wear capes, thank you to some amazing people
2020-02-08 01:05:13 +01:00
// 💖 @valyala, @erikdubbelboer, @savsgio, @julienschmidt, @koddr
2020-01-20 04:33:55 +01:00
package fiber
import (
2020-02-11 01:26:09 +01:00
"bufio"
2020-01-20 04:33:55 +01:00
"flag"
2020-02-10 01:24:59 +01:00
"fmt"
2020-02-11 01:26:09 +01:00
"io/ioutil"
2020-02-10 01:24:59 +01:00
"log"
"net"
2020-02-11 01:26:09 +01:00
"net/http"
"net/http/httputil"
2020-02-10 01:24:59 +01:00
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
2020-01-20 04:33:55 +01:00
"time"
"github.com/valyala/fasthttp"
2020-02-10 01:24:59 +01:00
"github.com/valyala/fasthttp/reuseport"
2020-01-20 04:33:55 +01:00
)
const (
// Version : Fiber version
2020-02-11 01:26:09 +01:00
Version = "1.4.3"
banner = "\x1b[1;32m" + ` ______ __ ______ ______ ______
2020-02-10 03:20:36 +01:00
/\ ___\ /\ \ /\ == \ /\ ___\ /\ == \
\ \ __\ \ \ \ \ \ __< \ \ __\ \ \ __<
\ \_\ \ \_\ \ \_____\ \ \_____\ \ \_\ \_\
\/_/ \/_/ \/_____/ \/_____/ \/_/ /_/
2020-02-11 01:26:09 +01:00
` + "\x1b[1;30mFiber \x1b[1;32mv%s\x1b[1;30m %s on \x1b[1;32m%s\x1b[1;30m, visit \x1b[1;32m%s\x1b[0m\n\n"
2020-01-20 04:33:55 +01:00
)
var (
prefork = flag.Bool("prefork", false, "use prefork")
child = flag.Bool("child", false, "is child process")
)
2020-02-11 01:26:09 +01:00
// Application structure
type Application struct {
2020-01-20 04:33:55 +01:00
// Server name header
Server string
httpServer *fasthttp.Server
2020-01-30 23:17:25 -05:00
// Show fiber banner
2020-01-20 04:33:55 +01:00
Banner bool
2020-01-30 23:17:25 -05:00
// https://github.com/valyala/fasthttp/blob/master/server.go#L150
2020-01-20 04:33:55 +01:00
Engine *engine
// https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
Prefork bool
2020-01-31 15:19:57 -05:00
child bool
2020-01-20 04:33:55 +01:00
// Stores all routes
routes []*Route
2020-01-20 04:33:55 +01:00
}
// 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
}
2020-02-08 04:56:57 +01:00
// New https://fiber.wiki/application#new
2020-02-11 01:26:09 +01:00
func New() *Application {
2020-01-20 04:33:55 +01:00
flag.Parse()
2020-02-11 01:26:09 +01:00
return &Application{
2020-02-07 00:43:16 +01:00
Server: "",
httpServer: nil,
Banner: true,
Prefork: *prefork,
child: *child,
2020-01-20 04:33:55 +01:00
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,
},
}
}
2020-02-10 01:24:59 +01:00
2020-02-10 05:48:52 +01:00
// Group :
type Group struct {
2020-02-11 01:26:09 +01:00
path string
app *Application
2020-02-10 05:48:52 +01:00
}
// Group :
2020-02-11 01:26:09 +01:00
func (app *Application) Group(path string) *Group {
2020-02-10 05:48:52 +01:00
return &Group{
2020-02-11 01:26:09 +01:00
path: path,
app: app,
2020-02-10 05:48:52 +01:00
}
}
2020-02-10 01:24:59 +01:00
// Connect establishes a tunnel to the server
// identified by the target resource.
2020-02-11 01:26:09 +01:00
func (app *Application) Connect(args ...interface{}) *Application {
app.register("CONNECT", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Connect for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Connect(args ...interface{}) *Group {
grp.register("CONNECT", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Put replaces all current representations
// of the target resource with the request payload.
2020-02-11 01:26:09 +01:00
func (app *Application) Put(args ...interface{}) *Application {
app.register("PUT", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Put for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Put(args ...interface{}) *Group {
grp.register("PUT", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Post is used to submit an entity to the specified resource,
// often causing a change in state or side effects on the server.
2020-02-11 01:26:09 +01:00
func (app *Application) Post(args ...interface{}) *Application {
app.register("POST", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Post for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Post(args ...interface{}) *Group {
grp.register("POST", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Delete deletes the specified resource.
2020-02-11 01:26:09 +01:00
func (app *Application) Delete(args ...interface{}) *Application {
app.register("DELETE", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Delete for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Delete(args ...interface{}) *Group {
grp.register("DELETE", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Head asks for a response identical to that of a GET request,
// but without the response body.
2020-02-11 01:26:09 +01:00
func (app *Application) Head(args ...interface{}) *Application {
app.register("HEAD", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Head for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Head(args ...interface{}) *Group {
grp.register("HEAD", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Patch is used to apply partial modifications to a resource.
2020-02-11 01:26:09 +01:00
func (app *Application) Patch(args ...interface{}) *Application {
app.register("PATCH", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Patch for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Patch(args ...interface{}) *Group {
grp.register("PATCH", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Options is used to describe the communication options
// for the target resource.
2020-02-11 01:26:09 +01:00
func (app *Application) Options(args ...interface{}) *Application {
app.register("OPTIONS", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Options for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Options(args ...interface{}) *Group {
grp.register("OPTIONS", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Trace performs a message loop-back test
// along the path to the target resource.
2020-02-11 01:26:09 +01:00
func (app *Application) Trace(args ...interface{}) *Application {
app.register("TRACE", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Trace for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Trace(args ...interface{}) *Group {
grp.register("TRACE", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Get requests a representation of the specified resource.
// Requests using GET should only retrieve data.
2020-02-11 01:26:09 +01:00
func (app *Application) Get(args ...interface{}) *Application {
app.register("GET", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Get for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Get(args ...interface{}) *Group {
grp.register("GET", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// All matches any HTTP method
2020-02-11 01:26:09 +01:00
func (app *Application) All(args ...interface{}) *Application {
app.register("ALL", args...)
return app
2020-02-10 05:48:52 +01:00
}
// All for group
2020-02-11 01:26:09 +01:00
func (grp *Group) All(args ...interface{}) *Group {
grp.register("ALL", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Use only matches the starting path
2020-02-11 01:26:09 +01:00
func (app *Application) Use(args ...interface{}) *Application {
app.register("USE", args...)
return app
2020-02-10 05:48:52 +01:00
}
// Use for group
2020-02-11 01:26:09 +01:00
func (grp *Group) Use(args ...interface{}) *Group {
grp.register("USE", args...)
return grp
2020-02-10 01:24:59 +01:00
}
// Static https://fiber.wiki/application#static
2020-02-11 01:26:09 +01:00
func (app *Application) Static(args ...string) {
2020-02-10 01:24:59 +01:00
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" {
2020-02-11 01:26:09 +01:00
app.routes = append(app.routes, &Route{"GET", prefix, wildcard, false, nil, nil, func(c *Ctx) {
2020-02-10 01:24:59 +01:00
c.SendFile(filePath, gzip)
}})
}
// Add the route + SendFile(filepath) to routes
2020-02-11 01:26:09 +01:00
app.routes = append(app.routes, &Route{"GET", path, wildcard, false, nil, nil, func(c *Ctx) {
2020-02-10 01:24:59 +01:00
c.SendFile(filePath, gzip)
}})
}
}
// Listen : https://fiber.wiki/application#listen
2020-02-11 01:26:09 +01:00
func (app *Application) Listen(address interface{}, tls ...string) {
2020-02-10 01:24:59 +01:00
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
2020-02-11 01:26:09 +01:00
app.httpServer = app.setupServer()
2020-02-10 10:14:54 +09:00
2020-02-10 01:24:59 +01:00
// Prefork enabled
2020-02-11 01:26:09 +01:00
if app.Prefork && runtime.NumCPU() > 1 {
if app.Banner && !app.child {
fmt.Printf(banner, Version, "preforking", host, "fiber.wiki")
2020-02-10 01:24:59 +01:00
}
2020-02-11 01:26:09 +01:00
app.prefork(host, tls...)
2020-02-10 01:24:59 +01:00
}
// Prefork disabled
2020-02-11 01:26:09 +01:00
if app.Banner {
fmt.Printf(banner, Version, "listening", host, "fiber.wiki")
2020-02-10 01:24:59 +01:00
}
ln, err := net.Listen("tcp4", host)
if err != nil {
log.Fatal("Listen: ", err)
}
// enable TLS/HTTPS
if len(tls) > 1 {
2020-02-11 01:26:09 +01:00
if err := app.httpServer.ServeTLS(ln, tls[0], tls[1]); err != nil {
2020-02-10 01:24:59 +01:00
log.Fatal("Listen: ", err)
}
}
2020-02-11 01:26:09 +01:00
if err := app.httpServer.Serve(ln); err != nil {
2020-02-10 01:24:59 +01:00
log.Fatal("Listen: ", err)
}
}
// Shutdown server gracefully
2020-02-11 01:26:09 +01:00
func (app *Application) Shutdown() error {
if app.httpServer == nil {
2020-02-10 01:24:59 +01:00
return fmt.Errorf("Server is not running")
}
2020-02-11 01:26:09 +01:00
return app.httpServer.Shutdown()
}
// Test takes a http.Request and execute a fake connection to the application
// It returns a http.Response when the connection was successfull
func (app *Application) Test(req *http.Request) (*http.Response, error) {
// Get raw http request
reqRaw, err := httputil.DumpRequest(req, true)
if err != nil {
return nil, err
}
// Setup a fiber server struct
app.httpServer = app.setupServer()
// Create fake connection
conn := &conn{}
// Pass HTTP request to conn
_, err = conn.r.Write(reqRaw)
if err != nil {
return nil, err
}
// Serve conn to server
channel := make(chan error)
go func() {
channel <- app.httpServer.ServeConn(conn)
}()
// Wait for callback
select {
case err := <-channel:
if err != nil {
return nil, err
}
// Throw timeout error after 200ms
case <-time.After(500 * time.Millisecond):
return nil, fmt.Errorf("Timeout")
}
// Get raw HTTP response
respRaw, err := ioutil.ReadAll(&conn.w)
if err != nil {
return nil, err
}
// Create buffer
reader := strings.NewReader(getString(respRaw))
buffer := bufio.NewReader(reader)
// Convert raw HTTP response to http.Response
resp, err := http.ReadResponse(buffer, req)
if err != nil {
return nil, err
}
// Return *http.Response
return resp, nil
2020-02-10 01:24:59 +01:00
}
// https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
2020-02-11 01:26:09 +01:00
func (app *Application) prefork(host string, tls ...string) {
2020-02-10 01:24:59 +01:00
// Master proc
2020-02-11 01:26:09 +01:00
if !app.child {
2020-02-10 01:24:59 +01:00
// 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 {
2020-02-11 01:26:09 +01:00
if err := app.httpServer.ServeTLS(ln, tls[0], tls[1]); err != nil {
2020-02-10 01:24:59 +01:00
log.Fatal("Listen-prefork: ", err)
}
}
2020-02-11 01:26:09 +01:00
if err := app.httpServer.Serve(ln); err != nil {
2020-02-10 01:24:59 +01:00
log.Fatal("Listen-prefork: ", err)
}
}
2020-02-11 01:26:09 +01:00
func (app *Application) setupServer() *fasthttp.Server {
2020-02-10 01:24:59 +01:00
return &fasthttp.Server{
2020-02-11 01:26:09 +01:00
Handler: app.handler,
Name: app.Server,
Concurrency: app.Engine.Concurrency,
DisableKeepalive: app.Engine.DisableKeepAlive,
ReadBufferSize: app.Engine.ReadBufferSize,
WriteBufferSize: app.Engine.WriteBufferSize,
ReadTimeout: app.Engine.ReadTimeout,
WriteTimeout: app.Engine.WriteTimeout,
IdleTimeout: app.Engine.IdleTimeout,
MaxConnsPerIP: app.Engine.MaxConnsPerIP,
MaxRequestsPerConn: app.Engine.MaxRequestsPerConn,
TCPKeepalive: app.Engine.TCPKeepalive,
TCPKeepalivePeriod: app.Engine.TCPKeepalivePeriod,
MaxRequestBodySize: app.Engine.MaxRequestBodySize,
ReduceMemoryUsage: app.Engine.ReduceMemoryUsage,
GetOnly: app.Engine.GetOnly,
DisableHeaderNamesNormalizing: app.Engine.DisableHeaderNamesNormalizing,
SleepWhenConcurrencyLimitsExceeded: app.Engine.SleepWhenConcurrencyLimitsExceeded,
NoDefaultServerHeader: app.Server == "",
NoDefaultContentType: app.Engine.NoDefaultContentType,
KeepHijackedConns: app.Engine.KeepHijackedConns,
2020-02-10 01:24:59 +01:00
}
}