mirror of https://github.com/gofiber/fiber.git
Cookie parser (#2656)
* prep for branching * feature: added a cookie parser and tests appropriate tests * ✨ feature: added a cookie parser and appropriate tests * made correction to docs * linted using gofumpt * ctx_test linted, cookieParser schema added * fix lint errors (Cookie parser #2656) * removed extra lines, tested return values --------- Co-authored-by: René Werner <rene.werner@verivox.com>pull/2675/head
parent
bb90fc1187
commit
e70b2e28d6
41
ctx.go
41
ctx.go
|
@ -38,22 +38,23 @@ const (
|
|||
// maxParams defines the maximum number of parameters per route.
|
||||
const maxParams = 30
|
||||
|
||||
// Some constants for BodyParser, QueryParser and ReqHeaderParser.
|
||||
// Some constants for BodyParser, QueryParser, CookieParser and ReqHeaderParser.
|
||||
const (
|
||||
queryTag = "query"
|
||||
reqHeaderTag = "reqHeader"
|
||||
bodyTag = "form"
|
||||
paramsTag = "params"
|
||||
cookieTag = "cookie"
|
||||
)
|
||||
|
||||
// userContextKey define the key name for storing context.Context in *fasthttp.RequestCtx
|
||||
const userContextKey = "__local_user_context__"
|
||||
|
||||
var (
|
||||
// decoderPoolMap helps to improve BodyParser's, QueryParser's and ReqHeaderParser's performance
|
||||
// decoderPoolMap helps to improve BodyParser's, QueryParser's, CookieParser's and ReqHeaderParser's performance
|
||||
decoderPoolMap = map[string]*sync.Pool{}
|
||||
// tags is used to classify parser's pool
|
||||
tags = []string{queryTag, bodyTag, reqHeaderTag, paramsTag}
|
||||
tags = []string{queryTag, bodyTag, reqHeaderTag, paramsTag, cookieTag}
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -502,6 +503,40 @@ func (c *Ctx) Cookies(key string, defaultValue ...string) string {
|
|||
return defaultString(c.app.getString(c.fasthttp.Request.Header.Cookie(key)), defaultValue)
|
||||
}
|
||||
|
||||
// CookieParser is used to bind cookies to a struct
|
||||
func (c *Ctx) CookieParser(out interface{}) error {
|
||||
data := make(map[string][]string)
|
||||
var err error
|
||||
|
||||
// loop through all cookies
|
||||
c.fasthttp.Request.Header.VisitAllCookie(func(key, val []byte) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
k := c.app.getString(key)
|
||||
v := c.app.getString(val)
|
||||
|
||||
if strings.Contains(k, "[") {
|
||||
k, err = parseParamSquareBrackets(k)
|
||||
}
|
||||
|
||||
if c.app.config.EnableSplittingOnParsers && strings.Contains(v, ",") && equalFieldType(out, reflect.Slice, k, cookieTag) {
|
||||
values := strings.Split(v, ",")
|
||||
for i := 0; i < len(values); i++ {
|
||||
data[k] = append(data[k], values[i])
|
||||
}
|
||||
} else {
|
||||
data[k] = append(data[k], v)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.parseToStruct(cookieTag, out, data)
|
||||
}
|
||||
|
||||
// Download transfers the file from path as an attachment.
|
||||
// Typically, browsers will prompt the user for download.
|
||||
// By default, the Content-Disposition header filename= parameter is the filepath (this typically appears in the browser dialog).
|
||||
|
|
146
ctx_test.go
146
ctx_test.go
|
@ -928,6 +928,152 @@ func Benchmark_Ctx_Cookie(b *testing.B) {
|
|||
utils.AssertEqual(b, "John=Doe; path=/; SameSite=Lax", app.getString(c.Response().Header.Peek("Set-Cookie")))
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_CookieParser -v
|
||||
func Test_Ctx_CookieParser(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New(Config{EnableSplittingOnParsers: true})
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
defer app.ReleaseCtx(c)
|
||||
type Cookie struct {
|
||||
Name string
|
||||
Class int
|
||||
Courses []string
|
||||
}
|
||||
c.Request().Header.Set("Cookie", "name=doe")
|
||||
c.Request().Header.Set("Cookie", "class=100")
|
||||
c.Request().Header.Set("Cookie", "courses=maths,english")
|
||||
cookie := new(Cookie)
|
||||
|
||||
// correct test cases
|
||||
utils.AssertEqual(t, nil, c.CookieParser(cookie))
|
||||
utils.AssertEqual(t, "doe", cookie.Name)
|
||||
utils.AssertEqual(t, 100, cookie.Class)
|
||||
utils.AssertEqual(t, 2, len(cookie.Courses))
|
||||
|
||||
// wrong test cases
|
||||
empty := new(Cookie)
|
||||
c.Request().Header.Set("Cookie", "name")
|
||||
c.Request().Header.Set("Cookie", "class")
|
||||
c.Request().Header.Set("Cookie", "courses")
|
||||
utils.AssertEqual(t, nil, c.CookieParser(cookie))
|
||||
utils.AssertEqual(t, "", empty.Name)
|
||||
utils.AssertEqual(t, 0, empty.Class)
|
||||
utils.AssertEqual(t, 0, len(empty.Courses))
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_CookieParserUsingTag -v
|
||||
func Test_Ctx_CookieParserUsingTag(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New(Config{EnableSplittingOnParsers: true})
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
defer app.ReleaseCtx(c)
|
||||
type Cook struct {
|
||||
ID int `cookie:"id"`
|
||||
Name string `cookie:"name"`
|
||||
Courses []string `cookie:"courses"`
|
||||
Enrolled bool `cookie:"student"`
|
||||
Fees float32 `cookie:"fee"`
|
||||
Grades []uint8 `cookie:"score"`
|
||||
}
|
||||
cookie1 := new(Cook)
|
||||
cookie1.Name = "Joseph"
|
||||
utils.AssertEqual(t, "Joseph", cookie1.Name)
|
||||
|
||||
c.Request().Header.Set("Cookie", "id=1")
|
||||
c.Request().Header.Set("Cookie", "name=Joey")
|
||||
c.Request().Header.Set("Cookie", "courses=maths,english, chemistry, physics")
|
||||
c.Request().Header.Set("Cookie", "student=true")
|
||||
c.Request().Header.Set("Cookie", "fee=45.78")
|
||||
c.Request().Header.Set("Cookie", "score=7,6,10")
|
||||
utils.AssertEqual(t, nil, c.CookieParser(cookie1))
|
||||
utils.AssertEqual(t, "Joey", cookie1.Name)
|
||||
utils.AssertEqual(t, true, cookie1.Enrolled)
|
||||
utils.AssertEqual(t, float32(45.78), cookie1.Fees)
|
||||
utils.AssertEqual(t, []uint8{7, 6, 10}, cookie1.Grades)
|
||||
|
||||
type RequiredCookie struct {
|
||||
House string `cookie:"house,required"`
|
||||
}
|
||||
rc := new(RequiredCookie)
|
||||
utils.AssertEqual(t, "failed to decode: house is empty", c.CookieParser(rc).Error())
|
||||
|
||||
type ArrayCookie struct {
|
||||
Dates []int
|
||||
}
|
||||
|
||||
ac := new(ArrayCookie)
|
||||
c.Request().Header.Set("Cookie", "dates[]=7,6,10")
|
||||
utils.AssertEqual(t, nil, c.CookieParser(ac))
|
||||
utils.AssertEqual(t, 3, len(ac.Dates))
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_CookieParserSchema -v
|
||||
func Test_Ctx_CookieParser_Schema(t *testing.T) {
|
||||
t.Parallel()
|
||||
app := New()
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
defer app.ReleaseCtx(c)
|
||||
type result struct {
|
||||
Maths int `cookie:"maths"`
|
||||
English int `cookie:"english"`
|
||||
}
|
||||
type resStruct struct {
|
||||
Name string `cookie:"name"`
|
||||
Age int `cookie:"age"`
|
||||
Result result `cookie:"result"`
|
||||
}
|
||||
res := &resStruct{
|
||||
Name: "Joseph",
|
||||
Age: 10,
|
||||
Result: result{
|
||||
Maths: 10,
|
||||
English: 10,
|
||||
},
|
||||
}
|
||||
|
||||
// set cookie
|
||||
c.Request().Header.Set("Cookie", "name=Joseph")
|
||||
c.Request().Header.Set("Cookie", "age=10")
|
||||
c.Request().Header.Set("Cookie", "result.maths=10")
|
||||
c.Request().Header.Set("Cookie", "result.english=10")
|
||||
hR := new(resStruct)
|
||||
r := c.CookieParser(hR)
|
||||
|
||||
utils.AssertEqual(t, nil, r)
|
||||
utils.AssertEqual(t, *res, *hR)
|
||||
}
|
||||
|
||||
// go test -run Benchmark_Ctx_CookieParser -v
|
||||
func Benchmark_Ctx_CookieParser(b *testing.B) {
|
||||
app := New(Config{EnableSplittingOnParsers: true})
|
||||
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||
defer app.ReleaseCtx(c)
|
||||
type Cook struct {
|
||||
ID int `cookie:"id"`
|
||||
Name string `cookie:"name"`
|
||||
Courses []string `cookie:"courses"`
|
||||
Enrolled bool `cookie:"student"`
|
||||
Fees float32 `cookie:"fee"`
|
||||
Grades []uint8 `cookie:"score"`
|
||||
}
|
||||
cookie1 := new(Cook)
|
||||
cookie1.Name = "Joseph"
|
||||
|
||||
c.Request().Header.Set("Cookie", "id=1")
|
||||
c.Request().Header.Set("Cookie", "name=Joey")
|
||||
c.Request().Header.Set("Cookie", "courses=maths,english, chemistry, physics")
|
||||
c.Request().Header.Set("Cookie", "student=true")
|
||||
c.Request().Header.Set("Cookie", "fee=45.78")
|
||||
c.Request().Header.Set("Cookie", "score=7,6,10")
|
||||
|
||||
var err error
|
||||
// Run the function b.N times
|
||||
for i := 0; i < b.N; i++ {
|
||||
err = c.CookieParser(cookie1)
|
||||
}
|
||||
utils.AssertEqual(b, nil, err)
|
||||
}
|
||||
|
||||
// go test -run Test_Ctx_Cookies
|
||||
func Test_Ctx_Cookies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
|
|
@ -403,6 +403,38 @@ app.Get("/", func(c *fiber.Ctx) error {
|
|||
})
|
||||
```
|
||||
|
||||
## CookieParser
|
||||
|
||||
This method is similar to [BodyParser](ctx.md#bodyparser), but for cookie parameters.
|
||||
It is important to use the struct tag "cookie". For example, if you want to parse a cookie with a field called Age, you would use a struct field of `cookie:"age"`.
|
||||
|
||||
```go title="Signature"
|
||||
func (c *Ctx) CookieParser(out interface{}) error
|
||||
```
|
||||
|
||||
```go title="Example"
|
||||
// Field names should start with an uppercase letter
|
||||
type Person struct {
|
||||
Name string `cookie:"name"`
|
||||
Age int `cookie:"age"`
|
||||
Job bool `cookie:"job"`
|
||||
}
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
p := new(Person)
|
||||
|
||||
if err := c.CookieParser(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println(p.Name) // Joseph
|
||||
log.Println(p.Age) // 23
|
||||
log.Println(p.Job) // true
|
||||
})
|
||||
// Run tests with the following curl command
|
||||
// curl.exe --cookie "name=Joseph; age=23; job=true" http://localhost:8000/
|
||||
```
|
||||
|
||||
## Cookies
|
||||
|
||||
Get cookie value by key, you could pass an optional default value that will be returned if the cookie key does not exist.
|
||||
|
|
Loading…
Reference in New Issue