diff --git a/.github/.gitpod.yml b/.github/.gitpod.yml
deleted file mode 100644
index f1e44ef146..0000000000
--- a/.github/.gitpod.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-tasks:
- - init: go get && go build ./... && go test ./...
diff --git a/.github/CNAME b/.github/CNAME
deleted file mode 100644
index 862e8d01f3..0000000000
--- a/.github/CNAME
+++ /dev/null
@@ -1 +0,0 @@
-fiber.wiki
\ No newline at end of file
diff --git a/app.go b/app.go
index 21227faaf1..35d9b6bbf0 100644
--- a/app.go
+++ b/app.go
@@ -24,7 +24,7 @@ import (
)
// Version of current package
-const Version = "1.9.2"
+const Version = "1.9.3"
// Map is a shortcut for map[string]interface{}
type Map map[string]interface{}
@@ -48,6 +48,10 @@ type Settings struct {
ServerHeader string // default: ""
// Enables handler values to be immutable even if you return from handler
Immutable bool // default: false
+ // Enable or disable ETag header generation, since both weak and strong etags are generated
+ // using the same hashing method (CRC-32). Weak ETags are the default when enabled.
+ // Optional. Default value false
+ ETag bool
// Max body size that the server accepts
BodyLimit int // default: 4 * 1024 * 1024
// Maximum number of concurrent connections.
@@ -80,6 +84,25 @@ type Group struct {
app *App
}
+// Static struct
+type Static struct {
+ // Transparently compresses responses if set to true
+ // This works differently than the github.com/gofiber/compression middleware
+ // The server tries minimizing CPU usage by caching compressed files.
+ // It adds ".fiber.gz" suffix to the original file name.
+ // Optional. Default value false
+ Compress bool
+ // Enables byte range requests if set to true.
+ // Optional. Default value false
+ ByteRange bool
+ // Enable directory browsing.
+ // Optional. Default value false.
+ Browse bool
+ // Index file for serving a directory.
+ // Optional. Default value "index.html".
+ Index string
+}
+
// New creates a new Fiber named instance.
// You can pass optional settings when creating a new instance.
func New(settings ...*Settings) *App {
@@ -125,25 +148,6 @@ func (app *App) Group(prefix string, handlers ...func(*Ctx)) *Group {
}
}
-// Static struct
-type Static struct {
- // Transparently compresses responses if set to true
- // This works differently than the github.com/gofiber/compression middleware
- // The server tries minimizing CPU usage by caching compressed files.
- // It adds ".fiber.gz" suffix to the original file name.
- // Optional. Default value false
- Compress bool
- // Enables byte range requests if set to true.
- // Optional. Default value false
- ByteRange bool
- // Enable directory browsing.
- // Optional. Default value false.
- Browse bool
- // Index file for serving a directory.
- // Optional. Default value "index.html".
- Index string
-}
-
// Static registers a new route with path prefix to serve static files from the provided root directory.
func (app *App) Static(prefix, root string, config ...Static) *App {
app.registerStatic(prefix, root, config...)
diff --git a/app_test.go b/app_test.go
index ee6220e915..d88c908983 100644
--- a/app_test.go
+++ b/app_test.go
@@ -33,7 +33,9 @@ func is200(t *testing.T, app *App, url string, m ...string) {
}
}
func Test_Methods(t *testing.T) {
- app := New()
+ app := New(&Settings{
+ DisableStartupMessage: true,
+ })
app.Connect("/:john?/:doe?", handler)
is200(t, app, "/", "CONNECT")
@@ -83,14 +85,18 @@ func Test_New(t *testing.T) {
}
func Test_Shutdown(t *testing.T) {
- app := New()
+ app := New(&Settings{
+ DisableStartupMessage: true,
+ })
_ = app.Shutdown()
}
func Test_Static(t *testing.T) {
- app := New()
+ app := New(&Settings{
+ DisableStartupMessage: true,
+ })
grp := app.Group("/v1")
- grp.Static("/v2", ".travis.yml")
+ grp.Static("/v2", ".github/auth_assign.yml")
app.Static("/*", ".github/FUNDING.yml")
app.Static("/john", "./.github")
req, _ := http.NewRequest("GET", "/john/stale.yml", nil)
@@ -140,7 +146,9 @@ func Test_Static(t *testing.T) {
}
func Test_Group(t *testing.T) {
- app := New()
+ app := New(&Settings{
+ DisableStartupMessage: true,
+ })
grp := app.Group("/test")
grp.Get("/", handler)
@@ -188,7 +196,9 @@ func Test_Group(t *testing.T) {
}
func Test_Listen(t *testing.T) {
- app := New()
+ app := New(&Settings{
+ DisableStartupMessage: true,
+ })
go func() {
time.Sleep(500 * time.Millisecond)
_ = app.Shutdown()
@@ -203,7 +213,8 @@ func Test_Listen(t *testing.T) {
func Test_Serve(t *testing.T) {
app := New(&Settings{
- Prefork: true,
+ DisableStartupMessage: true,
+ Prefork: true,
})
ln, err := net.Listen("tcp4", ":3004")
if err != nil {
diff --git a/ctx.go b/ctx.go
index 9bdf19fbdb..b812910d0b 100644
--- a/ctx.go
+++ b/ctx.go
@@ -409,7 +409,11 @@ func (ctx *Ctx) FormValue(key string) (value string) {
return getString(ctx.Fasthttp.FormValue(key))
}
-// Fresh is not implemented yet, pull requests are welcome!
+// Fresh When the response is still “fresh” in the client’s cache true is returned,
+// otherwise false is returned to indicate that the client cache is now stale
+// and the full response should be sent.
+// When a client sends the Cache-Control: no-cache request header to indicate an end-to-end
+// reload request, this module will return false to make handling these requests transparent.
// https://github.com/jshttp/fresh/blob/10e0471669dbbfbfd8de65bc6efac2ddd0bfa057/index.js#L33
func (ctx *Ctx) Fresh() bool {
// fields
@@ -545,7 +549,6 @@ func (ctx *Ctx) JSONP(data interface{}, callback ...string) error {
ctx.Set(HeaderXContentTypeOptions, "nosniff")
ctx.Fasthttp.Response.Header.SetContentType(MIMEApplicationJavaScript)
ctx.Fasthttp.Response.SetBodyString(str)
-
return nil
}
diff --git a/ctx_test.go b/ctx_test.go
index 4488944a4c..93c575b491 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -21,7 +21,9 @@ import (
)
func Test_Accepts(t *testing.T) {
- app := New()
+ app := New(&Settings{
+ DisableStartupMessage: true,
+ })
app.Get("/test", func(c *Ctx) {
expect := ""
result := c.Accepts(expect)
@@ -155,6 +157,11 @@ func Test_BodyParser(t *testing.T) {
type Demo struct {
Name string `json:"name" xml:"name" form:"name" query:"name"`
}
+ type Query struct {
+ ID int
+ Name string
+ Hobby []string
+ }
app.Post("/test", func(c *Ctx) {
d := new(Demo)
err := c.BodyParser(d)
@@ -162,9 +169,21 @@ func Test_BodyParser(t *testing.T) {
t.Fatalf(`%s: BodyParser %v`, t.Name(), err)
}
if d.Name != "john" {
- t.Fatalf(`%s: Expect %s got %s`, t.Name(), "john", d)
+ t.Fatalf(`%s: Expect %s got %v`, t.Name(), "john", d)
+ }
+ })
+
+ app.Get("/query", func(c *Ctx) {
+ d := new(Query)
+ err := c.BodyParser(d)
+ if err != nil {
+ t.Fatalf(`%s: BodyParser %v`, t.Name(), err)
+ }
+ if len(d.Hobby) != 2 {
+ t.Fatalf(`%s: Expect length %d got %v`, t.Name(), 2, d)
}
})
+
req := httptest.NewRequest("POST", "/test", bytes.NewBuffer([]byte(`{"name":"john"}`)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Content-Length", strconv.Itoa(len([]byte(`{"name":"john"}`))))
@@ -174,25 +193,12 @@ func Test_BodyParser(t *testing.T) {
t.Fatalf(`%s: %s`, t.Name(), err)
}
- // data := url.Values{}
- // data.Set("name", "john")
- // req = httptest.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())))
-
- // _, err = app.Test(req)
- // if err != nil {
- // t.Fatalf(`%s: %s`, t.Name(), err)
- // }
+ req = httptest.NewRequest("GET", "/query?id=1&name=tom&hobby=basketball&hobby=football", nil)
- // req = httptest.NewRequest("POST", "/test", bytes.NewBuffer([]byte(`john`)))
- // req.Header.Set("Content-Type", "application/xml")
- // req.Header.Set("Content-Length", strconv.Itoa(len([]byte(`john`))))
-
- // _, err = app.Test(req)
- // if err != nil {
- // t.Fatalf(`%s: %s`, t.Name(), err)
- // }
+ _, err = app.Test(req)
+ if err != nil {
+ t.Fatalf(`%s: %s`, t.Name(), err)
+ }
}
func Test_Cookies(t *testing.T) {
app := New()
diff --git a/router.go b/router.go
index f3fc46c4af..cee7e0e8b8 100644
--- a/router.go
+++ b/router.go
@@ -42,9 +42,14 @@ func (app *App) nextRoute(ctx *Ctx) {
ctx.route = route
ctx.values = values
route.Handler(ctx)
+ // Generate ETag if enabled / found
+ if app.Settings.ETag {
+ setETag(ctx, ctx.Fasthttp.Response.Body(), false)
+ }
return
}
}
+ // Send a 404
if len(ctx.Fasthttp.Response.Body()) == 0 {
ctx.SendStatus(404)
}
diff --git a/utils.go b/utils.go
index 9495f231ee..b18cd18f85 100644
--- a/utils.go
+++ b/utils.go
@@ -7,6 +7,7 @@ package fiber
import (
"bytes"
"fmt"
+ "hash/crc32"
"net"
"os"
"path/filepath"
@@ -16,6 +17,47 @@ import (
"unsafe"
)
+// Document elke line gelijk even
+func setETag(ctx *Ctx, body []byte, weak bool) {
+ // Skips ETag if no response body is present
+ if len(body) <= 0 {
+ return
+ }
+ // Get ETag header from request
+ clientEtag := ctx.Get("If-None-Match")
+
+ // Generate ETag for response
+ crc32q := crc32.MakeTable(0xD5828281)
+ etag := fmt.Sprintf("\"%d-%v\"", len(body), crc32.Checksum(body, crc32q))
+
+ // Enable weak tag
+ if weak {
+ etag = "W/" + "\"" + etag + "\""
+ }
+
+ // Check if client's ETag is weak
+ if strings.HasPrefix(clientEtag, "W/") {
+ // Check if server's ETag is weak
+ if clientEtag[2:] == etag || clientEtag[2:] == etag[2:] {
+ // W/1 == 1 || W/1 == W/1
+ ctx.SendStatus(304)
+ ctx.Fasthttp.ResetBody()
+ return
+ }
+ // W/1 != W/2 || W/1 != 2
+ ctx.Set("ETag", etag)
+ return
+ }
+ if strings.Contains(clientEtag, etag) {
+ // 1 == 1
+ ctx.SendStatus(304)
+ ctx.Fasthttp.ResetBody()
+ return
+ }
+ // 1 != 2
+ ctx.Set("ETag", etag)
+}
+
func groupPaths(prefix, path string) string {
if path == "/" {
path = ""