1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-23 06:04:23 +00:00

👷 Add etag middleware

This commit is contained in:
kiyon 2020-10-14 17:01:13 +08:00
parent d4e604f1a3
commit fb4cf7e887
2 changed files with 153 additions and 0 deletions

121
middleware/etag/etag.go Normal file
View File

@ -0,0 +1,121 @@
package etag
import (
"bytes"
"hash/crc32"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/internal/bytebufferpool"
)
// Config defines the config for middleware.
type Config struct {
// Weak
Weak bool
// Next defines a function to skip this middleware when returned true.
//
// Optional. Default: nil
Next func(c *fiber.Ctx) bool
}
// ConfigDefault is the default config
var ConfigDefault = Config{}
var normalizedHeaderETag = []byte("Etag")
var weakPrefix = []byte("W/")
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := ConfigDefault
// Override config if provided
if len(config) > 0 {
cfg = config[0]
}
// Return new handler
return func(c *fiber.Ctx) (err error) {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Return err if next handler returns one
if err = c.Next(); err != nil {
return
}
// Don't generate ETags for invalid responses
if c.Response().StatusCode() != fiber.StatusOK {
return
}
body := c.Response().Body()
// Skips ETag if no response body is present
if len(body) <= 0 {
return
}
// Get ETag header from request
clientEtag := c.Request().Header.Peek(fiber.HeaderIfNoneMatch)
// Generate ETag for response
crc32q := crc32.MakeTable(0xD5828281)
bb := bytebufferpool.Get()
defer bytebufferpool.Put(bb)
// Enable weak tag
if cfg.Weak {
_, _ = bb.Write(weakPrefix)
}
_ = bb.WriteByte('"')
appendUint(bb.Bytes(), uint32(len(body)))
_ = bb.WriteByte('-')
appendUint(bb.Bytes(), crc32.Checksum(body, crc32q))
_ = bb.WriteByte('"')
etag := bb.Bytes()
// Check if client's ETag is weak
if bytes.HasPrefix(clientEtag, weakPrefix) {
// Check if server's ETag is weak
if bytes.Equal(clientEtag[2:], etag) || bytes.Equal(clientEtag[2:], etag[2:]) {
// W/1 == 1 || W/1 == W/1
c.Context().ResetBody()
return c.SendStatus(fiber.StatusNotModified)
}
// W/1 != W/2 || W/1 != 2
c.Response().Header.SetCanonical(normalizedHeaderETag, etag)
return
}
if bytes.Contains(clientEtag, etag) {
// 1 == 1
c.Context().ResetBody()
return c.SendStatus(fiber.StatusNotModified)
}
// 1 != 2
c.Response().Header.SetCanonical(normalizedHeaderETag, etag)
return
}
}
// appendUint appends n to dst and returns the extended dst.
func appendUint(dst []byte, n uint32) []byte {
var b [20]byte
buf := b[:]
i := len(buf)
var q uint32
for n >= 10 {
i--
q = n / 10
buf[i] = '0' + byte(n-q*10)
n = q
}
i--
buf[i] = '0' + byte(n)
dst = append(dst, buf[i:]...)
return dst
}

View File

@ -0,0 +1,32 @@
package etag
import (
"net/http/httptest"
"testing"
"github.com/valyala/fasthttp"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
)
// go test -run Test_ETag
func Test_ETag(t *testing.T) {
i := 1<<32 - 1
t.Logf("%d, %d, %s", int32(i), uint32(i), fasthttp.AppendUint(nil, i))
}
// go test -run Test_ETag_Next
func Test_ETag_Next(t *testing.T) {
app := fiber.New()
app.Use(New(Config{
Next: func(_ *fiber.Ctx) bool {
return true
},
}))
resp, err := app.Test(httptest.NewRequest("GET", "/", nil))
utils.AssertEqual(t, nil, err)
utils.AssertEqual(t, fiber.StatusNotFound, resp.StatusCode)
}