From 6cd50c9e7b22501c302f1e6d5299deb5fb7e96c2 Mon Sep 17 00:00:00 2001 From: Fenny <25108519+Fenny@users.noreply.github.com> Date: Sat, 23 May 2020 09:25:18 +0200 Subject: [PATCH] Add RenderEngine & DisableHeaderNormalizing - RenderEngine setting - DisableHeaderNormalizing setting - Move sync pool to *App - Return *Route Co-Authored-By: RW --- app.go | 321 ++++++++++++++++++++++----------------------------------- 1 file changed, 123 insertions(+), 198 deletions(-) diff --git a/app.go b/app.go index 1d410d32..20ecaba5 100644 --- a/app.go +++ b/app.go @@ -21,6 +21,7 @@ import ( "sync" "time" + utils "github.com/gofiber/utils" fasthttp "github.com/valyala/fasthttp" ) @@ -32,120 +33,161 @@ type Map map[string]interface{} // App denotes the Fiber application. type App struct { - // Layer stack - stack [][]*Layer + mutex sync.Mutex + // Route stack + stack [][]*Route + // Ctx pool + pool sync.Pool // Fasthttp server server *fasthttp.Server - mutex sync.Mutex // App settings Settings *Settings } +// Enables automatic redirection if the current route can't be matched but a handler for the path with (without) the trailing slash exists. For example if /foo/ is requested but a route only exists for /foo, the client is redirected to /foo with http status code 301 for GET requests and 308 for all other request methods. + // Settings holds is a struct holding the server settings type Settings struct { // This will spawn multiple Go processes listening on the same port Prefork bool // default: false + // Enable strict routing. When enabled, the router treats "/foo" and "/foo/" as different. - StrictRouting bool // default: false - // Enable case sensitivity. When enabled, "/Foo" and "/foo" are different routes. - CaseSensitive bool // default: false + // By default this is disabled and both "/foo" and "/foo/" will execute the same handler. + StrictRouting bool + + // Enable case sensitive routing. When enabled, "/FoO" and "/foo" are different routes. + // By default this is disabled and both "/FoO" and "/foo" will execute the same handler. + CaseSensitive bool + // Enables the "Server: value" HTTP header. ServerHeader string // default: "" + // Enables handler values to be immutable even if you return from handler Immutable bool // default: false + // Enable or disable ETag header generation, since both weak and strong etags are generated // using the same hashing method (CRC-32). Weak ETags are the default when enabled. // Optional. Default value false ETag bool + // Max body size that the server accepts BodyLimit int // default: 4 * 1024 * 1024 + // Maximum number of concurrent connections. Concurrency int // default: 256 * 1024 + // Disable keep-alive connections, the server will close incoming connections after sending the first response to client DisableKeepalive bool // default: false + // When set to true causes the default date header to be excluded from the response. DisableDefaultDate bool // default: false + // When set to true, causes the default Content-Type header to be excluded from the Response. DisableDefaultContentType bool // default: false - DisableHeaderNormalizing bool // default: false + + // By default all header names are normalized: conteNT-tYPE -> Content-Type + DisableHeaderNormalizing bool // default: false + // When set to true, it will not print out the fiber ASCII and "listening" on message DisableStartupMessage bool - // Folder containing template files - TemplateFolder string // default: "" - // Template engine: html, amber, handlebars , mustache or pug - TemplateEngine func(raw string, bind interface{}) (string, error) // default: nil - // Extension for the template files - TemplateExtension string // default: "" + + // RenderEngine is the interface that wraps the Render function. + RenderEngine RenderEngine + // The amount of time allowed to read the full request including body. ReadTimeout time.Duration // default: unlimited + // The maximum duration before timing out writes of the response. WriteTimeout time.Duration // default: unlimited + // The maximum amount of time to wait for the next request when keep-alive is enabled. IdleTimeout time.Duration // default: unlimited -} -// Group struct -type Group struct { - prefix string - app *App + // TODO: v1.11 + // The router executes the same handler by default if StrictRouting or CaseSensitive is disabled. + // Enabling RedirectFixedPath will change this behaviour into a client redirect to the original route path. + // Using the status code 301 for GET requests and 308 for all other request methods. + // RedirectFixedPath bool } // Static struct type Static struct { - // Transparently compresses responses if set to true // This works differently than the github.com/gofiber/compression middleware // The server tries minimizing CPU usage by caching compressed files. // It adds ".fiber.gz" suffix to the original file name. // Optional. Default value false Compress bool + // Enables byte range requests if set to true. // Optional. Default value false ByteRange bool + // Enable directory browsing. // Optional. Default value false. Browse bool + // Index file for serving a directory. // Optional. Default value "index.html". Index string } +// TODO: v1.11 Potential feature to get all registered routes +// func (app *App) Routes(print ...bool) map[string][]string { +// routes := make(map[string][]string) +// for i := range app.stack { +// method := intMethod[i] +// routes[method] = []string{} +// for k := range app.stack[i] { +// routes[method] = append(routes[method], app.stack[i][k].Path) +// } +// } +// if len(print) > 0 && print[0] { +// b, _ := json.MarshalIndent(routes, "", " ") +// fmt.Print(string(b)) +// } +// return routes +// } + // New creates a new Fiber named instance. // You can pass optional settings when creating a new instance. func New(settings ...*Settings) *App { - schemaDecoderForm.SetAliasTag("form") - schemaDecoderForm.IgnoreUnknownKeys(true) - schemaDecoderQuery.SetAliasTag("query") - schemaDecoderQuery.IgnoreUnknownKeys(true) - // Create app - app := new(App) - // Create route stack - app.stack = make([][]*Layer, len(methodINT)) - // Create settings - app.Settings = new(Settings) - // Set default settings - app.Settings.Prefork = getArgument("-prefork") - app.Settings.BodyLimit = 4 * 1024 * 1024 - // If settings exist, set defaults + // Create a new app + app := &App{ + // Create router stack + stack: make([][]*Route, len(methodINT)), + // Create Ctx pool + pool: sync.Pool{ + New: func() interface{} { + return new(Ctx) + }, + }, + // Set default settings + Settings: &Settings{ + Prefork: utils.GetArgument("-prefork"), + BodyLimit: 4 * 1024 * 1024, + Concurrency: 256 * 1024, + }, + } + // Overwrite settings if provided if len(settings) > 0 { - app.Settings = settings[0] // Set custom settings + app.Settings = settings[0] if !app.Settings.Prefork { // Default to -prefork flag if false - app.Settings.Prefork = getArgument("-prefork") + app.Settings.Prefork = utils.GetArgument("-prefork") } - if app.Settings.BodyLimit <= 0 { // Default MaxRequestBodySize + if app.Settings.BodyLimit <= 0 { app.Settings.BodyLimit = 4 * 1024 * 1024 } if app.Settings.Concurrency <= 0 { app.Settings.Concurrency = 256 * 1024 } - if app.Settings.Immutable { // Replace unsafe conversion funcs - getString = getStringImmutable + // Replace unsafe conversion functions + if app.Settings.Immutable { getBytes = getBytesImmutable + getString = getStringImmutable } } - // Create server - app.init() - // Return application - return app + // Initialize app + return app.init() } // Use registers a middleware route. @@ -155,7 +197,7 @@ func New(settings ...*Settings) *App { // - app.Use(handler) // - app.Use("/api", handler) // - app.Use("/api", handler, handler) -func (app *App) Use(args ...interface{}) *App { +func (app *App) Use(args ...interface{}) *Route { var prefix string var handlers []func(*Ctx) @@ -172,194 +214,76 @@ func (app *App) Use(args ...interface{}) *App { return app.register("USE", prefix, handlers...) } -// All ... -func (app *App) All(path string, handlers ...func(*Ctx)) *App { - for m := range methodINT { - app.register(m, path, handlers...) - } - return app -} - // Get ... -func (app *App) Get(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodGet, path, handlers...) +func (app *App) Get(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodGet, path, handlers...) } // Head ... -func (app *App) Head(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodHead, path, handlers...) +func (app *App) Head(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodHead, path, handlers...) } // Post ... -func (app *App) Post(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodPost, path, handlers...) +func (app *App) Post(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodPost, path, handlers...) } // Put ... -func (app *App) Put(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodPut, path, handlers...) +func (app *App) Put(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodPut, path, handlers...) } // Delete ... -func (app *App) Delete(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodDelete, path, handlers...) +func (app *App) Delete(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodDelete, path, handlers...) } // Connect ... -func (app *App) Connect(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodConnect, path, handlers...) +func (app *App) Connect(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodConnect, path, handlers...) } // Options ... -func (app *App) Options(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodOptions, path, handlers...) +func (app *App) Options(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodOptions, path, handlers...) } // Trace ... -func (app *App) Trace(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodTrace, path, handlers...) +func (app *App) Trace(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodTrace, path, handlers...) } // Patch ... -func (app *App) Patch(path string, handlers ...func(*Ctx)) *App { - return app.register(MethodPatch, path, handlers...) +func (app *App) Patch(path string, handlers ...func(*Ctx)) *Route { + return app.Add(MethodPatch, path, handlers...) } // Add ... -func (app *App) Add(method, path string, handlers ...func(*Ctx)) *App { - method = toUpper(method) - if methodINT[method] == 0 && method != MethodGet { - log.Fatalf("Add: Invalid HTTP method %s", method) - } +func (app *App) Add(method, path string, handlers ...func(*Ctx)) *Route { return app.register(method, path, handlers...) } +// Static ... +func (app *App) Static(prefix, root string, config ...Static) *Route { + return app.registerStatic(prefix, root, config...) +} + +// All ... +func (app *App) All(path string, handlers ...func(*Ctx)) []*Route { + routes := make([]*Route, len(methodINT)) + for method, i := range methodINT { + routes[i] = app.Add(method, path, handlers...) + } + return routes +} + // Group is used for Routes with common prefix to define a new sub-router with optional middleware. func (app *App) Group(prefix string, handlers ...func(*Ctx)) *Group { if len(handlers) > 0 { app.register("USE", prefix, handlers...) } - return &Group{ - prefix: prefix, - app: app, - } -} - -// Static registers a new route with path prefix to serve static files from the provided root directory. -func (app *App) Static(prefix, root string, config ...Static) *App { - app.registerStatic(prefix, root, config...) - return app -} - -// Group is used for Routes with common prefix to define a new sub-router with optional middleware. -func (grp *Group) Group(prefix string, handlers ...func(*Ctx)) *Group { - prefix = getGroupPath(grp.prefix, prefix) - if len(handlers) > 0 { - grp.app.register("USE", prefix, handlers...) - } - return &Group{ - prefix: prefix, - app: grp.app, - } -} - -// Static : https://fiber.wiki/application#static -func (grp *Group) Static(prefix, root string, config ...Static) *Group { - prefix = getGroupPath(grp.prefix, prefix) - grp.app.registerStatic(prefix, root, config...) - return grp -} - -// Use registers a middleware route. -// Middleware matches requests beginning with the provided prefix. -// Providing a prefix is optional, it defaults to "/" -func (grp *Group) Use(args ...interface{}) *Group { - var path = "" - var handlers []func(*Ctx) - for i := 0; i < len(args); i++ { - switch arg := args[i].(type) { - case string: - path = arg - case func(*Ctx): - handlers = append(handlers, arg) - default: - //log.Fatalf("Invalid Use() arguments, must be (prefix, handler) or (handler)") - } - } - grp.app.register("USE", getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Add : https://fiber.wiki/application#http-methods -func (grp *Group) Add(method, path string, handlers ...func(*Ctx)) *Group { - method = toUpper(method) - if methodINT[method] == 0 && method != MethodGet { - log.Fatalf("Add: Invalid HTTP method %s", method) - } - grp.app.register(method, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Connect : https://fiber.wiki/application#http-methods -func (grp *Group) Connect(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodConnect, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Put : https://fiber.wiki/application#http-methods -func (grp *Group) Put(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodPut, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Post : https://fiber.wiki/application#http-methods -func (grp *Group) Post(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodPost, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Delete : https://fiber.wiki/application#http-methods -func (grp *Group) Delete(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodDelete, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Head : https://fiber.wiki/application#http-methods -func (grp *Group) Head(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodHead, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Patch : https://fiber.wiki/application#http-methods -func (grp *Group) Patch(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodPatch, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Options : https://fiber.wiki/application#http-methods -func (grp *Group) Options(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodOptions, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Trace : https://fiber.wiki/application#http-methods -func (grp *Group) Trace(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodTrace, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// Get : https://fiber.wiki/application#http-methods -func (grp *Group) Get(path string, handlers ...func(*Ctx)) *Group { - grp.app.register(MethodGet, getGroupPath(grp.prefix, path), handlers...) - return grp -} - -// All matches all HTTP methods and complete paths -func (grp *Group) All(path string, handlers ...func(*Ctx)) *Group { - for m := range methodINT { - grp.app.register(m, getGroupPath(grp.prefix, path), handlers...) - } - return grp + return &Group{prefix: prefix, app: app} } // Serve can be used to pass a custom listener @@ -415,7 +339,7 @@ func (app *App) Listen(address interface{}, tlsconfig ...*tls.Config) error { ln = tls.NewListener(ln, tlsconfig[0]) } // Print listening message - if !app.Settings.DisableStartupMessage && !getArgument("-child") { + if !app.Settings.DisableStartupMessage && !utils.GetArgument("-child") { fmt.Printf(" _______ __\n ____ / ____(_) /_ ___ _____\n_____ / /_ / / __ \\/ _ \\/ ___/\n __ / __/ / / /_/ / __/ /\n /_/ /_/_.___/\\___/_/ v%s\n", Version) fmt.Printf("Started listening on %s\n", ln.Addr().String()) } @@ -497,7 +421,7 @@ func (app *App) Test(request *http.Request, msTimeout ...int) (*http.Response, e // Sharding: https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ func (app *App) prefork(address string) (ln net.Listener, err error) { // Master proc - if !getArgument("-child") { + if !utils.GetArgument("-child") { addr, err := net.ResolveTCPAddr("tcp", address) if err != nil { return ln, err @@ -543,7 +467,7 @@ func (dl *disableLogger) Printf(format string, args ...interface{}) { // fmt.Println(fmt.Sprintf(format, args...)) } -func (app *App) init() { +func (app *App) init() *App { app.mutex.Lock() if app.server == nil { app.server = &fasthttp.Server{ @@ -581,4 +505,5 @@ func (app *App) init() { app.server.WriteTimeout = app.Settings.WriteTimeout app.server.IdleTimeout = app.Settings.IdleTimeout app.mutex.Unlock() + return app }