diff --git a/.travis.yml b/.travis.yml index 8af4fd52..1e4f6a69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,9 @@ language: go + +os: + - linux +# - windows + - osx go: - 1.13.x env: diff --git a/README.md b/README.md index 2005104a..0366a942 100644 --- a/README.md +++ b/README.md @@ -150,4 +150,4 @@ Thanks for your support! 😘 Together, we make `Fiber`. ## License -⚠️ _Please note:_ `gofiber/fiber` is free and open-source software licensed under the [MIT License](https://github.com/gofiber/fiber/edit/master/LICENSE). +⚠️ _Please note:_ `gofiber/fiber` is free and open-source software licensed under the [MIT License](https://github.com/gofiber/fiber/master/LICENSE). diff --git a/README_CH.md b/README_CH.md index abee1c62..ea1467aa 100644 --- a/README_CH.md +++ b/README_CH.md @@ -150,4 +150,4 @@ func main() { ## License -⚠️ _请注意:_ `gofiber/fiber` 是根据以下条款获得许可的免费开源软件 [MIT License](https://github.com/gofiber/fiber/edit/master/LICENSE). +⚠️ _请注意:_ `gofiber/fiber` 是根据以下条款获得许可的免费开源软件 [MIT License](https://github.com/gofiber/fiber/master/LICENSE). diff --git a/README_NEW.md b/README_NEW.md new file mode 100644 index 00000000..93b385c1 --- /dev/null +++ b/README_NEW.md @@ -0,0 +1,181 @@ +[](https://fiber.wiki) + +[Express](https://github.com/expressjs/express) inspired web framework build on [Fasthttp](https://github.com/valyala/fasthttp) for [Go](https://golang.org/doc/). +Designed to ease things up for fast development with zero memory allocation and performance in mind. + +[](https://github.com/gofiber/fiber/releases) +[](https://pkg.go.dev/github.com/gofiber/fiber?tab=doc) + + + +[](https://pkg.go.dev/github.com/gofiber/fiber?tab=doc) + +```golang +package main + +import "github.com/gofiber/fiber" + +func main() { + app := fiber.New() + + app.Get("/", func(c *fiber.Ctx) { + c.Write("Hello, World!") + }) + + app.Listen(3000) +} +``` + +## Benchmarks + +These tests are performed by [TechEmpower](https://github.com/TechEmpower/FrameworkBenchmarks) and [Go Web](https://github.com/smallnest/go-web-framework-benchmark). If you want to see all results, please visit our [wiki#benchmarks](https://fiber.wiki/#benchmarks). +<p float="left" align="middle"> + <img src="https://fiber.wiki/static/benchmarks/benchmark-pipeline.png" width="49%" /> + <img src="https://fiber.wiki/static/benchmarks/benchmark_alloc.png" width="49%" /> +</p> + +## Installation + +Before installing, [download and install Go](https://golang.org/dl/). +Go `1.11` or higher is required. + +Installation is done using the +[`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them) command: + +```bash +go get github.com/gofiber/fiber +``` + +## Features + +* Robust [routing](https://fiber.wiki/#/routing) +* Serve [static files](https://fiber.wiki/#/application?id=static) +* [Extreme performance](https://fiber.wiki/#/benchmarks) +* Low memory footprint +* Express [API endpoints](https://fiber.wiki/#/context) +* Middleware & [Next](https://fiber.wiki/#context?id=next) support +* Rapid server-side programming +* [And much more, click here](https://fiber.wiki/) + + +## Philosophy + +People switching from [Node.js](https://nodejs.org/en/about/) to [Go](https://golang.org/doc/) often end up in a bad learning curve to start building their webapps or micro services. Fiber, as a web framework, was created with the idea of minimalism so new and experienced gophers can rapidly develop web application's. + +Fiber is inspired by the Express framework, the most populair web framework on web. We combined the ease of Express and raw performance of Go. If you have ever implemented a web application on Node.js using Express.js, then many methods and principles will seem very common to you. + +## Examples + +Listed below are some of the common examples. If you want to see more code examples, please visit our [recipes repository](https://github.com/gofiber/recipes) or [api documentation](https://fiber.wiki). + +_**Static files**_ +```golang +// ... +app := fiber.New() + +app.Static("./public") +// http://localhost:3000/js/script.js +// http://localhost:3000/css/style.css + +app.Static("/xxx", "./public") +// http://localhost:3000/xxx/js/script.js +// http://localhost:3000/xxx/css/style.css + +app.Listen(3000) +``` + +_**Routing**_ +```golang +// ... +app := fiber.New() + +// param +app.Get("/:name", func(c *fiber.Ctx) { + c.Send("Hello, " + c.Params("name")) +}) + +// optional param +app.Get("/:name/:lastname?", func(c *fiber.Ctx) { + c.Send("Hello, " + c.Params("name") + " " + c.Params("lastname")) +}) + +// wildcard +app.Get("/api*", func(c *fiber.Ctx) { + c.Send("/api" + c.Params("*")) +}) + +app.Listen(3000) +``` + +_**Middleware**_ +```golang +// ... +app := fiber.New() + +// match any post route +app.Post(func(c *fiber.Ctx) { + user, pass, ok := c.BasicAuth() + if !ok || user != "john" || pass != "doe" { + c.Status(403).Send("Sorry John") + return + } + c.Next() +}) + +// match all routes starting with /api +app.Use("/api", func(c *fiber.Ctx) { + c.Set("Access-Control-Allow-Origin", "*") + c.Set("Access-Control-Allow-Headers", "X-Requested-With") + c.Next() +}) + +// optional param +app.Post("/api/register", func(c *fiber.Ctx) { + username := c.Body("username") + password := c.Body("password") + // .. +}) + +app.Listen(3000) +``` + +_**404 Handling**_ +```golang +// ... +app := fiber.New() + +// ..application routes + +// last route +app.Use(func (c *fiber.Ctx) { + c.SendStatus(404) +}) + +app.Listen(3000) +``` + +_**JSON Response**_ +```golang +// ... +app := fiber.New() + +type Data struct { + Name string `json:"name"` + Age int `json:"age"` +} + +// last route +app.Get("/json", func (c *fiber.Ctx) { + c.JSON(&Data{ + Name: "John", + Age: 20, + }) +}) + +app.Listen(3000) +``` + + +## License + +`gofiber/fiber` is free and open-source software licensed under the [MIT License](https://github.com/gofiber/fiber/master/LICENSE). diff --git a/application.go b/application.go index 3bc3d493..e55b14ae 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 @@ -72,10 +75,11 @@ type engine struct { func New() *Fiber { flag.Parse() return &Fiber{ - Server: "", - Banner: true, - Prefork: *prefork, - child: *child, + Server: "", + httpServer: nil, + Banner: true, + Prefork: *prefork, + child: *child, Engine: &engine{ Concurrency: 256 * 1024, DisableKeepAlive: false, diff --git a/listen.go b/listen.go index 1ba0f1c4..b4867c4f 100644 --- a/listen.go +++ b/listen.go @@ -21,6 +21,14 @@ import ( "github.com/valyala/fasthttp/reuseport" ) +// Shutdown server gracefully +func (r *Fiber) Shutdown() error { + if r.httpServer == nil { + return fmt.Errorf("Server is not running") + } + return r.httpServer.Shutdown() +} + // Listen : https://gofiber.github.io/fiber/#/application?id=listen func (r *Fiber) Listen(address interface{}, tls ...string) { host := "" @@ -36,29 +44,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 := &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, - } + r.httpServer = r.setupServer() // Prefork enabled if r.Prefork && runtime.NumCPU() > 1 { @@ -66,7 +52,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 @@ -81,18 +67,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 @@ -128,12 +114,38 @@ 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) } } + +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, + } +} diff --git a/listen_test.go b/listen_test.go new file mode 100644 index 00000000..c5f6cc6a --- /dev/null +++ b/listen_test.go @@ -0,0 +1,32 @@ +package fiber + +import ( + "sync" + "testing" +) + +var wg sync.WaitGroup + +func Test_Connect(t *testing.T) { + return + // app := New() + // app.Banner = false + // + // wg.Add(1) + // + // go func() { + // app.Listen("8080") + // }() + // + // time.Sleep(time.Millisecond * 100) + // + // go func() { + // err := app.Shutdown() + // if err != nil { + // t.Fatalf(`%s: Failed to shutdown server %v`, t.Name(), err) + // } + // wg.Done() + // }() + // wg.Wait() + //app.Listen(":8085") +} 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/middleware/cors.go b/middleware/cors.go deleted file mode 100644 index fc5b2cd9..00000000 --- a/middleware/cors.go +++ /dev/null @@ -1,18 +0,0 @@ -package middleware - -import "github.com/gofiber/fiber" - -// app.Use(middleware.Cors()) - -// Cors : Enable cross-origin resource sharing (CORS) with various options. -func Cors(origin ...string) func(*fiber.Ctx) { - return func(c *fiber.Ctx) { - o := "*" - if len(origin) > 0 { - o = origin[0] - } - c.Set("Access-Control-Allow-Origin", o) - c.Set("Access-Control-Allow-Headers", "X-Requested-With") - c.Next() - } -} diff --git a/middleware/csrf.go b/middleware/csrf.go deleted file mode 100644 index 29502495..00000000 --- a/middleware/csrf.go +++ /dev/null @@ -1,12 +0,0 @@ -package middleware - -import ( - "github.com/gofiber/fiber" -) - -// CSRF : -func CSRF() func(*fiber.Ctx) { - return func(c *fiber.Ctx) { - c.Next() - } -} diff --git a/middleware/helmet.go b/middleware/helmet.go deleted file mode 100644 index d937d6b3..00000000 --- a/middleware/helmet.go +++ /dev/null @@ -1,32 +0,0 @@ -package middleware - -import ( - "fmt" - - "github.com/gofiber/fiber" -) - -// HelmetOptions https://github.com/helmetjs/helmet#how-it-works -type HelmetOptions struct { - ContentSecurityPolicy string - CrossDomain string - DNSPrefetchControl string // default - ExpectCt string - FeaturePolicy string - FrameGuard string // default - Hpkp string - Hsts string // default - IeNoOpen string // default - NoCache string - NoSniff string // default - ReferrerPolicy string - XSSFilter string // default -} - -// Helmet : Helps secure your apps by setting various HTTP headers. -func Helmet(opt ...*HelmetOptions) func(*fiber.Ctx) { - return func(c *fiber.Ctx) { - fmt.Println("Helmet is still under development, this middleware does nothing yet.") - c.Next() - } -} diff --git a/middleware/limiter.go b/middleware/limiter.go deleted file mode 100644 index bf25b74b..00000000 --- a/middleware/limiter.go +++ /dev/null @@ -1,12 +0,0 @@ -package middleware - -import ( - "github.com/gofiber/fiber" -) - -// Limiter : -func Limiter() func(*fiber.Ctx) { - return func(c *fiber.Ctx) { - c.Next() - } -} diff --git a/middleware/logger.go b/middleware/logger.go deleted file mode 100644 index 46e047cb..00000000 --- a/middleware/logger.go +++ /dev/null @@ -1,17 +0,0 @@ -package middleware - -import ( - "fmt" - "time" - - "github.com/gofiber/fiber" -) - -// Logger : Simple logger -func Logger() func(*fiber.Ctx) { - return func(c *fiber.Ctx) { - currentTime := time.Now().Format("02 Jan, 15:04:05") - fmt.Printf("%s \x1b[1;32m%s \x1b[1;37m%s\x1b[0000m, %s\n", currentTime, c.Method(), c.Path(), c.Get("User-Agent")) - c.Next() - } -} diff --git a/middleware/session.go b/middleware/session.go deleted file mode 100644 index 06eb87f2..00000000 --- a/middleware/session.go +++ /dev/null @@ -1,12 +0,0 @@ -package middleware - -import ( - "github.com/gofiber/fiber" -) - -// Session : -func Session() func(*fiber.Ctx) { - return func(c *fiber.Ctx) { - c.Next() - } -} diff --git a/request.go b/request.go index d0b172a5..23a0aa9f 100644 --- a/request.go +++ b/request.go @@ -9,11 +9,13 @@ package fiber import ( "encoding/base64" + "encoding/xml" "fmt" "mime" "mime/multipart" "strings" + jsoniter "github.com/json-iterator/go" "github.com/valyala/fasthttp" ) @@ -132,6 +134,12 @@ func (ctx *Ctx) AcceptsLanguages(offers ...string) string { return "" } +// BaseUrl : https://gofiber.github.io/fiber/#/context?id=baseurl +func (ctx *Ctx) BaseUrl() string { + fmt.Println("Fiber deprecated c.BaseUrl(), this will be removed in v2: Use c.BaseURL() instead") + return ctx.BaseURL() +} + // BaseURL : https://gofiber.github.io/fiber/#/context?id=baseurl func (ctx *Ctx) BaseURL() string { return ctx.Protocol() + "://" + ctx.Hostname() @@ -175,6 +183,8 @@ func (ctx *Ctx) Body(args ...interface{}) string { switch arg := args[0].(type) { case string: return getString(ctx.Fasthttp.Request.PostArgs().Peek(arg)) + case []byte: + return getString(ctx.Fasthttp.Request.PostArgs().PeekBytes(arg)) case func(string, string): ctx.Fasthttp.Request.PostArgs().VisitAll(func(k []byte, v []byte) { arg(getString(k), getString(v)) @@ -186,6 +196,17 @@ func (ctx *Ctx) Body(args ...interface{}) string { return "" } +// BodyParser : https://gofiber.github.io/fiber/#/context?id=bodyparser +func (ctx *Ctx) BodyParser(v interface{}) error { + cType := getString(ctx.Fasthttp.Request.Header.ContentType()) + if cType == contentTypeJSON { + return jsoniter.Unmarshal(ctx.Fasthttp.Request.Body(), v) + } else if cType == contentTypeXML { + return xml.Unmarshal(ctx.Fasthttp.Request.Body(), v) + } + return fmt.Errorf("Cannot Parse Content-Type: %v", cType) +} + // Cookies : https://gofiber.github.io/fiber/#/context?id=cookies func (ctx *Ctx) Cookies(args ...interface{}) string { if len(args) == 0 { @@ -312,10 +333,6 @@ func (ctx *Ctx) OriginalURL() string { // Params : https://gofiber.github.io/fiber/#/context?id=params func (ctx *Ctx) Params(key string) string { - if ctx.params == nil { - return "" - } - for i := 0; i < len(*ctx.params); i++ { if (*ctx.params)[i] == key { return ctx.values[i] diff --git a/request_test.go b/request_test.go index 74d39505..45f09c2f 100644 --- a/request_test.go +++ b/request_test.go @@ -1,235 +1,673 @@ package fiber import ( + "bytes" + "fmt" + "mime/multipart" + "net/http" + "net/http/httptest" + "net/url" + "strconv" "strings" "testing" ) func Test_Accepts(t *testing.T) { - // Raw http request - req := "GET / HTTP/1.1\r\nHost: localhost:8080\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\r\n" - // Create fiber app app := New() - app.Get("/", func(c *Ctx) { - expecting := "html" - result := c.Accepts(expecting) - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) + app.Get("/test", func(c *Ctx) { + expect := "" + result := c.Accepts(expect) + if c.Accepts() != "" { + t.Fatalf(`Expecting %s, got %s`, expect, result) } - - expecting = ".xml" - result = c.Accepts(expecting) - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) + expect = ".xml" + result = c.Accepts(expect) + if result != expect { + t.Fatalf(`Expecting %s, got %s`, expect, result) } }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + req := httptest.NewRequest("GET", "/test", nil) + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + 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) } } func Test_AcceptsCharsets(t *testing.T) { - // Raw http request - req := "GET / HTTP/1.1\r\nHost: localhost:8080\r\nAccept-Charset: utf-8, iso-8859-1;q=0.5\r\n\r\n" - // Raw http request app := New() - app.Get("/", func(c *Ctx) { - expecting := "utf-8" - result := c.AcceptsCharsets(expecting) - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) - } + app.Get("/test", func(c *Ctx) { + c.AcceptsCharsets() - expecting = "iso-8859-1" - result = c.AcceptsCharsets(expecting) - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) + expect := "utf-8" + result := c.AcceptsCharsets(expect) + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5") + 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) } } func Test_AcceptsEncodings(t *testing.T) { - // Raw http request - req := "GET / HTTP/1.1\r\nHost: localhost:8080\r\nAccept-Encoding: deflate, gzip;q=1.0, *;q=0.5\r\n\r\n" - // Raw http request app := New() - app.Get("/", func(c *Ctx) { - expecting := "gzip" - result := c.AcceptsEncodings(expecting) - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) - } - - expecting = "*" - result = c.AcceptsEncodings(expecting) - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) + app.Get("/test", func(c *Ctx) { + c.AcceptsEncodings() + expect := "gzip" + result := c.AcceptsEncodings(expect) + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Accept-Encoding", "deflate, gzip;q=1.0, *;q=0.5") + 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) } } func Test_AcceptsLanguages(t *testing.T) { - // Raw http request - req := "GET / HTTP/1.1\r\nHost: localhost:8080\r\nAccept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5\r\n\r\n" - // Raw http request app := New() - app.Get("/", func(c *Ctx) { - expecting := "fr" - result := c.AcceptsLanguages(expecting) - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) - } - - expecting = "en" - result = c.AcceptsLanguages(expecting) - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) + app.Get("/test", func(c *Ctx) { + c.AcceptsLanguages() + expect := "fr" + result := c.AcceptsLanguages(expect) + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Accept-Language", "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5") + 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) } } func Test_BaseURL(t *testing.T) { - // Raw http request - req := "GET / HTTP/1.1\r\nHost: localhost:8080\r\n\r\n" - // Raw http request app := New() - app.Get("/", func(c *Ctx) { - expecting := "http://localhost:8080" + app.Get("/test", func(c *Ctx) { + c.BaseUrl() // deprecated + expect := "http://google.com" result := c.BaseURL() - if result != expecting { - t.Fatalf(`%s: Expecting %s`, t.Name(), expecting) + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + req, _ := http.NewRequest("GET", "http://google.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) } } func Test_BasicAuth(t *testing.T) { - // Raw http request - req := "GET / HTTP/1.1\r\nHost: localhost:8080\r\nAuthorization: Basic am9objpkb2U=\r\n\r\n" - // Raw http request app := New() - app.Get("/", func(c *Ctx) { - user, pass, ok := c.BasicAuth() - if !ok { - t.Fatalf(`%s: Expecting %s`, t.Name(), "ok") + app.Get("/test", func(c *Ctx) { + expect1 := "john" + expect2 := "doe" + result1, result2, _ := c.BasicAuth() + if result1 != expect1 { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect1, expect1) } - if user != "john" || pass != "doe" { - if !ok { - t.Fatalf(`%s: Expecting john & doe`, t.Name()) - } + if result2 != expect2 { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), result2, expect2) } }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + req, _ := http.NewRequest("GET", "/test", nil) + req.SetBasicAuth("john", "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) } } func Test_Body(t *testing.T) { - // Raw http request - req := "POST /test HTTP/1.1\r\nHost: localhost:8080\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 9\r\n\r\nuser=john" - // Raw http request app := New() app.Post("/test", func(c *Ctx) { - if c.Body() != "user=john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "user=john") + c.Body(1) + expect := "john=doe" + result := c.Body() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } - if c.Body("user") != "john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "john") + expect = "doe" + result = c.Body("john") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + expect = "doe" + result = c.Body([]byte("john")) + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } c.Body(func(k, v string) { - if k != "user" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "user") + expect = "john" + if k != "john" { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, k) } - if v != "john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "john") + expect = "doe" + if v != "doe" { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, v) } }) }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + data := url.Values{} + data.Set("john", "doe") + req, _ := http.NewRequest("POST", "/test", strings.NewReader(data.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) + 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) } } func Test_Cookies(t *testing.T) { - // Raw http request - req := "GET /test HTTP/1.1\r\nHost: localhost:8080\r\nCookie: user=john\r\n\r\n" - // Raw http request app := New() app.Get("/test", func(c *Ctx) { - if c.Cookies() != "user=john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "user=john") + c.Cookies(1) + expect := "john=doe" + result := c.Cookies() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } - if c.Cookies("user") != "john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "john") + expect = "doe" + result = c.Cookies("john") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + expect = "doe" + result = c.Cookies([]byte("john")) + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } c.Cookies(func(k, v string) { - if k != "user" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "user") + expect = "john" + if k != "john" { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, k) } - if v != "john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "john") + expect = "doe" + if v != "doe" { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, v) } }) }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + + req, _ := http.NewRequest("GET", "/test", nil) + req.AddCookie(&http.Cookie{Name: "john", Value: "doe"}) + _, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) } } func Test_FormFile(t *testing.T) { - // Raw http request - req := "POST /test HTTP/1.1\r\nHost: localhost:8080\r\nCookie: user=john\r\n\r\n" - // Raw http request + // TODO +} +func Test_FormValue(t *testing.T) { app := New() app.Post("/test", func(c *Ctx) { - if c.Cookies() != "user=john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "user=john") + expect := "john" + result := c.FormValue("name") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) } - if c.Cookies("user") != "john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "john") - } - c.Cookies(func(k, v string) { - if k != "user" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "user") - } - if v != "john" { - t.Fatalf(`%s: Expecting %s`, t.Name(), "john") - } - }) }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + writer.WriteField("name", "john") + writer.Close() + req, _ := http.NewRequest("POST", "/test", body) + contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary()) + + req.Header.Set("Content-Type", contentType) + req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes()))) + 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) } } +func Test_Fresh(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Fresh() + }) + req, _ := http.NewRequest("GET", "/test", nil) + _, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } +} +func Test_Get(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + expect := "utf-8, iso-8859-1;q=0.5" + result := c.Get("Accept-Charset") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + expect = "Monster" + result = c.Get("referrer") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5") + req.Header.Set("Referer", "Monster") + 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) + } +} +func Test_Hostname(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + expect := "google.com" + result := c.Hostname() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "http://google.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) + } +} +func Test_IP(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Ip() // deprecated + expect := "0.0.0.0" + result := c.IP() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "http://google.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) + } +} +func Test_IPs(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Ips() // deprecated + expect := []string{"0.0.0.0", "1.1.1.1"} + result := c.IPs() + if result[0] != expect[0] && result[1] != expect[1] { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("X-Forwarded-For", "0.0.0.0, 1.1.1.1") + 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) + } +} +func Test_Is(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Is(".json") + expect := true + result := c.Is("html") + if result != expect { + t.Fatalf(`%s: Expecting %v, got %v`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("Content-Type", "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) + } +} +func Test_Locals(t *testing.T) { + app := New() + app.Use(func(c *Ctx) { + c.Locals("john", "doe") + c.Next() + }) + app.Get("/test", func(c *Ctx) { + expect := "doe" + result := c.Locals("john") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + 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) + } +} +func Test_Method(t *testing.T) { + app := New() + app.Get(func(c *Ctx) { + expect := "GET" + result := c.Method() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + app.Post(func(c *Ctx) { + expect := "POST" + result := c.Method() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + app.Put(func(c *Ctx) { + expect := "PUT" + result := c.Method() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + 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) + } + req, _ = http.NewRequest("POST", "/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) + } + req, _ = http.NewRequest("PUT", "/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) + } +} +func Test_MultipartForm(t *testing.T) { + app := New() + app.Post("/test", func(c *Ctx) { + expect := "john" + result, err := c.MultipartForm() + if err != nil { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, err) + } + if result.Value["name"][0] != expect { + t.Fatalf(`%s: Expecting %s, got %v`, t.Name(), expect, result) + } + }) -// TODO: add all functions from request.go + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + writer.WriteField("name", "john") + writer.Close() + req, _ := http.NewRequest("POST", "/test", body) + contentType := fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary()) + + req.Header.Set("Content-Type", contentType) + req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes()))) + 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) + } +} +func Test_OriginalURL(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.OriginalUrl() // deprecated + expect := "/test?search=demo" + result := c.OriginalURL() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "http://google.com/test?search=demo", 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) + } +} +func Test_Params(t *testing.T) { + app := New() + app.Get("/test/:user", func(c *Ctx) { + expect := "john" + result := c.Params("user") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + app.Get("/test2/*", func(c *Ctx) { + expect := "im/a/cookie" + result := c.Params("*") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "/test/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) + } + req, _ = http.NewRequest("GET", "/test2/im/a/cookie", 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) + } +} +func Test_Path(t *testing.T) { + app := New() + app.Get("/test/:user", func(c *Ctx) { + expect := "/test/john" + result := c.Path() + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "/test/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) + } +} +func Test_Query(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + expect := "john" + result := c.Query("search") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + expect = "20" + result = c.Query("age") + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "/test?search=john&age=20", 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) + } +} +func Test_Range(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Range() + }) + req, _ := http.NewRequest("GET", "/test", nil) + _, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } +} +func Test_Route(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + expect := "/test" + result := c.Route().Path + if result != expect { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + 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) + } +} +func Test_SaveFile(t *testing.T) { + // TODO +} +func Test_Secure(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + expect := false + result := c.Secure() + if result != expect { + t.Fatalf(`%s: Expecting %v, got %v`, t.Name(), expect, result) + } + }) + 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) + } +} +func Test_SignedCookies(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.SignedCookies() + }) + 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) + } +} +func Test_Stale(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Stale() + }) + req, _ := http.NewRequest("GET", "/test", nil) + _, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) + } +} +func Test_Subdomains(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + expect := []string{"john", "doe"} + result := c.Subdomains() + if result[0] != expect[0] && result[1] != expect[1] { + t.Fatalf(`%s: Expecting %s, got %s`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "http://john.doe.google.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) + } +} +func Test_XHR(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Xhr() // deprecated + expect := true + result := c.XHR() + if result != expect { + t.Fatalf(`%s: Expecting %v, got %v`, t.Name(), expect, result) + } + }) + req, _ := http.NewRequest("GET", "/test", nil) + req.Header.Set("X-Requested-With", "XMLHttpRequest") + 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/response.go b/response.go index d573e74e..4a71839f 100644 --- a/response.go +++ b/response.go @@ -24,8 +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] @@ -51,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)) }) @@ -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 88146579..abf1cc88 100644 --- a/response_test.go +++ b/response_test.go @@ -1,28 +1,567 @@ package fiber import ( + "io/ioutil" + "net/http" "strings" "testing" ) func Test_Append(t *testing.T) { - // Raw http request - req := "GET / HTTP/1.1\r\nHost: localhost:8080\r\n\r\n" - // Create fiber app app := New() - app.Get("/", func(c *Ctx) { - c.Append("X-Test", "hello", "world") + app.Get("/test", func(c *Ctx) { + c.Append("X-Test", "hel") + c.Append("X-Test", "lo", "world") }) - // Send fake request - res, err := app.FakeRequest(req) - // Check for errors and if route was handled - if err != nil || !strings.Contains(res, "HTTP/1.1 200 OK") { - t.Fatalf(`%s: Error serving FakeRequest %s`, t.Name(), err) + req, _ := http.NewRequest("GET", "/test", nil) + resp, err := app.Test(req) + if err != nil { + t.Fatalf(`%s: %s`, t.Name(), err) } - // Check if function works correctly - if !strings.Contains(res, "X-Test: hello, world") { - t.Fatalf(`%s: Expecting %s`, t.Name(), "X-Test: hello, world") + if resp.StatusCode != 200 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if resp.Header.Get("X-Test") != "hel, lo, world" { + 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) != "<p>Hello, World!</p>" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "<p>Hello, World!</p>") + } -// 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") != `<http://api.example.com/users?page=2>; rel="next",<http://api.example.com/users?page=5>; rel="last"` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `Link: <http://api.example.com/users?page=2>; rel="next",<http://api.example.com/users?page=5>; rel="last"`) + } +} +func Test_Location(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Location("http://example.com") + }) + 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("Location") != "http://example.com" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "http://example.com") + } +} +func Test_Next(t *testing.T) { + app := New() + app.Use("/", func(c *Ctx) { + c.Next() + }) + app.Get("/test", func(c *Ctx) { + c.Set("X-Next-Result", "Works") + }) + 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("X-Next-Result") != "Works" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "X-Next-Results: Works") + } +} +func Test_Redirect(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Redirect("http://example.com", 301) + }) + 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 != 301 { + t.Fatalf(`%s: StatusCode %v`, t.Name(), resp.StatusCode) + } + if resp.Header.Get("Location") != "http://example.com" { + t.Fatalf(`%s: Expecting %s`, t.Name(), "Location: http://example.com") + } +} +func Test_Render(t *testing.T) { + // TODO +} +func Test_Send(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Send([]byte("Hello, World")) + c.Send("Don't crash please") + c.Send(1337) + }) + 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) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `1337` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `1337`) + } +} +func Test_SendBytes(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.SendBytes([]byte("Hello, World")) + }) + 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) + } + 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_SendStatus(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.SendStatus(415) + }) + 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 != 415 { + 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) != `Unsupported Media Type` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `Unsupported Media Type`) + } +} +func Test_SendString(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.SendString("Don't crash please") + }) + 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) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `Don't crash please` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `Don't crash please`) + } +} +func Test_Set(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Set("X-1", "1") + c.Set("X-2", "2") + c.Set("X-3", "3") + }) + 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("X-1") != "1" { + t.Fatalf(`%s: Expected %v`, t.Name(), "X-1: 1") + } + if resp.Header.Get("X-2") != "2" { + t.Fatalf(`%s: Expected %v`, t.Name(), "X-2: 2") + } + if resp.Header.Get("X-3") != "3" { + t.Fatalf(`%s: Expected %v`, t.Name(), "X-3: 3") + } +} +func Test_Status(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Status(400) + c.Status(415).Send("Hello, World") + }) + 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 != 415 { + 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_Type(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Type(".json") + }) + 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: Expected %v`, t.Name(), `Content-Type: application/json`) + } +} +func Test_Vary(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Vary("Origin") + c.Vary("User-Agent") + c.Vary("Accept-Encoding", "Accept") + }) + 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("Vary") != "Origin, User-Agent, Accept-Encoding, Accept" { + t.Fatalf(`%s: Expected %v`, t.Name(), `Vary: Origin, User-Agent, Accept-Encoding, Accept`) + } +} +func Test_Write(t *testing.T) { + app := New() + app.Get("/test", func(c *Ctx) { + c.Write("Hello, ") + c.Write([]byte("World! ")) + c.Write(123) + }) + 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) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `Hello, World! 123` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `Hello, World! 123`) + } +} +func Test_XML(t *testing.T) { + type person struct { + Name string `xml:"name"` + Stars int `xml:"stars"` + } + app := New() + app.Get("/test", func(c *Ctx) { + c.Xml("") + c.XML(person{"John", 50}) + }) + 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/xml" { + t.Fatalf(`%s: Expected %v`, t.Name(), "application/xml") + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf(`%s: Error %s`, t.Name(), err) + } + if string(body) != `<person><name>John</name><stars>50</stars></person>` { + t.Fatalf(`%s: Expecting %s`, t.Name(), `<person><name>John</name><stars>50</stars></person>`) + } +} diff --git a/static.go b/static.go index 6463b759..4de39730 100644 --- a/static.go +++ b/static.go @@ -9,6 +9,7 @@ package fiber import ( "log" + "os" "path/filepath" "strings" ) @@ -36,6 +37,11 @@ func (r *Fiber) Static(args ...string) { 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 { diff --git a/static_test.go b/static_test.go new file mode 100644 index 00000000..e291c9a7 --- /dev/null +++ b/static_test.go @@ -0,0 +1,35 @@ +package fiber + +import ( + "net/http" + "testing" +) + +func Test_Static(t *testing.T) { + app := New() + app.Static("./.github") + app.Static("/john", "./.github") + app.Static("*", "./.github/stale.yml") + req, _ := http.NewRequest("GET", "/stale.yml", 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-Length") == "" { + t.Fatalf(`%s: Missing Content-Length`, t.Name()) + } + req, _ = http.NewRequest("GET", "/john/stale.yml", 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-Length") == "" { + t.Fatalf(`%s: Missing Content-Length`, t.Name()) + } +} diff --git a/utils.go b/utils.go index 14dbf384..3ac0f0e0 100644 --- a/utils.go +++ b/utils.go @@ -8,10 +8,13 @@ package fiber import ( + "bufio" "bytes" "fmt" "io/ioutil" "net" + "net/http" + "net/http/httputil" "os" "path/filepath" "reflect" @@ -19,8 +22,6 @@ import ( "strings" "time" "unsafe" - - "github.com/valyala/fasthttp" ) func getParams(path string) (params []string) { @@ -75,6 +76,9 @@ func getFiles(root string) (files []string, isDir bool, err error) { } func getType(ext string) (mime string) { + if ext == "" { + return mime + } if ext[0] == '.' { ext = ext[1:] } @@ -107,92 +111,68 @@ func getBytes(s string) (b []byte) { return b } -// FakeRequest creates a readWriter and calls ServeConn on local servver -func (r *Fiber) FakeRequest(raw string) (string, error) { - server := &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, +// Test takes a http.Request and execute a fake connection to the application +// It returns a http.Response when the connection was successfull +func (r *Fiber) Test(req *http.Request) (*http.Response, error) { + // Get raw http request + reqRaw, err := httputil.DumpRequest(req, true) + if err != nil { + return nil, err } - rw := &readWriter{} - rw.r.WriteString(raw) - - ch := make(chan error) + // Setup a fiber server struct + r.httpServer = r.setupServer() + // Create fake connection + conn := &conn{} + // Pass HTTP request to conn + conn.r.Write(reqRaw) + // Serve conn to server + channel := make(chan error) go func() { - ch <- server.ServeConn(rw) + channel <- r.httpServer.ServeConn(conn) }() - + // Wait for callback select { - case err := <-ch: + case err := <-channel: if err != nil { - return "", err + return nil, err } - case <-time.After(200 * time.Millisecond): - return "", fmt.Errorf("Timeout") + // Throw timeout error after 200ms + case <-time.After(500 * time.Millisecond): + return nil, fmt.Errorf("Timeout") } - - err := server.ServeConn(rw) + // Get raw HTTP response + respRaw, err := ioutil.ReadAll(&conn.w) if err != nil { - return "", err + return nil, err } - resp, err := ioutil.ReadAll(&rw.w) + // 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 "", err + return nil, err } - return getString(resp), nil + // Return *http.Response + return resp, nil } -// Readwriter for test cases -type readWriter struct { +// https://golang.org/src/net/net.go#L113 +type conn struct { net.Conn r bytes.Buffer w bytes.Buffer } -func (rw *readWriter) Close() error { - return nil -} - -func (rw *readWriter) Read(b []byte) (int, error) { - return rw.r.Read(b) -} - -func (rw *readWriter) Write(b []byte) (int, error) { - return rw.w.Write(b) -} - -func (rw *readWriter) RemoteAddr() net.Addr { +func (c *conn) RemoteAddr() net.Addr { return &net.TCPAddr{ - IP: net.IPv4zero, + IP: net.IPv4(0, 0, 0, 0), } } - -func (rw *readWriter) LocalAddr() net.Addr { - return rw.RemoteAddr() -} - -func (rw *readWriter) SetReadDeadline(t time.Time) error { - return nil -} - -func (rw *readWriter) SetWriteDeadline(t time.Time) error { - return nil -} +func (c *conn) LocalAddr() net.Addr { return c.LocalAddr() } +func (c *conn) Read(b []byte) (int, error) { return c.r.Read(b) } +func (c *conn) Write(b []byte) (int, error) { return c.w.Write(b) } +func (c *conn) Close() error { return nil } +func (c *conn) SetDeadline(t time.Time) error { return nil } +func (c *conn) SetReadDeadline(t time.Time) error { return nil } +func (c *conn) SetWriteDeadline(t time.Time) error { return nil }