diff --git a/app.go b/app.go index b839c192..310940cd 100644 --- a/app.go +++ b/app.go @@ -25,7 +25,7 @@ import ( ) // Version of current package -const Version = "1.9.5" +const Version = "1.9.6" // Map is a shortcut for map[string]interface{} type Map map[string]interface{} @@ -33,13 +33,15 @@ type Map map[string]interface{} // App denotes the Fiber application. type App struct { // Internal fields - mutex sync.Mutex // Mutual exclusion - server *fasthttp.Server // FastHTTP server - testconn *testConn // Test connection - routes [][]*Route // Route stack + testconn *testConn // Test connection + routes [][]*Route // Route stack // External fields Settings *Settings // Fiber settings + + // Fasthttp server + mutex sync.Mutex // Mutual exclusion + server *fasthttp.Server // FastHTTP server } // Settings holds is a struct holding the server settings @@ -123,13 +125,13 @@ func New(settings ...*Settings) *App { // Create settings app.Settings = new(Settings) // Set default settings - app.Settings.Prefork = isPrefork() + app.Settings.Prefork = getArgument("-prefork") app.Settings.BodyLimit = 4 * 1024 * 1024 // If settings exist, set defaults if len(settings) > 0 { app.Settings = settings[0] // Set custom settings if !app.Settings.Prefork { // Default to -prefork flag if false - app.Settings.Prefork = isPrefork() + app.Settings.Prefork = getArgument("-prefork") } if app.Settings.BodyLimit <= 0 { // Default MaxRequestBodySize app.Settings.BodyLimit = 4 * 1024 * 1024 @@ -142,7 +144,7 @@ func New(settings ...*Settings) *App { getBytes = getBytesImmutable } } - // Setup test connection + // Setup test listener app.testconn = new(testConn) // Setup server app.server = app.newServer() @@ -403,7 +405,7 @@ func (app *App) Listen(address interface{}, tlsconfig ...*tls.Config) error { ln = tls.NewListener(ln, tlsconfig[0]) } // Print listening message - if !app.Settings.DisableStartupMessage && !isChild() { + if !app.Settings.DisableStartupMessage && !getArgument("-child") { fmt.Printf(" _______ __\n ____ / ____(_) /_ ___ _____\n_____ / /_ / / __ \\/ _ \\/ ___/\n __ / __/ / / /_/ / __/ /\n /_/ /_/_.___/\\___/_/ v%s\n", Version) fmt.Printf("Started listening on %s\n", ln.Addr().String()) } @@ -487,7 +489,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 !isChild() { + if !getArgument("-child") { addr, err := net.ResolveTCPAddr("tcp", address) if err != nil { return ln, err diff --git a/app_bench_test.go b/app_bench_test.go new file mode 100644 index 00000000..f944de6a --- /dev/null +++ b/app_bench_test.go @@ -0,0 +1,5 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber diff --git a/ctx.go b/ctx.go index 7be06d45..51244414 100644 --- a/ctx.go +++ b/ctx.go @@ -69,30 +69,34 @@ var schemaDecoderForm = schema.NewDecoder() var schemaDecoderQuery = schema.NewDecoder() var cacheControlNoCacheRegexp, _ = regexp.Compile(`/(?:^|,)\s*?no-cache\s*?(?:,|$)/`) -// Ctx pool -var poolCtx = sync.Pool{ +var ctxPool = sync.Pool{ New: func() interface{} { return new(Ctx) }, } -// Acquire Ctx from pool -func acquireCtx(fctx *fasthttp.RequestCtx) *Ctx { - ctx := poolCtx.Get().(*Ctx) +// AcquireCtx from pool +func AcquireCtx(fctx *fasthttp.RequestCtx) *Ctx { + ctx := ctxPool.Get().(*Ctx) + // Set stack index ctx.index = -1 + // Set path ctx.path = getString(fctx.URI().Path()) + // Set method ctx.method = getString(fctx.Request.Header.Method()) + // Attach fasthttp request to ctx ctx.Fasthttp = fctx return ctx } -// Return Ctx to pool -func releaseCtx(ctx *Ctx) { +// ReleaseCtx to pool +func ReleaseCtx(ctx *Ctx) { + // Reset values ctx.route = nil ctx.values = nil ctx.Fasthttp = nil ctx.err = nil - poolCtx.Put(ctx) + ctxPool.Put(ctx) } // Accepts checks if the specified extensions or content types are acceptable. @@ -605,6 +609,9 @@ func (ctx *Ctx) MultipartForm() (*multipart.Form, error) { // Next executes the next method in the stack that matches the current route. // You can pass an optional error for custom error handling. func (ctx *Ctx) Next(err ...error) { + if ctx.app == nil { + return + } ctx.route = nil ctx.values = nil if len(err) > 0 { @@ -635,7 +642,7 @@ func (ctx *Ctx) Params(key string) (value string) { // Path returns the path part of the request URL. // Optionally, you could override the path. func (ctx *Ctx) Path(override ...string) string { - if len(override) > 0 { + if len(override) > 0 && ctx.app != nil { // Non strict routing if !ctx.app.Settings.StrictRouting && len(override[0]) > 1 { override[0] = strings.TrimRight(override[0], "/") @@ -722,17 +729,18 @@ func (ctx *Ctx) Render(file string, bind interface{}) error { var err error var raw []byte var html string - - if ctx.app.Settings.TemplateFolder != "" { - file = filepath.Join(ctx.app.Settings.TemplateFolder, file) + if ctx.app != nil { + if ctx.app.Settings.TemplateFolder != "" { + file = filepath.Join(ctx.app.Settings.TemplateFolder, file) + } + if ctx.app.Settings.TemplateExtension != "" { + file = file + ctx.app.Settings.TemplateExtension + } + if raw, err = ioutil.ReadFile(filepath.Clean(file)); err != nil { + return err + } } - if ctx.app.Settings.TemplateExtension != "" { - file = file + ctx.app.Settings.TemplateExtension - } - if raw, err = ioutil.ReadFile(filepath.Clean(file)); err != nil { - return err - } - if ctx.app.Settings.TemplateEngine != nil { + if ctx.app != nil && ctx.app.Settings.TemplateEngine != nil { // Custom template engine // https://github.com/gofiber/template if html, err = ctx.app.Settings.TemplateEngine(getString(raw), bind); err != nil { @@ -855,16 +863,19 @@ func (ctx *Ctx) Vary(fields ...string) { if len(fields) == 0 { return } - - h := getString(ctx.Fasthttp.Response.Header.Peek(HeaderVary)) + h := strings.ToLower(getString(ctx.Fasthttp.Response.Header.Peek(HeaderVary))) for i := range fields { - if h == "" { + fields[i] = strings.ToLower(fields[i]) + if len(h) == 0 { h += fields[i] - } else { + } else if !strings.Contains(h, " "+fields[i]) && !strings.Contains(h, fields[i]+"") || !strings.Contains(h, fields[i]+",") { + // Next developer, it's your job to optimize the following problem + // Does the header value "Accept" exist in the following header value + // "Origin, User-Agent, Accept-Encoding" + // "Accept-Encoding" contains "Accept", false positive h += ", " + fields[i] } } - ctx.Set(HeaderVary, h) } diff --git a/ctx_bench_test.go b/ctx_bench_test.go new file mode 100644 index 00000000..fd1d49d5 --- /dev/null +++ b/ctx_bench_test.go @@ -0,0 +1,436 @@ +// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️ +// 🤖 Github Repository: https://github.com/gofiber/fiber +// 📌 API Documentation: https://docs.gofiber.io + +package fiber + +import ( + "bytes" + "mime/multipart" + "strconv" + "testing" + + "github.com/valyala/fasthttp" +) + +func Benchmark_Ctx_Accepts(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + c.Fasthttp.Request.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9") + + var res string + for n := 0; n < b.N; n++ { + res = c.Accepts(".xml") + } + + assertEqual(b, ".xml", res) +} + +func Benchmark_Ctx_AcceptsCharsets(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + c.Fasthttp.Request.Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5") + + var res string + for n := 0; n < b.N; n++ { + res = c.AcceptsCharsets("utf-8") + } + + assertEqual(b, "utf-8", res) +} + +func Benchmark_Ctx_AcceptsEncodings(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + c.Fasthttp.Request.Header.Set("Accept-Encoding", "deflate, gzip;q=1.0, *;q=0.5") + + var res string + for n := 0; n < b.N; n++ { + res = c.AcceptsEncodings("gzip") + } + + assertEqual(b, "gzip", res) +} + +func Benchmark_Ctx_AcceptsLanguages(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + c.Fasthttp.Request.Header.Set("Accept-Language", "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5") + + var res string + for n := 0; n < b.N; n++ { + res = c.AcceptsEncodings("fr") + } + + assertEqual(b, "fr", res) +} + +func Benchmark_Ctx_BaseURL(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + c.Fasthttp.Request.SetHost("google.com:1337") + + var res string + for n := 0; n < b.N; n++ { + res = c.BaseURL() + } + + assertEqual(b, "http://google.com:1337", res) +} + +func Benchmark_Ctx_Body(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + c.Fasthttp.Request.SetBody([]byte("The best thing about a boolean is even if you are wrong, you are only off by a bit.")) + + var res string + for n := 0; n < b.N; n++ { + res = c.Body() + } + + assertEqual(b, "The best thing about a boolean is even if you are wrong, you are only off by a bit.", res) +} + +// TODO +// func Benchmark_Ctx_BodyParser(b *testing.B) { + +// } + +func Benchmark_Ctx_Cookies(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + c.Fasthttp.Request.Header.Set("Cookie", "john=doe") + + var res string + for n := 0; n < b.N; n++ { + res = c.Cookies("john") + } + + assertEqual(b, "doe", res) +} + +func Benchmark_Ctx_FormFile(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + ioWriter, _ := writer.CreateFormFile("file", "test") + _, _ = ioWriter.Write([]byte("hello world")) + writer.Close() + + c.Fasthttp.Request.Header.SetMethod("POST") + c.Fasthttp.Request.Header.Set("Content-Type", writer.FormDataContentType()) + c.Fasthttp.Request.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes()))) + c.Fasthttp.Request.SetBody(body.Bytes()) + + var res *multipart.FileHeader + for n := 0; n < b.N; n++ { + res, _ = c.FormFile("file") + } + + assertEqual(b, "test", res.Filename) + assertEqual(b, "application/octet-stream", res.Header["Content-Type"][0]) + assertEqual(b, int64(11), res.Size) +} + +// func Benchmark_Ctx_FormValue(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Fresh(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Get(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Hostname(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_IP(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_IPs(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Is(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Locals(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Method(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_MultipartForm(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_OriginalURL(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Params(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Path(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Query(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Range(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Route(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_SaveFile(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Secure(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Stale(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Subdomains(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Append(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Attachment(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_ClearCookie(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Cookie(b *testing.B) { +// TODO +// } + +// func Benchmark_Ctx_Download(b *testing.B) { +// TODO +// } + +func Benchmark_Ctx_Format(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + c.Fasthttp.Request.Header.Set("Accept", "text/html") + + for n := 0; n < b.N; n++ { + c.Format("Hello, World!") + } + + assertEqual(b, "
Hello, World!
", string(c.Fasthttp.Response.Body())) +} + +func Benchmark_Ctx_JSON(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + type SomeStruct struct { + Name string + Age uint8 + } + + data := SomeStruct{ + Name: "Grame", + Age: 20, + } + + for n := 0; n < b.N; n++ { + c.JSON(data) + } + + assertEqual(b, `{"Name":"Grame","Age":20}`, string(c.Fasthttp.Response.Body())) +} + +func Benchmark_Ctx_JSONP(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + type SomeStruct struct { + Name string + Age uint8 + } + + data := SomeStruct{ + Name: "Grame", + Age: 20, + } + + for n := 0; n < b.N; n++ { + c.JSONP(data, "john") + } + + assertEqual(b, `john({"Name":"Grame","Age":20});`, string(c.Fasthttp.Response.Body())) +} + +func Benchmark_Ctx_Links(b *testing.B) { + c := AcquireCtx(&fasthttp.RequestCtx{}) + defer ReleaseCtx(c) + + for n := 0; n < b.N; n++ { + c.Links( + "http://api.example.com/users?page=2", "next", + "http://api.example.com/users?page=5", "last", + ) + } + assertEqual(b, `