🐛 Fix Mutability issue of UserContext in fiber.Ctx (#1522)

* existing tests clean up

* concurrent test for UserContext Reused

* comment on Ctx.userContext field

* userContext tests edited & fixed, userContext field removed, constant of userContextKey defined
pull/1525/head
Thammachart Chinvarapon 2021-09-09 13:11:26 +07:00 committed by GitHub
parent 5d1f89942c
commit 842b63022d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 26 deletions

15
ctx.go
View File

@ -34,6 +34,9 @@ const maxParams = 30
const queryTag = "query"
// userContextKey define the key name for storing context.Context in *fasthttp.RequestCtx
const userContextKey = "__local_user_context__"
// Ctx represents the Context which hold the HTTP request and response.
// It has methods for the request query string, parameters, body, HTTP headers and so on.
type Ctx struct {
@ -53,7 +56,6 @@ type Ctx struct {
values [maxParams]string // Route parameter values
fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx
matched bool // Non use route matched
userContext context.Context
}
// Range data for c.Range
@ -342,15 +344,18 @@ func (c *Ctx) Context() *fasthttp.RequestCtx {
// UserContext returns a context implementation that was set by
// user earlier or returns a non-nil, empty context,if it was not set earlier.
func (c *Ctx) UserContext() context.Context {
if c.userContext == nil {
c.userContext = context.Background()
ctx, ok := c.fasthttp.UserValue(userContextKey).(context.Context)
if !ok {
ctx = context.Background()
c.SetUserContext(ctx)
}
return c.userContext
return ctx
}
// SetUserContext sets a context implementation by user.
func (c *Ctx) SetUserContext(ctx context.Context) {
c.userContext = ctx
c.fasthttp.SetUserValue(userContextKey, ctx)
}
// Cookie sets a cookie by passing a cookie struct.

View File

@ -358,21 +358,21 @@ func Test_Ctx_BodyParser(t *testing.T) {
Name string `json:"name" xml:"name" form:"name" query:"name"`
}
{
var gzipJSON bytes.Buffer
w := gzip.NewWriter(&gzipJSON)
_, _ = w.Write([]byte(`{"name":"john"}`))
_ = w.Close()
{
var gzipJSON bytes.Buffer
w := gzip.NewWriter(&gzipJSON)
_, _ = w.Write([]byte(`{"name":"john"}`))
_ = w.Close()
c.Request().Header.SetContentType(MIMEApplicationJSON)
c.Request().Header.Set(HeaderContentEncoding, "gzip")
c.Request().SetBody(gzipJSON.Bytes())
c.Request().Header.SetContentLength(len(gzipJSON.Bytes()))
d := new(Demo)
utils.AssertEqual(t, nil, c.BodyParser(d))
utils.AssertEqual(t, "john", d.Name)
c.Request().Header.Del(HeaderContentEncoding)
}
c.Request().Header.SetContentType(MIMEApplicationJSON)
c.Request().Header.Set(HeaderContentEncoding, "gzip")
c.Request().SetBody(gzipJSON.Bytes())
c.Request().Header.SetContentLength(len(gzipJSON.Bytes()))
d := new(Demo)
utils.AssertEqual(t, nil, c.BodyParser(d))
utils.AssertEqual(t, "john", d.Name)
c.Request().Header.Del(HeaderContentEncoding)
}
testDecodeParser := func(contentType, body string) {
c.Request().Header.SetContentType(contentType)
@ -508,7 +508,10 @@ func Test_Ctx_Context(t *testing.T) {
// go test -run Test_Ctx_UserContext
func Test_Ctx_UserContext(t *testing.T) {
c := Ctx{}
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
t.Run("Nil_Context", func(t *testing.T) {
ctx := c.UserContext()
utils.AssertEqual(t, ctx, context.Background())
@ -518,16 +521,56 @@ func Test_Ctx_UserContext(t *testing.T) {
testKey := "Test Key"
testValue := "Test Value"
ctx := context.WithValue(context.Background(), testKey, testValue)
utils.AssertEqual(t, ctx.Value(testKey), testValue)
utils.AssertEqual(t, testValue, ctx.Value(testKey))
})
}
// go test -run Test_Ctx_UserContext
// go test -run Test_Ctx_SetUserContext
func Test_Ctx_SetUserContext(t *testing.T) {
c := Ctx{}
c.SetUserContext(context.Background())
utils.AssertEqual(t, c.UserContext(), context.Background())
app := New()
c := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(c)
testKey := "Test Key"
testValue := "Test Value"
ctx := context.WithValue(context.Background(), testKey, testValue)
c.SetUserContext(ctx)
utils.AssertEqual(t, testValue, c.UserContext().Value(testKey))
}
// go test -run Test_Ctx_UserContext_Multiple_Requests
func Test_Ctx_UserContext_Multiple_Requests(t *testing.T) {
testKey := "foobar-key"
testValue := "foobar-value"
app := New()
app.Get("/", func(c *Ctx) error {
ctx := c.UserContext()
if ctx.Value(testKey) != nil {
return c.SendStatus(StatusInternalServerError)
}
input := utils.CopyString(c.Query("input", "NO_VALUE"))
ctx = context.WithValue(ctx, testKey, fmt.Sprintf("%s_%s", testValue, input))
c.SetUserContext(ctx)
return c.Status(StatusOK).SendString(fmt.Sprintf("resp_%s_returned", input))
})
// Consecutive Requests
for i := 1; i <= 10; i++ {
t.Run(fmt.Sprintf("request_%d", i), func(t *testing.T) {
resp, err := app.Test(httptest.NewRequest(MethodGet, fmt.Sprintf("/?input=%d", i), nil))
utils.AssertEqual(t, nil, err, "Unexpected error from response")
utils.AssertEqual(t, StatusOK, resp.StatusCode, "context.Context returned from c.UserContext() is reused")
b, err := ioutil.ReadAll(resp.Body)
utils.AssertEqual(t, nil, err, "Unexpected error from reading response body")
utils.AssertEqual(t, fmt.Sprintf("resp_%d_returned", i), string(b), "response text incorrect")
})
}
}
// go test -run Test_Ctx_Cookie