diff --git a/application.go b/application.go index 3bc3d493..6abf8c92 100644 --- a/application.go +++ b/application.go @@ -10,6 +10,8 @@ package fiber import ( "flag" "time" + + "github.com/valyala/fasthttp" ) const ( @@ -33,7 +35,8 @@ var ( // Fiber structure type Fiber struct { // Server name header - Server string + Server string + httpServer *fasthttp.Server // Show fiber banner Banner bool // https://github.com/valyala/fasthttp/blob/master/server.go#L150 diff --git a/listen.go b/listen.go index 8d465840..2d04b490 100644 --- a/listen.go +++ b/listen.go @@ -21,6 +21,11 @@ import ( "github.com/valyala/fasthttp/reuseport" ) +// Shutdown server gracefully +func (r *Fiber) Shutdown() error { + return r.httpServer.Shutdown() +} + // Listen : https://gofiber.github.io/fiber/#/application?id=listen func (r *Fiber) Listen(address interface{}, tls ...string) { host := "" @@ -36,7 +41,7 @@ func (r *Fiber) Listen(address interface{}, tls ...string) { log.Fatal("Listen: Host must be an INT port or STRING address") } // Create fasthttp server - server := r.setupServer() + r.httpServer = r.setupServer() // Prefork enabled if r.Prefork && runtime.NumCPU() > 1 { @@ -44,7 +49,7 @@ func (r *Fiber) Listen(address interface{}, tls ...string) { cores := fmt.Sprintf("%s\x1b[1;30m %v cores", host, runtime.NumCPU()) fmt.Printf(banner, Version, " prefork", "Express on steroids", cores) } - r.prefork(server, host, tls...) + r.prefork(host, tls...) } // Prefork disabled @@ -59,18 +64,18 @@ func (r *Fiber) Listen(address interface{}, tls ...string) { // enable TLS/HTTPS if len(tls) > 1 { - if err := server.ServeTLS(ln, tls[0], tls[1]); err != nil { + if err := r.httpServer.ServeTLS(ln, tls[0], tls[1]); err != nil { log.Fatal("Listen: ", err) } } - if err := server.Serve(ln); err != nil { + if err := r.httpServer.Serve(ln); err != nil { log.Fatal("Listen: ", err) } } // https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/ -func (r *Fiber) prefork(server *fasthttp.Server, host string, tls ...string) { +func (r *Fiber) prefork(host string, tls ...string) { // Master proc if !r.child { // Create babies @@ -106,12 +111,12 @@ func (r *Fiber) prefork(server *fasthttp.Server, host string, tls ...string) { // enable TLS/HTTPS if len(tls) > 1 { - if err := server.ServeTLS(ln, tls[0], tls[1]); err != nil { + if err := r.httpServer.ServeTLS(ln, tls[0], tls[1]); err != nil { log.Fatal("Listen-prefork: ", err) } } - if err := server.Serve(ln); err != nil { + if err := r.httpServer.Serve(ln); err != nil { log.Fatal("Listen-prefork: ", err) } } diff --git a/listen_test.go b/listen_test.go new file mode 100644 index 00000000..63d784da --- /dev/null +++ b/listen_test.go @@ -0,0 +1,23 @@ +package fiber + +import ( + "testing" + "time" +) + +func Test_Connect(t *testing.T) { + app := New() + app.Banner = false + app.Get("/", func(c *Ctx) { + + }) + go func() { + app.Listen(":8085") + }() + time.Sleep(1 * time.Second) + err := app.Shutdown() + if err != nil { + t.Fatalf(`%s: Failed to shutdown server %v`, t.Name(), err) + } + return +} diff --git a/methods_test.go b/methods_test.go new file mode 100644 index 00000000..6d8f32db --- /dev/null +++ b/methods_test.go @@ -0,0 +1,109 @@ +package fiber + +import ( + "net/http" + "testing" +) + +func Test_Methods(t *testing.T) { + app := New() + app.Connect(func(c *Ctx) {}) + app.Put(func(c *Ctx) {}) + app.Post(func(c *Ctx) {}) + app.Delete(func(c *Ctx) {}) + app.Head(func(c *Ctx) {}) + app.Patch(func(c *Ctx) {}) + app.Options(func(c *Ctx) {}) + app.Trace(func(c *Ctx) {}) + app.Get(func(c *Ctx) {}) + app.All("/special", func(c *Ctx) {}) + app.Use("/special/john", func(c *Ctx) {}) + req, _ := http.NewRequest("CONNECT", "/", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("PUT", "/", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("POST", "/", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("DELETE", "/", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("HEAD", "/", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("PATCH", "/", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("OPTIONS", "/", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("TRACE", "/", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("GET", "/", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("GET", "/special", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + req, _ = http.NewRequest("GET", "/special/john", nil) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } +} diff --git a/request_test.go b/request_test.go index 84280a91..45f09c2f 100644 --- a/request_test.go +++ b/request_test.go @@ -270,13 +270,7 @@ func Test_Get(t *testing.T) { if result != expect { t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } - expect = "Hello, World!" - c.Set("Accept-Charset", expect) - result = c.Get("Accept-Charset") - if result != expect { - t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) - } - expect = "Cookie" + expect = "Monster" result = c.Get("referrer") if result != expect { t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) @@ -284,7 +278,7 @@ func Test_Get(t *testing.T) { }) req, _ := http.NewRequest("GET", "/test", nil) req.Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5") - req.Header.Set("Referer", "Cookie") + req.Header.Set("Referer", "Monster") resp, err := app.Test(req) if err != nil { t.Fatalf(`%s: %s`, t.Name(), err) diff --git a/response.go b/response.go index 06d49663..4a71839f 100644 --- a/response.go +++ b/response.go @@ -24,7 +24,7 @@ func (ctx *Ctx) Append(field string, values ...string) { if len(values) == 0 { return } - h := ctx.Get(field) + h := getString(ctx.Fasthttp.Response.Header.Peek(field)) for i := range values { if h == "" { h += values[i] @@ -50,11 +50,12 @@ func (ctx *Ctx) Attachment(name ...string) { func (ctx *Ctx) ClearCookie(name ...string) { if len(name) > 0 { for i := range name { + //ctx.Fasthttp.Request.Header.DelAllCookies() ctx.Fasthttp.Response.Header.DelClientCookie(name[i]) } return } - + //ctx.Fasthttp.Response.Header.DelAllCookies() ctx.Fasthttp.Request.Header.VisitAllCookie(func(k, v []byte) { ctx.Fasthttp.Response.Header.DelClientCookie(getString(k)) }) @@ -330,7 +331,6 @@ func (ctx *Ctx) SendString(body string) { // Set : https://gofiber.github.io/fiber/#/context?id=set func (ctx *Ctx) Set(key string, val string) { - ctx.Fasthttp.Request.Header.SetCanonical(getBytes(key), getBytes(val)) ctx.Fasthttp.Response.Header.SetCanonical(getBytes(key), getBytes(val)) } @@ -352,14 +352,16 @@ func (ctx *Ctx) Vary(fields ...string) { return } - vary := ctx.Get(fasthttp.HeaderVary) - for _, field := range fields { - if !strings.Contains(vary, field) { - vary += ", " + field + h := getString(ctx.Fasthttp.Response.Header.Peek(fasthttp.HeaderVary)) + for i := range fields { + if h == "" { + h += fields[i] + } else { + h += ", " + fields[i] } } - ctx.Set(fasthttp.HeaderVary, vary) + ctx.Set(fasthttp.HeaderVary, h) } // Write : https://gofiber.github.io/fiber/#/context?id=write diff --git a/response_test.go b/response_test.go index d677455e..abf1cc88 100644 --- a/response_test.go +++ b/response_test.go @@ -1,7 +1,9 @@ package fiber import ( + "io/ioutil" "net/http" + "strings" "testing" ) @@ -23,5 +25,543 @@ func Test_Append(t *testing.T) { t.Fatalf(`%s: Expecting %s`, t.Name(), "X-Test: hel, lo, world") } } +func Test_Attachment(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Attachment("./static/img/logo.png") + }) + req, _ := http.NewRequest("GET", "/test", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if resp.Header.Get("Content-Disposition") != `attachment; filename="logo.png"` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `attachment; filename="logo.png"`) + } + if resp.Header.Get("Content-Type") != "image/png" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "image/png") + } +} +func Test_ClearCookie(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.ClearCookie() + }) + app.Get("/test2", func(c *Ctx) { + c.ClearCookie("john") + }) + req, _ := http.NewRequest("GET", "/test", nil) + req.AddCookie(&http.Cookie{Name: "john", Value: "doe"}) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if !strings.Contains(resp.Header.Get("Set-Cookie"), "expires=") { + t.Fatalf(`%s: Expecting %s`, t.Name(), "expires=") + } + req, _ = http.NewRequest("GET", "/test2", nil) + req.AddCookie(&http.Cookie{Name: "john", Value: "doe"}) + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if !strings.Contains(resp.Header.Get("Set-Cookie"), "expires=") { + t.Fatalf(`%s: Expecting %s`, t.Name(), "expires=") + } +} +func Test_Cookie(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + options := &Cookie{ + MaxAge: 60, + Domain: "example.com", + Path: "/", + HTTPOnly: true, + Secure: false, + SameSite: "lax", + } + c.Cookie("name", "john", options) + }) + req, _ := http.NewRequest("GET", "http://example.com/test", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if !strings.Contains(resp.Header.Get("Set-Cookie"), "name=john; max-age=60; domain=example.com; path=/; HttpOnly; SameSite=Lax") { + t.Fatalf(`%s: Expecting %s`, t.Name(), "name=john; max-age=60; domain=example.com; path=/; HttpOnly; SameSite=Lax") + } +} +func Test_Download(t *testing.T) { + // TODO +} +func Test_Format(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Format("Hello, World!") + }) + app.Get("/test2", func(c *Ctx) { + c.Format("Hello, World!") + }) + req, _ := http.NewRequest("GET", "http://example.com/test", nil) + req.Header.Set("Accept", "text/html") + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != "
Hello, World!
" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "Hello, World!
") + } -// TODO: add all functions from response.go + req, _ = http.NewRequest("GET", "http://example.com/test2", nil) + req.Header.Set("Accept", "application/json") + resp, err = app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `"Hello, World!"` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `"Hello, World!"`) + } +} +func Test_HeadersSent(t *testing.T) { + // TODO +} +func Test_JSON(t *testing.T) { + type SomeStruct struct { + Name string + Age uint8 + } + app := New() + app.Get("/test", func(c *Ctx) { + c.Json("") + data := SomeStruct{ + Name: "Grame", + Age: 20, + } + c.JSON(data) + }) + req, _ := http.NewRequest("GET", "http://example.com/test", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if resp.Header.Get("Content-Type") != "application/json" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "application/json") + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `{"Name":"Grame","Age":20}` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `{"Name":"Grame","Age":20}`) + } +} +func Test_JSONBytes(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.JsonBytes([]byte("")) + c.JSONBytes([]byte(`{"Name":"Grame","Age":20}`)) + }) + req, _ := http.NewRequest("GET", "http://example.com/test", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if resp.Header.Get("Content-Type") != "application/json" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "application/json") + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `{"Name":"Grame","Age":20}` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `{"Name":"Grame","Age":20}`) + } +} +func Test_JSONP(t *testing.T) { + type SomeStruct struct { + Name string + Age uint8 + } + app := New() + app.Get("/test", func(c *Ctx) { + c.Jsonp("") + data := SomeStruct{ + Name: "Grame", + Age: 20, + } + c.JSONP(data, "alwaysjohn") + }) + req, _ := http.NewRequest("GET", "http://example.com/test", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if resp.Header.Get("Content-Type") != "application/javascript" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "application/javascript") + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `alwaysjohn({"Name":"Grame","Age":20});` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `alwaysjohn({"Name":"Grame","Age":20});`) + } +} +func Test_JSONString(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.JsonString("") + c.JSONString(`{"Name":"Grame","Age":20}`) + }) + req, _ := http.NewRequest("GET", "http://example.com/test", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if resp.Header.Get("Content-Type") != "application/json" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "application/json") + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `{"Name":"Grame","Age":20}` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `{"Name":"Grame","Age":20}`) + } +} +func Test_Links(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Links( + "http://api.example.com/users?page=2", "next", + "http://api.example.com/users?page=5", "last", + ) + }) + req, _ := http.NewRequest("GET", "http://example.com/test", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if resp.Header.Get("Link") != `