feat(context): add SetCookieStruct (#4215)# This is a combination of 2 commits.

feat(context): add SetCookieStruct (#4215)

feat(context): add SetCookieStruct (#4215)
This commit is contained in:
Rikiya Narita 2025-05-17 01:32:11 +09:00
parent 2e2bd1f408
commit f348e695ed
3 changed files with 125 additions and 0 deletions

View File

@ -1027,6 +1027,17 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string,
}) })
} }
// SetCookieStruct adds a Set-Cookie header to the ResponseWriter's headers.
// It accepts a pointer to http.Cookie structure for more flexibility in setting cookie attributes.
// The provided cookie must have a valid Name. Invalid cookies may be silently dropped.
func (c *Context) SetCookieStruct(cookie *http.Cookie) {
if cookie.Path == "" {
cookie.Path = "/"
}
cookie.SameSite = c.sameSite
http.SetCookie(c.Writer, cookie)
}
// Cookie returns the named cookie provided in the request or // Cookie returns the named cookie provided in the request or
// ErrNoCookie if not found. And return the named cookie is unescaped. // ErrNoCookie if not found. And return the named cookie is unescaped.
// If multiple cookies match the given name, only one cookie will // If multiple cookies match the given name, only one cookie will

View File

@ -3123,3 +3123,76 @@ func TestContextNext(t *testing.T) {
assert.True(t, exists) assert.True(t, exists)
assert.Equal(t, "value3", value) assert.Equal(t, "value3", value)
} }
func TestContextSetCookieStruct(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.SetSameSite(http.SameSiteLaxMode)
// Basic cookie settings
cookie := &http.Cookie{
Name: "user",
Value: "gin",
MaxAge: 1,
Path: "/",
Domain: "localhost",
Secure: true,
HttpOnly: true,
}
c.SetCookieStruct(cookie)
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie"))
// Test that when Path is empty, "/" is automatically set
cookie = &http.Cookie{
Name: "user",
Value: "gin",
MaxAge: 1,
Path: "",
Domain: "localhost",
Secure: true,
HttpOnly: true,
}
c.SetCookieStruct(cookie)
assert.Equal(t, "user=gin; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure; SameSite=Lax", c.Writer.Header().Get("Set-Cookie"))
// Test additional cookie attributes (Expires)
expireTime := time.Now().Add(24 * time.Hour)
cookie = &http.Cookie{
Name: "user",
Value: "gin",
Path: "/",
Domain: "localhost",
Expires: expireTime,
Secure: true,
HttpOnly: true,
}
c.SetCookieStruct(cookie)
// Since the Expires value varies by time, partially verify with Contains
setCookie := c.Writer.Header().Get("Set-Cookie")
assert.Contains(t, setCookie, "user=gin")
assert.Contains(t, setCookie, "Path=/")
assert.Contains(t, setCookie, "Domain=localhost")
assert.Contains(t, setCookie, "HttpOnly")
assert.Contains(t, setCookie, "Secure")
assert.Contains(t, setCookie, "SameSite=Lax")
// Test for Partitioned attribute (Go 1.18+)
cookie = &http.Cookie{
Name: "user",
Value: "gin",
Path: "/",
Domain: "localhost",
Secure: true,
HttpOnly: true,
Partitioned: true,
}
c.SetCookieStruct(cookie)
setCookie = c.Writer.Header().Get("Set-Cookie")
assert.Contains(t, setCookie, "user=gin")
assert.Contains(t, setCookie, "Path=/")
assert.Contains(t, setCookie, "Domain=localhost")
assert.Contains(t, setCookie, "HttpOnly")
assert.Contains(t, setCookie, "Secure")
assert.Contains(t, setCookie, "SameSite=Lax")
// Not testing for Partitioned attribute as it may not be supported in all Go versions
}

View File

@ -2319,6 +2319,47 @@ func main() {
} }
``` ```
You can also use the `SetCookieStruct` method, which accepts a `*http.Cookie` directly for more flexibility:
```go
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/cookie", func(c *gin.Context) {
cookie, err := c.Cookie("gin_cookie")
if err != nil {
cookie = "NotSet"
// Using http.Cookie struct for more control
c.SetCookieStruct(&http.Cookie{
Name: "gin_cookie",
Value: "test",
Path: "/",
Domain: "localhost",
MaxAge: 3600,
Secure: false,
HttpOnly: true,
// Additional fields available in http.Cookie
Expires: time.Now().Add(24 * time.Hour),
// Partitioned: true, // Available in newer Go versions
})
}
fmt.Printf("Cookie value: %s \n", cookie)
})
router.Run()
}
```
## Don't trust all proxies ## Don't trust all proxies
Gin lets you specify which headers to hold the real client IP (if any), Gin lets you specify which headers to hold the real client IP (if any),