diff --git a/app_test.go b/app_test.go index 39890ead48..892ef1192e 100644 --- a/app_test.go +++ b/app_test.go @@ -981,8 +981,8 @@ func Test_App_Static_Custom_CacheControl(t *testing.T) { app := New() app.Static("/", "./.github", Static{ModifyResponse: func(c Ctx) error { - if strings.Contains(c.GetRespHeader("Content-Type"), "text/html") { - c.Response().Header.Set("Cache-Control", "no-cache, no-store, must-revalidate") + if strings.Contains(c.Res().Get("Content-Type"), "text/html") { + c.Set("Cache-Control", "no-cache, no-store, must-revalidate") } return nil }}) @@ -1666,7 +1666,7 @@ func Test_App_ReadBodyStream(t *testing.T) { app := New(Config{StreamRequestBody: true}) app.Post("/", func(c Ctx) error { // Calling c.Body() automatically reads the entire stream. - return c.SendString(fmt.Sprintf("%v %s", c.Request().IsBodyStream(), c.Body())) + return c.SendString(fmt.Sprintf("%v %s", c.Context().Request.IsBodyStream(), c.Body())) }) testString := "this is a test" resp, err := app.Test(httptest.NewRequest(MethodPost, "/", bytes.NewBufferString(testString))) @@ -1683,7 +1683,7 @@ func Test_App_DisablePreParseMultipartForm(t *testing.T) { app := New(Config{DisablePreParseMultipartForm: true, StreamRequestBody: true}) app.Post("/", func(c Ctx) error { - req := c.Request() + req := &c.Context().Request mpf, err := req.MultipartForm() if err != nil { return err diff --git a/bind.go b/bind.go index f7e449f6e3..be1eaadcb0 100644 --- a/bind.go +++ b/bind.go @@ -76,7 +76,7 @@ func (b *Bind) Custom(name string, dest any) error { // Header binds the request header strings into the struct, map[string]string and map[string][]string. func (b *Bind) Header(out any) error { - if err := b.returnErr(binder.HeaderBinder.Bind(b.ctx.Request(), out)); err != nil { + if err := b.returnErr(binder.HeaderBinder.Bind(&b.ctx.Context().Request, out)); err != nil { return err } @@ -85,7 +85,7 @@ func (b *Bind) Header(out any) error { // RespHeader binds the response header strings into the struct, map[string]string and map[string][]string. func (b *Bind) RespHeader(out any) error { - if err := b.returnErr(binder.RespHeaderBinder.Bind(b.ctx.Response(), out)); err != nil { + if err := b.returnErr(binder.RespHeaderBinder.Bind(&b.ctx.Context().Response, out)); err != nil { return err } diff --git a/bind_test.go b/bind_test.go index c2edaca6a3..ac839b020a 100644 --- a/bind_test.go +++ b/bind_test.go @@ -30,25 +30,25 @@ func Test_Bind_Query(t *testing.T) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := new(Query) require.NoError(t, c.Bind().Query(q)) require.Len(t, q.Hobby, 2) - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") q = new(Query) require.NoError(t, c.Bind().Query(q)) require.Len(t, q.Hobby, 2) - c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football") q = new(Query) require.NoError(t, c.Bind().Query(q)) require.Len(t, q.Hobby, 3) empty := new(Query) - c.Request().URI().SetQueryString("") + c.Context().URI().SetQueryString("") require.NoError(t, c.Bind().Query(empty)) require.Empty(t, empty.Hobby) @@ -63,7 +63,7 @@ func Test_Bind_Query(t *testing.T) { No []int64 } - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1") q2 := new(Query2) q2.Bool = true q2.Name = helloWorld @@ -81,14 +81,14 @@ func Test_Bind_Query(t *testing.T) { Name string `query:"name,required"` } rq := new(RequiredQuery) - c.Request().URI().SetQueryString("") + c.Context().URI().SetQueryString("") require.Equal(t, "name is empty", c.Bind().Query(rq).Error()) type ArrayQuery struct { Data []string } aq := new(ArrayQuery) - c.Request().URI().SetQueryString("data[]=john&data[]=doe") + c.Context().URI().SetQueryString("data[]=john&data[]=doe") require.NoError(t, c.Bind().Query(aq)) require.Len(t, aq.Data, 2) } @@ -100,35 +100,35 @@ func Test_Bind_Query_Map(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := make(map[string][]string) require.NoError(t, c.Bind().Query(&q)) require.Len(t, q["hobby"], 2) - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") q = make(map[string][]string) require.NoError(t, c.Bind().Query(&q)) require.Len(t, q["hobby"], 2) - c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football") q = make(map[string][]string) require.NoError(t, c.Bind().Query(&q)) require.Len(t, q["hobby"], 3) - c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=scoccer") qq := make(map[string]string) require.NoError(t, c.Bind().Query(&qq)) require.Equal(t, "1", qq["id"]) empty := make(map[string][]string) - c.Request().URI().SetQueryString("") + c.Context().URI().SetQueryString("") require.NoError(t, c.Bind().Query(&empty)) require.Empty(t, empty["hobby"]) em := make(map[string][]int) - c.Request().URI().SetQueryString("") + c.Context().URI().SetQueryString("") require.ErrorIs(t, c.Bind().Query(&em), binder.ErrMapNotConvertable) } @@ -164,18 +164,18 @@ func Test_Bind_Query_WithSetParserDecoder(t *testing.T) { Body string `query:"body"` } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") q := new(NonRFCTimeInput) - c.Request().URI().SetQueryString("date=2021-04-10&title=CustomDateTest&Body=October") + c.Context().URI().SetQueryString("date=2021-04-10&title=CustomDateTest&Body=October") require.NoError(t, c.Bind().Query(q)) require.Equal(t, "CustomDateTest", q.Title) date := fmt.Sprintf("%v", q.Date) require.Equal(t, "{0 63753609600 }", date) require.Equal(t, "October", q.Body) - c.Request().URI().SetQueryString("date=2021-04-10&title&Body=October") + c.Context().URI().SetQueryString("date=2021-04-10&title&Body=October") q = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", @@ -196,21 +196,21 @@ func Test_Bind_Query_Schema(t *testing.T) { Age int `query:"age"` } `query:"nested,required"` } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") - c.Request().URI().SetQueryString("name=tom&nested.age=10") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") + c.Context().URI().SetQueryString("name=tom&nested.age=10") q := new(Query1) require.NoError(t, c.Bind().Query(q)) - c.Request().URI().SetQueryString("namex=tom&nested.age=10") + c.Context().URI().SetQueryString("namex=tom&nested.age=10") q = new(Query1) require.Equal(t, "name is empty", c.Bind().Query(q).Error()) - c.Request().URI().SetQueryString("name=tom&nested.agex=10") + c.Context().URI().SetQueryString("name=tom&nested.agex=10") q = new(Query1) require.NoError(t, c.Bind().Query(q)) - c.Request().URI().SetQueryString("name=tom&test.age=10") + c.Context().URI().SetQueryString("name=tom&test.age=10") q = new(Query1) require.Equal(t, "nested is empty", c.Bind().Query(q).Error()) @@ -220,19 +220,19 @@ func Test_Bind_Query_Schema(t *testing.T) { Age int `query:"age,required"` } `query:"nested"` } - c.Request().URI().SetQueryString("name=tom&nested.age=10") + c.Context().URI().SetQueryString("name=tom&nested.age=10") q2 := new(Query2) require.NoError(t, c.Bind().Query(q2)) - c.Request().URI().SetQueryString("nested.age=10") + c.Context().URI().SetQueryString("nested.age=10") q2 = new(Query2) require.NoError(t, c.Bind().Query(q2)) - c.Request().URI().SetQueryString("nested.agex=10") + c.Context().URI().SetQueryString("nested.agex=10") q2 = new(Query2) require.Equal(t, "nested.age is empty", c.Bind().Query(q2).Error()) - c.Request().URI().SetQueryString("nested.agex=10") + c.Context().URI().SetQueryString("nested.agex=10") q2 = new(Query2) require.Equal(t, "nested.age is empty", c.Bind().Query(q2).Error()) @@ -240,17 +240,17 @@ func Test_Bind_Query_Schema(t *testing.T) { Value int `query:"val,required"` Next *Node `query:"next,required"` } - c.Request().URI().SetQueryString("val=1&next.val=3") + c.Context().URI().SetQueryString("val=1&next.val=3") n := new(Node) require.NoError(t, c.Bind().Query(n)) require.Equal(t, 1, n.Value) require.Equal(t, 3, n.Next.Value) - c.Request().URI().SetQueryString("next.val=2") + c.Context().URI().SetQueryString("next.val=2") n = new(Node) require.Equal(t, "val is empty", c.Bind().Query(n).Error()) - c.Request().URI().SetQueryString("val=3&next.value=2") + c.Context().URI().SetQueryString("val=3&next.value=2") n = new(Node) n.Next = new(Node) require.NoError(t, c.Bind().Query(n)) @@ -266,7 +266,7 @@ func Test_Bind_Query_Schema(t *testing.T) { Data []Person `query:"data"` } - c.Request().URI().SetQueryString("data[0][name]=john&data[0][age]=10&data[1][name]=doe&data[1][age]=12") + c.Context().URI().SetQueryString("data[0][name]=john&data[0][age]=10&data[1][name]=doe&data[1][age]=12") cq := new(CollectionQuery) require.NoError(t, c.Bind().Query(cq)) require.Len(t, cq.Data, 2) @@ -275,7 +275,7 @@ func Test_Bind_Query_Schema(t *testing.T) { require.Equal(t, "doe", cq.Data[1].Name) require.Equal(t, 12, cq.Data[1].Age) - c.Request().URI().SetQueryString("data.0.name=john&data.0.age=10&data.1.name=doe&data.1.age=12") + c.Context().URI().SetQueryString("data.0.name=john&data.0.age=10&data.1.name=doe&data.1.age=12") cq = new(CollectionQuery) require.NoError(t, c.Bind().Query(cq)) require.Len(t, cq.Data, 2) @@ -296,24 +296,24 @@ func Test_Bind_Header(t *testing.T) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.Add("id", "1") - c.Request().Header.Add("Name", "John Doe") - c.Request().Header.Add("Hobby", "golang,fiber") + c.Context().Request.Header.Add("id", "1") + c.Context().Request.Header.Add("Name", "John Doe") + c.Context().Request.Header.Add("Hobby", "golang,fiber") q := new(Header) require.NoError(t, c.Bind().Header(q)) require.Len(t, q.Hobby, 2) - c.Request().Header.Del("hobby") - c.Request().Header.Add("Hobby", "golang,fiber,go") + c.Context().Request.Header.Del("hobby") + c.Context().Request.Header.Add("Hobby", "golang,fiber,go") q = new(Header) require.NoError(t, c.Bind().Header(q)) require.Len(t, q.Hobby, 3) empty := new(Header) - c.Request().Header.Del("hobby") + c.Context().Request.Header.Del("hobby") require.NoError(t, c.Bind().Query(empty)) require.Empty(t, empty.Hobby) @@ -328,13 +328,13 @@ func Test_Bind_Header(t *testing.T) { No []int64 } - c.Request().Header.Add("id", "2") - c.Request().Header.Add("Name", "Jane Doe") - c.Request().Header.Del("hobby") - c.Request().Header.Add("Hobby", "go,fiber") - c.Request().Header.Add("favouriteDrinks", "milo,coke,pepsi") - c.Request().Header.Add("alloc", "") - c.Request().Header.Add("no", "1") + c.Context().Request.Header.Add("id", "2") + c.Context().Request.Header.Add("Name", "Jane Doe") + c.Context().Request.Header.Del("hobby") + c.Context().Request.Header.Add("Hobby", "go,fiber") + c.Context().Request.Header.Add("favouriteDrinks", "milo,coke,pepsi") + c.Context().Request.Header.Add("alloc", "") + c.Context().Request.Header.Add("no", "1") h2 := new(Header2) h2.Bool = true @@ -353,7 +353,7 @@ func Test_Bind_Header(t *testing.T) { Name string `header:"name,required"` } rh := new(RequiredHeader) - c.Request().Header.Del("name") + c.Context().Request.Header.Del("name") require.Equal(t, "name is empty", c.Bind().Header(rh).Error()) } @@ -364,24 +364,24 @@ func Test_Bind_Header_Map(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.Add("id", "1") - c.Request().Header.Add("Name", "John Doe") - c.Request().Header.Add("Hobby", "golang,fiber") + c.Context().Request.Header.Add("id", "1") + c.Context().Request.Header.Add("Name", "John Doe") + c.Context().Request.Header.Add("Hobby", "golang,fiber") q := make(map[string][]string, 0) require.NoError(t, c.Bind().Header(&q)) require.Len(t, q["Hobby"], 2) - c.Request().Header.Del("hobby") - c.Request().Header.Add("Hobby", "golang,fiber,go") + c.Context().Request.Header.Del("hobby") + c.Context().Request.Header.Add("Hobby", "golang,fiber,go") q = make(map[string][]string, 0) require.NoError(t, c.Bind().Header(&q)) require.Len(t, q["Hobby"], 3) empty := make(map[string][]string, 0) - c.Request().Header.Del("hobby") + c.Context().Request.Header.Del("hobby") require.NoError(t, c.Bind().Query(&empty)) require.Empty(t, empty["Hobby"]) } @@ -418,13 +418,13 @@ func Test_Bind_Header_WithSetParserDecoder(t *testing.T) { Body string `req:"body"` } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") r := new(NonRFCTimeInput) - c.Request().Header.Add("Date", "2021-04-10") - c.Request().Header.Add("Title", "CustomDateTest") - c.Request().Header.Add("Body", "October") + c.Context().Request.Header.Add("Date", "2021-04-10") + c.Context().Request.Header.Add("Title", "CustomDateTest") + c.Context().Request.Header.Add("Body", "October") require.NoError(t, c.Bind().Header(r)) require.Equal(t, "CustomDateTest", r.Title) @@ -432,7 +432,7 @@ func Test_Bind_Header_WithSetParserDecoder(t *testing.T) { require.Equal(t, "{0 63753609600 }", date) require.Equal(t, "October", r.Body) - c.Request().Header.Add("Title", "") + c.Context().Request.Header.Add("Title", "") r = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", @@ -453,30 +453,30 @@ func Test_Bind_Header_Schema(t *testing.T) { Age int `header:"Age"` } `header:"Nested,required"` } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.Add("Name", "tom") - c.Request().Header.Add("Nested.Age", "10") + c.Context().Request.Header.Add("Name", "tom") + c.Context().Request.Header.Add("Nested.Age", "10") q := new(Header1) require.NoError(t, c.Bind().Header(q)) - c.Request().Header.Del("Name") + c.Context().Request.Header.Del("Name") q = new(Header1) require.Equal(t, "Name is empty", c.Bind().Header(q).Error()) - c.Request().Header.Add("Name", "tom") - c.Request().Header.Del("Nested.Age") - c.Request().Header.Add("Nested.Agex", "10") + c.Context().Request.Header.Add("Name", "tom") + c.Context().Request.Header.Del("Nested.Age") + c.Context().Request.Header.Add("Nested.Agex", "10") q = new(Header1) require.NoError(t, c.Bind().Header(q)) - c.Request().Header.Del("Nested.Agex") + c.Context().Request.Header.Del("Nested.Agex") q = new(Header1) require.Equal(t, "Nested is empty", c.Bind().Header(q).Error()) - c.Request().Header.Del("Nested.Agex") - c.Request().Header.Del("Name") + c.Context().Request.Header.Del("Nested.Agex") + c.Context().Request.Header.Del("Name") type Header2 struct { Name string `header:"Name"` @@ -485,19 +485,19 @@ func Test_Bind_Header_Schema(t *testing.T) { } `header:"Nested"` } - c.Request().Header.Add("Name", "tom") - c.Request().Header.Add("Nested.Age", "10") + c.Context().Request.Header.Add("Name", "tom") + c.Context().Request.Header.Add("Nested.Age", "10") h2 := new(Header2) require.NoError(t, c.Bind().Header(h2)) - c.Request().Header.Del("Name") + c.Context().Request.Header.Del("Name") h2 = new(Header2) require.NoError(t, c.Bind().Header(h2)) - c.Request().Header.Del("Name") - c.Request().Header.Del("Nested.Age") - c.Request().Header.Add("Nested.Agex", "10") + c.Context().Request.Header.Del("Name") + c.Context().Request.Header.Del("Nested.Age") + c.Context().Request.Header.Add("Nested.Agex", "10") h2 = new(Header2) require.Equal(t, "Nested.age is empty", c.Bind().Header(h2).Error()) @@ -505,20 +505,20 @@ func Test_Bind_Header_Schema(t *testing.T) { Value int `header:"Val,required"` Next *Node `header:"Next,required"` } - c.Request().Header.Add("Val", "1") - c.Request().Header.Add("Next.Val", "3") + c.Context().Request.Header.Add("Val", "1") + c.Context().Request.Header.Add("Next.Val", "3") n := new(Node) require.NoError(t, c.Bind().Header(n)) require.Equal(t, 1, n.Value) require.Equal(t, 3, n.Next.Value) - c.Request().Header.Del("Val") + c.Context().Request.Header.Del("Val") n = new(Node) require.Equal(t, "Val is empty", c.Bind().Header(n).Error()) - c.Request().Header.Add("Val", "3") - c.Request().Header.Del("Next.Val") - c.Request().Header.Add("Next.Value", "2") + c.Context().Request.Header.Add("Val", "3") + c.Context().Request.Header.Del("Next.Val") + c.Context().Request.Header.Add("Next.Value", "2") n = new(Node) n.Next = new(Node) require.NoError(t, c.Bind().Header(n)) @@ -537,24 +537,24 @@ func Test_Bind_RespHeader(t *testing.T) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Response().Header.Add("id", "1") - c.Response().Header.Add("Name", "John Doe") - c.Response().Header.Add("Hobby", "golang,fiber") + c.Context().Response.Header.Add("id", "1") + c.Context().Response.Header.Add("Name", "John Doe") + c.Context().Response.Header.Add("Hobby", "golang,fiber") q := new(Header) require.NoError(t, c.Bind().RespHeader(q)) require.Len(t, q.Hobby, 2) - c.Response().Header.Del("hobby") - c.Response().Header.Add("Hobby", "golang,fiber,go") + c.Context().Response.Header.Del("hobby") + c.Context().Response.Header.Add("Hobby", "golang,fiber,go") q = new(Header) require.NoError(t, c.Bind().RespHeader(q)) require.Len(t, q.Hobby, 3) empty := new(Header) - c.Response().Header.Del("hobby") + c.Context().Response.Header.Del("hobby") require.NoError(t, c.Bind().Query(empty)) require.Empty(t, empty.Hobby) @@ -569,13 +569,13 @@ func Test_Bind_RespHeader(t *testing.T) { No []int64 } - c.Response().Header.Add("id", "2") - c.Response().Header.Add("Name", "Jane Doe") - c.Response().Header.Del("hobby") - c.Response().Header.Add("Hobby", "go,fiber") - c.Response().Header.Add("favouriteDrinks", "milo,coke,pepsi") - c.Response().Header.Add("alloc", "") - c.Response().Header.Add("no", "1") + c.Context().Response.Header.Add("id", "2") + c.Context().Response.Header.Add("Name", "Jane Doe") + c.Context().Response.Header.Del("hobby") + c.Context().Response.Header.Add("Hobby", "go,fiber") + c.Context().Response.Header.Add("favouriteDrinks", "milo,coke,pepsi") + c.Context().Response.Header.Add("alloc", "") + c.Context().Response.Header.Add("no", "1") h2 := new(Header2) h2.Bool = true @@ -594,7 +594,7 @@ func Test_Bind_RespHeader(t *testing.T) { Name string `respHeader:"name,required"` } rh := new(RequiredHeader) - c.Response().Header.Del("name") + c.Context().Response.Header.Del("name") require.Equal(t, "name is empty", c.Bind().RespHeader(rh).Error()) } @@ -605,24 +605,24 @@ func Test_Bind_RespHeader_Map(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Response().Header.Add("id", "1") - c.Response().Header.Add("Name", "John Doe") - c.Response().Header.Add("Hobby", "golang,fiber") + c.Context().Response.Header.Add("id", "1") + c.Context().Response.Header.Add("Name", "John Doe") + c.Context().Response.Header.Add("Hobby", "golang,fiber") q := make(map[string][]string, 0) require.NoError(t, c.Bind().RespHeader(&q)) require.Len(t, q["Hobby"], 2) - c.Response().Header.Del("hobby") - c.Response().Header.Add("Hobby", "golang,fiber,go") + c.Context().Response.Header.Del("hobby") + c.Context().Response.Header.Add("Hobby", "golang,fiber,go") q = make(map[string][]string, 0) require.NoError(t, c.Bind().RespHeader(&q)) require.Len(t, q["Hobby"], 3) empty := make(map[string][]string, 0) - c.Response().Header.Del("hobby") + c.Context().Response.Header.Del("hobby") require.NoError(t, c.Bind().Query(&empty)) require.Empty(t, empty["Hobby"]) } @@ -639,9 +639,9 @@ func Benchmark_Bind_Query(b *testing.B) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := new(Query) b.ReportAllocs() b.ResetTimer() @@ -658,9 +658,9 @@ func Benchmark_Bind_Query_Map(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football") q := make(map[string][]string) b.ReportAllocs() b.ResetTimer() @@ -686,9 +686,9 @@ func Benchmark_Bind_Query_WithParseParam(b *testing.B) { Data []Person `query:"data"` } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") - c.Request().URI().SetQueryString("data[0][name]=john&data[0][age]=10") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") + c.Context().URI().SetQueryString("data[0][name]=john&data[0][age]=10") cq := new(CollectionQuery) b.ReportAllocs() @@ -712,9 +712,9 @@ func Benchmark_Bind_Query_Comma(b *testing.B) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball,football") q := new(Query) b.ReportAllocs() b.ResetTimer() @@ -736,12 +736,12 @@ func Benchmark_Bind_Header(b *testing.B) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.Add("id", "1") - c.Request().Header.Add("Name", "John Doe") - c.Request().Header.Add("Hobby", "golang,fiber") + c.Context().Request.Header.Add("id", "1") + c.Context().Request.Header.Add("Name", "John Doe") + c.Context().Request.Header.Add("Hobby", "golang,fiber") q := new(ReqHeader) b.ReportAllocs() @@ -758,12 +758,12 @@ func Benchmark_Bind_Header_Map(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.Add("id", "1") - c.Request().Header.Add("Name", "John Doe") - c.Request().Header.Add("Hobby", "golang,fiber") + c.Context().Request.Header.Add("id", "1") + c.Context().Request.Header.Add("Name", "John Doe") + c.Context().Request.Header.Add("Hobby", "golang,fiber") q := make(map[string][]string) b.ReportAllocs() @@ -786,12 +786,12 @@ func Benchmark_Bind_RespHeader(b *testing.B) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Response().Header.Add("id", "1") - c.Response().Header.Add("Name", "John Doe") - c.Response().Header.Add("Hobby", "golang,fiber") + c.Context().Response.Header.Add("id", "1") + c.Context().Response.Header.Add("Name", "John Doe") + c.Context().Response.Header.Add("Hobby", "golang,fiber") q := new(ReqHeader) b.ReportAllocs() @@ -808,12 +808,12 @@ func Benchmark_Bind_RespHeader_Map(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Response().Header.Add("id", "1") - c.Response().Header.Add("Name", "John Doe") - c.Response().Header.Add("Hobby", "golang,fiber") + c.Context().Response.Header.Add("id", "1") + c.Context().Response.Header.Add("Name", "John Doe") + c.Context().Response.Header.Add("Hobby", "golang,fiber") q := make(map[string][]string) b.ReportAllocs() @@ -842,20 +842,20 @@ func Test_Bind_Body(t *testing.T) { err = w.Close() require.NoError(t, err) - c.Request().Header.SetContentType(MIMEApplicationJSON) - c.Request().Header.Set(HeaderContentEncoding, "gzip") - c.Request().SetBody(gzipJSON.Bytes()) - c.Request().Header.SetContentLength(len(gzipJSON.Bytes())) + c.Context().Request.Header.SetContentType(MIMEApplicationJSON) + c.Context().Request.Header.SetContentEncoding("gzip") + c.Context().Request.SetBody(gzipJSON.Bytes()) + c.Context().Request.Header.SetContentLength(len(gzipJSON.Bytes())) d := new(Demo) require.NoError(t, c.Bind().Body(d)) require.Equal(t, "john", d.Name) - c.Request().Header.Del(HeaderContentEncoding) + c.Context().Request.Header.Del(HeaderContentEncoding) } testDecodeParser := func(contentType, body string) { - c.Request().Header.SetContentType(contentType) - c.Request().SetBody([]byte(body)) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.Header.SetContentType(contentType) + c.Context().Request.SetBody([]byte(body)) + c.Context().Request.Header.SetContentLength(len(body)) d := new(Demo) require.NoError(t, c.Bind().Body(d)) require.Equal(t, "john", d.Name) @@ -867,9 +867,9 @@ func Test_Bind_Body(t *testing.T) { testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--") testDecodeParserError := func(contentType, body string) { - c.Request().Header.SetContentType(contentType) - c.Request().SetBody([]byte(body)) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.Header.SetContentType(contentType) + c.Context().Request.SetBody([]byte(body)) + c.Context().Request.Header.SetContentLength(len(body)) require.Error(t, c.Bind().Body(nil)) } @@ -880,20 +880,20 @@ func Test_Bind_Body(t *testing.T) { Data []Demo `query:"data"` } - c.Request().Reset() - c.Request().Header.SetContentType(MIMEApplicationForm) - c.Request().SetBody([]byte("data[0][name]=john&data[1][name]=doe")) - c.Request().Header.SetContentLength(len(c.Body())) + c.Context().Request.Reset() + c.Context().Request.Header.SetContentType(MIMEApplicationForm) + c.Context().Request.SetBody([]byte("data[0][name]=john&data[1][name]=doe")) + c.Context().Request.Header.SetContentLength(len(c.Body())) cq := new(CollectionQuery) require.NoError(t, c.Bind().Body(cq)) require.Len(t, cq.Data, 2) require.Equal(t, "john", cq.Data[0].Name) require.Equal(t, "doe", cq.Data[1].Name) - c.Request().Reset() - c.Request().Header.SetContentType(MIMEApplicationForm) - c.Request().SetBody([]byte("data.0.name=john&data.1.name=doe")) - c.Request().Header.SetContentLength(len(c.Body())) + c.Context().Request.Reset() + c.Context().Request.Header.SetContentType(MIMEApplicationForm) + c.Context().Request.SetBody([]byte("data.0.name=john&data.1.name=doe")) + c.Context().Request.Header.SetContentLength(len(c.Body())) cq = new(CollectionQuery) require.NoError(t, c.Bind().Body(cq)) require.Len(t, cq.Data, 2) @@ -934,9 +934,9 @@ func Test_Bind_Body_WithSetParserDecoder(t *testing.T) { } testDecodeParser := func(contentType, body string) { - c.Request().Header.SetContentType(contentType) - c.Request().SetBody([]byte(body)) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.Header.SetContentType(contentType) + c.Context().Request.SetBody([]byte(body)) + c.Context().Request.Header.SetContentLength(len(body)) d := Demo{ Title: "Existing title", Body: "Existing Body", @@ -963,9 +963,9 @@ func Benchmark_Bind_Body_JSON(b *testing.B) { Name string `json:"name"` } body := []byte(`{"name":"john"}`) - c.Request().SetBody(body) - c.Request().Header.SetContentType(MIMEApplicationJSON) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.SetBody(body) + c.Context().Request.Header.SetContentType(MIMEApplicationJSON) + c.Context().Request.Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() @@ -989,9 +989,9 @@ func Benchmark_Bind_Body_XML(b *testing.B) { Name string `xml:"name"` } body := []byte("john") - c.Request().SetBody(body) - c.Request().Header.SetContentType(MIMEApplicationXML) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.SetBody(body) + c.Context().Request.Header.SetContentType(MIMEApplicationXML) + c.Context().Request.Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() @@ -1015,9 +1015,9 @@ func Benchmark_Bind_Body_Form(b *testing.B) { Name string `form:"name"` } body := []byte("name=john") - c.Request().SetBody(body) - c.Request().Header.SetContentType(MIMEApplicationForm) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.SetBody(body) + c.Context().Request.Header.SetContentType(MIMEApplicationForm) + c.Context().Request.Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() @@ -1042,9 +1042,9 @@ func Benchmark_Bind_Body_MultipartForm(b *testing.B) { } body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--") - c.Request().SetBody(body) - c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary="b"`) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.SetBody(body) + c.Context().Request.Header.SetContentType(MIMEMultipartForm + `;boundary="b"`) + c.Context().Request.Header.SetContentLength(len(body)) d := new(Demo) b.ReportAllocs() @@ -1065,9 +1065,9 @@ func Benchmark_Bind_Body_Form_Map(b *testing.B) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) body := []byte("name=john") - c.Request().SetBody(body) - c.Request().Header.SetContentType(MIMEApplicationForm) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.SetBody(body) + c.Context().Request.Header.SetContentType(MIMEApplicationForm) + c.Context().Request.Header.SetContentLength(len(body)) d := make(map[string]string) b.ReportAllocs() @@ -1132,12 +1132,12 @@ func Benchmark_Bind_URI(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.route = &Route{ + c.req.route = &Route{ Params: []string{ "param1", "param2", "param3", "param4", }, } - c.values = [maxParams]string{ + c.req.values = [maxParams]string{ "john", "doe", "is", "awesome", } @@ -1169,12 +1169,12 @@ func Benchmark_Bind_URI_Map(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.route = &Route{ + c.req.route = &Route{ Params: []string{ "param1", "param2", "param3", "param4", }, } - c.values = [maxParams]string{ + c.req.values = [maxParams]string{ "john", "doe", "is", "awesome", } @@ -1206,24 +1206,24 @@ func Test_Bind_Cookie(t *testing.T) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.SetCookie("id", "1") - c.Request().Header.SetCookie("Name", "John Doe") - c.Request().Header.SetCookie("Hobby", "golang,fiber") + c.Context().Request.Header.SetCookie("id", "1") + c.Context().Request.Header.SetCookie("Name", "John Doe") + c.Context().Request.Header.SetCookie("Hobby", "golang,fiber") q := new(Cookie) require.NoError(t, c.Bind().Cookie(q)) require.Len(t, q.Hobby, 2) - c.Request().Header.DelCookie("hobby") - c.Request().Header.SetCookie("Hobby", "golang,fiber,go") + c.Context().Request.Header.DelCookie("hobby") + c.Context().Request.Header.SetCookie("Hobby", "golang,fiber,go") q = new(Cookie) require.NoError(t, c.Bind().Cookie(q)) require.Len(t, q.Hobby, 3) empty := new(Cookie) - c.Request().Header.DelCookie("hobby") + c.Context().Request.Header.DelCookie("hobby") require.NoError(t, c.Bind().Query(empty)) require.Empty(t, empty.Hobby) @@ -1238,13 +1238,13 @@ func Test_Bind_Cookie(t *testing.T) { No []int64 } - c.Request().Header.SetCookie("id", "2") - c.Request().Header.SetCookie("Name", "Jane Doe") - c.Request().Header.DelCookie("hobby") - c.Request().Header.SetCookie("Hobby", "go,fiber") - c.Request().Header.SetCookie("favouriteDrinks", "milo,coke,pepsi") - c.Request().Header.SetCookie("alloc", "") - c.Request().Header.SetCookie("no", "1") + c.Context().Request.Header.SetCookie("id", "2") + c.Context().Request.Header.SetCookie("Name", "Jane Doe") + c.Context().Request.Header.DelCookie("hobby") + c.Context().Request.Header.SetCookie("Hobby", "go,fiber") + c.Context().Request.Header.SetCookie("favouriteDrinks", "milo,coke,pepsi") + c.Context().Request.Header.SetCookie("alloc", "") + c.Context().Request.Header.SetCookie("no", "1") h2 := new(Cookie2) h2.Bool = true @@ -1263,7 +1263,7 @@ func Test_Bind_Cookie(t *testing.T) { Name string `cookie:"name,required"` } rh := new(RequiredCookie) - c.Request().Header.DelCookie("name") + c.Context().Request.Header.DelCookie("name") require.Equal(t, "name is empty", c.Bind().Cookie(rh).Error()) } @@ -1274,24 +1274,24 @@ func Test_Bind_Cookie_Map(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.SetCookie("id", "1") - c.Request().Header.SetCookie("Name", "John Doe") - c.Request().Header.SetCookie("Hobby", "golang,fiber") + c.Context().Request.Header.SetCookie("id", "1") + c.Context().Request.Header.SetCookie("Name", "John Doe") + c.Context().Request.Header.SetCookie("Hobby", "golang,fiber") q := make(map[string][]string) require.NoError(t, c.Bind().Cookie(&q)) require.Len(t, q["Hobby"], 2) - c.Request().Header.DelCookie("hobby") - c.Request().Header.SetCookie("Hobby", "golang,fiber,go") + c.Context().Request.Header.DelCookie("hobby") + c.Context().Request.Header.SetCookie("Hobby", "golang,fiber,go") q = make(map[string][]string) require.NoError(t, c.Bind().Cookie(&q)) require.Len(t, q["Hobby"], 3) empty := make(map[string][]string) - c.Request().Header.DelCookie("hobby") + c.Context().Request.Header.DelCookie("hobby") require.NoError(t, c.Bind().Query(&empty)) require.Empty(t, empty["Hobby"]) } @@ -1328,13 +1328,13 @@ func Test_Bind_Cookie_WithSetParserDecoder(t *testing.T) { Body string `cerez:"body"` } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") r := new(NonRFCTimeInput) - c.Request().Header.SetCookie("Date", "2021-04-10") - c.Request().Header.SetCookie("Title", "CustomDateTest") - c.Request().Header.SetCookie("Body", "October") + c.Context().Request.Header.SetCookie("Date", "2021-04-10") + c.Context().Request.Header.SetCookie("Title", "CustomDateTest") + c.Context().Request.Header.SetCookie("Body", "October") require.NoError(t, c.Bind().Cookie(r)) require.Equal(t, "CustomDateTest", r.Title) @@ -1342,7 +1342,7 @@ func Test_Bind_Cookie_WithSetParserDecoder(t *testing.T) { require.Equal(t, "{0 63753609600 }", date) require.Equal(t, "October", r.Body) - c.Request().Header.SetCookie("Title", "") + c.Context().Request.Header.SetCookie("Title", "") r = &NonRFCTimeInput{ Title: "Existing title", Body: "Existing Body", @@ -1364,30 +1364,30 @@ func Test_Bind_Cookie_Schema(t *testing.T) { Age int `cookie:"Age"` } `cookie:"Nested,required"` } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.SetCookie("Name", "tom") - c.Request().Header.SetCookie("Nested.Age", "10") + c.Context().Request.Header.SetCookie("Name", "tom") + c.Context().Request.Header.SetCookie("Nested.Age", "10") q := new(Cookie1) require.NoError(t, c.Bind().Cookie(q)) - c.Request().Header.DelCookie("Name") + c.Context().Request.Header.DelCookie("Name") q = new(Cookie1) require.Equal(t, "Name is empty", c.Bind().Cookie(q).Error()) - c.Request().Header.SetCookie("Name", "tom") - c.Request().Header.DelCookie("Nested.Age") - c.Request().Header.SetCookie("Nested.Agex", "10") + c.Context().Request.Header.SetCookie("Name", "tom") + c.Context().Request.Header.DelCookie("Nested.Age") + c.Context().Request.Header.SetCookie("Nested.Agex", "10") q = new(Cookie1) require.NoError(t, c.Bind().Cookie(q)) - c.Request().Header.DelCookie("Nested.Agex") + c.Context().Request.Header.DelCookie("Nested.Agex") q = new(Cookie1) require.Equal(t, "Nested is empty", c.Bind().Cookie(q).Error()) - c.Request().Header.DelCookie("Nested.Agex") - c.Request().Header.DelCookie("Name") + c.Context().Request.Header.DelCookie("Nested.Agex") + c.Context().Request.Header.DelCookie("Name") type Cookie2 struct { Name string `cookie:"Name"` @@ -1396,19 +1396,19 @@ func Test_Bind_Cookie_Schema(t *testing.T) { } `cookie:"Nested"` } - c.Request().Header.SetCookie("Name", "tom") - c.Request().Header.SetCookie("Nested.Age", "10") + c.Context().Request.Header.SetCookie("Name", "tom") + c.Context().Request.Header.SetCookie("Nested.Age", "10") h2 := new(Cookie2) require.NoError(t, c.Bind().Cookie(h2)) - c.Request().Header.DelCookie("Name") + c.Context().Request.Header.DelCookie("Name") h2 = new(Cookie2) require.NoError(t, c.Bind().Cookie(h2)) - c.Request().Header.DelCookie("Name") - c.Request().Header.DelCookie("Nested.Age") - c.Request().Header.SetCookie("Nested.Agex", "10") + c.Context().Request.Header.DelCookie("Name") + c.Context().Request.Header.DelCookie("Nested.Age") + c.Context().Request.Header.SetCookie("Nested.Agex", "10") h2 = new(Cookie2) require.Equal(t, "Nested.Age is empty", c.Bind().Cookie(h2).Error()) @@ -1416,20 +1416,20 @@ func Test_Bind_Cookie_Schema(t *testing.T) { Value int `cookie:"Val,required"` Next *Node `cookie:"Next,required"` } - c.Request().Header.SetCookie("Val", "1") - c.Request().Header.SetCookie("Next.Val", "3") + c.Context().Request.Header.SetCookie("Val", "1") + c.Context().Request.Header.SetCookie("Next.Val", "3") n := new(Node) require.NoError(t, c.Bind().Cookie(n)) require.Equal(t, 1, n.Value) require.Equal(t, 3, n.Next.Value) - c.Request().Header.DelCookie("Val") + c.Context().Request.Header.DelCookie("Val") n = new(Node) require.Equal(t, "Val is empty", c.Bind().Cookie(n).Error()) - c.Request().Header.SetCookie("Val", "3") - c.Request().Header.DelCookie("Next.Val") - c.Request().Header.SetCookie("Next.Value", "2") + c.Context().Request.Header.SetCookie("Val", "3") + c.Context().Request.Header.DelCookie("Next.Val") + c.Context().Request.Header.SetCookie("Next.Value", "2") n = new(Node) n.Next = new(Node) require.NoError(t, c.Bind().Cookie(n)) @@ -1449,12 +1449,12 @@ func Benchmark_Bind_Cookie(b *testing.B) { Name string Hobby []string } - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.SetCookie("id", "1") - c.Request().Header.SetCookie("Name", "John Doe") - c.Request().Header.SetCookie("Hobby", "golang,fiber") + c.Context().Request.Header.SetCookie("id", "1") + c.Context().Request.Header.SetCookie("Name", "John Doe") + c.Context().Request.Header.SetCookie("Hobby", "golang,fiber") q := new(Cookie) b.ReportAllocs() @@ -1473,12 +1473,12 @@ func Benchmark_Bind_Cookie_Map(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") - c.Request().Header.SetCookie("id", "1") - c.Request().Header.SetCookie("Name", "John Doe") - c.Request().Header.SetCookie("Hobby", "golang,fiber") + c.Context().Request.Header.SetCookie("id", "1") + c.Context().Request.Header.SetCookie("Name", "John Doe") + c.Context().Request.Header.SetCookie("Hobby", "golang,fiber") q := make(map[string][]string) b.ReportAllocs() @@ -1518,9 +1518,9 @@ func Test_Bind_CustomBinder(t *testing.T) { Name string `json:"name"` } body := []byte(`{"name":"john"}`) - c.Request().SetBody(body) - c.Request().Header.SetContentType("test") - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.SetBody(body) + c.Context().Request.Header.SetContentType("test") + c.Context().Request.Header.SetContentLength(len(body)) d := new(Demo) require.NoError(t, c.Bind().Body(d)) @@ -1538,9 +1538,9 @@ func Test_Bind_Must(t *testing.T) { Name string `query:"name,required"` } rq := new(RequiredQuery) - c.Request().URI().SetQueryString("") + c.Context().URI().SetQueryString("") err := c.Bind().Must().Query(rq) - require.Equal(t, StatusBadRequest, c.Response().StatusCode()) + require.Equal(t, StatusBadRequest, c.Context().Response.StatusCode()) require.Equal(t, "Bad request: name is empty", err.Error()) } @@ -1571,11 +1571,11 @@ func Test_Bind_StructValidator(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) rq := new(simpleQuery) - c.Request().URI().SetQueryString("name=efe") + c.Context().URI().SetQueryString("name=efe") require.Equal(t, "you should have entered right name", c.Bind().Query(rq).Error()) rq = new(simpleQuery) - c.Request().URI().SetQueryString("name=john") + c.Context().URI().SetQueryString("name=john") require.NoError(t, c.Bind().Query(rq)) } @@ -1594,11 +1594,11 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) { r := new(Request) - c.Request().URI().SetQueryString("query_param=query_param") + c.Context().URI().SetQueryString("query_param=query_param") require.NoError(t, c.Bind().Query(r)) require.Equal(t, "query_param", r.QueryParam) - c.Request().Header.Add("header_param", "header_param") + c.Context().Request.Header.Add("header_param", "header_param") require.NoError(t, c.Bind().Header(r)) require.Equal(t, "header_param", r.HeaderParam) @@ -1608,18 +1608,18 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) { require.NoError(t, err) err = w.Close() require.NoError(t, err) - c.Request().Header.SetContentType(MIMEApplicationJSON) - c.Request().Header.Set(HeaderContentEncoding, "gzip") - c.Request().SetBody(gzipJSON.Bytes()) - c.Request().Header.SetContentLength(len(gzipJSON.Bytes())) + c.Context().Request.Header.SetContentType(MIMEApplicationJSON) + c.Context().Request.Header.SetContentEncoding("gzip") + c.Context().Request.SetBody(gzipJSON.Bytes()) + c.Context().Request.Header.SetContentLength(len(gzipJSON.Bytes())) require.NoError(t, c.Bind().Body(r)) require.Equal(t, "body_param", r.BodyParam) - c.Request().Header.Del(HeaderContentEncoding) + c.Context().Request.Header.Del(HeaderContentEncoding) testDecodeParser := func(contentType, body string) { - c.Request().Header.SetContentType(contentType) - c.Request().SetBody([]byte(body)) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.Header.SetContentType(contentType) + c.Context().Request.SetBody([]byte(body)) + c.Context().Request.Header.SetContentLength(len(body)) require.NoError(t, c.Bind().Body(r)) require.Equal(t, "body_param", r.BodyParam) } diff --git a/client/client_test.go b/client/client_test.go index 0f81dda9e0..923ecd3955 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -656,7 +656,7 @@ func Test_Client_UserAgent(t *testing.T) { setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Get("/", func(c fiber.Ctx) error { - return c.Send(c.Request().Header.UserAgent()) + return c.SendString(c.Get(fiber.HeaderUserAgent)) }) }) @@ -778,7 +778,7 @@ func Test_Client_Header(t *testing.T) { func Test_Client_Header_With_Server(t *testing.T) { handler := func(c fiber.Ctx) error { - c.Request().Header.VisitAll(func(key, value []byte) { + c.Context().Request.Header.VisitAll(func(key, value []byte) { if k := string(key); k == "K1" || k == "K2" { _, _ = c.Write(key) //nolint:errcheck // It is fine to ignore the error here _, _ = c.Write(value) //nolint:errcheck // It is fine to ignore the error here @@ -1011,7 +1011,7 @@ func Test_Client_CookieJar_Response(t *testing.T) { func Test_Client_Referer(t *testing.T) { handler := func(c fiber.Ctx) error { - return c.Send(c.Request().Header.Referer()) + return c.SendString(c.Get(fiber.HeaderReferer)) } wrapAgent := func(c *Client) { @@ -1383,7 +1383,7 @@ func Test_Replace(t *testing.T) { app, dial, start := createHelperServer(t) app.Get("/", func(c fiber.Ctx) error { - return c.SendString(string(c.Request().Header.Peek("k1"))) + return c.SendString(c.Get("k1")) }) go start() diff --git a/client/request_test.go b/client/request_test.go index e5369fbbaf..807d78118e 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -856,7 +856,7 @@ func Test_Request_Patch(t *testing.T) { func Test_Request_Header_With_Server(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { - c.Request().Header.VisitAll(func(key, value []byte) { + c.Context().Request.Header.VisitAll(func(key, value []byte) { if k := string(key); k == "K1" || k == "K2" { _, err := c.Write(key) require.NoError(t, err) @@ -886,7 +886,7 @@ func Test_Request_UserAgent_With_Server(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { - return c.Send(c.Request().Header.UserAgent()) + return c.SendString(c.Get(fiber.HeaderUserAgent)) } t.Run("default", func(t *testing.T) { @@ -924,7 +924,7 @@ func Test_Request_Cookie_With_Server(t *testing.T) { func Test_Request_Referer_With_Server(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { - return c.Send(c.Request().Header.Referer()) + return c.SendString(c.Get(fiber.HeaderReferer)) } wrapAgent := func(req *Request) { @@ -937,7 +937,7 @@ func Test_Request_Referer_With_Server(t *testing.T) { func Test_Request_QueryString_With_Server(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { - return c.Send(c.Request().URI().QueryString()) + return c.Send(c.Context().URI().QueryString()) } wrapAgent := func(req *Request) { @@ -975,8 +975,8 @@ func Test_Request_Body_With_Server(t *testing.T) { t.Parallel() testRequest(t, func(c fiber.Ctx) error { - require.Equal(t, "application/json", string(c.Request().Header.ContentType())) - return c.SendString(string(c.Request().Body())) + require.Equal(t, "application/json", c.Get(fiber.HeaderContentType)) + return c.SendString(string(c.Req().BodyRaw())) }, func(agent *Request) { agent.SetJSON(map[string]string{ @@ -991,8 +991,8 @@ func Test_Request_Body_With_Server(t *testing.T) { t.Parallel() testRequest(t, func(c fiber.Ctx) error { - require.Equal(t, "application/xml", string(c.Request().Header.ContentType())) - return c.SendString(string(c.Request().Body())) + require.Equal(t, "application/xml", c.Get(fiber.HeaderContentType)) + return c.SendString(string(c.Req().BodyRaw())) }, func(agent *Request) { type args struct { @@ -1010,7 +1010,7 @@ func Test_Request_Body_With_Server(t *testing.T) { t.Parallel() testRequest(t, func(c fiber.Ctx) error { - require.Equal(t, fiber.MIMEApplicationForm, string(c.Request().Header.ContentType())) + require.Equal(t, fiber.MIMEApplicationForm, c.Get(fiber.HeaderContentType)) return c.Send([]byte("foo=" + c.FormValue("foo") + "&bar=" + c.FormValue("bar") + "&fiber=" + c.FormValue("fiber"))) }, func(agent *Request) { @@ -1034,7 +1034,7 @@ func Test_Request_Body_With_Server(t *testing.T) { require.NoError(t, err) require.Equal(t, "bar", mf.Value["foo"][0]) - return c.Send(c.Request().Body()) + return c.Send(c.Req().Body()) }) go start() @@ -1126,7 +1126,7 @@ func Test_Request_Body_With_Server(t *testing.T) { reg := regexp.MustCompile(`multipart/form-data; boundary=[\-\w]{35}`) require.True(t, reg.MatchString(c.Get(fiber.HeaderContentType))) - return c.Send(c.Request().Body()) + return c.Send(c.Req().BodyRaw()) }) go start() @@ -1151,7 +1151,7 @@ func Test_Request_Body_With_Server(t *testing.T) { t.Parallel() testRequest(t, func(c fiber.Ctx) error { - return c.SendString(string(c.Request().Body())) + return c.SendString(string(c.Req().BodyRaw())) }, func(agent *Request) { agent.SetRawBody([]byte("hello")) @@ -1237,7 +1237,7 @@ func Test_Request_MaxRedirects(t *testing.T) { app := fiber.New() app.Get("/", func(c fiber.Ctx) error { - if c.Request().URI().QueryArgs().Has("foo") { + if c.Context().URI().QueryArgs().Has("foo") { return c.Redirect().To("/foo") } return c.Redirect().To("/") diff --git a/client/response_test.go b/client/response_test.go index ab22ab388a..b370e016ac 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -182,7 +182,7 @@ func Test_Response_Header(t *testing.T) { server := startTestServer(t, func(app *fiber.App) { app.Get("/", func(c fiber.Ctx) error { - c.Response().Header.Add("foo", "bar") + c.Set("foo", "bar") return c.SendString("helo world") }) }) diff --git a/ctx.go b/ctx.go index e630a345f6..301acb926a 100644 --- a/ctx.go +++ b/ctx.go @@ -5,20 +5,12 @@ package fiber import ( - "bytes" "context" "crypto/tls" - "errors" "fmt" "io" "mime/multipart" - "net" - "net/http" - "path/filepath" "strconv" - "strings" - "sync" - "text/template" "time" "github.com/gofiber/utils/v2" @@ -42,26 +34,15 @@ type contextKey int const userContextKey contextKey = 0 // __local_user_context__ type DefaultCtx struct { - app *App // Reference to *App - route *Route // Reference to *Route - indexRoute int // Index of the current route - indexHandler int // Index of the current handler - method string // HTTP method - methodINT int // HTTP method INT equivalent - baseURI string // HTTP base uri - path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer - pathBuffer []byte // HTTP path buffer - detectionPath string // Route detection path -> string copy from detectionPathBuffer - detectionPathBuffer []byte // HTTP detectionPath buffer - treePath string // Path for the search in the tree - pathOriginal string // Original HTTP path - values [maxParams]string // Route parameter values - fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx - matched bool // Non use route matched - viewBindMap sync.Map // Default view map to bind template engine - bind *Bind // Default bind reference - redirect *Redirect // Default redirect reference - redirectionMessages []string // Messages of the previous redirect + app *App // Reference to *App + req Request // Reference to *Request + res Response // Reference to *Response + indexRoute int // Index of the current route + indexHandler int // Index of the current handler + fasthttp *fasthttp.RequestCtx // Reference to *fasthttp.RequestCtx + matched bool // Non use route matched + bind *Bind // Default bind reference + redirect *Redirect // Default redirect reference } // TLSHandler object @@ -109,30 +90,24 @@ type Views interface { Render(out io.Writer, name string, binding any, layout ...string) error } -// ResFmt associates a Content Type to a fiber.Handler for c.Format -type ResFmt struct { - MediaType string - Handler func(Ctx) error -} - -// Accepts checks if the specified extensions or content types are acceptable. +// Accepts is an alias of [Request.Accepts] func (c *DefaultCtx) Accepts(offers ...string) string { - return getOffer(c.fasthttp.Request.Header.Peek(HeaderAccept), acceptsOfferType, offers...) + return c.req.Accepts(offers...) } -// AcceptsCharsets checks if the specified charset is acceptable. +// AcceptsCharsets is an alias of [Request.AcceptsCharsets] func (c *DefaultCtx) AcceptsCharsets(offers ...string) string { - return getOffer(c.fasthttp.Request.Header.Peek(HeaderAcceptCharset), acceptsOffer, offers...) + return c.req.AcceptsCharsets(offers...) } -// AcceptsEncodings checks if the specified encoding is acceptable. +// AcceptsEncodings is an alias of [Request.AcceptsEncodings] func (c *DefaultCtx) AcceptsEncodings(offers ...string) string { - return getOffer(c.fasthttp.Request.Header.Peek(HeaderAcceptEncoding), acceptsOffer, offers...) + return c.req.AcceptsEncodings(offers...) } -// AcceptsLanguages checks if the specified language is acceptable. +// AcceptsLanguages is an alias of [Request.AcceptsLanguages] func (c *DefaultCtx) AcceptsLanguages(offers ...string) string { - return getOffer(c.fasthttp.Request.Header.Peek(HeaderAcceptLanguage), acceptsOffer, offers...) + return c.req.AcceptsLanguages(offers...) } // App returns the *App reference to the instance of the Fiber application @@ -140,164 +115,34 @@ func (c *DefaultCtx) App() *App { return c.app } -// Append the specified value to the HTTP response header field. -// If the header is not already set, it creates the header with the specified value. +// Append is an alias of [Response.Append]. func (c *DefaultCtx) Append(field string, values ...string) { - if len(values) == 0 { - return - } - h := c.app.getString(c.fasthttp.Response.Header.Peek(field)) - originalH := h - for _, value := range values { - if len(h) == 0 { - h = value - } else if h != value && !strings.HasPrefix(h, value+",") && !strings.HasSuffix(h, " "+value) && - !strings.Contains(h, " "+value+",") { - h += ", " + value - } - } - if originalH != h { - c.Set(field, h) - } + c.res.Append(field, values...) } -// Attachment sets the HTTP response Content-Disposition header field to attachment. +// Attachment is an alias of [Response.Attachment]. func (c *DefaultCtx) Attachment(filename ...string) { - if len(filename) > 0 { - fname := filepath.Base(filename[0]) - c.Type(filepath.Ext(fname)) - - c.setCanonical(HeaderContentDisposition, `attachment; filename="`+c.app.quoteString(fname)+`"`) - return - } - c.setCanonical(HeaderContentDisposition, "attachment") + c.res.Attachment(filename...) } -// BaseURL returns (protocol + host + base path). +// BaseURL is an alias of [Request.BaseURL]. func (c *DefaultCtx) BaseURL() string { - // TODO: Could be improved: 53.8 ns/op 32 B/op 1 allocs/op - // Should work like https://codeigniter.com/user_guide/helpers/url_helper.html - if c.baseURI != "" { - return c.baseURI - } - c.baseURI = c.Scheme() + "://" + c.Host() - return c.baseURI + return c.req.BaseURL() } -// BodyRaw contains the raw body submitted in a POST request. -// Returned value is only valid within the handler. Do not store any references. -// Make copies or use the Immutable setting instead. +// BodyRaw is an alias of [Request.BodyRaw]. func (c *DefaultCtx) BodyRaw() []byte { - if c.app.config.Immutable { - return utils.CopyBytes(c.fasthttp.Request.Body()) - } - return c.fasthttp.Request.Body() -} - -func (c *DefaultCtx) tryDecodeBodyInOrder( - originalBody *[]byte, - encodings []string, -) ([]byte, uint8, error) { - var ( - err error - body []byte - decodesRealized uint8 - ) - - for index, encoding := range encodings { - decodesRealized++ - switch encoding { - case StrGzip: - body, err = c.fasthttp.Request.BodyGunzip() - case StrBr, StrBrotli: - body, err = c.fasthttp.Request.BodyUnbrotli() - case StrDeflate: - body, err = c.fasthttp.Request.BodyInflate() - default: - decodesRealized-- - if len(encodings) == 1 { - body = c.fasthttp.Request.Body() - } - return body, decodesRealized, nil - } - - if err != nil { - return nil, decodesRealized, err - } - - // Only execute body raw update if it has a next iteration to try to decode - if index < len(encodings)-1 && decodesRealized > 0 { - if index == 0 { - tempBody := c.fasthttp.Request.Body() - *originalBody = make([]byte, len(tempBody)) - copy(*originalBody, tempBody) - } - c.fasthttp.Request.SetBodyRaw(body) - } - } - - return body, decodesRealized, nil + return c.req.BodyRaw() } -// Body contains the raw body submitted in a POST request. -// This method will decompress the body if the 'Content-Encoding' header is provided. -// It returns the original (or decompressed) body data which is valid only within the handler. -// Don't store direct references to the returned data. -// If you need to keep the body's data later, make a copy or use the Immutable option. +// Body is an alias of [Request.Body]. func (c *DefaultCtx) Body() []byte { - var ( - err error - body, originalBody []byte - headerEncoding string - encodingOrder = []string{"", "", ""} - ) - - // faster than peek - c.Request().Header.VisitAll(func(key, value []byte) { - if c.app.getString(key) == HeaderContentEncoding { - headerEncoding = c.app.getString(value) - } - }) - - // Split and get the encodings list, in order to attend the - // rule defined at: https://www.rfc-editor.org/rfc/rfc9110#section-8.4-5 - encodingOrder = getSplicedStrList(headerEncoding, encodingOrder) - if len(encodingOrder) == 0 { - if c.app.config.Immutable { - return utils.CopyBytes(c.fasthttp.Request.Body()) - } - return c.fasthttp.Request.Body() - } - - var decodesRealized uint8 - body, decodesRealized, err = c.tryDecodeBodyInOrder(&originalBody, encodingOrder) - - // Ensure that the body will be the original - if originalBody != nil && decodesRealized > 0 { - c.fasthttp.Request.SetBodyRaw(originalBody) - } - if err != nil { - return []byte(err.Error()) - } - - if c.app.config.Immutable { - return utils.CopyBytes(body) - } - return body + return c.req.Body() } -// ClearCookie expires a specific cookie by key on the client side. -// If no key is provided it expires all cookies that came with the request. +// ClearCookie is an alias of [Response.ClearCookie]. func (c *DefaultCtx) ClearCookie(key ...string) { - if len(key) > 0 { - for i := range key { - c.fasthttp.Response.Header.DelClientCookie(key[i]) - } - return - } - c.fasthttp.Request.Header.VisitAllCookie(func(k, _ []byte) { - c.fasthttp.Response.Header.DelClientCookieBytes(k) - }) + c.res.ClearCookie(key...) } // Context returns *fasthttp.RequestCtx that carries a deadline @@ -323,67 +168,29 @@ func (c *DefaultCtx) SetUserContext(ctx context.Context) { c.fasthttp.SetUserValue(userContextKey, ctx) } -// Cookie sets a cookie by passing a cookie struct. +// Cookie is an alias of [Response.Cookie]. func (c *DefaultCtx) Cookie(cookie *Cookie) { - fcookie := fasthttp.AcquireCookie() - fcookie.SetKey(cookie.Name) - fcookie.SetValue(cookie.Value) - fcookie.SetPath(cookie.Path) - fcookie.SetDomain(cookie.Domain) - // only set max age and expiry when SessionOnly is false - // i.e. cookie supposed to last beyond browser session - // refer: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie - if !cookie.SessionOnly { - fcookie.SetMaxAge(cookie.MaxAge) - fcookie.SetExpire(cookie.Expires) - } - fcookie.SetSecure(cookie.Secure) - fcookie.SetHTTPOnly(cookie.HTTPOnly) - - switch utils.ToLower(cookie.SameSite) { - case CookieSameSiteStrictMode: - fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode) - case CookieSameSiteNoneMode: - fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode) - case CookieSameSiteDisabled: - fcookie.SetSameSite(fasthttp.CookieSameSiteDisabled) - default: - fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode) - } - - c.fasthttp.Response.Header.SetCookie(fcookie) - fasthttp.ReleaseCookie(fcookie) + c.res.Cookie(cookie) } -// Cookies are used for getting a cookie value by key. -// Defaults to the empty string "" if the cookie doesn't exist. -// If a default value is given, it will return that value if the cookie doesn't exist. -// The returned value is only valid within the handler. Do not store any references. -// Make copies or use the Immutable setting to use the value outside the Handler. +// Cookies is an alias of [Request.Cookies] func (c *DefaultCtx) Cookies(key string, defaultValue ...string) string { - return defaultString(c.app.getString(c.fasthttp.Request.Header.Cookie(key)), defaultValue) + return c.req.Cookies(key, defaultValue...) } -// 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). -// Override this default with the filename parameter. +// Download is an alias of [Response.Download]. func (c *DefaultCtx) Download(file string, filename ...string) error { - var fname string - if len(filename) > 0 { - fname = filename[0] - } else { - fname = filepath.Base(file) - } - c.setCanonical(HeaderContentDisposition, `attachment; filename="`+c.app.quoteString(fname)+`"`) - return c.SendFile(file) + return c.res.Download(file, filename...) } -// Request return the *fasthttp.Request object -// This allows you to use all fasthttp request methods -// https://godoc.org/github.com/valyala/fasthttp#Request -func (c *DefaultCtx) Request() *fasthttp.Request { - return &c.fasthttp.Request +// Req returns the Request object for the current context. +func (c *DefaultCtx) Req() *Request { + return &c.req +} + +// Res returns the Response object for the current context. +func (c *DefaultCtx) Res() *Response { + return &c.res } // Response return the *fasthttp.Response object @@ -393,54 +200,9 @@ func (c *DefaultCtx) Response() *fasthttp.Response { return &c.fasthttp.Response } -// Format performs content-negotiation on the Accept HTTP header. -// It uses Accepts to select a proper format and calls the matching -// user-provided handler function. -// If no accepted format is found, and a format with MediaType "default" is given, -// that default handler is called. If no format is found and no default is given, -// StatusNotAcceptable is sent. +// Format is an alias of [Response.Format] func (c *DefaultCtx) Format(handlers ...ResFmt) error { - if len(handlers) == 0 { - return ErrNoHandlers - } - - c.Vary(HeaderAccept) - - if c.Get(HeaderAccept) == "" { - c.Response().Header.SetContentType(handlers[0].MediaType) - return handlers[0].Handler(c) - } - - // Using an int literal as the slice capacity allows for the slice to be - // allocated on the stack. The number was chosen arbitrarily as an - // approximation of the maximum number of content types a user might handle. - // If the user goes over, it just causes allocations, so it's not a problem. - types := make([]string, 0, 8) - var defaultHandler Handler - for _, h := range handlers { - if h.MediaType == "default" { - defaultHandler = h.Handler - continue - } - types = append(types, h.MediaType) - } - accept := c.Accepts(types...) - - if accept == "" { - if defaultHandler == nil { - return c.SendStatus(StatusNotAcceptable) - } - return defaultHandler(c) - } - - for _, h := range handlers { - if h.MediaType == accept { - c.Response().Header.SetContentType(h.MediaType) - return h.Handler(c) - } - } - - return fmt.Errorf("%w: format: an Accept was found but no handler was called", errUnreachable) + return c.res.Format(handlers...) } // AutoFormat performs content-negotiation on the Accept HTTP header. @@ -493,79 +255,26 @@ func (c *DefaultCtx) FormValue(key string, defaultValue ...string) string { return defaultString(c.app.getString(c.fasthttp.FormValue(key)), defaultValue) } -// Fresh returns true when the response is still “fresh” in the client's cache, -// 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 +// Fresh is an alias of [Request.Fresh] func (c *DefaultCtx) Fresh() bool { - // fields - modifiedSince := c.Get(HeaderIfModifiedSince) - noneMatch := c.Get(HeaderIfNoneMatch) - - // unconditional request - if modifiedSince == "" && noneMatch == "" { - return false - } - - // Always return stale when Cache-Control: no-cache - // to support end-to-end reload requests - // https://tools.ietf.org/html/rfc2616#section-14.9.4 - cacheControl := c.Get(HeaderCacheControl) - if cacheControl != "" && isNoCache(cacheControl) { - return false - } - - // if-none-match - if noneMatch != "" && noneMatch != "*" { - etag := c.app.getString(c.fasthttp.Response.Header.Peek(HeaderETag)) - if etag == "" { - return false - } - if c.app.isEtagStale(etag, c.app.getBytes(noneMatch)) { - return false - } - - if modifiedSince != "" { - lastModified := c.app.getString(c.fasthttp.Response.Header.Peek(HeaderLastModified)) - if lastModified != "" { - lastModifiedTime, err := http.ParseTime(lastModified) - if err != nil { - return false - } - modifiedSinceTime, err := http.ParseTime(modifiedSince) - if err != nil { - return false - } - return lastModifiedTime.Before(modifiedSinceTime) - } - } - } - return true + return c.req.Fresh() } -// Get returns the HTTP request header specified by field. -// Field names are case-insensitive -// Returned value is only valid within the handler. Do not store any references. -// Make copies or use the Immutable setting instead. +// Get is an alias of [Request.Get]. func (c *DefaultCtx) Get(key string, defaultValue ...string) string { - return GetReqHeader(c, key, defaultValue...) + return c.req.Get(key, defaultValue...) } // GetReqHeader returns the HTTP request header specified by filed. // This function is generic and can handle differnet headers type values. func GetReqHeader[V GenericType](c Ctx, key string, defaultValue ...V) V { var v V - return genericParseType[V](c.App().getString(c.Request().Header.Peek(key)), v, defaultValue...) + return genericParseType[V](c.Req().Get(key), v, defaultValue...) } -// GetRespHeader returns the HTTP response header specified by field. -// Field names are case-insensitive -// Returned value is only valid within the handler. Do not store any references. -// Make copies or use the Immutable setting instead. +// GetRespHeader is an alias of [Response.Get]. func (c *DefaultCtx) GetRespHeader(key string, defaultValue ...string) string { - return defaultString(c.app.getString(c.fasthttp.Response.Header.Peek(key)), defaultValue) + return c.res.Get(key, defaultValue...) } // GetRespHeaders returns the HTTP response headers. @@ -585,271 +294,61 @@ func (c *DefaultCtx) GetRespHeaders() map[string][]string { // Make copies or use the Immutable setting instead. func (c *DefaultCtx) GetReqHeaders() map[string][]string { headers := make(map[string][]string) - c.Request().Header.VisitAll(func(k, v []byte) { + c.fasthttp.Request.Header.VisitAll(func(k, v []byte) { key := c.app.getString(k) headers[key] = append(headers[key], c.app.getString(v)) }) return headers } -// Host contains the host derived from the X-Forwarded-Host or Host HTTP header. -// Returned value is only valid within the handler. Do not store any references. -// In a network context, `Host` refers to the combination of a hostname and potentially a port number used for connecting, -// while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. -// Example: URL: https://example.com:8080 -> Host: example.com:8080 -// Make copies or use the Immutable setting instead. -// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +// Host is an alias of [Request.Host]. func (c *DefaultCtx) Host() string { - if c.IsProxyTrusted() { - if host := c.Get(HeaderXForwardedHost); len(host) > 0 { - commaPos := strings.Index(host, ",") - if commaPos != -1 { - return host[:commaPos] - } - return host - } - } - return c.app.getString(c.fasthttp.Request.URI().Host()) + return c.req.Host() } -// Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the c.Host() method. -// Returned value is only valid within the handler. Do not store any references. -// Example: URL: https://example.com:8080 -> Hostname: example.com -// Make copies or use the Immutable setting instead. -// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +// Hostname is an alias of [Request.Hostname]. func (c *DefaultCtx) Hostname() string { - addr, _ := parseAddr(c.Host()) - - return addr + return c.req.Hostname() } -// Port returns the remote port of the request. +// Port is an alias of [Request.Port]. func (c *DefaultCtx) Port() string { - tcpaddr, ok := c.fasthttp.RemoteAddr().(*net.TCPAddr) - if !ok { - panic(errors.New("failed to type-assert to *net.TCPAddr")) - } - return strconv.Itoa(tcpaddr.Port) + return c.req.Port() } -// IP returns the remote IP address of the request. -// If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. -// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +// IP is an alias of [Request.IP]. func (c *DefaultCtx) IP() string { - if c.IsProxyTrusted() && len(c.app.config.ProxyHeader) > 0 { - return c.extractIPFromHeader(c.app.config.ProxyHeader) - } - - return c.fasthttp.RemoteIP().String() + return c.req.IP() } -// extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear. -// When IP validation is enabled, any invalid IPs will be omitted. -func (c *DefaultCtx) extractIPsFromHeader(header string) []string { - // TODO: Reuse the c.extractIPFromHeader func somehow in here - - headerValue := c.Get(header) - - // We can't know how many IPs we will return, but we will try to guess with this constant division. - // Counting ',' makes function slower for about 50ns in general case. - const maxEstimatedCount = 8 - estimatedCount := len(headerValue) / maxEstimatedCount - if estimatedCount > maxEstimatedCount { - estimatedCount = maxEstimatedCount // Avoid big allocation on big header - } - - ipsFound := make([]string, 0, estimatedCount) - - i := 0 - j := -1 - -iploop: - for { - var v4, v6 bool - - // Manually splitting string without allocating slice, working with parts directly - i, j = j+1, j+2 - - if j > len(headerValue) { - break - } - - for j < len(headerValue) && headerValue[j] != ',' { - if headerValue[j] == ':' { - v6 = true - } else if headerValue[j] == '.' { - v4 = true - } - j++ - } - - for i < j && (headerValue[i] == ' ' || headerValue[i] == ',') { - i++ - } - - s := strings.TrimRight(headerValue[i:j], " ") - - if c.app.config.EnableIPValidation { - // Skip validation if IP is clearly not IPv4/IPv6, otherwise validate without allocations - if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) { - continue iploop - } - } - - ipsFound = append(ipsFound, s) - } - - return ipsFound -} - -// extractIPFromHeader will attempt to pull the real client IP from the given header when IP validation is enabled. -// currently, it will return the first valid IP address in header. -// when IP validation is disabled, it will simply return the value of the header without any inspection. -// Implementation is almost the same as in extractIPsFromHeader, but without allocation of []string. -func (c *DefaultCtx) extractIPFromHeader(header string) string { - if c.app.config.EnableIPValidation { - headerValue := c.Get(header) - - i := 0 - j := -1 - - iploop: - for { - var v4, v6 bool - - // Manually splitting string without allocating slice, working with parts directly - i, j = j+1, j+2 - - if j > len(headerValue) { - break - } - - for j < len(headerValue) && headerValue[j] != ',' { - if headerValue[j] == ':' { - v6 = true - } else if headerValue[j] == '.' { - v4 = true - } - j++ - } - - for i < j && headerValue[i] == ' ' { - i++ - } - - s := strings.TrimRight(headerValue[i:j], " ") - - if c.app.config.EnableIPValidation { - if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) { - continue iploop - } - } - - return s - } - - return c.fasthttp.RemoteIP().String() - } - - // default behavior if IP validation is not enabled is just to return whatever value is - // in the proxy header. Even if it is empty or invalid - return c.Get(c.app.config.ProxyHeader) -} - -// IPs returns a string slice of IP addresses specified in the X-Forwarded-For request header. -// When IP validation is enabled, only valid IPs are returned. +// IPs is an alias of [Request.IPs]. func (c *DefaultCtx) IPs() []string { - return c.extractIPsFromHeader(HeaderXForwardedFor) + return c.req.IPs() } -// Is returns the matching content type, -// if the incoming request's Content-Type HTTP header field matches the MIME type specified by the type parameter +// Is is an alias of [Request.Is]. func (c *DefaultCtx) Is(extension string) bool { - extensionHeader := utils.GetMIME(extension) - if extensionHeader == "" { - return false - } - - return strings.HasPrefix( - strings.TrimLeft(utils.UnsafeString(c.fasthttp.Request.Header.ContentType()), " "), - extensionHeader, - ) + return c.req.Is(extension) } -// JSON converts any interface or string to JSON. -// Array and slice values encode as JSON arrays, -// except that []byte encodes as a base64-encoded string, -// and a nil slice encodes as the null JSON value. -// If the ctype parameter is given, this method will set the -// Content-Type header equal to ctype. If ctype is not given, -// The Content-Type header will be set to application/json. +// JSON is an alias of [Response.JSON]. func (c *DefaultCtx) JSON(data any, ctype ...string) error { - raw, err := c.app.config.JSONEncoder(data) - if err != nil { - return err - } - c.fasthttp.Response.SetBodyRaw(raw) - if len(ctype) > 0 { - c.fasthttp.Response.Header.SetContentType(ctype[0]) - } else { - c.fasthttp.Response.Header.SetContentType(MIMEApplicationJSON) - } - return nil + return c.res.JSON(data, ctype...) } -// JSONP sends a JSON response with JSONP support. -// This method is identical to JSON, except that it opts-in to JSONP callback support. -// By default, the callback name is simply callback. +// JSONP is an alias of [Response.JSONP]. func (c *DefaultCtx) JSONP(data any, callback ...string) error { - raw, err := c.app.config.JSONEncoder(data) - if err != nil { - return err - } - - var result, cb string - - if len(callback) > 0 { - cb = callback[0] - } else { - cb = "callback" - } - - result = cb + "(" + c.app.getString(raw) + ");" - - c.setCanonical(HeaderXContentTypeOptions, "nosniff") - c.fasthttp.Response.Header.SetContentType(MIMETextJavaScriptCharsetUTF8) - return c.SendString(result) + return c.res.JSONP(data, callback...) } -// XML converts any interface or string to XML. -// This method also sets the content header to application/xml. +// XML is an alias of [Response.XML]. func (c *DefaultCtx) XML(data any) error { - raw, err := c.app.config.XMLEncoder(data) - if err != nil { - return err - } - c.fasthttp.Response.SetBodyRaw(raw) - c.fasthttp.Response.Header.SetContentType(MIMEApplicationXML) - return nil + return c.res.XML(data) } -// Links joins the links followed by the property to populate the response's Link HTTP header field. +// Links is an alias of [Response.Links]. func (c *DefaultCtx) Links(link ...string) { - if len(link) == 0 { - return - } - bb := bytebufferpool.Get() - for i := range link { - if i%2 == 0 { - bb.WriteByte('<') - bb.WriteString(link[i]) - bb.WriteByte('>') - } else { - bb.WriteString(`; rel="` + link[i] + `",`) - } - } - c.setCanonical(HeaderLink, strings.TrimRight(c.app.getString(bb.Bytes()), ",")) - bytebufferpool.Put(bb) + c.res.Links(link...) } // Locals makes it possible to pass any values under keys scoped to the request @@ -878,36 +377,19 @@ func Locals[V any](c Ctx, key any, value ...V) V { return v } -// Location sets the response Location HTTP header to the specified path parameter. +// Location is an alias of [Response.Location]. func (c *DefaultCtx) Location(path string) { - c.setCanonical(HeaderLocation, path) + c.res.Location(path) } -// Method returns the HTTP request method for the context, optionally overridden by the provided argument. -// If no override is given or if the provided override is not a valid HTTP method, it returns the current method from the context. -// Otherwise, it updates the context's method and returns the overridden method as a string. +// Method is an alias of [Request.Method]. func (c *DefaultCtx) Method(override ...string) string { - if len(override) == 0 { - // Nothing to override, just return current method from context - return c.method - } - - method := utils.ToUpper(override[0]) - mINT := c.app.methodInt(method) - if mINT == -1 { - // Provided override does not valid HTTP method, no override, return current method - return c.method - } - - c.method = method - c.methodINT = mINT - return c.method + return c.req.Method(override...) } -// MultipartForm parse form entries from binary. -// This returns a map[string][]string, so given a key the value will be a string slice. +// MultipartForm is an alias of [Request.MultipartForm]. func (c *DefaultCtx) MultipartForm() (*multipart.Form, error) { - return c.fasthttp.MultipartForm() + return c.req.MultipartForm() } // ClientHelloInfo return CHI from context @@ -925,9 +407,9 @@ func (c *DefaultCtx) Next() error { c.indexHandler++ var err error // Did we execute all route handlers? - if c.indexHandler < len(c.route.Handlers) { + if c.indexHandler < len(c.req.route.Handlers) { // Continue route stack - err = c.route.Handlers[c.indexHandler](c) + err = c.req.route.Handlers[c.indexHandler](c) } else { // Continue handler stack if c.app.newCtxFunc != nil { @@ -953,35 +435,14 @@ func (c *DefaultCtx) RestartRouting() error { return err } -// OriginalURL contains the original request URL. -// Returned value is only valid within the handler. Do not store any references. -// Make copies or use the Immutable setting to use the value outside the Handler. +// OriginalURL is an alias of [Request.OriginalURL] func (c *DefaultCtx) OriginalURL() string { - return c.app.getString(c.fasthttp.Request.Header.RequestURI()) + return c.req.OriginalURL() } -// Params is used to get the route parameters. -// Defaults to empty string "" if the param doesn't exist. -// If a default value is given, it will return that value if the param doesn't exist. -// Returned value is only valid within the handler. Do not store any references. -// Make copies or use the Immutable setting to use the value outside the Handler. +// Params is an alias of [Request.Params]. func (c *DefaultCtx) Params(key string, defaultValue ...string) string { - if key == "*" || key == "+" { - key += "1" - } - for i := range c.route.Params { - if len(key) != len(c.route.Params[i]) { - continue - } - if c.route.Params[i] == key || (!c.app.config.CaseSensitive && utils.EqualFold(c.route.Params[i], key)) { - // in case values are not here - if len(c.values) <= i || len(c.values[i]) == 0 { - break - } - return c.values[i] - } - } - return defaultString("", defaultValue) + return c.req.Params(key, defaultValue...) } // Params is used to get the route parameters. @@ -1002,94 +463,27 @@ func Params[V GenericType](c Ctx, key string, defaultValue ...V) V { return genericParseType(c.Params(key), v, defaultValue...) } -// Path returns the path part of the request URL. -// Optionally, you could override the path. +// Path is an alias of [Request.Path]. func (c *DefaultCtx) Path(override ...string) string { - if len(override) != 0 && c.path != override[0] { - // Set new path to context - c.pathOriginal = override[0] - - // Set new path to request context - c.fasthttp.Request.URI().SetPath(c.pathOriginal) - // Prettify path - c.configDependentPaths() - } - return c.path + return c.req.Path(override...) } -// Scheme contains the request protocol string: http or https for TLS requests. -// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +// Scheme is an alias of [Request.Scheme]. func (c *DefaultCtx) Scheme() string { - if c.fasthttp.IsTLS() { - return schemeHTTPS - } - if !c.IsProxyTrusted() { - return schemeHTTP - } - - scheme := schemeHTTP - const lenXHeaderName = 12 - c.fasthttp.Request.Header.VisitAll(func(key, val []byte) { - if len(key) < lenXHeaderName { - return // Neither "X-Forwarded-" nor "X-Url-Scheme" - } - switch { - case bytes.HasPrefix(key, []byte("X-Forwarded-")): - if bytes.Equal(key, []byte(HeaderXForwardedProto)) || - bytes.Equal(key, []byte(HeaderXForwardedProtocol)) { - v := c.app.getString(val) - commaPos := strings.Index(v, ",") - if commaPos != -1 { - scheme = v[:commaPos] - } else { - scheme = v - } - } else if bytes.Equal(key, []byte(HeaderXForwardedSsl)) && bytes.Equal(val, []byte("on")) { - scheme = schemeHTTPS - } - - case bytes.Equal(key, []byte(HeaderXUrlScheme)): - scheme = c.app.getString(val) - } - }) - return scheme + return c.req.Scheme() } -// Protocol returns the HTTP protocol of request: HTTP/1.1 and HTTP/2. +// Protocol is an alias of [Request.Protocol]. func (c *DefaultCtx) Protocol() string { - return utils.UnsafeString(c.fasthttp.Request.Header.Protocol()) + return c.req.Protocol() } -// Query returns the query string parameter in the url. -// Defaults to empty string "" if the query doesn't exist. -// If a default value is given, it will return that value if the query doesn't exist. -// Returned value is only valid within the handler. Do not store any references. -// Make copies or use the Immutable setting to use the value outside the Handler. +// Query is an alias of [Request.Query]. func (c *DefaultCtx) Query(key string, defaultValue ...string) string { - return Query[string](c, key, defaultValue...) + return c.req.Query(key, defaultValue...) } -// Queries returns a map of query parameters and their values. -// -// GET /?name=alex&wanna_cake=2&id= -// Queries()["name"] == "alex" -// Queries()["wanna_cake"] == "2" -// Queries()["id"] == "" -// -// GET /?field1=value1&field1=value2&field2=value3 -// Queries()["field1"] == "value2" -// Queries()["field2"] == "value3" -// -// GET /?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 -// Queries()["list_a"] == "3" -// Queries()["list_b[]"] == "3" -// Queries()["list_c"] == "1,2,3" -// -// GET /api/search?filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending -// Queries()["filters.author.name"] == "John" -// Queries()["filters.category.name"] == "Technology" -// Queries()["filters[customer][name]"] == "Alice" -// Queries()["filters[status]"] == "pending" +// Queries is an alias of [Request.Queries]. func (c *DefaultCtx) Queries() map[string]string { m := make(map[string]string, c.Context().QueryArgs().Len()) c.Context().QueryArgs().VisitAll(func(key, value []byte) { @@ -1121,76 +515,12 @@ func (c *DefaultCtx) Queries() map[string]string { // unknown := Query[string](c, "unknown", "default") // Returns "default" since the query parameter "unknown" is not found func Query[V GenericType](c Ctx, key string, defaultValue ...V) V { var v V - q := c.App().getString(c.Context().QueryArgs().Peek(key)) - - return genericParseType[V](q, v, defaultValue...) + return genericParseType[V](c.Req().Query(key), v, defaultValue...) } -// Range returns a struct containing the type and a slice of ranges. +// Range is an alias of [Request.Range]. func (c *DefaultCtx) Range(size int) (Range, error) { - var ( - rangeData Range - ranges string - ) - rangeStr := c.Get(HeaderRange) - - i := strings.IndexByte(rangeStr, '=') - if i == -1 || strings.Contains(rangeStr[i+1:], "=") { - return rangeData, ErrRangeMalformed - } - rangeData.Type = rangeStr[:i] - ranges = rangeStr[i+1:] - - var ( - singleRange string - moreRanges = ranges - ) - for moreRanges != "" { - singleRange = moreRanges - if i := strings.IndexByte(moreRanges, ','); i >= 0 { - singleRange = moreRanges[:i] - moreRanges = moreRanges[i+1:] - } else { - moreRanges = "" - } - - var ( - startStr, endStr string - i int - ) - if i = strings.IndexByte(singleRange, '-'); i == -1 { - return rangeData, ErrRangeMalformed - } - startStr = singleRange[:i] - endStr = singleRange[i+1:] - - start, startErr := fasthttp.ParseUint(utils.UnsafeBytes(startStr)) - end, endErr := fasthttp.ParseUint(utils.UnsafeBytes(endStr)) - if startErr != nil { // -nnn - start = size - end - end = size - 1 - } else if endErr != nil { // nnn- - end = size - 1 - } - if end > size-1 { // limit last-byte-pos to current length - end = size - 1 - } - if start > end || start < 0 { - continue - } - rangeData.Ranges = append(rangeData.Ranges, struct { - Start int - End int - }{ - start, - end, - }) - } - if len(rangeData.Ranges) < 1 { - return rangeData, ErrRangeUnsatisfiable - } - - return rangeData, nil + return c.req.Range(size) } // Redirect returns the Redirect reference. @@ -1206,14 +536,9 @@ func (c *DefaultCtx) Redirect() *Redirect { return c.redirect } -// ViewBind Add vars to default view var map binding to template engine. -// Variables are read by the Render method and may be overwritten. +// ViewBind is an alias of [Response.ViewBind]. func (c *DefaultCtx) ViewBind(vars Map) error { - // init viewBindMap - lazy map - for k, v := range vars { - c.viewBindMap.Store(k, v) - } - return nil + return c.res.ViewBind(vars) } // getLocationFromRoute get URL location from route using parameters @@ -1250,115 +575,14 @@ func (c *DefaultCtx) GetRouteURL(routeName string, params Map) (string, error) { return c.getLocationFromRoute(c.App().GetRoute(routeName), params) } -// Render a template with data and sends a text/html response. -// We support the following engines: https://github.com/gofiber/template +// Render is an alias of [Response.Render]. func (c *DefaultCtx) Render(name string, bind Map, layouts ...string) error { - // Get new buffer from pool - buf := bytebufferpool.Get() - defer bytebufferpool.Put(buf) - - // Initialize empty bind map if bind is nil - if bind == nil { - bind = make(Map) - } - - // Pass-locals-to-views, bind, appListKeys - c.renderExtensions(bind) - - var rendered bool - for i := len(c.app.mountFields.appListKeys) - 1; i >= 0; i-- { - prefix := c.app.mountFields.appListKeys[i] - app := c.app.mountFields.appList[prefix] - if prefix == "" || strings.Contains(c.OriginalURL(), prefix) { - if len(layouts) == 0 && app.config.ViewsLayout != "" { - layouts = []string{ - app.config.ViewsLayout, - } - } - - // Render template from Views - if app.config.Views != nil { - if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil { - return fmt.Errorf("failed to render: %w", err) - } - - rendered = true - break - } - } - } - - if !rendered { - // Render raw template using 'name' as filepath if no engine is set - var tmpl *template.Template - if _, err := readContent(buf, name); err != nil { - return err - } - // Parse template - tmpl, err := template.New("").Parse(c.app.getString(buf.Bytes())) - if err != nil { - return fmt.Errorf("failed to parse: %w", err) - } - buf.Reset() - // Render template - if err := tmpl.Execute(buf, bind); err != nil { - return fmt.Errorf("failed to execute: %w", err) - } - } - - // Set Content-Type to text/html - c.fasthttp.Response.Header.SetContentType(MIMETextHTMLCharsetUTF8) - // Set rendered template to body - c.fasthttp.Response.SetBody(buf.Bytes()) - - return nil + return c.res.Render(name, bind, layouts...) } -func (c *DefaultCtx) renderExtensions(bind any) { - if bindMap, ok := bind.(Map); ok { - // Bind view map - c.viewBindMap.Range(func(key, value any) bool { - keyValue, ok := key.(string) - if !ok { - return true - } - if _, ok := bindMap[keyValue]; !ok { - bindMap[keyValue] = value - } - return true - }) - - // Check if the PassLocalsToViews option is enabled (by default it is disabled) - if c.app.config.PassLocalsToViews { - // Loop through each local and set it in the map - c.fasthttp.VisitUserValues(func(key []byte, val any) { - // check if bindMap doesn't contain the key - if _, ok := bindMap[c.app.getString(key)]; !ok { - // Set the key and value in the bindMap - bindMap[c.app.getString(key)] = val - } - }) - } - } - - if len(c.app.mountFields.appListKeys) == 0 { - c.app.generateAppListKeys() - } -} - -// Route returns the matched Route struct. +// Route is an alias of [Request.Route]. func (c *DefaultCtx) Route() *Route { - if c.route == nil { - // Fallback for fasthttp error handler - return &Route{ - path: c.pathOriginal, - Path: c.pathOriginal, - Method: c.method, - Handlers: make([]Handler, 0), - Params: make([]string, 0), - } - } - return c.route + return c.req.Route() } // SaveFile saves any multipart file to disk. @@ -1385,117 +609,29 @@ func (*DefaultCtx) SaveFileToStorage(fileheader *multipart.FileHeader, path stri return nil } -// Secure returns whether a secure connection was established. +// Secure is an alias of [Request.Secure]. func (c *DefaultCtx) Secure() bool { - return c.Protocol() == schemeHTTPS + return c.req.Secure() } -// Send sets the HTTP response body without copying it. -// From this point onward the body argument must not be changed. +// Send is an alias of [Response.Send]. func (c *DefaultCtx) Send(body []byte) error { - // Write response body - c.fasthttp.Response.SetBodyRaw(body) - return nil + return c.res.Send(body) } -var ( - sendFileOnce sync.Once - sendFileFS *fasthttp.FS - sendFileHandler fasthttp.RequestHandler -) - -// SendFile transfers the file from the given path. -// The file is not compressed by default, enable this by passing a 'true' argument -// Sets the Content-Type response HTTP header field based on the filenames extension. +// SendFile is an alias of [Response.SendFile]. func (c *DefaultCtx) SendFile(file string, compress ...bool) error { - // Save the filename, we will need it in the error message if the file isn't found - filename := file - - // https://github.com/valyala/fasthttp/blob/c7576cc10cabfc9c993317a2d3f8355497bea156/fs.go#L129-L134 - sendFileOnce.Do(func() { - const cacheDuration = 10 * time.Second - sendFileFS = &fasthttp.FS{ - Root: "", - AllowEmptyRoot: true, - GenerateIndexPages: false, - AcceptByteRange: true, - Compress: true, - CompressedFileSuffix: c.app.config.CompressedFileSuffix, - CacheDuration: cacheDuration, - IndexNames: []string{"index.html"}, - PathNotFound: func(ctx *fasthttp.RequestCtx) { - ctx.Response.SetStatusCode(StatusNotFound) - }, - } - sendFileHandler = sendFileFS.NewRequestHandler() - }) - - // Keep original path for mutable params - c.pathOriginal = utils.CopyString(c.pathOriginal) - // Disable compression - if len(compress) == 0 || !compress[0] { - // https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L55 - c.fasthttp.Request.Header.Del(HeaderAcceptEncoding) - } - // copy of https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L103-L121 with small adjustments - if len(file) == 0 || !filepath.IsAbs(file) { - // extend relative path to absolute path - hasTrailingSlash := len(file) > 0 && (file[len(file)-1] == '/' || file[len(file)-1] == '\\') - - var err error - file = filepath.FromSlash(file) - if file, err = filepath.Abs(file); err != nil { - return fmt.Errorf("failed to determine abs file path: %w", err) - } - if hasTrailingSlash { - file += "/" - } - } - // convert the path to forward slashes regardless the OS in order to set the URI properly - // the handler will convert back to OS path separator before opening the file - file = filepath.ToSlash(file) - - // Restore the original requested URL - originalURL := utils.CopyString(c.OriginalURL()) - defer c.fasthttp.Request.SetRequestURI(originalURL) - // Set new URI for fileHandler - c.fasthttp.Request.SetRequestURI(file) - // Save status code - status := c.fasthttp.Response.StatusCode() - // Serve file - sendFileHandler(c.fasthttp) - // Get the status code which is set by fasthttp - fsStatus := c.fasthttp.Response.StatusCode() - // Set the status code set by the user if it is different from the fasthttp status code and 200 - if status != fsStatus && status != StatusOK { - c.Status(status) - } - // Check for error - if status != StatusNotFound && fsStatus == StatusNotFound { - return NewError(StatusNotFound, fmt.Sprintf("sendfile: file %s not found", filename)) - } - return nil + return c.res.SendFile(file, compress...) } -// SendStatus sets the HTTP status code and if the response body is empty, -// it sets the correct status message in the body. +// SendStatus is an alias of [Response.SendStatus]. func (c *DefaultCtx) SendStatus(status int) error { - c.Status(status) - - // Only set status body when there is no response body - if len(c.fasthttp.Response.Body()) == 0 { - return c.SendString(utils.StatusMessage(status)) - } - - return nil + return c.res.SendStatus(status) } -// SendString sets the HTTP response body for string types. -// This means no type assertion, recommended for faster performance +// SendString is an alias of [Response.SendString]. func (c *DefaultCtx) SendString(body string) error { - c.fasthttp.Response.SetBodyString(body) - - return nil + return c.res.SendString(body) } // SendStream sets response body stream and optional body size. @@ -1509,41 +645,25 @@ func (c *DefaultCtx) SendStream(stream io.Reader, size ...int) error { return nil } -// Set sets the response's HTTP header field to the specified key, value. +// Set is an alias of [Response.Set]. func (c *DefaultCtx) Set(key, val string) { - c.fasthttp.Response.Header.Set(key, val) -} - -func (c *DefaultCtx) setCanonical(key, val string) { - c.fasthttp.Response.Header.SetCanonical(utils.UnsafeBytes(key), utils.UnsafeBytes(val)) + c.res.Set(key, val) } -// Subdomains returns a string slice of subdomains in the domain name of the request. -// The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments. +// Subdomains is an alias of [Request.Subdomains]. func (c *DefaultCtx) Subdomains(offset ...int) []string { - o := 2 - if len(offset) > 0 { - o = offset[0] - } - subdomains := strings.Split(c.Host(), ".") - l := len(subdomains) - o - // Check index to avoid slice bounds out of range panic - if l < 0 { - l = len(subdomains) - } - subdomains = subdomains[:l] - return subdomains + return c.req.Subdomains(offset...) } -// Stale is not implemented yet, pull requests are welcome! +// Stale is an alias of [Request.Stale]. func (c *DefaultCtx) Stale() bool { - return !c.Fresh() + return c.req.Stale() } -// Status sets the HTTP status for the response. +// Status is an alias of [Response.Status]. // This method is chainable. func (c *DefaultCtx) Status(status int) Ctx { - c.fasthttp.Response.SetStatusCode(status) + c.res.Status(status) return c } @@ -1586,76 +706,36 @@ func (c *DefaultCtx) String() string { return str } -// Type sets the Content-Type HTTP header to the MIME type specified by the file extension. +// Type is an alias of [Response.Type]. func (c *DefaultCtx) Type(extension string, charset ...string) Ctx { - if len(charset) > 0 { - c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0]) - } else { - c.fasthttp.Response.Header.SetContentType(utils.GetMIME(extension)) - } + c.res.Type(extension, charset...) return c } -// Vary adds the given header field to the Vary response header. -// This will append the header, if not already listed, otherwise leaves it listed in the current location. +// Vary is an alias of [Response.Vary]. func (c *DefaultCtx) Vary(fields ...string) { c.Append(HeaderVary, fields...) } -// Write appends p into response body. +// Write is an alias of [Response.Write]. func (c *DefaultCtx) Write(p []byte) (int, error) { c.fasthttp.Response.AppendBody(p) return len(p), nil } -// Writef appends f & a into response body writer. +// Writef is an alias of [Response.Writef]. func (c *DefaultCtx) Writef(f string, a ...any) (int, error) { - //nolint:wrapcheck // This must not be wrapped - return fmt.Fprintf(c.fasthttp.Response.BodyWriter(), f, a...) + return c.res.Writef(f, a...) } -// WriteString appends s to response body. +// WriteString is an alias of [Response.WriteString]. func (c *DefaultCtx) WriteString(s string) (int, error) { - c.fasthttp.Response.AppendBodyString(s) - return len(s), nil + return c.res.WriteString(s) } -// XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest, -// indicating that the request was issued by a client library (such as jQuery). +// XHR is an alis of [Request.XHR]. func (c *DefaultCtx) XHR() bool { - return utils.EqualFold(c.app.getBytes(c.Get(HeaderXRequestedWith)), []byte("xmlhttprequest")) -} - -// configDependentPaths set paths for route recognition and prepared paths for the user, -// here the features for caseSensitive, decoded paths, strict paths are evaluated -func (c *DefaultCtx) configDependentPaths() { - c.pathBuffer = append(c.pathBuffer[0:0], c.pathOriginal...) - // If UnescapePath enabled, we decode the path and save it for the framework user - if c.app.config.UnescapePath { - c.pathBuffer = fasthttp.AppendUnquotedArg(c.pathBuffer[:0], c.pathBuffer) - } - c.path = c.app.getString(c.pathBuffer) - - // another path is specified which is for routing recognition only - // use the path that was changed by the previous configuration flags - c.detectionPathBuffer = append(c.detectionPathBuffer[0:0], c.pathBuffer...) - // If CaseSensitive is disabled, we lowercase the original path - if !c.app.config.CaseSensitive { - c.detectionPathBuffer = utils.ToLowerBytes(c.detectionPathBuffer) - } - // If StrictRouting is disabled, we strip all trailing slashes - if !c.app.config.StrictRouting && len(c.detectionPathBuffer) > 1 && c.detectionPathBuffer[len(c.detectionPathBuffer)-1] == '/' { - c.detectionPathBuffer = bytes.TrimRight(c.detectionPathBuffer, "/") - } - c.detectionPath = c.app.getString(c.detectionPathBuffer) - - // Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed, - // since the first three characters area select a list of routes - c.treePath = c.treePath[0:0] - const maxDetectionPaths = 3 - if len(c.detectionPath) >= maxDetectionPaths { - c.treePath = c.detectionPath[:maxDetectionPaths] - } + return c.req.XHR() } // IsProxyTrusted checks trustworthiness of remote ip. @@ -1681,21 +761,9 @@ func (c *DefaultCtx) IsProxyTrusted() bool { return false } -var localHosts = [...]string{"127.0.0.1", "::1"} - -// IsLocalHost will return true if address is a localhost address. -func (*DefaultCtx) isLocalHost(address string) bool { - for _, h := range localHosts { - if address == h { - return true - } - } - return false -} - // IsFromLocal will return true if request came from local. func (c *DefaultCtx) IsFromLocal() bool { - return c.isLocalHost(c.fasthttp.RemoteIP().String()) + return c.req.IsFromLocal() } // Bind You can bind body, cookie, headers etc. into the map, map slice, struct easily by using Binding method. diff --git a/ctx_interface.go b/ctx_interface.go index 2950c088de..213fc90618 100644 --- a/ctx_interface.go +++ b/ctx_interface.go @@ -86,15 +86,13 @@ type Ctx interface { // Override this default with the filename parameter. Download(file string, filename ...string) error - // Request return the *fasthttp.Request object - // This allows you to use all fasthttp request methods - // https://godoc.org/github.com/valyala/fasthttp#Request - Request() *fasthttp.Request + // Req returns the [Request] object for the current request context. + // To access the underlying fasthttp request object, use [Ctx.Context]. + Req() *Request - // Response return the *fasthttp.Response object - // This allows you to use all fasthttp response methods - // https://godoc.org/github.com/valyala/fasthttp#Response - Response() *fasthttp.Response + // Res returns the [Response] object for the current request context. + // To access the underlying fasthttp response object, use [Ctx.Context]. + Res() *Response // Format performs content-negotiation on the Accept HTTP header. // It uses Accepts to select a proper format and calls the matching @@ -132,6 +130,7 @@ type Ctx interface { // Field names are case-insensitive // Returned value is only valid within the handler. Do not store any references. // Make copies or use the Immutable setting instead. + // Deprecated: Use c.Res().Get() GetRespHeader(key string, defaultValue ...string) string // GetRespHeaders returns the HTTP response headers. @@ -405,10 +404,19 @@ type CustomCtx interface { func NewDefaultCtx(app *App) *DefaultCtx { // return ctx - return &DefaultCtx{ + ctx := &DefaultCtx{ // Set app reference app: app, + req: Request{ + app: app, + }, + res: Response{ + app: app, + }, } + ctx.req.ctx = ctx + ctx.res.ctx = ctx + return ctx } func (app *App) newCtx() Ctx { @@ -449,25 +457,26 @@ func (c *DefaultCtx) Reset(fctx *fasthttp.RequestCtx) { // Reset matched flag c.matched = false // Set paths - c.pathOriginal = c.app.getString(fctx.URI().PathOriginal()) - // Set method - c.method = c.app.getString(fctx.Request.Header.Method()) - c.methodINT = c.app.methodInt(c.method) + c.req.pathOriginal = c.app.getString(fctx.URI().PathOriginal()) // Attach *fasthttp.RequestCtx to ctx c.fasthttp = fctx + c.req.fasthttp = &fctx.Request + c.res.fasthttp = &fctx.Response + // Set method + c.req.method = c.app.getString(fctx.Request.Header.Method()) + c.req.methodINT = c.app.methodInt(c.req.method) // reset base uri - c.baseURI = "" + c.req.baseURI = "" // Prettify path - c.configDependentPaths() + c.req.configDependentPaths() } // Release is a method to reset context fields when to use ReleaseCtx() func (c *DefaultCtx) release() { - c.route = nil + c.req.route = nil c.fasthttp = nil c.bind = nil - c.redirectionMessages = c.redirectionMessages[:0] - c.viewBindMap = sync.Map{} + c.res.viewBindMap = sync.Map{} if c.redirect != nil { ReleaseRedirect(c.redirect) c.redirect = nil @@ -476,7 +485,7 @@ func (c *DefaultCtx) release() { // Methods to use with next stack. func (c *DefaultCtx) getMethodINT() int { - return c.methodINT + return c.req.methodINT } func (c *DefaultCtx) getIndexRoute() int { @@ -484,19 +493,19 @@ func (c *DefaultCtx) getIndexRoute() int { } func (c *DefaultCtx) getTreePath() string { - return c.treePath + return c.req.treePath } func (c *DefaultCtx) getDetectionPath() string { - return c.detectionPath + return c.req.detectionPath } func (c *DefaultCtx) getPathOriginal() string { - return c.pathOriginal + return c.req.pathOriginal } func (c *DefaultCtx) getValues() *[maxParams]string { - return &c.values + return &c.req.values } func (c *DefaultCtx) getMatched() bool { @@ -516,5 +525,5 @@ func (c *DefaultCtx) setMatched(matched bool) { } func (c *DefaultCtx) setRoute(route *Route) { - c.route = route + c.req.route = route } diff --git a/ctx_test.go b/ctx_test.go index 524869bc2d..6653ca2142 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -42,17 +42,17 @@ func Test_Ctx_Accepts(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderAccept, "text/html,application/xhtml+xml,application/xml;q=0.9") + c.Context().Request.Header.Set(HeaderAccept, "text/html,application/xhtml+xml,application/xml;q=0.9") require.Equal(t, "", c.Accepts("")) require.Equal(t, "", c.Accepts()) require.Equal(t, ".xml", c.Accepts(".xml")) require.Equal(t, "", c.Accepts(".john")) require.Equal(t, "application/xhtml+xml", c.Accepts("application/xml", "application/xml+rss", "application/yaml", "application/xhtml+xml"), "must use client-preferred mime type") - c.Request().Header.Set(HeaderAccept, "application/json, text/plain, */*;q=0") + c.Context().Request.Header.Set(HeaderAccept, "application/json, text/plain, */*;q=0") require.Equal(t, "", c.Accepts("html"), "must treat */*;q=0 as not acceptable") - c.Request().Header.Set(HeaderAccept, "text/*, application/json") + c.Context().Request.Header.Set(HeaderAccept, "text/*, application/json") require.Equal(t, "html", c.Accepts("html")) require.Equal(t, "text/html", c.Accepts("text/html")) require.Equal(t, "json", c.Accepts("json", "text")) @@ -60,10 +60,10 @@ func Test_Ctx_Accepts(t *testing.T) { require.Equal(t, "", c.Accepts("image/png")) require.Equal(t, "", c.Accepts("png")) - c.Request().Header.Set(HeaderAccept, "text/html, application/json") + c.Context().Request.Header.Set(HeaderAccept, "text/html, application/json") require.Equal(t, "text/*", c.Accepts("text/*")) - c.Request().Header.Set(HeaderAccept, "*/*") + c.Context().Request.Header.Set(HeaderAccept, "*/*") require.Equal(t, "html", c.Accepts("html")) } @@ -73,7 +73,7 @@ func Benchmark_Ctx_Accepts(b *testing.B) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) acceptHeader := "text/html,application/xhtml+xml,application/xml;q=0.9" - c.Request().Header.Set("Accept", acceptHeader) + c.Context().Request.Header.Set("Accept", acceptHeader) acceptValues := [][]string{ {".xml"}, {"json", "xml"}, @@ -140,11 +140,11 @@ func Test_Ctx_Accepts_Wildcard(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderAccept, "*/*;q=0.9") + c.Context().Request.Header.Set(HeaderAccept, "*/*;q=0.9") require.Equal(t, "html", c.Accepts("html")) require.Equal(t, "foo", c.Accepts("foo")) require.Equal(t, ".bar", c.Accepts(".bar")) - c.Request().Header.Set(HeaderAccept, "text/html,application/*;q=0.9") + c.Context().Request.Header.Set(HeaderAccept, "text/html,application/*;q=0.9") require.Equal(t, "xml", c.Accepts("xml")) } @@ -154,7 +154,7 @@ func Test_Ctx_AcceptsCharsets(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderAcceptCharset, "utf-8, iso-8859-1;q=0.5") + c.Context().Request.Header.Set(HeaderAcceptCharset, "utf-8, iso-8859-1;q=0.5") require.Equal(t, "utf-8", c.AcceptsCharsets("utf-8")) } @@ -163,7 +163,7 @@ func Benchmark_Ctx_AcceptsCharsets(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5") + c.Context().Request.Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5") var res string b.ReportAllocs() b.ResetTimer() @@ -179,7 +179,7 @@ func Test_Ctx_AcceptsEncodings(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5") + c.Context().Request.Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5") require.Equal(t, "gzip", c.AcceptsEncodings("gzip")) require.Equal(t, "abc", c.AcceptsEncodings("abc")) } @@ -189,7 +189,7 @@ func Benchmark_Ctx_AcceptsEncodings(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5") + c.Context().Request.Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5") var res string b.ReportAllocs() b.ResetTimer() @@ -205,7 +205,7 @@ func Test_Ctx_AcceptsLanguages(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderAcceptLanguage, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5") + c.Context().Request.Header.Set(HeaderAcceptLanguage, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5") require.Equal(t, "fr", c.AcceptsLanguages("fr")) } @@ -214,7 +214,7 @@ func Benchmark_Ctx_AcceptsLanguages(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set(HeaderAcceptLanguage, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5") + c.Context().Request.Header.Set(HeaderAcceptLanguage, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5") var res string b.ReportAllocs() b.ResetTimer() @@ -263,11 +263,11 @@ func Test_Ctx_Append(t *testing.T) { // without append value c.Append("X-Custom-Header") - require.Equal(t, "Hello, World", string(c.Response().Header.Peek("X-Test"))) - require.Equal(t, "World, XHello, Hello", string(c.Response().Header.Peek("X2-Test"))) - require.Equal(t, "XHello, World, Hello", string(c.Response().Header.Peek("X3-Test"))) - require.Equal(t, "XHello, Hello, HelloZ, YHello", string(c.Response().Header.Peek("X4-Test"))) - require.Equal(t, "", string(c.Response().Header.Peek("x-custom-header"))) + require.Equal(t, "Hello, World", c.Res().Get("X-Test")) + require.Equal(t, "World, XHello, Hello", c.Res().Get("X2-Test")) + require.Equal(t, "XHello, World, Hello", c.Res().Get("X3-Test")) + require.Equal(t, "XHello, Hello, HelloZ, YHello", c.Res().Get("X4-Test")) + require.Equal(t, "", c.Res().Get("x-custom-header")) } // go test -v -run=^$ -bench=Benchmark_Ctx_Append -benchmem -count=4 @@ -282,7 +282,7 @@ func Benchmark_Ctx_Append(b *testing.B) { c.Append("X-Custom-Header", "World") c.Append("X-Custom-Header", "Hello") } - require.Equal(b, "Hello, World", app.getString(c.Response().Header.Peek("X-Custom-Header"))) + require.Equal(b, "Hello, World", c.Res().Get("X-Custom-Header")) } // go test -run Test_Ctx_Attachment @@ -293,14 +293,14 @@ func Test_Ctx_Attachment(t *testing.T) { // empty c.Attachment() - require.Equal(t, `attachment`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(t, `attachment`, c.Res().Get(HeaderContentDisposition)) // real filename c.Attachment("./static/img/logo.png") - require.Equal(t, `attachment; filename="logo.png"`, string(c.Response().Header.Peek(HeaderContentDisposition))) - require.Equal(t, "image/png", string(c.Response().Header.Peek(HeaderContentType))) + require.Equal(t, `attachment; filename="logo.png"`, c.Res().Get(HeaderContentDisposition)) + require.Equal(t, "image/png", c.Res().Get(HeaderContentType)) // check quoting c.Attachment("another document.pdf\"\r\nBla: \"fasel") - require.Equal(t, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(t, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, c.Res().Get(HeaderContentDisposition)) } // go test -v -run=^$ -bench=Benchmark_Ctx_Attachment -benchmem -count=4 @@ -314,7 +314,7 @@ func Benchmark_Ctx_Attachment(b *testing.B) { // example with quote params c.Attachment("another document.pdf\"\r\nBla: \"fasel") } - require.Equal(b, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(b, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, c.Res().Get(HeaderContentDisposition)) } // go test -run Test_Ctx_BaseURL @@ -323,7 +323,7 @@ func Test_Ctx_BaseURL(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") + c.Context().Request.SetRequestURI("http://google.com/test") require.Equal(t, "http://google.com", c.BaseURL()) // Check cache require.Equal(t, "http://google.com", c.BaseURL()) @@ -334,8 +334,8 @@ func Benchmark_Ctx_BaseURL(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().SetHost("google.com:1337") - c.Request().URI().SetPath("/haha/oke/lol") + c.Context().URI().SetHost("google.com:1337") + c.Context().URI().SetPath("/haha/oke/lol") var res string b.ReportAllocs() b.ResetTimer() @@ -351,7 +351,7 @@ func Test_Ctx_Body(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().SetBody([]byte("john=doe")) + c.Context().Request.SetBody([]byte("john=doe")) require.Equal(t, []byte("john=doe"), c.Body()) } @@ -362,7 +362,7 @@ func Benchmark_Ctx_Body(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().SetBody([]byte(input)) + c.Context().Request.SetBody([]byte(input)) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -379,7 +379,7 @@ func Test_Ctx_Body_Immutable(t *testing.T) { app.config.Immutable = true c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().SetBody([]byte("john=doe")) + c.Context().Request.SetBody([]byte("john=doe")) require.Equal(t, []byte("john=doe"), c.Body()) } @@ -391,7 +391,7 @@ func Benchmark_Ctx_Body_Immutable(b *testing.B) { app.config.Immutable = true c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().SetBody([]byte(input)) + c.Context().Request.SetBody([]byte(input)) b.ReportAllocs() b.ResetTimer() @@ -443,7 +443,7 @@ func Test_Ctx_Body_With_Compression(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set("Content-Encoding", tCase.contentEncoding) + c.Context().Request.Header.Set("Content-Encoding", tCase.contentEncoding) if strings.Contains(tCase.contentEncoding, "gzip") { var b bytes.Buffer @@ -460,13 +460,13 @@ func Test_Ctx_Body_With_Compression(t *testing.T) { tCase.body = b.Bytes() } - c.Request().SetBody(tCase.body) + c.Context().Request.SetBody(tCase.body) body := c.Body() require.Equal(t, tCase.expectedBody, body) // Check if body raw is the same as previous before decompression require.Equal( - t, tCase.body, c.Request().Body(), + t, tCase.body, c.Req().BodyRaw(), "Body raw must be the same as set before", ) }) @@ -580,11 +580,11 @@ func Benchmark_Ctx_Body_With_Compression(b *testing.B) { const input = "john=doe" c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("Content-Encoding", ct.contentEncoding) + c.Context().Request.Header.Set("Content-Encoding", ct.contentEncoding) compressedBody, err := ct.compressWriter([]byte(input)) require.NoError(b, err) - c.Request().SetBody(compressedBody) + c.Context().Request.SetBody(compressedBody) for i := 0; i < b.N; i++ { _ = c.Body() } @@ -636,7 +636,7 @@ func Test_Ctx_Body_With_Compression_Immutable(t *testing.T) { app := New() app.config.Immutable = true c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set("Content-Encoding", tCase.contentEncoding) + c.Context().Request.Header.Set("Content-Encoding", tCase.contentEncoding) if strings.Contains(tCase.contentEncoding, "gzip") { var b bytes.Buffer @@ -653,13 +653,13 @@ func Test_Ctx_Body_With_Compression_Immutable(t *testing.T) { tCase.body = b.Bytes() } - c.Request().SetBody(tCase.body) + c.Context().Request.SetBody(tCase.body) body := c.Body() require.Equal(t, tCase.expectedBody, body) // Check if body raw is the same as previous before decompression require.Equal( - t, tCase.body, c.Request().Body(), + t, tCase.body, c.Req().BodyRaw(), "Body raw must be the same as set before", ) }) @@ -774,11 +774,11 @@ func Benchmark_Ctx_Body_With_Compression_Immutable(b *testing.B) { const input = "john=doe" c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("Content-Encoding", ct.contentEncoding) + c.Context().Request.Header.Set("Content-Encoding", ct.contentEncoding) compressedBody, err := ct.compressWriter([]byte(input)) require.NoError(b, err) - c.Request().SetBody(compressedBody) + c.Context().Request.SetBody(compressedBody) for i := 0; i < b.N; i++ { _ = c.Body() } @@ -886,23 +886,23 @@ func Test_Ctx_Cookie(t *testing.T) { } c.Cookie(cookie) expect := "username=john; expires=" + httpdate + "; path=/; SameSite=Lax" - require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, c.Res().Get(HeaderSetCookie)) expect = "username=john; expires=" + httpdate + "; path=/" cookie.SameSite = CookieSameSiteDisabled c.Cookie(cookie) - require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, c.Res().Get(HeaderSetCookie)) expect = "username=john; expires=" + httpdate + "; path=/; SameSite=Strict" cookie.SameSite = CookieSameSiteStrictMode c.Cookie(cookie) - require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, c.Res().Get(HeaderSetCookie)) expect = "username=john; expires=" + httpdate + "; path=/; secure; SameSite=None" cookie.Secure = true cookie.SameSite = CookieSameSiteNoneMode c.Cookie(cookie) - require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, c.Res().Get(HeaderSetCookie)) expect = "username=john; path=/; secure; SameSite=None" // should remove expires and max-age headers @@ -910,7 +910,7 @@ func Test_Ctx_Cookie(t *testing.T) { cookie.Expires = expire cookie.MaxAge = 10000 c.Cookie(cookie) - require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, c.Res().Get(HeaderSetCookie)) expect = "username=john; path=/; secure; SameSite=None" // should remove expires and max-age headers when no expire and no MaxAge (default time) @@ -918,7 +918,7 @@ func Test_Ctx_Cookie(t *testing.T) { cookie.Expires = time.Time{} cookie.MaxAge = 0 c.Cookie(cookie) - require.Equal(t, expect, string(c.Response().Header.Peek(HeaderSetCookie))) + require.Equal(t, expect, c.Res().Get(HeaderSetCookie)) } // go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4 @@ -934,7 +934,7 @@ func Benchmark_Ctx_Cookie(b *testing.B) { Value: "Doe", }) } - require.Equal(b, "John=Doe; path=/; SameSite=Lax", app.getString(c.Response().Header.Peek("Set-Cookie"))) + require.Equal(b, "John=Doe; path=/; SameSite=Lax", c.Res().Get("Set-Cookie")) } // go test -run Test_Ctx_Cookies @@ -943,7 +943,7 @@ func Test_Ctx_Cookies(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("Cookie", "john=doe") + c.Context().Request.Header.Set("Cookie", "john=doe") require.Equal(t, "doe", c.Cookies("john")) require.Equal(t, "default", c.Cookies("unknown", "default")) } @@ -968,26 +968,26 @@ func Test_Ctx_Format(t *testing.T) { return fmts } - c.Request().Header.Set(HeaderAccept, `text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7`) + c.Context().Request.Header.Set(HeaderAccept, `text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7`) err := c.Format(formatHandlers("application/xhtml+xml", "application/xml", "foo/bar")...) require.Equal(t, "application/xhtml+xml", accepted) require.Equal(t, "application/xhtml+xml", c.GetRespHeader(HeaderContentType)) require.NoError(t, err) - require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode()) + require.NotEqual(t, StatusNotAcceptable, c.Context().Response.StatusCode()) err = c.Format(formatHandlers("foo/bar;a=b")...) require.Equal(t, "foo/bar;a=b", accepted) require.Equal(t, "foo/bar;a=b", c.GetRespHeader(HeaderContentType)) require.NoError(t, err) - require.NotEqual(t, StatusNotAcceptable, c.Response().StatusCode()) + require.NotEqual(t, StatusNotAcceptable, c.Context().Response.StatusCode()) myError := errors.New("this is an error") err = c.Format(ResFmt{"text/html", func(_ Ctx) error { return myError }}) require.ErrorIs(t, err, myError) - c.Request().Header.Set(HeaderAccept, "application/json") + c.Context().Request.Header.Set(HeaderAccept, "application/json") err = c.Format(ResFmt{"text/html", func(c Ctx) error { return c.SendStatus(StatusOK) }}) - require.Equal(t, StatusNotAcceptable, c.Response().StatusCode()) + require.Equal(t, StatusNotAcceptable, c.Context().Response.StatusCode()) require.NoError(t, err) err = c.Format(formatHandlers("text/html", "default")...) @@ -1002,7 +1002,7 @@ func Test_Ctx_Format(t *testing.T) { func Benchmark_Ctx_Format(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderAccept, "application/json,text/plain; format=flowed; q=0.9") + c.Context().Request.Header.Set(HeaderAccept, "application/json,text/plain; format=flowed; q=0.9") fail := func(_ Ctx) error { require.FailNow(b, "Wrong type chosen") @@ -1038,7 +1038,7 @@ func Benchmark_Ctx_Format(b *testing.B) { require.NoError(b, err) }) - c.Request().Header.Set("Accept", "text/plain") + c.Context().Request.Header.Set("Accept", "text/plain") b.Run("text/plain", func(b *testing.B) { offers := []ResFmt{ {"application/xml", fail}, @@ -1050,7 +1050,7 @@ func Benchmark_Ctx_Format(b *testing.B) { require.NoError(b, err) }) - c.Request().Header.Set("Accept", "json") + c.Context().Request.Header.Set("Accept", "json") b.Run("json", func(b *testing.B) { offers := []ResFmt{ {"xml", fail}, @@ -1070,45 +1070,45 @@ func Test_Ctx_AutoFormat(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderAccept, MIMETextPlain) + c.Context().Request.Header.Set(HeaderAccept, MIMETextPlain) err := c.AutoFormat([]byte("Hello, World!")) require.NoError(t, err) - require.Equal(t, "Hello, World!", string(c.Response().Body())) + require.Equal(t, "Hello, World!", string(c.Context().Response.Body())) - c.Request().Header.Set(HeaderAccept, MIMETextHTML) + c.Context().Request.Header.Set(HeaderAccept, MIMETextHTML) err = c.AutoFormat("Hello, World!") require.NoError(t, err) - require.Equal(t, "

Hello, World!

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

", string(c.Context().Response.Body())) - c.Request().Header.Set(HeaderAccept, MIMEApplicationJSON) + c.Context().Request.Header.Set(HeaderAccept, MIMEApplicationJSON) err = c.AutoFormat("Hello, World!") require.NoError(t, err) - require.Equal(t, `"Hello, World!"`, string(c.Response().Body())) + require.Equal(t, `"Hello, World!"`, string(c.Context().Response.Body())) - c.Request().Header.Set(HeaderAccept, MIMETextPlain) + c.Context().Request.Header.Set(HeaderAccept, MIMETextPlain) err = c.AutoFormat(complex(1, 1)) require.NoError(t, err) - require.Equal(t, "(1+1i)", string(c.Response().Body())) + require.Equal(t, "(1+1i)", string(c.Context().Response.Body())) - c.Request().Header.Set(HeaderAccept, MIMEApplicationXML) + c.Context().Request.Header.Set(HeaderAccept, MIMEApplicationXML) err = c.AutoFormat("Hello, World!") require.NoError(t, err) - require.Equal(t, `Hello, World!`, string(c.Response().Body())) + require.Equal(t, `Hello, World!`, string(c.Context().Response.Body())) err = c.AutoFormat(complex(1, 1)) require.Error(t, err) - c.Request().Header.Set(HeaderAccept, MIMETextPlain) + c.Context().Request.Header.Set(HeaderAccept, MIMETextPlain) err = c.AutoFormat(Map{}) require.NoError(t, err) - require.Equal(t, "map[]", string(c.Response().Body())) + require.Equal(t, "map[]", string(c.Context().Response.Body())) type broken string - c.Request().Header.Set(HeaderAccept, "broken/accept") + c.Context().Request.Header.Set(HeaderAccept, "broken/accept") require.NoError(t, err) err = c.AutoFormat(broken("Hello, World!")) require.NoError(t, err) - require.Equal(t, `Hello, World!`, string(c.Response().Body())) + require.Equal(t, `Hello, World!`, string(c.Context().Response.Body())) } func Test_Ctx_AutoFormat_Struct(t *testing.T) { @@ -1127,20 +1127,20 @@ func Test_Ctx_AutoFormat_Struct(t *testing.T) { Urgency: 3, } - c.Request().Header.Set(HeaderAccept, MIMEApplicationJSON) + c.Context().Request.Header.Set(HeaderAccept, MIMEApplicationJSON) err := c.AutoFormat(data) require.NoError(t, err) require.Equal(t, `{"Recipients":["Alice","Bob"],"Sender":"Carol","Urgency":3}`, - string(c.Response().Body()), + string(c.Context().Response.Body()), ) - c.Request().Header.Set(HeaderAccept, MIMEApplicationXML) + c.Context().Request.Header.Set(HeaderAccept, MIMEApplicationXML) err = c.AutoFormat(data) require.NoError(t, err) require.Equal(t, `AliceBob`, - string(c.Response().Body()), + string(c.Context().Response.Body()), ) } @@ -1149,7 +1149,7 @@ func Benchmark_Ctx_AutoFormat(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("Accept", "text/plain") + c.Context().Request.Header.Set("Accept", "text/plain") b.ReportAllocs() b.ResetTimer() @@ -1158,7 +1158,7 @@ func Benchmark_Ctx_AutoFormat(b *testing.B) { err = c.AutoFormat("Hello, World!") } require.NoError(b, err) - require.Equal(b, `Hello, World!`, string(c.Response().Body())) + require.Equal(b, `Hello, World!`, string(c.Context().Response.Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_AutoFormat_HTML -benchmem -count=4 @@ -1166,7 +1166,7 @@ func Benchmark_Ctx_AutoFormat_HTML(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("Accept", "text/html") + c.Context().Request.Header.Set("Accept", "text/html") b.ReportAllocs() b.ResetTimer() @@ -1175,7 +1175,7 @@ func Benchmark_Ctx_AutoFormat_HTML(b *testing.B) { err = c.AutoFormat("Hello, World!") } require.NoError(b, err) - require.Equal(b, "

Hello, World!

", string(c.Response().Body())) + require.Equal(b, "

Hello, World!

", string(c.Context().Response.Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_AutoFormat_JSON -benchmem -count=4 @@ -1183,7 +1183,7 @@ func Benchmark_Ctx_AutoFormat_JSON(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("Accept", "application/json") + c.Context().Request.Header.Set("Accept", "application/json") b.ReportAllocs() b.ResetTimer() @@ -1192,7 +1192,7 @@ func Benchmark_Ctx_AutoFormat_JSON(b *testing.B) { err = c.AutoFormat("Hello, World!") } require.NoError(b, err) - require.Equal(b, `"Hello, World!"`, string(c.Response().Body())) + require.Equal(b, `"Hello, World!"`, string(c.Context().Response.Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_AutoFormat_XML -benchmem -count=4 @@ -1200,7 +1200,7 @@ func Benchmark_Ctx_AutoFormat_XML(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("Accept", "application/xml") + c.Context().Request.Header.Set("Accept", "application/xml") b.ReportAllocs() b.ResetTimer() @@ -1209,7 +1209,7 @@ func Benchmark_Ctx_AutoFormat_XML(b *testing.B) { err = c.AutoFormat("Hello, World!") } require.NoError(b, err) - require.Equal(b, `Hello, World!`, string(c.Response().Body())) + require.Equal(b, `Hello, World!`, string(c.Context().Response.Body())) } // go test -run Test_Ctx_FormFile @@ -1285,12 +1285,12 @@ func Benchmark_Ctx_Fresh_StaleEtag(b *testing.B) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) for n := 0; n < b.N; n++ { - c.Request().Header.Set(HeaderIfNoneMatch, "a, b, c, d") - c.Request().Header.Set(HeaderCacheControl, "c") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "a, b, c, d") + c.Context().Request.Header.Set(HeaderCacheControl, "c") c.Fresh() - c.Request().Header.Set(HeaderIfNoneMatch, "a, b, c, d") - c.Request().Header.Set(HeaderCacheControl, "e") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "a, b, c, d") + c.Context().Request.Header.Set(HeaderCacheControl, "e") c.Fresh() } } @@ -1303,41 +1303,41 @@ func Test_Ctx_Fresh(t *testing.T) { require.False(t, c.Fresh()) - c.Request().Header.Set(HeaderIfNoneMatch, "*") - c.Request().Header.Set(HeaderCacheControl, "no-cache") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "*") + c.Context().Request.Header.Set(HeaderCacheControl, "no-cache") require.False(t, c.Fresh()) - c.Request().Header.Set(HeaderIfNoneMatch, "*") - c.Request().Header.Set(HeaderCacheControl, ",no-cache,") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "*") + c.Context().Request.Header.Set(HeaderCacheControl, ",no-cache,") require.False(t, c.Fresh()) - c.Request().Header.Set(HeaderIfNoneMatch, "*") - c.Request().Header.Set(HeaderCacheControl, "aa,no-cache,") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "*") + c.Context().Request.Header.Set(HeaderCacheControl, "aa,no-cache,") require.False(t, c.Fresh()) - c.Request().Header.Set(HeaderIfNoneMatch, "*") - c.Request().Header.Set(HeaderCacheControl, ",no-cache,bb") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "*") + c.Context().Request.Header.Set(HeaderCacheControl, ",no-cache,bb") require.False(t, c.Fresh()) - c.Request().Header.Set(HeaderIfNoneMatch, "675af34563dc-tr34") - c.Request().Header.Set(HeaderCacheControl, "public") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "675af34563dc-tr34") + c.Context().Request.Header.Set(HeaderCacheControl, "public") require.False(t, c.Fresh()) - c.Request().Header.Set(HeaderIfNoneMatch, "a, b") - c.Response().Header.Set(HeaderETag, "c") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "a, b") + c.Set(HeaderETag, "c") require.False(t, c.Fresh()) - c.Response().Header.Set(HeaderETag, "a") + c.Set(HeaderETag, "a") require.True(t, c.Fresh()) - c.Request().Header.Set(HeaderIfModifiedSince, "xxWed, 21 Oct 2015 07:28:00 GMT") - c.Response().Header.Set(HeaderLastModified, "xxWed, 21 Oct 2015 07:28:00 GMT") + c.Context().Request.Header.Set(HeaderIfModifiedSince, "xxWed, 21 Oct 2015 07:28:00 GMT") + c.Set(HeaderLastModified, "xxWed, 21 Oct 2015 07:28:00 GMT") require.False(t, c.Fresh()) - c.Response().Header.Set(HeaderLastModified, "Wed, 21 Oct 2015 07:28:00 GMT") + c.Set(HeaderLastModified, "Wed, 21 Oct 2015 07:28:00 GMT") require.False(t, c.Fresh()) - c.Request().Header.Set(HeaderIfModifiedSince, "Wed, 21 Oct 2015 07:28:00 GMT") + c.Context().Request.Header.Set(HeaderIfModifiedSince, "Wed, 21 Oct 2015 07:28:00 GMT") require.False(t, c.Fresh()) } @@ -1346,8 +1346,8 @@ func Benchmark_Ctx_Fresh_WithNoCache(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderIfNoneMatch, "*") - c.Request().Header.Set(HeaderCacheControl, "no-cache") + c.Context().Request.Header.Set(HeaderIfNoneMatch, "*") + c.Context().Request.Header.Set(HeaderCacheControl, "no-cache") for n := 0; n < b.N; n++ { c.Fresh() } @@ -1389,24 +1389,24 @@ func Test_Ctx_Binders(t *testing.T) { t.Run("Body:xml", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { - c.Request().Header.SetContentType(MIMEApplicationXML) - c.Request().SetBody([]byte(`foo111bar222foobartest`)) + c.Context().Request.Header.SetContentType(MIMEApplicationXML) + c.Context().Request.SetBody([]byte(`foo111bar222foobartest`)) return c.Bind().Body(testStruct) }) }) t.Run("Body:form", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { - c.Request().Header.SetContentType(MIMEApplicationForm) - c.Request().SetBody([]byte(`name=foo&class=111&name2=bar&class2=222&names=foo,bar,test`)) + c.Context().Request.Header.SetContentType(MIMEApplicationForm) + c.Context().Request.SetBody([]byte(`name=foo&class=111&name2=bar&class2=222&names=foo,bar,test`)) return c.Bind().Body(testStruct) }) }) t.Run("BodyParser:json", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { - c.Request().Header.SetContentType(MIMEApplicationJSON) - c.Request().SetBody([]byte(`{"name":"foo","class":111,"name2":"bar","class2":222,"names":["foo","bar","test"]}`)) + c.Context().Request.Header.SetContentType(MIMEApplicationJSON) + c.Context().Request.SetBody([]byte(`{"name":"foo","class":111,"name2":"bar","class2":222,"names":["foo","bar","test"]}`)) return c.Bind().Body(testStruct) }) }) @@ -1414,23 +1414,23 @@ func Test_Ctx_Binders(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\nfoo\r\n--b\r\nContent-Disposition: form-data; name=\"class\"\r\n\r\n111\r\n--b\r\nContent-Disposition: form-data; name=\"name2\"\r\n\r\nbar\r\n--b\r\nContent-Disposition: form-data; name=\"class2\"\r\n\r\n222\r\n--b\r\nContent-Disposition: form-data; name=\"names\"\r\n\r\nfoo\r\n--b\r\nContent-Disposition: form-data; name=\"names\"\r\n\r\nbar\r\n--b\r\nContent-Disposition: form-data; name=\"names\"\r\n\r\ntest\r\n--b--") - c.Request().SetBody(body) - c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary="b"`) - c.Request().Header.SetContentLength(len(body)) + c.Context().Request.SetBody(body) + c.Context().Request.Header.SetContentType(MIMEMultipartForm + `;boundary="b"`) + c.Context().Request.Header.SetContentLength(len(body)) return c.Bind().Body(testStruct) }) }) t.Run("Cookie", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { - c.Request().Header.Set("Cookie", "name=foo;name2=bar;class=111;class2=222;names=foo,bar,test") + c.Context().Request.Header.Set("Cookie", "name=foo;name2=bar;class=111;class2=222;names=foo,bar,test") return c.Bind().Cookie(testStruct) }) }) t.Run("Query", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { - c.Request().URI().SetQueryString("name=foo&name2=bar&class=111&class2=222&names=foo,bar,test") + c.Context().URI().SetQueryString("name=foo&name2=bar&class=111&class2=222&names=foo,bar,test") return c.Bind().Query(testStruct) }) }) @@ -1447,11 +1447,11 @@ func Test_Ctx_Binders(t *testing.T) { t.Run("ReqHeader", func(t *testing.T) { t.Parallel() withValues(t, func(c Ctx, testStruct *TestStruct) error { - c.Request().Header.Add("name", "foo") - c.Request().Header.Add("name2", "bar") - c.Request().Header.Add("class", "111") - c.Request().Header.Add("class2", "222") - c.Request().Header.Add("names", "foo,bar,test") + c.Context().Request.Header.Add("name", "foo") + c.Context().Request.Header.Add("name2", "bar") + c.Context().Request.Header.Add("class", "111") + c.Context().Request.Header.Add("class2", "222") + c.Context().Request.Header.Add("names", "foo,bar,test") return c.Bind().Header(testStruct) }) }) @@ -1463,8 +1463,8 @@ func Test_Ctx_Get(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderAcceptCharset, "utf-8, iso-8859-1;q=0.5") - c.Request().Header.Set(HeaderReferer, "Monster") + c.Context().Request.Header.Set(HeaderAcceptCharset, "utf-8, iso-8859-1;q=0.5") + c.Context().Request.Header.Set(HeaderReferer, "Monster") require.Equal(t, "utf-8, iso-8859-1;q=0.5", c.Get(HeaderAcceptCharset)) require.Equal(t, "Monster", c.Get(HeaderReferer)) require.Equal(t, "default", c.Get("unknown", "default")) @@ -1476,8 +1476,8 @@ func Test_Ctx_GetReqHeader(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("foo", "bar") - c.Request().Header.Set("id", "123") + c.Context().Request.Header.Set("foo", "bar") + c.Context().Request.Header.Set("id", "123") require.Equal(t, 123, GetReqHeader[int](c, "id")) require.Equal(t, "bar", GetReqHeader[string](c, "foo")) } @@ -1488,7 +1488,7 @@ func Test_Ctx_Host(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") + c.Context().Request.SetRequestURI("http://google.com/test") require.Equal(t, "google.com", c.Host()) } @@ -1499,8 +1499,8 @@ func Test_Ctx_Host_UntrustedProxy(t *testing.T) { { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(t, "google.com", c.Host()) app.ReleaseCtx(c) } @@ -1508,8 +1508,8 @@ func Test_Ctx_Host_UntrustedProxy(t *testing.T) { { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(t, "google.com", c.Host()) app.ReleaseCtx(c) } @@ -1521,8 +1521,8 @@ func Test_Ctx_Host_TrustedProxy(t *testing.T) { { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(t, "google1.com", c.Host()) app.ReleaseCtx(c) } @@ -1534,8 +1534,8 @@ func Test_Ctx_Host_TrustedProxyRange(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(t, "google1.com", c.Host()) app.ReleaseCtx(c) } @@ -1546,8 +1546,8 @@ func Test_Ctx_Host_UntrustedProxyRange(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.0.0.0/30"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(t, "google.com", c.Host()) app.ReleaseCtx(c) } @@ -1556,7 +1556,7 @@ func Test_Ctx_Host_UntrustedProxyRange(t *testing.T) { func Benchmark_Ctx_Host(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") + c.Context().Request.SetRequestURI("http://google.com/test") var host string b.ReportAllocs() b.ResetTimer() @@ -1663,10 +1663,10 @@ func Test_Ctx_Hostname(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") + c.Context().Request.SetRequestURI("http://google.com/test") require.Equal(t, "google.com", c.Hostname()) - c.Request().SetRequestURI("http://google.com:8080/test") + c.Context().Request.SetRequestURI("http://google.com:8080/test") require.Equal(t, "google.com", c.Hostname()) } @@ -1674,7 +1674,7 @@ func Test_Ctx_Hostname(t *testing.T) { func Benchmark_Ctx_Hostname(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com:8080/test") + c.Context().Request.SetRequestURI("http://google.com:8080/test") var hostname string b.ReportAllocs() b.ResetTimer() @@ -1685,8 +1685,8 @@ func Benchmark_Ctx_Hostname(b *testing.B) { { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(b, "google.com", hostname) app.ReleaseCtx(c) } @@ -1698,8 +1698,8 @@ func Test_Ctx_Hostname_TrustedProxy(t *testing.T) { { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(t, "google1.com", c.Hostname()) app.ReleaseCtx(c) } @@ -1711,8 +1711,8 @@ func Test_Ctx_Hostname_TrustedProxy_Multiple(t *testing.T) { { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com, google2.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com, google2.com") require.Equal(t, "google1.com", c.Hostname()) app.ReleaseCtx(c) } @@ -1724,8 +1724,8 @@ func Test_Ctx_Hostname_TrustedProxyRange(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(t, "google1.com", c.Hostname()) app.ReleaseCtx(c) } @@ -1736,8 +1736,8 @@ func Test_Ctx_Hostname_UntrustedProxyRange(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.0.0.0/30"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") require.Equal(t, "google.com", c.Hostname()) app.ReleaseCtx(c) } @@ -1780,7 +1780,7 @@ func Test_Ctx_IP(t *testing.T) { require.Equal(t, "0.0.0.0", c.IP()) // X-Forwarded-For is set, but it is ignored because proxyHeader is not set - c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "0.0.0.1") require.Equal(t, "0.0.0.0", c.IP()) } @@ -1795,23 +1795,23 @@ func Test_Ctx_IP_ProxyHeader(t *testing.T) { app := New(Config{ProxyHeader: proxyHeaderName}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(proxyHeaderName, "0.0.0.1") + c.Context().Request.Header.Set(proxyHeaderName, "0.0.0.1") require.Equal(t, "0.0.0.1", c.IP()) // without IP validation we return the full string - c.Request().Header.Set(proxyHeaderName, "0.0.0.1, 0.0.0.2") + c.Context().Request.Header.Set(proxyHeaderName, "0.0.0.1, 0.0.0.2") require.Equal(t, "0.0.0.1, 0.0.0.2", c.IP()) // without IP validation we return invalid IPs - c.Request().Header.Set(proxyHeaderName, "invalid, 0.0.0.2, 0.0.0.3") + c.Context().Request.Header.Set(proxyHeaderName, "invalid, 0.0.0.2, 0.0.0.3") require.Equal(t, "invalid, 0.0.0.2, 0.0.0.3", c.IP()) // when proxy header is enabled but the value is empty, without IP validation we return an empty string - c.Request().Header.Set(proxyHeaderName, "") + c.Context().Request.Header.Set(proxyHeaderName, "") require.Equal(t, "", c.IP()) // without IP validation we return an invalid IP - c.Request().Header.Set(proxyHeaderName, "not-valid-ip") + c.Context().Request.Header.Set(proxyHeaderName, "not-valid-ip") require.Equal(t, "not-valid-ip", c.IP()) } } @@ -1828,23 +1828,23 @@ func Test_Ctx_IP_ProxyHeader_With_IP_Validation(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) // when proxy header & validation is enabled and the value is a valid IP, we return it - c.Request().Header.Set(proxyHeaderName, "0.0.0.1") + c.Context().Request.Header.Set(proxyHeaderName, "0.0.0.1") require.Equal(t, "0.0.0.1", c.IP()) // when proxy header & validation is enabled and the value is a list of IPs, we return the first valid IP - c.Request().Header.Set(proxyHeaderName, "0.0.0.1, 0.0.0.2") + c.Context().Request.Header.Set(proxyHeaderName, "0.0.0.1, 0.0.0.2") require.Equal(t, "0.0.0.1", c.IP()) - c.Request().Header.Set(proxyHeaderName, "invalid, 0.0.0.2, 0.0.0.3") + c.Context().Request.Header.Set(proxyHeaderName, "invalid, 0.0.0.2, 0.0.0.3") require.Equal(t, "0.0.0.2", c.IP()) // when proxy header & validation is enabled but the value is empty, we will ignore the header - c.Request().Header.Set(proxyHeaderName, "") + c.Context().Request.Header.Set(proxyHeaderName, "") require.Equal(t, "0.0.0.0", c.IP()) // when proxy header & validation is enabled but the value is not an IP, we will ignore the header // and return the IP of the caller - c.Request().Header.Set(proxyHeaderName, "not-valid-ip") + c.Context().Request.Header.Set(proxyHeaderName, "not-valid-ip") require.Equal(t, "0.0.0.0", c.IP()) } } @@ -1854,7 +1854,7 @@ func Test_Ctx_IP_UntrustedProxy(t *testing.T) { t.Parallel() app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}, ProxyHeader: HeaderXForwardedFor}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "0.0.0.1") require.Equal(t, "0.0.0.0", c.IP()) } @@ -1863,7 +1863,7 @@ func Test_Ctx_IP_TrustedProxy(t *testing.T) { t.Parallel() app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}, ProxyHeader: HeaderXForwardedFor}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "0.0.0.1") require.Equal(t, "0.0.0.1", c.IP()) } @@ -1874,33 +1874,33 @@ func Test_Ctx_IPs(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) // normal happy path test case - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.2, 127.0.0.3") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.2, 127.0.0.3") require.Equal(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) // inconsistent space formatting - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1,127.0.0.2 ,127.0.0.3") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1,127.0.0.2 ,127.0.0.3") require.Equal(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) // invalid IPs are allowed to be returned - c.Request().Header.Set(HeaderXForwardedFor, "invalid, 127.0.0.1, 127.0.0.2") + c.Context().Request.Header.Set(HeaderXForwardedFor, "invalid, 127.0.0.1, 127.0.0.2") require.Equal(t, []string{"invalid", "127.0.0.1", "127.0.0.2"}, c.IPs()) - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.2") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.2") require.Equal(t, []string{"127.0.0.1", "invalid", "127.0.0.2"}, c.IPs()) // ensure that the ordering of IPs in the header is maintained - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.3, 127.0.0.1, 127.0.0.2") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.3, 127.0.0.1, 127.0.0.2") require.Equal(t, []string{"127.0.0.3", "127.0.0.1", "127.0.0.2"}, c.IPs()) // ensure for IPv6 - c.Request().Header.Set(HeaderXForwardedFor, "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d, invalid, 2345:0425:2CA1::0567:5673:23b5") + c.Context().Request.Header.Set(HeaderXForwardedFor, "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d, invalid, 2345:0425:2CA1::0567:5673:23b5") require.Equal(t, []string{"9396:9549:b4f7:8ed0:4791:1330:8c06:e62d", "invalid", "2345:0425:2CA1::0567:5673:23b5"}, c.IPs()) // empty header - c.Request().Header.Set(HeaderXForwardedFor, "") + c.Context().Request.Header.Set(HeaderXForwardedFor, "") require.Empty(t, c.IPs()) // missing header - c.Request() + c.Req() require.Empty(t, c.IPs()) } @@ -1910,33 +1910,33 @@ func Test_Ctx_IPs_With_IP_Validation(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) // normal happy path test case - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.2, 127.0.0.3") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.2, 127.0.0.3") require.Equal(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) // inconsistent space formatting - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1,127.0.0.2 ,127.0.0.3") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1,127.0.0.2 ,127.0.0.3") require.Equal(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs()) // invalid IPs are in the header - c.Request().Header.Set(HeaderXForwardedFor, "invalid, 127.0.0.1, 127.0.0.2") + c.Context().Request.Header.Set(HeaderXForwardedFor, "invalid, 127.0.0.1, 127.0.0.2") require.Equal(t, []string{"127.0.0.1", "127.0.0.2"}, c.IPs()) - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.2") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.2") require.Equal(t, []string{"127.0.0.1", "127.0.0.2"}, c.IPs()) // ensure that the ordering of IPs in the header is maintained - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.3, 127.0.0.1, 127.0.0.2") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.3, 127.0.0.1, 127.0.0.2") require.Equal(t, []string{"127.0.0.3", "127.0.0.1", "127.0.0.2"}, c.IPs()) // ensure for IPv6 - c.Request().Header.Set(HeaderXForwardedFor, "f037:825e:eadb:1b7b:1667:6f0a:5356:f604, invalid, 9396:9549:b4f7:8ed0:4791:1330:8c06:e62d") + c.Context().Request.Header.Set(HeaderXForwardedFor, "f037:825e:eadb:1b7b:1667:6f0a:5356:f604, invalid, 9396:9549:b4f7:8ed0:4791:1330:8c06:e62d") require.Equal(t, []string{"f037:825e:eadb:1b7b:1667:6f0a:5356:f604", "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d"}, c.IPs()) // empty header - c.Request().Header.Set(HeaderXForwardedFor, "") + c.Context().Request.Header.Set(HeaderXForwardedFor, "") require.Empty(t, c.IPs()) // missing header - c.Request() + c.Req() require.Empty(t, c.IPs()) } @@ -1944,7 +1944,7 @@ func Test_Ctx_IPs_With_IP_Validation(t *testing.T) { func Benchmark_Ctx_IPs(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.1") var res []string b.ReportAllocs() b.ResetTimer() @@ -1958,7 +1958,7 @@ func Benchmark_Ctx_IPs_v6(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - c.Request().Header.Set(HeaderXForwardedFor, "f037:825e:eadb:1b7b:1667:6f0a:5356:f604, invalid, 2345:0425:2CA1::0567:5673:23b5") + c.Context().Request.Header.Set(HeaderXForwardedFor, "f037:825e:eadb:1b7b:1667:6f0a:5356:f604, invalid, 2345:0425:2CA1::0567:5673:23b5") var res []string b.ReportAllocs() b.ResetTimer() @@ -1971,7 +1971,7 @@ func Benchmark_Ctx_IPs_v6(b *testing.B) { func Benchmark_Ctx_IPs_With_IP_Validation(b *testing.B) { app := New(Config{EnableIPValidation: true}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.1") var res []string b.ReportAllocs() b.ResetTimer() @@ -1985,7 +1985,7 @@ func Benchmark_Ctx_IPs_v6_With_IP_Validation(b *testing.B) { app := New(Config{EnableIPValidation: true}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - c.Request().Header.Set(HeaderXForwardedFor, "2345:0425:2CA1:0000:0000:0567:5673:23b5, invalid, 2345:0425:2CA1::0567:5673:23b5") + c.Context().Request.Header.Set(HeaderXForwardedFor, "2345:0425:2CA1:0000:0000:0567:5673:23b5, invalid, 2345:0425:2CA1::0567:5673:23b5") var res []string b.ReportAllocs() b.ResetTimer() @@ -1998,7 +1998,7 @@ func Benchmark_Ctx_IPs_v6_With_IP_Validation(b *testing.B) { func Benchmark_Ctx_IP_With_ProxyHeader(b *testing.B) { app := New(Config{ProxyHeader: HeaderXForwardedFor}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1") var res string b.ReportAllocs() b.ResetTimer() @@ -2011,7 +2011,7 @@ func Benchmark_Ctx_IP_With_ProxyHeader(b *testing.B) { func Benchmark_Ctx_IP_With_ProxyHeader_and_IP_Validation(b *testing.B) { app := New(Config{ProxyHeader: HeaderXForwardedFor, EnableIPValidation: true}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1") var res string b.ReportAllocs() b.ResetTimer() @@ -2024,7 +2024,7 @@ func Benchmark_Ctx_IP_With_ProxyHeader_and_IP_Validation(b *testing.B) { func Benchmark_Ctx_IP(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request() + c.Req() var res string b.ReportAllocs() b.ResetTimer() @@ -2040,7 +2040,7 @@ func Test_Ctx_Is(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderContentType, MIMETextHTML+"; boundary=something") + c.Context().Request.Header.Set(HeaderContentType, MIMETextHTML+"; boundary=something") require.True(t, c.Is(".html")) require.True(t, c.Is("html")) require.False(t, c.Is("json")) @@ -2048,22 +2048,22 @@ func Test_Ctx_Is(t *testing.T) { require.False(t, c.Is("")) require.False(t, c.Is(".foooo")) - c.Request().Header.Set(HeaderContentType, MIMEApplicationJSONCharsetUTF8) + c.Context().Request.Header.Set(HeaderContentType, MIMEApplicationJSONCharsetUTF8) require.False(t, c.Is("html")) require.True(t, c.Is("json")) require.True(t, c.Is(".json")) - c.Request().Header.Set(HeaderContentType, " application/json;charset=UTF-8") + c.Context().Request.Header.Set(HeaderContentType, " application/json;charset=UTF-8") require.False(t, c.Is("html")) require.True(t, c.Is("json")) require.True(t, c.Is(".json")) - c.Request().Header.Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8) + c.Context().Request.Header.Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8) require.False(t, c.Is("html")) require.True(t, c.Is("xml")) require.True(t, c.Is(".xml")) - c.Request().Header.Set(HeaderContentType, MIMETextPlain) + c.Context().Request.Header.Set(HeaderContentType, MIMETextPlain) require.False(t, c.Is("html")) require.True(t, c.Is("txt")) require.True(t, c.Is(".txt")) @@ -2074,7 +2074,7 @@ func Benchmark_Ctx_Is(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderContentType, MIMEApplicationJSON) + c.Context().Request.Header.Set(HeaderContentType, MIMEApplicationJSON) var res bool b.ReportAllocs() b.ResetTimer() @@ -2311,7 +2311,7 @@ func Test_Ctx_OriginalURL(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.SetRequestURI("http://google.com/test?search=demo") + c.Context().Request.Header.SetRequestURI("http://google.com/test?search=demo") require.Equal(t, "http://google.com/test?search=demo", c.OriginalURL()) } @@ -2393,12 +2393,12 @@ func Benchmark_Ctx_Params(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.route = &Route{ + c.req.route = &Route{ Params: []string{ "param1", "param2", "param3", "param4", }, } - c.values = [maxParams]string{ + c.req.values = [maxParams]string{ "john", "doe", "is", "awesome", } var res string @@ -2446,7 +2446,7 @@ func Test_Ctx_Protocol(t *testing.T) { require.Equal(t, "HTTP/1.1", c.Protocol()) - c.Request().Header.SetProtocol("HTTP/2") + c.Context().Request.Header.SetProtocol("HTTP/2") require.Equal(t, "HTTP/2", c.Protocol()) } @@ -2474,30 +2474,31 @@ func Test_Ctx_Scheme(t *testing.T) { freq.Request.Header.Set("X-Forwarded", "invalid") c := app.AcquireCtx(freq) + defer app.ReleaseCtx(c) - c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProto, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProtocol, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedProto, "https, http") + c.Context().Request.Header.Set(HeaderXForwardedProto, "https, http") require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, "https, http") + c.Context().Request.Header.Set(HeaderXForwardedProtocol, "https, http") require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedSsl, "on") + c.Context().Request.Header.Set(HeaderXForwardedSsl, "on") require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXUrlScheme, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() require.Equal(t, schemeHTTP, c.Scheme()) } @@ -2522,21 +2523,21 @@ func Test_Ctx_Scheme_TrustedProxy(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProto, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProtocol, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedSsl, "on") + c.Context().Request.Header.Set(HeaderXForwardedSsl, "on") require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXUrlScheme, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() require.Equal(t, schemeHTTP, c.Scheme()) } @@ -2547,21 +2548,21 @@ func Test_Ctx_Scheme_TrustedProxyRange(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProto, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProtocol, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedSsl, "on") + c.Context().Request.Header.Set(HeaderXForwardedSsl, "on") require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXUrlScheme, schemeHTTPS) require.Equal(t, schemeHTTPS, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() require.Equal(t, schemeHTTP, c.Scheme()) } @@ -2572,21 +2573,21 @@ func Test_Ctx_Scheme_UntrustedProxyRange(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.1.1.1/30"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProto, schemeHTTPS) require.Equal(t, schemeHTTP, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProtocol, schemeHTTPS) require.Equal(t, schemeHTTP, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedSsl, "on") + c.Context().Request.Header.Set(HeaderXForwardedSsl, "on") require.Equal(t, schemeHTTP, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXUrlScheme, schemeHTTPS) require.Equal(t, schemeHTTP, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() require.Equal(t, schemeHTTP, c.Scheme()) } @@ -2597,21 +2598,21 @@ func Test_Ctx_Scheme_UnTrustedProxy(t *testing.T) { app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}}) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProto, schemeHTTPS) require.Equal(t, schemeHTTP, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXForwardedProtocol, schemeHTTPS) require.Equal(t, schemeHTTP, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXForwardedSsl, "on") + c.Context().Request.Header.Set(HeaderXForwardedSsl, "on") require.Equal(t, schemeHTTP, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() - c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS) + c.Context().Request.Header.Set(HeaderXUrlScheme, schemeHTTPS) require.Equal(t, schemeHTTP, c.Scheme()) - c.Request().Header.Reset() + c.Context().Request.Header.Reset() require.Equal(t, schemeHTTP, c.Scheme()) } @@ -2621,8 +2622,9 @@ func Test_Ctx_Query(t *testing.T) { t.Parallel() app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) + defer app.ReleaseCtx(c) - c.Request().URI().SetQueryString("search=john&age=20") + c.Context().URI().SetQueryString("search=john&age=20") require.Equal(t, "john", c.Query("search")) require.Equal(t, "20", c.Query("age")) require.Equal(t, "default", c.Query("unknown", "default")) @@ -2637,7 +2639,7 @@ func Test_Ctx_Query(t *testing.T) { func Benchmark_Ctx_Query(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetQueryString("search=john&age=8") + c.Context().URI().SetQueryString("search=john&age=8") var res string b.ReportAllocs() b.ResetTimer() @@ -2654,7 +2656,7 @@ func Test_Ctx_Range(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) testRange := func(header string, ranges ...RangeSet) { - c.Request().Header.Set(HeaderRange, header) + c.Context().Request.Header.Set(HeaderRange, header) result, err := c.Range(1000) if len(ranges) == 0 { require.Error(t, err) @@ -2700,7 +2702,7 @@ func Benchmark_Ctx_Range(b *testing.B) { for _, tc := range testCases { b.Run(tc.str, func(b *testing.B) { - c.Request().Header.Set(HeaderRange, tc.str) + c.Context().Request.Header.Set(HeaderRange, tc.str) var ( result Range err error @@ -2862,10 +2864,10 @@ func Test_Ctx_Subdomains(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().URI().SetHost("john.doe.is.awesome.google.com") + c.Context().URI().SetHost("john.doe.is.awesome.google.com") require.Equal(t, []string{"john", "doe"}, c.Subdomains(4)) - c.Request().URI().SetHost("localhost:3000") + c.Context().URI().SetHost("localhost:3000") require.Equal(t, []string{"localhost:3000"}, c.Subdomains()) } @@ -2874,7 +2876,7 @@ func Benchmark_Ctx_Subdomains(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://john.doe.google.com") + c.Context().Request.SetRequestURI("http://john.doe.google.com") var res []string b.ReportAllocs() b.ResetTimer() @@ -2890,15 +2892,15 @@ func Test_Ctx_ClearCookie(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderCookie, "john=doe") + c.Context().Request.Header.Set(HeaderCookie, "john=doe") c.ClearCookie("john") - require.True(t, strings.HasPrefix(string(c.Response().Header.Peek(HeaderSetCookie)), "john=; expires=")) + require.True(t, strings.HasPrefix(c.Res().Get(HeaderSetCookie), "john=; expires=")) - c.Request().Header.Set(HeaderCookie, "test1=dummy") - c.Request().Header.Set(HeaderCookie, "test2=dummy") + c.Context().Request.Header.Set(HeaderCookie, "test1=dummy") + c.Context().Request.Header.Set(HeaderCookie, "test2=dummy") c.ClearCookie() - require.Contains(t, string(c.Response().Header.Peek(HeaderSetCookie)), "test1=; expires=") - require.Contains(t, string(c.Response().Header.Peek(HeaderSetCookie)), "test2=; expires=") + require.Contains(t, c.Res().Get(HeaderSetCookie), "test1=; expires=") + require.Contains(t, c.Res().Get(HeaderSetCookie), "test2=; expires=") } // go test -race -run Test_Ctx_Download @@ -2917,11 +2919,11 @@ func Test_Ctx_Download(t *testing.T) { expect, err := io.ReadAll(f) require.NoError(t, err) - require.Equal(t, expect, c.Response().Body()) - require.Equal(t, `attachment; filename="Awesome+File%21"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(t, expect, c.Context().Response.Body()) + require.Equal(t, `attachment; filename="Awesome+File%21"`, c.Res().Get(HeaderContentDisposition)) require.NoError(t, c.Download("ctx.go")) - require.Equal(t, `attachment; filename="ctx.go"`, string(c.Response().Header.Peek(HeaderContentDisposition))) + require.Equal(t, `attachment; filename="ctx.go"`, c.Res().Get(HeaderContentDisposition)) } // go test -race -run Test_Ctx_SendFile @@ -2946,8 +2948,8 @@ func Test_Ctx_SendFile(t *testing.T) { err = c.SendFile("ctx.go") // check expectation require.NoError(t, err) - require.Equal(t, expectFileContent, c.Response().Body()) - require.Equal(t, StatusOK, c.Response().StatusCode()) + require.Equal(t, expectFileContent, c.Context().Response.Body()) + require.Equal(t, StatusOK, c.Context().Response.StatusCode()) app.ReleaseCtx(c) // test with custom error code @@ -2955,18 +2957,18 @@ func Test_Ctx_SendFile(t *testing.T) { err = c.Status(StatusInternalServerError).SendFile("ctx.go") // check expectation require.NoError(t, err) - require.Equal(t, expectFileContent, c.Response().Body()) - require.Equal(t, StatusInternalServerError, c.Response().StatusCode()) + require.Equal(t, expectFileContent, c.Context().Response.Body()) + require.Equal(t, StatusInternalServerError, c.Context().Response.StatusCode()) app.ReleaseCtx(c) // test not modified c = app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderIfModifiedSince, fI.ModTime().Format(time.RFC1123)) + c.Context().Request.Header.Set(HeaderIfModifiedSince, fI.ModTime().Format(time.RFC1123)) err = c.SendFile("ctx.go") // check expectation require.NoError(t, err) - require.Equal(t, StatusNotModified, c.Response().StatusCode()) - require.Equal(t, []byte(nil), c.Response().Body()) + require.Equal(t, StatusNotModified, c.Context().Response.StatusCode()) + require.Equal(t, []byte(nil), c.Context().Response.Body()) app.ReleaseCtx(c) } @@ -3064,8 +3066,8 @@ func Test_Ctx_JSON(t *testing.T) { "Age": 20, }) require.NoError(t, err) - require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body())) - require.Equal(t, "application/json", string(c.Response().Header.Peek("content-type"))) + require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Context().Response.Body())) + require.Equal(t, "application/json", c.Res().Get("content-type")) // Test with ctype err = c.JSON(Map{ // map has no order @@ -3073,13 +3075,13 @@ func Test_Ctx_JSON(t *testing.T) { "Age": 20, }, "application/problem+json") require.NoError(t, err) - require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body())) - require.Equal(t, "application/problem+json", string(c.Response().Header.Peek("content-type"))) + require.Equal(t, `{"Age":20,"Name":"Grame"}`, string(c.Context().Response.Body())) + require.Equal(t, "application/problem+json", c.Res().Get("content-type")) testEmpty := func(v any, r string) { err := c.JSON(v) require.NoError(t, err) - require.Equal(t, r, string(c.Response().Body())) + require.Equal(t, r, string(c.Context().Response.Body())) } testEmpty(nil, "null") @@ -3102,8 +3104,8 @@ func Test_Ctx_JSON(t *testing.T) { "Age": 20, }) require.NoError(t, err) - require.Equal(t, `["custom","json"]`, string(c.Response().Body())) - require.Equal(t, "application/json", string(c.Response().Header.Peek("content-type"))) + require.Equal(t, `["custom","json"]`, string(c.Context().Response.Body())) + require.Equal(t, "application/json", c.Res().Get("content-type")) }) } @@ -3127,7 +3129,7 @@ func Benchmark_Ctx_JSON(b *testing.B) { err = c.JSON(data) } require.NoError(b, err) - require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body())) + require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Context().Response.Body())) } // go test -run=^$ -bench=Benchmark_Ctx_JSON_Ctype -benchmem -count=4 @@ -3150,8 +3152,8 @@ func Benchmark_Ctx_JSON_Ctype(b *testing.B) { err = c.JSON(data, "application/problem+json") } require.NoError(b, err) - require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body())) - require.Equal(b, "application/problem+json", string(c.Response().Header.Peek("content-type"))) + require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Context().Response.Body())) + require.Equal(b, "application/problem+json", c.Res().Get("content-type")) } // go test -run Test_Ctx_JSONP @@ -3167,16 +3169,16 @@ func Test_Ctx_JSONP(t *testing.T) { "Age": 20, }) require.NoError(t, err) - require.Equal(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Response().Body())) - require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type"))) + require.Equal(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Context().Response.Body())) + require.Equal(t, "text/javascript; charset=utf-8", c.Res().Get("content-type")) err = c.JSONP(Map{ "Name": "Grame", "Age": 20, }, "john") require.NoError(t, err) - require.Equal(t, `john({"Age":20,"Name":"Grame"});`, string(c.Response().Body())) - require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type"))) + require.Equal(t, `john({"Age":20,"Name":"Grame"});`, string(c.Context().Response.Body())) + require.Equal(t, "text/javascript; charset=utf-8", c.Res().Get("content-type")) t.Run("custom json encoder", func(t *testing.T) { t.Parallel() @@ -3193,8 +3195,8 @@ func Test_Ctx_JSONP(t *testing.T) { "Age": 20, }) require.NoError(t, err) - require.Equal(t, `callback(["custom","json"]);`, string(c.Response().Body())) - require.Equal(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type"))) + require.Equal(t, `callback(["custom","json"]);`, string(c.Context().Response.Body())) + require.Equal(t, "text/javascript; charset=utf-8", c.Res().Get("content-type")) }) } @@ -3219,7 +3221,7 @@ func Benchmark_Ctx_JSONP(b *testing.B) { err = c.JSONP(data, callback) } require.NoError(b, err) - require.Equal(b, `emit({"Name":"Grame","Age":20});`, string(c.Response().Body())) + require.Equal(b, `emit({"Name":"Grame","Age":20});`, string(c.Context().Response.Body())) } // go test -run Test_Ctx_XML @@ -3241,13 +3243,13 @@ func Test_Ctx_XML(t *testing.T) { Ages: []int{1, 12, 20}, }) require.NoError(t, err) - require.Equal(t, `GrameJohn11220`, string(c.Response().Body())) - require.Equal(t, "application/xml", string(c.Response().Header.Peek("content-type"))) + require.Equal(t, `GrameJohn11220`, string(c.Context().Response.Body())) + require.Equal(t, "application/xml", c.Res().Get("content-type")) testEmpty := func(v any, r string) { err := c.XML(v) require.NoError(t, err) - require.Equal(t, r, string(c.Response().Body())) + require.Equal(t, r, string(c.Context().Response.Body())) } testEmpty(nil, "") @@ -3277,8 +3279,8 @@ func Test_Ctx_XML(t *testing.T) { }) require.NoError(t, err) - require.Equal(t, `xml`, string(c.Response().Body())) - require.Equal(t, "application/xml", string(c.Response().Header.Peek("content-type"))) + require.Equal(t, `xml`, string(c.Context().Response.Body())) + require.Equal(t, "application/xml", c.Res().Get("content-type")) }) } @@ -3302,7 +3304,7 @@ func Benchmark_Ctx_XML(b *testing.B) { } require.NoError(b, err) - require.Equal(b, `Grame20`, string(c.Response().Body())) + require.Equal(b, `Grame20`, string(c.Context().Response.Body())) } // go test -run Test_Ctx_Links @@ -3312,13 +3314,13 @@ func Test_Ctx_Links(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Links() - require.Equal(t, "", string(c.Response().Header.Peek(HeaderLink))) + require.Equal(t, "", c.Res().Get(HeaderLink)) c.Links( "http://api.example.com/users?page=2", "next", "http://api.example.com/users?page=5", "last", ) - require.Equal(t, `; rel="next",; rel="last"`, string(c.Response().Header.Peek(HeaderLink))) + require.Equal(t, `; rel="next",; rel="last"`, c.Res().Get(HeaderLink)) } // go test -v -run=^$ -bench=Benchmark_Ctx_Links -benchmem -count=4 @@ -3343,7 +3345,7 @@ func Test_Ctx_Location(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Location("http://example.com") - require.Equal(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, "http://example.com", c.Res().Get(HeaderLocation)) } // go test -run Test_Ctx_Next @@ -3389,7 +3391,7 @@ func Test_Ctx_Render(t *testing.T) { }) require.NoError(t, err) - require.Equal(t, "

Hello, World!

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

", string(c.Context().Response.Body())) err = c.Render("./.github/testdata/template-non-exists.html", nil) require.Error(t, err) @@ -3409,7 +3411,7 @@ func Test_Ctx_RenderWithoutLocals(t *testing.T) { err := c.Render("./.github/testdata/index.tmpl", Map{}) require.NoError(t, err) - require.Equal(t, "

", string(c.Response().Body())) + require.Equal(t, "

", string(c.Context().Response.Body())) } func Test_Ctx_RenderWithLocals(t *testing.T) { @@ -3426,7 +3428,7 @@ func Test_Ctx_RenderWithLocals(t *testing.T) { err := c.Render("./.github/testdata/index.tmpl", Map{}) require.NoError(t, err) - require.Equal(t, "

Hello, World!

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

", string(c.Context().Response.Body())) }) t.Run("NilBind", func(t *testing.T) { @@ -3437,7 +3439,7 @@ func Test_Ctx_RenderWithLocals(t *testing.T) { err := c.Render("./.github/testdata/index.tmpl", nil) require.NoError(t, err) - require.Equal(t, "

Hello, World!

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

", string(c.Context().Response.Body())) }) } @@ -3459,7 +3461,7 @@ func Test_Ctx_RenderWithViewBind(t *testing.T) { defer bytebufferpool.Put(buf) require.NoError(t, err) - require.Equal(t, "

Hello, World!

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

", string(c.Context().Response.Body())) } func Test_Ctx_RenderWithOverwrittenViewBind(t *testing.T) { @@ -3481,7 +3483,7 @@ func Test_Ctx_RenderWithOverwrittenViewBind(t *testing.T) { buf.WriteString("overwrite") defer bytebufferpool.Put(buf) - require.Equal(t, "

Hello from Fiber!

", string(c.Response().Body())) + require.Equal(t, "

Hello from Fiber!

", string(c.Context().Response.Body())) } func Test_Ctx_RenderWithViewBindLocals(t *testing.T) { @@ -3501,9 +3503,9 @@ func Test_Ctx_RenderWithViewBindLocals(t *testing.T) { err = c.Render("./.github/testdata/template.tmpl", Map{}) require.NoError(t, err) - require.Equal(t, "

Hello, World! Test

", string(c.Response().Body())) + require.Equal(t, "

Hello, World! Test

", string(c.Context().Response.Body())) - require.Equal(t, "

Hello, World! Test

", string(c.Response().Body())) + require.Equal(t, "

Hello, World! Test

", string(c.Context().Response.Body())) } func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) { @@ -3525,7 +3527,7 @@ func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) { }) require.NoError(t, err) - require.Equal(t, "

Hello, World!

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

", string(c.Context().Response.Body())) } func Benchmark_Ctx_RenderWithLocalsAndViewBind(b *testing.B) { @@ -3552,7 +3554,7 @@ func Benchmark_Ctx_RenderWithLocalsAndViewBind(b *testing.B) { } require.NoError(b, err) - require.Equal(b, "

Hello, World! Test

", string(c.Response().Body())) + require.Equal(b, "

Hello, World! Test

", string(c.Context().Response.Body())) } func Benchmark_Ctx_RenderLocals(b *testing.B) { @@ -3575,7 +3577,7 @@ func Benchmark_Ctx_RenderLocals(b *testing.B) { } require.NoError(b, err) - require.Equal(b, "

Hello, World!

", string(c.Response().Body())) + require.Equal(b, "

Hello, World!

", string(c.Context().Response.Body())) } func Benchmark_Ctx_RenderViewBind(b *testing.B) { @@ -3599,7 +3601,7 @@ func Benchmark_Ctx_RenderViewBind(b *testing.B) { } require.NoError(b, err) - require.Equal(b, "

Hello, World!

", string(c.Response().Body())) + require.Equal(b, "

Hello, World!

", string(c.Context().Response.Body())) } // go test -run Test_Ctx_RestartRouting @@ -3709,7 +3711,7 @@ func Test_Ctx_Render_Engine(t *testing.T) { "Title": "Hello, World!", }) require.NoError(t, err) - require.Equal(t, "

Hello, World!

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

", string(c.Context().Response.Body())) } // go test -run Test_Ctx_Render_Engine_With_View_Layout @@ -3725,7 +3727,7 @@ func Test_Ctx_Render_Engine_With_View_Layout(t *testing.T) { "Title": "Hello, World!", }) require.NoError(t, err) - require.Equal(t, "

Hello, World!

I'm main

", string(c.Response().Body())) + require.Equal(t, "

Hello, World!

I'm main

", string(c.Context().Response.Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Render_Engine -benchmem -count=4 @@ -3745,7 +3747,7 @@ func Benchmark_Ctx_Render_Engine(b *testing.B) { }) } require.NoError(b, err) - require.Equal(b, "

Hello, World!

", string(c.Response().Body())) + require.Equal(b, "

Hello, World!

", string(c.Context().Response.Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Get_Location_From_Route -benchmem -count=4 @@ -3885,7 +3887,7 @@ func Test_Ctx_Render_Go_Template(t *testing.T) { err = c.Render(file.Name(), nil) require.NoError(t, err) - require.Equal(t, "template", string(c.Response().Body())) + require.Equal(t, "template", string(c.Context().Response.Body())) } // go test -run Test_Ctx_Send @@ -3897,7 +3899,7 @@ func Test_Ctx_Send(t *testing.T) { require.NoError(t, c.Send([]byte("Hello, World"))) require.NoError(t, c.Send([]byte("Don't crash please"))) require.NoError(t, c.Send([]byte("1337"))) - require.Equal(t, "1337", string(c.Response().Body())) + require.Equal(t, "1337", string(c.Context().Response.Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Send -benchmem -count=4 @@ -3914,7 +3916,7 @@ func Benchmark_Ctx_Send(b *testing.B) { err = c.Send(byt) } require.NoError(b, err) - require.Equal(b, "Hello, World!", string(c.Response().Body())) + require.Equal(b, "Hello, World!", string(c.Context().Response.Body())) } // go test -run Test_Ctx_SendStatus @@ -3925,8 +3927,8 @@ func Test_Ctx_SendStatus(t *testing.T) { err := c.SendStatus(415) require.NoError(t, err) - require.Equal(t, 415, c.Response().StatusCode()) - require.Equal(t, "Unsupported Media Type", string(c.Response().Body())) + require.Equal(t, 415, c.Context().Response.StatusCode()) + require.Equal(t, "Unsupported Media Type", string(c.Context().Response.Body())) } // go test -run Test_Ctx_SendString @@ -3937,7 +3939,7 @@ func Test_Ctx_SendString(t *testing.T) { err := c.SendString("Don't crash please") require.NoError(t, err) - require.Equal(t, "Don't crash please", string(c.Response().Body())) + require.Equal(t, "Don't crash please", string(c.Context().Response.Body())) } // go test -run Test_Ctx_SendStream @@ -3948,15 +3950,15 @@ func Test_Ctx_SendStream(t *testing.T) { err := c.SendStream(bytes.NewReader([]byte("Don't crash please"))) require.NoError(t, err) - require.Equal(t, "Don't crash please", string(c.Response().Body())) + require.Equal(t, "Don't crash please", string(c.Context().Response.Body())) err = c.SendStream(bytes.NewReader([]byte("Don't crash please")), len([]byte("Don't crash please"))) require.NoError(t, err) - require.Equal(t, "Don't crash please", string(c.Response().Body())) + require.Equal(t, "Don't crash please", string(c.Context().Response.Body())) err = c.SendStream(bufio.NewReader(bytes.NewReader([]byte("Hello bufio")))) require.NoError(t, err) - require.Equal(t, "Hello bufio", string(c.Response().Body())) + require.Equal(t, "Hello bufio", string(c.Context().Response.Body())) } // go test -run Test_Ctx_Set @@ -3969,9 +3971,9 @@ func Test_Ctx_Set(t *testing.T) { c.Set("X-2", "2") c.Set("X-3", "3") c.Set("X-3", "1337") - require.Equal(t, "1", string(c.Response().Header.Peek("x-1"))) - require.Equal(t, "2", string(c.Response().Header.Peek("x-2"))) - require.Equal(t, "1337", string(c.Response().Header.Peek("x-3"))) + require.Equal(t, "1", c.Res().Get("x-1")) + require.Equal(t, "2", c.Res().Get("x-2")) + require.Equal(t, "1337", c.Res().Get("x-3")) } // go test -run Test_Ctx_Set_Splitter @@ -3981,11 +3983,11 @@ func Test_Ctx_Set_Splitter(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Set("Location", "foo\r\nSet-Cookie:%20SESSIONID=MaliciousValue\r\n") - h := string(c.Response().Header.Peek("Location")) + h := c.Res().Get("Location") require.NotContains(t, h, "\r\n") c.Set("Location", "foo\nSet-Cookie:%20SESSIONID=MaliciousValue\n") - h = string(c.Response().Header.Peek("Location")) + h = c.Res().Get("Location") require.NotContains(t, h, "\n") } @@ -4009,11 +4011,11 @@ func Test_Ctx_Status(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Status(400) - require.Equal(t, 400, c.Response().StatusCode()) + require.Equal(t, 400, c.Context().Response.StatusCode()) err := c.Status(415).Send([]byte("Hello, World")) require.NoError(t, err) - require.Equal(t, 415, c.Response().StatusCode()) - require.Equal(t, "Hello, World", string(c.Response().Body())) + require.Equal(t, 415, c.Context().Response.StatusCode()) + require.Equal(t, "Hello, World", string(c.Context().Response.Body())) } // go test -run Test_Ctx_Type @@ -4023,16 +4025,16 @@ func Test_Ctx_Type(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Type(".json") - require.Equal(t, "application/json", string(c.Response().Header.Peek("Content-Type"))) + require.Equal(t, "application/json", c.Res().Get("Content-Type")) c.Type("json", "utf-8") - require.Equal(t, "application/json; charset=utf-8", string(c.Response().Header.Peek("Content-Type"))) + require.Equal(t, "application/json; charset=utf-8", c.Res().Get("Content-Type")) c.Type(".html") - require.Equal(t, "text/html", string(c.Response().Header.Peek("Content-Type"))) + require.Equal(t, "text/html", c.Res().Get("Content-Type")) c.Type("html", "utf-8") - require.Equal(t, "text/html; charset=utf-8", string(c.Response().Header.Peek("Content-Type"))) + require.Equal(t, "text/html; charset=utf-8", c.Res().Get("Content-Type")) } // go test -v -run=^$ -bench=Benchmark_Ctx_Type -benchmem -count=4 @@ -4070,7 +4072,7 @@ func Test_Ctx_Vary(t *testing.T) { c.Vary("Origin") c.Vary("User-Agent") c.Vary("Accept-Encoding", "Accept") - require.Equal(t, "Origin, User-Agent, Accept-Encoding, Accept", string(c.Response().Header.Peek("Vary"))) + require.Equal(t, "Origin, User-Agent, Accept-Encoding, Accept", c.Res().Get("Vary")) } // go test -v -run=^$ -bench=Benchmark_Ctx_Vary -benchmem -count=4 @@ -4095,7 +4097,7 @@ func Test_Ctx_Write(t *testing.T) { require.NoError(t, err) _, err = c.Write([]byte("World!")) require.NoError(t, err) - require.Equal(t, "Hello, World!", string(c.Response().Body())) + require.Equal(t, "Hello, World!", string(c.Context().Response.Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Write -benchmem -count=4 @@ -4123,7 +4125,7 @@ func Test_Ctx_Writef(t *testing.T) { world := "World!" _, err := c.Writef("Hello, %s", world) require.NoError(t, err) - require.Equal(t, "Hello, World!", string(c.Response().Body())) + require.Equal(t, "Hello, World!", string(c.Context().Response.Body())) } // go test -v -run=^$ -bench=Benchmark_Ctx_Writef -benchmem -count=4 @@ -4152,7 +4154,7 @@ func Test_Ctx_WriteString(t *testing.T) { require.NoError(t, err) _, err = c.WriteString("World!") require.NoError(t, err) - require.Equal(t, "Hello, World!", string(c.Response().Body())) + require.Equal(t, "Hello, World!", string(c.Context().Response.Body())) } // go test -run Test_Ctx_XHR @@ -4161,7 +4163,7 @@ func Test_Ctx_XHR(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXRequestedWith, "XMLHttpRequest") + c.Context().Request.Header.Set(HeaderXRequestedWith, "XMLHttpRequest") require.True(t, c.XHR()) } @@ -4170,7 +4172,7 @@ func Benchmark_Ctx_XHR(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXRequestedWith, "XMLHttpRequest") + c.Context().Request.Header.Set(HeaderXRequestedWith, "XMLHttpRequest") var equal bool b.ReportAllocs() b.ResetTimer() @@ -4194,7 +4196,7 @@ func Benchmark_Ctx_SendString_B(b *testing.B) { err = c.SendString(body) } require.NoError(b, err) - require.Equal(b, []byte("Hello, world!"), c.Response().Body()) + require.Equal(b, []byte("Hello, world!"), c.Context().Response.Body()) } // go test -run Test_Ctx_Queries -v @@ -4203,9 +4205,9 @@ func Test_Ctx_Queries(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetBody([]byte(``)) - c.Request().Header.SetContentType("") - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1&field1=value1&field1=value2&field2=value3&list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3") + c.Context().Request.SetBody([]byte(``)) + c.Context().Request.Header.SetContentType("") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1&field1=value1&field1=value2&field2=value3&list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3") queries := c.Queries() require.Equal(t, "1", queries["id"]) @@ -4220,7 +4222,7 @@ func Test_Ctx_Queries(t *testing.T) { require.Equal(t, "3", queries["list_b[]"]) require.Equal(t, "1,2,3", queries["list_c"]) - c.Request().URI().SetQueryString("filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending") + c.Context().URI().SetQueryString("filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending") queries = c.Queries() require.Equal(t, "John", queries["filters.author.name"]) @@ -4228,7 +4230,7 @@ func Test_Ctx_Queries(t *testing.T) { require.Equal(t, "Alice", queries["filters[customer][name]"]) require.Equal(t, "pending", queries["filters[status]"]) - c.Request().URI().SetQueryString("tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits") + c.Context().URI().SetQueryString("tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits") queries = c.Queries() require.Equal(t, "apple,orange,banana", queries["tags"]) @@ -4237,7 +4239,7 @@ func Test_Ctx_Queries(t *testing.T) { require.Equal(t, "apple,orange,banana", queries["filters.tags"]) require.Equal(t, "fruits", queries["filters.category.name"]) - c.Request().URI().SetQueryString("filters[tags][0]=apple&filters[tags][1]=orange&filters[tags][2]=banana&filters[category][name]=fruits") + c.Context().URI().SetQueryString("filters[tags][0]=apple&filters[tags][1]=orange&filters[tags][2]=banana&filters[category][name]=fruits") queries = c.Queries() require.Equal(t, "apple", queries["filters[tags][0]"]) @@ -4253,7 +4255,7 @@ func Benchmark_Ctx_Queries(b *testing.B) { b.ReportAllocs() b.ResetTimer() - c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1") + c.Context().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1") var queries map[string]string for n := 0; n < b.N; n++ { @@ -4352,7 +4354,7 @@ func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) { { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "127.0.0.1") defer app.ReleaseCtx(c) require.False(t, c.IsFromLocal()) } @@ -4360,7 +4362,7 @@ func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) { { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "::1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "::1") defer app.ReleaseCtx(c) require.False(t, c.IsFromLocal()) } @@ -4368,7 +4370,7 @@ func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) { { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "0:0:0:0:0:0:0:1") + c.Context().Request.Header.Set(HeaderXForwardedFor, "0:0:0:0:0:0:0:1") defer app.ReleaseCtx(c) require.False(t, c.IsFromLocal()) } @@ -4376,7 +4378,7 @@ func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) { { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90") + c.Context().Request.Header.Set(HeaderXForwardedFor, "93.46.8.90") require.False(t, c.IsFromLocal()) } @@ -4458,7 +4460,7 @@ func Test_Ctx_IsFromLocal_RemoteAddr(t *testing.T) { func Test_Ctx_extractIPsFromHeader(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("x-forwarded-for", "1.1.1.1,8.8.8.8 , /n, \n,1.1, a.c, 6.,6., , a,,42.118.81.169,10.0.137.108") + c.Context().Request.Header.Set("x-forwarded-for", "1.1.1.1,8.8.8.8 , /n, \n,1.1, a.c, 6.,6., , a,,42.118.81.169,10.0.137.108") ips := c.IPs() res := ips[len(ips)-2] require.Equal(t, "42.118.81.169", res) @@ -4469,7 +4471,7 @@ func Test_Ctx_extractIPsFromHeader_EnableValidateIp(t *testing.T) { app := New() app.config.EnableIPValidation = true c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("x-forwarded-for", "1.1.1.1,8.8.8.8 , /n, \n,1.1, a.c, 6.,6., , a,,42.118.81.169,10.0.137.108") + c.Context().Request.Header.Set("x-forwarded-for", "1.1.1.1,8.8.8.8 , /n, \n,1.1, a.c, 6.,6., , a,,42.118.81.169,10.0.137.108") ips := c.IPs() res := ips[len(ips)-2] require.Equal(t, "42.118.81.169", res) @@ -4483,9 +4485,9 @@ func Test_Ctx_GetRespHeaders(t *testing.T) { c.Set("test", "Hello, World 👋!") c.Set("foo", "bar") - c.Response().Header.Set("multi", "one") - c.Response().Header.Add("multi", "two") - c.Response().Header.Set(HeaderContentType, "application/json") + c.Context().Response.Header.Set("multi", "one") + c.Context().Response.Header.Add("multi", "two") + c.Context().Response.Header.Set(HeaderContentType, "application/json") require.Equal(t, map[string][]string{ "Content-Type": {"application/json"}, @@ -4499,9 +4501,9 @@ func Benchmark_Ctx_GetRespHeaders(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Response().Header.Set("test", "Hello, World 👋!") - c.Response().Header.Set("foo", "bar") - c.Response().Header.Set(HeaderContentType, "application/json") + c.Set("test", "Hello, World 👋!") + c.Set("foo", "bar") + c.Set(HeaderContentType, "application/json") b.ReportAllocs() b.ResetTimer() @@ -4524,11 +4526,11 @@ func Test_Ctx_GetReqHeaders(t *testing.T) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("test", "Hello, World 👋!") - c.Request().Header.Set("foo", "bar") - c.Request().Header.Set("multi", "one") - c.Request().Header.Add("multi", "two") - c.Request().Header.Set(HeaderContentType, "application/json") + c.Context().Request.Header.Set("test", "Hello, World 👋!") + c.Context().Request.Header.Set("foo", "bar") + c.Context().Request.Header.Set("multi", "one") + c.Context().Request.Header.Add("multi", "two") + c.Context().Request.Header.Set(HeaderContentType, "application/json") require.Equal(t, map[string][]string{ "Content-Type": {"application/json"}, @@ -4542,9 +4544,9 @@ func Benchmark_Ctx_GetReqHeaders(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set("test", "Hello, World 👋!") - c.Request().Header.Set("foo", "bar") - c.Request().Header.Set(HeaderContentType, "application/json") + c.Context().Request.Header.Set("test", "Hello, World 👋!") + c.Context().Request.Header.Set("foo", "bar") + c.Context().Request.Header.Set(HeaderContentType, "application/json") b.ReportAllocs() b.ResetTimer() @@ -5762,7 +5764,7 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { b.Run("NoProxyCheck", func(b *testing.B) { app := New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com:8080/test") + c.Context().Request.SetRequestURI("http://google.com:8080/test") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -5778,7 +5780,7 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { b.ResetTimer() b.RunParallel(func(pb *testing.PB) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com:8080/test") + c.Context().Request.SetRequestURI("http://google.com:8080/test") for pb.Next() { c.IsProxyTrusted() } @@ -5792,8 +5794,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { EnableTrustedProxyCheck: true, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -5811,8 +5813,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { b.ResetTimer() b.RunParallel(func(pb *testing.PB) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") for pb.Next() { c.IsProxyTrusted() } @@ -5827,8 +5829,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { TrustedProxies: []string{"0.0.0.0"}, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -5847,8 +5849,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { b.ResetTimer() b.RunParallel(func(pb *testing.PB) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") for pb.Next() { c.IsProxyTrusted() } @@ -5863,8 +5865,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { TrustedProxies: []string{"0.0.0.0/8"}, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -5883,8 +5885,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { b.ResetTimer() b.RunParallel(func(pb *testing.PB) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") for pb.Next() { c.IsProxyTrusted() } @@ -5899,8 +5901,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { TrustedProxies: []string{"192.168.0.0/24", "10.0.0.0/16", "0.0.0.0/8"}, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -5919,8 +5921,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { b.ResetTimer() b.RunParallel(func(pb *testing.PB) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") for pb.Next() { c.IsProxyTrusted() } @@ -5945,8 +5947,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { }, }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/test") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/test") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { @@ -5975,8 +5977,8 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { b.ResetTimer() b.RunParallel(func(pb *testing.PB) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().SetRequestURI("http://google.com/") - c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + c.Context().Request.SetRequestURI("http://google.com/") + c.Context().Request.Header.Set(HeaderXForwardedHost, "google1.com") for pb.Next() { c.IsProxyTrusted() } diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 4b1c1d5115..39c93c1409 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -8,101 +8,52 @@ description: >- sidebar_position: 3 --- -## Accepts +The Ctx interface 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. The Ctx API is split between methods acting on the +incoming [`Request`](#request) and the outgoing [`Response`](#response). -Checks, if the specified **extensions** or **content** **types** are acceptable. - -:::info -Based on the request’s [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. +:::tip +Ctx provides aliases for many `Request` and `Response` methods. For example, `c.Res().Send()` is the same as `c.Send()`. Examples on this page will show usage both ways. ::: -```go title="Signature" -func (c Ctx) Accepts(offers ...string) string -func (c Ctx) AcceptsCharsets(offers ...string) string -func (c Ctx) AcceptsEncodings(offers ...string) string -func (c Ctx) AcceptsLanguages(offers ...string) string -``` +## Req -```go title="Example" -// Accept: text/html, application/json; q=0.8, text/plain; q=0.5; charset="utf-8" +Req returns the fiber [`Request`](#request) object, which contains accessors and +methods to interact with the incoming HTTP request data. -app.Get("/", func(c fiber.Ctx) error { - c.Accepts("html") // "html" - c.Accepts("text/html") // "text/html" - c.Accepts("json", "text") // "json" - c.Accepts("application/json") // "application/json" - c.Accepts("text/plain", "application/json") // "application/json", due to quality - c.Accepts("image/png") // "" - c.Accepts("png") // "" - // ... -}) +```go title="Signature" +func (c Ctx) Req() *Request ``` -```go title="Example 2" -// Accept: text/html, text/*, application/json, */*; q=0 - +```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.Accepts("text/plain", "application/json") // "application/json", due to specificity - c.Accepts("application/json", "text/html") // "text/html", due to first match - c.Accepts("image/png") // "", due to */* without q factor 0 is Not Acceptable - // ... + c.Req().Method() + // => "GET" + return nil }) ``` -Media-Type parameters are supported. - -```go title="Example 3" -// Accept: text/plain, application/json; version=1; foo=bar - -app.Get("/", func(c fiber.Ctx) error { - // Extra parameters in the accept are ignored - c.Accepts("text/plain;format=flowed") // "text/plain;format=flowed" - - // An offer must contain all parameters present in the Accept type - c.Accepts("application/json") // "" - - // Parameter order and capitalization does not matter. Quotes on values are stripped. - c.Accepts(`application/json;foo="bar";VERSION=1`) // "application/json;foo="bar";VERSION=1" -}) -``` +## Res -```go title="Example 4" -// Accept: text/plain;format=flowed;q=0.9, text/plain -// i.e., "I prefer text/plain;format=flowed less than other forms of text/plain" -app.Get("/", func(c fiber.Ctx) error { - // Beware: the order in which offers are listed matters. - // Although the client specified they prefer not to receive format=flowed, - // the text/plain Accept matches with "text/plain;format=flowed" first, so it is returned. - c.Accepts("text/plain;format=flowed", "text/plain") // "text/plain;format=flowed" +Res returns the fiber [`Response`](#response) object, which contains methods to set and +modify the outgoing HTTP response data. - // Here, things behave as expected: - c.Accepts("text/plain", "text/plain;format=flowed") // "text/plain" -}) +```go title="Signature" +func (c Ctx) Res() *Response ``` -Fiber provides similar functions for the other accept headers. - -```go -// Accept-Charset: utf-8, iso-8859-1;q=0.2 -// Accept-Encoding: gzip, compress;q=0.2 -// Accept-Language: en;q=0.8, nl, ru - +```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.AcceptsCharsets("utf-16", "iso-8859-1") - // "iso-8859-1" - - c.AcceptsEncodings("compress", "br") - // "compress" - - c.AcceptsLanguages("pt", "nl", "ru") - // "nl" - // ... + c.Res().WriteString("Hello, World!") + // => "Hello, World!" + return nil }) ``` ## App -Returns the [\*App](ctx.md) reference so you could easily access all application settings. +Returns the [\*App](ctx.md) reference so you can easily access all application settings. ```go title="Signature" func (c Ctx) App() *App @@ -114,142 +65,130 @@ app.Get("/stack", func(c fiber.Ctx) error { }) ``` -## Append +## Bind -Appends the specified **value** to the HTTP response header field. +Bind is a method that support supports bindings for the request/response body, query parameters, URL parameters, cookies and much more. +It returns a pointer to the [Bind](./bind.md) struct which contains all the methods to bind the request/response data. -:::caution -If the header is **not** already set, it creates the header with the specified value. -::: +For detailed information check the [Bind](./bind.md) documentation. ```go title="Signature" -func (c Ctx) Append(field string, values ...string) +func (c Ctx) Bind() *Bind ``` ```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - c.Append("Link", "http://google.com", "http://localhost") - // => Link: http://localhost, http://google.com - - c.Append("Link", "Test") - // => Link: http://localhost, http://google.com, Test - - // ... +app.Post("/", func(c fiber.Ctx) error { + user := new(User) + // Bind the request body to a struct: + return c.Bind().Body(user) }) ``` -## Attachment +## Context -Sets the HTTP response [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header field to `attachment`. +Returns [\*fasthttp.RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) that is compatible with the context.Context interface that requires a deadline, a cancellation signal, and other values across API boundaries. ```go title="Signature" -func (c Ctx) Attachment(filename ...string) +func (c Ctx) Context() *fasthttp.RequestCtx ``` -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - c.Attachment() - // => Content-Disposition: attachment +:::info +Please read the [Fasthttp Documentation](https://pkg.go.dev/github.com/valyala/fasthttp?tab=doc) for more information. +::: - c.Attachment("./upload/images/logo.png") - // => Content-Disposition: attachment; filename="logo.png" - // => Content-Type: image/png +## ClientHelloInfo + +ClientHelloInfo contains information from a ClientHello message in order to guide application logic in the GetCertificate and GetConfigForClient callbacks. +You can refer to the [ClientHelloInfo](https://golang.org/pkg/crypto/tls/#ClientHelloInfo) struct documentation for more information on the returned struct. + +```go title="Signature" +func (c Ctx) ClientHelloInfo() *tls.ClientHelloInfo +``` +```go title="Example" +// GET http://example.com/hello +app.Get("/hello", func(c fiber.Ctx) error { + chi := c.ClientHelloInfo() // ... }) ``` -## AutoFormat - -Performs content-negotiation on the [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. It uses [Accepts](ctx.md#accepts) to select a proper format. -The supported content types are `text/html`, `text/plain`, `application/json`, and `application/xml`. -For more flexible content negotiation, use [Format](ctx.md#format). - +## FormFile -:::info -If the header is **not** specified or there is **no** proper format, **text/plain** is used. -::: +MultipartForm files can be retrieved by name, the **first** file from the given key is returned. ```go title="Signature" -func (c Ctx) AutoFormat(body any) error +func (c Ctx) FormFile(key string) (*multipart.FileHeader, error) ``` ```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - // Accept: text/plain - c.AutoFormat("Hello, World!") - // => Hello, World! - - // Accept: text/html - c.AutoFormat("Hello, World!") - // =>

Hello, World!

- - type User struct { - Name string - } - user := User{"John Doe"} - - // Accept: application/json - c.AutoFormat(user) - // => {"Name":"John Doe"} +app.Post("/", func(c fiber.Ctx) error { + // Get first file from form field "document": + file, err := c.FormFile("document") - // Accept: application/xml - c.AutoFormat(user) - // => John Doe - // .. + // Save file to root directory: + return c.SaveFile(file, fmt.Sprintf("./%s", file.Filename)) }) ``` -## BaseURL +## FormValue -Returns the base URL \(**protocol** + **host**\) as a `string`. +Any form values can be retrieved by name, the **first** value from the given key is returned. ```go title="Signature" -func (c Ctx) BaseURL() string +func (c Ctx) FormValue(key string, defaultValue ...string) string ``` ```go title="Example" -// GET https://example.com/page#chapter-1 +app.Post("/", func(c fiber.Ctx) error { + // Get first value from form field "name": + c.FormValue("name") + // => "john" or "" if not exist -app.Get("/", func(c fiber.Ctx) error { - c.BaseURL() // https://example.com - // ... + // .. }) ``` -## Bind +:::info -Bind is a method that support supports bindings for the request/response body, query parameters, URL parameters, cookies and much more. -It returns a pointer to the [Bind](./bind.md) struct which contains all the methods to bind the request/response data. +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) -For detailed information check the [Bind](./bind.md) documentation. +::: + +## GetReqHeaders + +Returns the HTTP request headers as a map. Since a header can be set multiple times in a single request, the values of the map are slices of strings containing all the different values of the header. ```go title="Signature" -func (c Ctx) Bind() *Bind +func (c Ctx) GetReqHeaders() map[string][]string ``` -```go title="Example" -app.Post("/", func(c fiber.Ctx) error { - user := new(User) - // Bind the request body to a struct: - return c.Bind().Body(user) -}) -``` +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) -## Body +::: -As per the header `Content-Encoding`, this method will try to perform a file decompression from the **body** bytes. In case no `Content-Encoding` header is sent, it will perform as [BodyRaw](#bodyraw). +## GetRespHeader + +Returns the HTTP response header specified by the field. + +:::tip +The match is **case-insensitive**. +::: ```go title="Signature" -func (c Ctx) Body() []byte +func (c Ctx) GetRespHeader(key string, defaultValue ...string) string ``` ```go title="Example" -// echo 'user=john' | gzip | curl -v -i --data-binary @- -H "Content-Encoding: gzip" http://localhost:8080 - -app.Post("/", func(c fiber.Ctx) error { - // Decompress body from POST request based on the Content-Encoding and return the raw content: - return c.Send(c.Body()) // []byte("user=john") +app.Get("/", func(c fiber.Ctx) error { + c.GetRespHeader("X-Request-Id") // "8d7ad5e3-aaf3-450b-a241-2beb887efd54" + c.GetRespHeader("Content-Type") // "text/plain" + c.GetRespHeader("something", "john") // "john" + // .. }) ``` @@ -260,21 +199,12 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. ::: -## BodyRaw +## GetRespHeaders -Returns the raw request **body**. +Returns the HTTP response headers as a map. Since a header can be set multiple times in a single request, the values of the map are slices of strings containing all the different values of the header. ```go title="Signature" -func (c Ctx) BodyRaw() []byte -``` - -```go title="Example" -// curl -X POST http://localhost:8080 -d user=john - -app.Post("/", func(c fiber.Ctx) error { - // Get raw body from POST request: - return c.Send(c.BodyRaw()) // []byte("user=john") -}) +func (c Ctx) GetRespHeaders() map[string][]string ``` :::info @@ -284,296 +214,463 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. ::: -## ClearCookie +## GetRouteURL -Expire a client cookie \(_or all cookies if left empty\)_ +Generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831" ```go title="Signature" -func (c Ctx) ClearCookie(key ...string) +func (c Ctx) GetRouteURL(routeName string, params Map) (string, error) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - // Clears all cookies: - c.ClearCookie() + return c.SendString("Home page") +}).Name("home") - // Expire specific cookie by name: - c.ClearCookie("user") +app.Get("/user/:id", func(c fiber.Ctx) error { + return c.SendString(c.Params("id")) +}).Name("user.show") - // Expire multiple cookies by names: - c.ClearCookie("token", "session", "track_id", "version") - // ... +app.Get("/test", func(c fiber.Ctx) error { + location, _ := c.GetRouteURL("user.show", fiber.Map{"id": 1}) + return c.SendString(location) }) + +// /test returns "/user/1" ``` -:::caution -Web browsers and other compliant clients will only clear the cookie if the given options are identical to those when creating the cookie, excluding expires and maxAge. ClearCookie will not set these values for you - a technique similar to the one shown below should be used to ensure your cookie is deleted. -::: +## IsFromLocal + +Returns true if request came from localhost + +```go title="Signature" +func (c Ctx) IsFromLocal() bool { +``` ```go title="Example" -app.Get("/set", func(c fiber.Ctx) error { - c.Cookie(&fiber.Cookie{ - Name: "token", - Value: "randomvalue", - Expires: time.Now().Add(24 * time.Hour), - HTTPOnly: true, - SameSite: "lax", - }) - // ... +app.Get("/", func(c fiber.Ctx) error { + // If request came from localhost, return true else return false + c.IsFromLocal() + + // ... }) +``` -app.Get("/delete", func(c fiber.Ctx) error { - c.Cookie(&fiber.Cookie{ - Name: "token", - // Set expiry date to the past - Expires: time.Now().Add(-(time.Hour * 2)), - HTTPOnly: true, - SameSite: "lax", - }) - - // ... -}) -``` - -## ClientHelloInfo +## IsProxyTrusted -ClientHelloInfo contains information from a ClientHello message in order to guide application logic in the GetCertificate and GetConfigForClient callbacks. -You can refer to the [ClientHelloInfo](https://golang.org/pkg/crypto/tls/#ClientHelloInfo) struct documentation for more information on the returned struct. +Checks trustworthiness of remote ip. +If [`EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) false, it returns true +IsProxyTrusted can check remote ip by proxy ranges and ip map. ```go title="Signature" -func (c Ctx) ClientHelloInfo() *tls.ClientHelloInfo +func (c Ctx) IsProxyTrusted() bool ``` ```go title="Example" -// GET http://example.com/hello -app.Get("/hello", func(c fiber.Ctx) error { - chi := c.ClientHelloInfo() + +app := fiber.New(fiber.Config{ + // EnableTrustedProxyCheck enables the trusted proxy check + EnableTrustedProxyCheck: true, + // TrustedProxies is a list of trusted proxy IP addresses + TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}, +}) + + +app.Get("/", func(c fiber.Ctx) error { + // If request came from trusted proxy, return true else return false + c.IsProxyTrusted() + // ... }) + ``` -## Context +## Locals -Returns [\*fasthttp.RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) that is compatible with the context.Context interface that requires a deadline, a cancellation signal, and other values across API boundaries. +A method that stores variables scoped to the request and, therefore, are available only to the routes that match the request. + +:::tip +This is useful if you want to pass some **specific** data to the next middleware. +::: ```go title="Signature" -func (c Ctx) Context() *fasthttp.RequestCtx +func (c Ctx) Locals(key any, value ...any) any ``` -:::info -Please read the [Fasthttp Documentation](https://pkg.go.dev/github.com/valyala/fasthttp?tab=doc) for more information. -::: +```go title="Example" -## Cookie +// key is an unexported type for keys defined in this package. +// This prevents collisions with keys defined in other packages. +type key int -Set cookie +// userKey is the key for user.User values in Contexts. It is +// unexported; clients use user.NewContext and user.FromContext +// instead of using this key directly. +var userKey key + +app.Use(func(c fiber.Ctx) error { + c.Locals(userKey, "admin") + return c.Next() +}) + +app.Get("/admin", func(c fiber.Ctx) error { + if c.Locals(userKey) == "admin" { + return c.Status(fiber.StatusOK).SendString("Welcome, admin!") + } + return c.SendStatus(fiber.StatusForbidden) + +}) +``` + +An alternative version of the Locals method that takes advantage of Go's generics feature is also available. This version +allows for the manipulation and retrieval of local values within a request's context with a more specific data type. ```go title="Signature" -func (c Ctx) Cookie(cookie *Cookie) +func Locals[V any](c Ctx, key any, value ...V) V ``` -```go -type Cookie struct { - Name string `json:"name"` - Value string `json:"value"` - Path string `json:"path"` - Domain string `json:"domain"` - MaxAge int `json:"max_age"` - Expires time.Time `json:"expires"` - Secure bool `json:"secure"` - HTTPOnly bool `json:"http_only"` - SameSite string `json:"same_site"` - SessionOnly bool `json:"session_only"` -} +```go title="Example" +app.Use(func(c Ctx) error { + fiber.Locals[string](c, "john", "doe") + fiber.Locals[int](c, "age", 18) + fiber.Locals[bool](c, "isHuman", true) + return c.Next() +}) +app.Get("/test", func(c Ctx) error { + fiber.Locals[string](c, "john") // "doe" + fiber.Locals[int](c, "age") // 18 + fiber.Locals[bool](c, "isHuman") // true + return nil +}) +```` + +Make sure to understand and correctly implement the Locals method in both its standard and generic form for better control +over route-specific data within your application. + +## Next + +When **Next** is called, it executes the next method in the stack that matches the current route. You can pass an error struct within the method that will end the chaining and call the [error handler](https://docs.gofiber.io/guide/error-handling). + +```go title="Signature" +func (c Ctx) Next() error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - // Create cookie - cookie := new(fiber.Cookie) - cookie.Name = "john" - cookie.Value = "doe" - cookie.Expires = time.Now().Add(24 * time.Hour) + fmt.Println("1st route!") + return c.Next() +}) - // Set cookie - c.Cookie(cookie) - // ... +app.Get("*", func(c fiber.Ctx) error { + fmt.Println("2nd route!") + return c.Next() +}) + +app.Get("/", func(c fiber.Ctx) error { + fmt.Println("3rd route!") + return c.SendString("Hello, World!") }) ``` -## Cookies -Get cookie value by key, you could pass an optional default value that will be returned if the cookie key does not exist. +## Redirect + +Returns the Redirect reference. + +For detailed information check the [Redirect](./redirect.md) documentation. ```go title="Signature" -func (c Ctx) Cookies(key string, defaultValue ...string) string +func (c Ctx) Redirect() *Redirect ``` ```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - // Get cookie by key: - c.Cookies("name") // "john" - c.Cookies("empty", "doe") // "doe" - // ... +app.Get("/coffee", func(c fiber.Ctx) error { + return c.Redirect().To("/teapot") }) -``` -:::info +app.Get("/teapot", func(c fiber.Ctx) error { + return c.Status(fiber.StatusTeapot).Send("🍵 short and stout 🍵") +}) +``` -Returned value is only valid within the handler. Do not store any references. -Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) +## Reset -::: +Reset the context fields by given request when to use server handlers. -## Download +```go title="Signature" +func (c Ctx) Reset(fctx *fasthttp.RequestCtx) +``` -Transfers the file from path as an `attachment`. +It is used outside of the Fiber Handlers to reset the context for the next request. -Typically, browsers will prompt the user to download. By default, the [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header `filename=` parameter is the file path \(_this typically appears in the browser dialog_\). +## RestartRouting -Override this default with the **filename** parameter. +Instead of executing the next method when calling [Next](ctx.md#next), **RestartRouting** restarts execution from the first method that matches the current route. This may be helpful after overriding the path, i.e. an internal redirect. Note that handlers might be executed again which could result in an infinite loop. ```go title="Signature" -func (c Ctx) Download(file string, filename ...string) error +func (c Ctx) RestartRouting() error ``` ```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - return c.Download("./files/report-12345.pdf"); - // => Download report-12345.pdf +app.Get("/new", func(c fiber.Ctx) error { + return c.SendString("From /new") +}) - return c.Download("./files/report-12345.pdf", "report.pdf"); - // => Download report.pdf +app.Get("/old", func(c fiber.Ctx) error { + c.Path("/new") + return c.RestartRouting() }) ``` -## Format - -Performs content-negotiation on the [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. It uses [Accepts](ctx.md#accepts) to select a proper format from the supplied offers. A default handler can be provided by setting the `MediaType` to `"default"`. If no offers match and no default is provided, a 406 (Not Acceptable) response is sent. The Content-Type is automatically set when a handler is selected. +## SaveFile -:::info -If the Accept header is **not** specified, the first handler will be used. -::: +Method is used to save **any** multipart file to disk. ```go title="Signature" -func (c Ctx) Format(handlers ...ResFmt) error +func (c Ctx) SaveFile(fh *multipart.FileHeader, path string) error ``` ```go title="Example" -// Accept: application/json => {"command":"eat","subject":"fruit"} -// Accept: text/plain => Eat Fruit! -// Accept: application/xml => Not Acceptable -app.Get("/no-default", func(c fiber.Ctx) error { - return c.Format( - fiber.ResFmt{"application/json", func(c fiber.Ctx) error { - return c.JSON(fiber.Map{ - "command": "eat", - "subject": "fruit", - }) - }}, - fiber.ResFmt{"text/plain", func(c fiber.Ctx) error { - return c.SendString("Eat Fruit!") - }}, - ) -}) +app.Post("/", func(c fiber.Ctx) error { + // Parse the multipart form: + if form, err := c.MultipartForm(); err == nil { + // => *multipart.Form -// Accept: application/json => {"command":"eat","subject":"fruit"} -// Accept: text/plain => Eat Fruit! -// Accept: application/xml => Eat Fruit! -app.Get("/default", func(c fiber.Ctx) error { - textHandler := func(c fiber.Ctx) error { - return c.SendString("Eat Fruit!") - } + // Get all files from "documents" key: + files := form.File["documents"] + // => []*multipart.FileHeader - handlers := []fiber.ResFmt{ - {"application/json", func(c fiber.Ctx) error { - return c.JSON(fiber.Map{ - "command": "eat", - "subject": "fruit", - }) - }}, - {"text/plain", textHandler}, - {"default", textHandler}, - } + // Loop through files: + for _, file := range files { + fmt.Println(file.Filename, file.Size, file.Header["Content-Type"][0]) + // => "tutorial.pdf" 360641 "application/pdf" - return c.Format(handlers...) + // Save the files to disk: + if err := c.SaveFile(file, fmt.Sprintf("./%s", file.Filename)); err != nil { + return err + } + } + return err + } }) ``` -## FormFile +## SaveFileToStorage -MultipartForm files can be retrieved by name, the **first** file from the given key is returned. +Method is used to save **any** multipart file to an external storage system. ```go title="Signature" -func (c Ctx) FormFile(key string) (*multipart.FileHeader, error) +func (c Ctx) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error ``` ```go title="Example" +storage := memory.New() + app.Post("/", func(c fiber.Ctx) error { - // Get first file from form field "document": - file, err := c.FormFile("document") + // Parse the multipart form: + if form, err := c.MultipartForm(); err == nil { + // => *multipart.Form - // Save file to root directory: - return c.SaveFile(file, fmt.Sprintf("./%s", file.Filename)) + // Get all files from "documents" key: + files := form.File["documents"] + // => []*multipart.FileHeader + + // Loop through files: + for _, file := range files { + fmt.Println(file.Filename, file.Size, file.Header["Content-Type"][0]) + // => "tutorial.pdf" 360641 "application/pdf" + + // Save the files to storage: + if err := c.SaveFileToStorage(file, fmt.Sprintf("./%s", file.Filename), storage); err != nil { + return err + } + } + return err + } }) ``` -## FormValue +## SetUserContext -Any form values can be retrieved by name, the **first** value from the given key is returned. +Sets the user specified implementation for context interface. ```go title="Signature" -func (c Ctx) FormValue(key string, defaultValue ...string) string +func (c Ctx) SetUserContext(ctx context.Context) ``` ```go title="Example" -app.Post("/", func(c fiber.Ctx) error { - // Get first value from form field "name": - c.FormValue("name") - // => "john" or "" if not exist +app.Get("/", func(c fiber.Ctx) error { + ctx := context.Background() + c.SetUserContext(ctx) + // Here ctx could be any context implementation - // .. + // ... }) ``` -:::info +## String -Returned value is only valid within the handler. Do not store any references. -Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) +Returns unique string representation of the ctx. -::: +```go title="Signature" +func (c Ctx) String() string +``` -## Fresh +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + c.String() // => "#0000000100000001 - 127.0.0.1:3000 <-> 127.0.0.1:61516 - GET http://localhost:3000/" -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, `Fresh` will return false to make handling these requests transparent. +## UserContext -Read more on [https://expressjs.com/en/4x/api.html\#req.fresh](https://expressjs.com/en/4x/api.html#req.fresh) +UserContext returns a context implementation that was set by user earlier +or returns a non-nil, empty context, if it was not set earlier. ```go title="Signature" -func (c Ctx) Fresh() bool +func (c Ctx) UserContext() context.Context ``` -## Get +```go title="Example" +app.Get("/", func(c fiber.Ctx) error { + ctx := c.UserContext() + // ctx is context implementation set by user -Returns the HTTP request header specified by the field. + // ... +}) +``` -:::tip -The match is **case-insensitive**. +## 📩 Request {#request} + +### Accepts + +Checks, if the specified **extensions** or **content** **types** are acceptable. + +:::info +Based on the request’s [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. ::: ```go title="Signature" -func (c Ctx) Get(key string, defaultValue ...string) string +func (r *Request) Accepts(offers ...string) string +func (r *Request) AcceptsCharsets(offers ...string) string +func (r *Request) AcceptsEncodings(offers ...string) string +func (r *Request) AcceptsLanguages(offers ...string) string ``` ```go title="Example" +// Accept: text/html, application/json; q=0.8, text/plain; q=0.5; charset="utf-8" + app.Get("/", func(c fiber.Ctx) error { - c.Get("Content-Type") // "text/plain" - c.Get("CoNtEnT-TypE") // "text/plain" - c.Get("something", "john") // "john" - // .. + c.Req().Accepts("html") // "html" + c.Req().Accepts("text/html") // "text/html" + c.Req().Accepts("json", "text") // "json" + c.Req().Accepts("application/json") // "application/json" + c.Req().Accepts("text/plain", "application/json") // "application/json", due to quality + c.Req().Accepts("image/png") // "" + c.Req().Accepts("png") // "" + c.Accepts("html") // "html" + // ... +}) +``` + +```go title="Example 2" +// Accept: text/html, text/*, application/json, */*; q=0 + +app.Get("/", func(c fiber.Ctx) error { + c.Req().Accepts("text/plain", "application/json") // "application/json", due to specificity + c.Req().Accepts("application/json", "text/html") // "text/html", due to first match + c.Req().Accepts("image/png") // "", due to */* without q factor 0 is Not Acceptable + // ... +}) +``` + +Media-Type parameters are supported. + +```go title="Example 3" +// Accept: text/plain, application/json; version=1; foo=bar + +app.Get("/", func(c fiber.Ctx) error { + // Extra parameters in the accept are ignored + c.Req().Accepts("text/plain;format=flowed") // "text/plain;format=flowed" + + // An offer must contain all parameters present in the Accept type + c.Req().Accepts("application/json") // "" + + // Parameter order and capitalization does not matter. Quotes on values are stripped. + c.Req().Accepts(`application/json;foo="bar";VERSION=1`) // "application/json;foo="bar";VERSION=1" +}) +``` + +```go title="Example 4" +// Accept: text/plain;format=flowed;q=0.9, text/plain +// i.e., "I prefer text/plain;format=flowed less than other forms of text/plain" +app.Get("/", func(c fiber.Ctx) error { + // Beware: the order in which offers are listed matters. + // Although the client specified they prefer not to receive format=flowed, + // the text/plain Accept matches with "text/plain;format=flowed" first, so it is returned. + c.Req().Accepts("text/plain;format=flowed", "text/plain") // "text/plain;format=flowed" + + // Here, things behave as expected: + c.Req().Accepts("text/plain", "text/plain;format=flowed") // "text/plain" +}) +``` + +Fiber provides similar functions for the other accept headers. + +```go +// Accept-Charset: utf-8, iso-8859-1;q=0.2 +// Accept-Encoding: gzip, compress;q=0.2 +// Accept-Language: en;q=0.8, nl, ru + +app.Get("/", func(c fiber.Ctx) error { + c.Req().AcceptsCharsets("utf-16", "iso-8859-1") + // "iso-8859-1" + + c.Req().AcceptsEncodings("compress", "br") + // "compress" + + c.Req().AcceptsLanguages("pt", "nl", "ru") + // "nl" + // ... +}) +``` + +### BaseURL + +Returns the base URL \(**protocol** + **host**\) as a `string`. + +```go title="Signature" +func (r *Request) BaseURL() string +``` + +```go title="Example" +// GET https://example.com/page#chapter-1 + +app.Get("/", func(c fiber.Ctx) error { + c.Req().BaseURL() // https://example.com + c.BaseUrl() // https://example.com + // ... +}) +``` + +### Body + +As per the header `Content-Encoding`, this method will try to perform a file decompression from the **body** bytes. In case no `Content-Encoding` header is sent, it will perform as [BodyRaw](#bodyraw). + +```go title="Signature" +func (r *Request) Body() []byte +``` + +```go title="Example" +// echo 'user=john' | gzip | curl -v -i --data-binary @- -H "Content-Encoding: gzip" http://localhost:8080 + +app.Post("/", func(c fiber.Ctx) error { + // Decompress body from POST request based on the Content-Encoding and return the raw content: + c.Req().Body() // []byte("user=john") + return c.Send(c.Body()) // []byte("user=john") }) ``` @@ -584,12 +681,22 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. ::: -## GetReqHeaders +### BodyRaw -Returns the HTTP request headers as a map. Since a header can be set multiple times in a single request, the values of the map are slices of strings containing all the different values of the header. +Returns the raw request **body**. + +```go title="Signature" +func (r *Request) BodyRaw() []byte +``` + +```go title="Example" +// curl -X POST http://localhost:8080 -d user=john -```go title="Signature" -func (c Ctx) GetReqHeaders() map[string][]string +app.Post("/", func(c fiber.Ctx) error { + // Get raw body from POST request: + c.Req().BodyRaw() // []byte("user=john") + return c.Send(c.BodyRaw()) // []byte("user=john") +}) ``` :::info @@ -599,24 +706,23 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. ::: -## GetRespHeader -Returns the HTTP response header specified by the field. -:::tip -The match is **case-insensitive**. -::: +### Cookies + +Get cookie value by key, you could pass an optional default value that will be returned if the cookie key does not exist. ```go title="Signature" -func (c Ctx) GetRespHeader(key string, defaultValue ...string) string +func (r *Request) Cookies(key string, defaultValue ...string) string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.GetRespHeader("X-Request-Id") // "8d7ad5e3-aaf3-450b-a241-2beb887efd54" - c.GetRespHeader("Content-Type") // "text/plain" - c.GetRespHeader("something", "john") // "john" - // .. + // Get cookie by key: + c.Req().Cookies("name") // "john" + c.Req().Cookies("empty", "doe") // "doe" + c.Cookies("name") // "john" + // ... }) ``` @@ -627,63 +733,65 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. ::: -## GetRespHeaders +### Fresh -Returns the HTTP response headers as a map. Since a header can be set multiple times in a single request, the values of the map are slices of strings containing all the different values of the header. +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, `Fresh` will return false to make handling these requests transparent. + +Read more on [https://expressjs.com/en/4x/api.html\#req.fresh](https://expressjs.com/en/4x/api.html#req.fresh) ```go title="Signature" -func (c Ctx) GetRespHeaders() map[string][]string +func (r *Request) Fresh() bool ``` -:::info +### Get -Returned value is only valid within the handler. Do not store any references. -Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) +Returns the HTTP request header specified by the field. +:::tip +The match is **case-insensitive**. ::: -## GetRouteURL - -Generates URLs to named routes, with parameters. URLs are relative, for example: "/user/1831" - ```go title="Signature" -func (c Ctx) GetRouteURL(routeName string, params Map) (string, error) +func (r *Request) Get(key string, defaultValue ...string) string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - return c.SendString("Home page") -}).Name("home") + c.Req().Get("Content-Type") // "text/plain" + c.Req().Get("CoNtEnT-TypE") // "text/plain" + c.Get("something", "john") // "john" + // .. +}) +``` -app.Get("/user/:id", func(c fiber.Ctx) error { - return c.SendString(c.Params("id")) -}).Name("user.show") +:::info -app.Get("/test", func(c fiber.Ctx) error { - location, _ := c.GetRouteURL("user.show", fiber.Map{"id": 1}) - return c.SendString(location) -}) +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: -// /test returns "/user/1" -``` -## Host +### Host Returns the host derived from the [Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) HTTP header. In a network context, [`Host`](#host) refers to the combination of a hostname and potentially a port number used for connecting, while [`Hostname`](#hostname) refers specifically to the name assigned to a device on a network, excluding any port information. ```go title="Signature" -func (c Ctx) Host() string +func (r *Request) Host() string ``` ```go title="Example" // GET http://google.com:8080/search app.Get("/", func(c fiber.Ctx) error { - c.Host() // "google.com:8080" - c.Hostname() // "google.com" - + c.Req().Host() // "google.com:8080" + c.Req().Hostname() // "google.com" + c.Host() // "google.com:8080" + c.Hostname() // "google.com" // ... }) ``` @@ -695,19 +803,20 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. ::: -## Hostname +### Hostname Returns the hostname derived from the [Host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) HTTP header. ```go title="Signature" -func (c Ctx) Hostname() string +func (r *Request) Hostname() string ``` ```go title="Example" // GET http://google.com/search app.Get("/", func(c fiber.Ctx) error { - c.Hostname() // "google.com" + c.Req().Hostname() // "google.com" + c.Hostname() // "google.com" // ... }) @@ -720,17 +829,18 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. ::: -## IP +### IP Returns the remote IP address of the request. ```go title="Signature" -func (c Ctx) IP() string +func (r *Request) IP() string ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.IP() // "127.0.0.1" + c.Req().IP() // "127.0.0.1" + c.IP() // "127.0.0.1" // ... }) @@ -744,19 +854,20 @@ app := fiber.New(fiber.Config{ }) ``` -## IPs +### IPs Returns an array of IP addresses specified in the [X-Forwarded-For](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) request header. ```go title="Signature" -func (c Ctx) IPs() []string +func (r *Request) IPs() []string ``` ```go title="Example" // X-Forwarded-For: proxy1, 127.0.0.1, proxy3 app.Get("/", func(c fiber.Ctx) error { - c.IPs() // ["proxy1", "127.0.0.1", "proxy3"] + c.Req().IPs() // ["proxy1", "127.0.0.1", "proxy3"] + c.IPs() // ["proxy1", "127.0.0.1", "proxy3"] // ... }) @@ -766,7 +877,7 @@ app.Get("/", func(c fiber.Ctx) error { Improper use of the X-Forwarded-For header can be a security risk. For details, see the [Security and privacy concerns](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#security_and_privacy_concerns) section. ::: -## Is +### Is Returns the matching **content type**, if the incoming request’s [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) HTTP header field matches the [MIME type](https://developer.mozilla.org/ru/docs/Web/HTTP/Basics_of_HTTP/MIME_types) specified by the type parameter. @@ -775,286 +886,73 @@ If the request has **no** body, it returns **false**. ::: ```go title="Signature" -func (c Ctx) Is(extension string) bool +func (r *Request) Is(extension string) bool ``` ```go title="Example" // Content-Type: text/html; charset=utf-8 -app.Get("/", func(c fiber.Ctx) error { - c.Is("html") // true - c.Is(".html") // true - c.Is("json") // false - - // ... -}) -``` - -## IsFromLocal - -Returns true if request came from localhost - -```go title="Signature" -func (c Ctx) IsFromLocal() bool { -``` - -```go title="Example" - -app.Get("/", func(c fiber.Ctx) error { - // If request came from localhost, return true else return false - c.IsFromLocal() - - // ... -}) -``` - -## IsProxyTrusted - -Checks trustworthiness of remote ip. -If [`EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) false, it returns true -IsProxyTrusted can check remote ip by proxy ranges and ip map. - -```go title="Signature" -func (c Ctx) IsProxyTrusted() bool -``` - -```go title="Example" - -app := fiber.New(fiber.Config{ - // EnableTrustedProxyCheck enables the trusted proxy check - EnableTrustedProxyCheck: true, - // TrustedProxies is a list of trusted proxy IP addresses - TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}, -}) - - -app.Get("/", func(c fiber.Ctx) error { - // If request came from trusted proxy, return true else return false - c.IsProxyTrusted() - - // ... -}) - -``` - -## JSON - -Converts any **interface** or **string** to JSON using the [encoding/json](https://pkg.go.dev/encoding/json) package. - -:::info -JSON also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/json`. -::: - -```go title="Signature" -func (c Ctx) JSON(data any, ctype ...string) error -``` - -```go title="Example" -type SomeStruct struct { - Name string - Age uint8 -} - -app.Get("/json", func(c fiber.Ctx) error { - // Create data struct: - data := SomeStruct{ - Name: "Grame", - Age: 20, - } - - return c.JSON(data) - // => Content-Type: application/json - // => "{"Name": "Grame", "Age": 20}" - - return c.JSON(fiber.Map{ - "name": "Grame", - "age": 20, - }) - // => Content-Type: application/json - // => "{"name": "Grame", "age": 20}" - - return c.JSON(fiber.Map{ - "type": "https://example.com/probs/out-of-credit", - "title": "You do not have enough credit.", - "status": 403, - "detail": "Your current balance is 30, but that costs 50.", - "instance": "/account/12345/msgs/abc", - }, "application/problem+json") - // => Content-Type: application/problem+json - // => "{ - // => "type": "https://example.com/probs/out-of-credit", - // => "title": "You do not have enough credit.", - // => "status": 403, - // => "detail": "Your current balance is 30, but that costs 50.", - // => "instance": "/account/12345/msgs/abc", - // => }" -}) -``` - -## JSONP - -Sends a JSON response with JSONP support. This method is identical to [JSON](ctx.md#json), except that it opts-in to JSONP callback support. By default, the callback name is simply callback. - -Override this by passing a **named string** in the method. - -```go title="Signature" -func (c Ctx) JSONP(data any, callback ...string) error -``` - -```go title="Example" -type SomeStruct struct { - name string - age uint8 -} - -app.Get("/", func(c fiber.Ctx) error { - // Create data struct: - data := SomeStruct{ - name: "Grame", - age: 20, - } - - return c.JSONP(data) - // => callback({"name": "Grame", "age": 20}) - - return c.JSONP(data, "customFunc") - // => customFunc({"name": "Grame", "age": 20}) -}) -``` - -## Links - -Joins the links followed by the property to populate the response’s [Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) HTTP header field. - -```go title="Signature" -func (c Ctx) Links(link ...string) -``` - -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - c.Links( - "http://api.example.com/users?page=2", "next", - "http://api.example.com/users?page=5", "last", - ) - // Link: ; rel="next", - // ; rel="last" - - // ... -}) -``` - -## Locals - -A method that stores variables scoped to the request and, therefore, are available only to the routes that match the request. - -:::tip -This is useful if you want to pass some **specific** data to the next middleware. -::: - -```go title="Signature" -func (c Ctx) Locals(key any, value ...any) any -``` - -```go title="Example" - -// key is an unexported type for keys defined in this package. -// This prevents collisions with keys defined in other packages. -type key int - -// userKey is the key for user.User values in Contexts. It is -// unexported; clients use user.NewContext and user.FromContext -// instead of using this key directly. -var userKey key - -app.Use(func(c fiber.Ctx) error { - c.Locals(userKey, "admin") - return c.Next() -}) - -app.Get("/admin", func(c fiber.Ctx) error { - if c.Locals(userKey) == "admin" { - return c.Status(fiber.StatusOK).SendString("Welcome, admin!") - } - return c.SendStatus(fiber.StatusForbidden) - -}) -``` - -An alternative version of the Locals method that takes advantage of Go's generics feature is also available. This version -allows for the manipulation and retrieval of local values within a request's context with a more specific data type. - -```go title="Signature" -func Locals[V any](c Ctx, key any, value ...V) V -``` - -```go title="Example" -app.Use(func(c Ctx) error { - fiber.Locals[string](c, "john", "doe") - fiber.Locals[int](c, "age", 18) - fiber.Locals[bool](c, "isHuman", true) - return c.Next() -}) -app.Get("/test", func(c Ctx) error { - fiber.Locals[string](c, "john") // "doe" - fiber.Locals[int](c, "age") // 18 - fiber.Locals[bool](c, "isHuman") // true - return nil -}) -```` +app.Get("/", func(c fiber.Ctx) error { + c.Req().Is("html") // true + c.Req().Is(".html") // true + c.Req().Is("json") // false + c.Is("json") // false -Make sure to understand and correctly implement the Locals method in both its standard and generic form for better control -over route-specific data within your application. + // ... +}) +``` -## Location +### IsFromLocal -Sets the response [Location](https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Location) HTTP header to the specified path parameter. +Returns true if request came from localhost ```go title="Signature" -func (c Ctx) Location(path string) +func (r *Request) IsFromLocal() bool { ``` ```go title="Example" -app.Post("/", func(c fiber.Ctx) error { - c.Location("http://example.com") - c.Location("/foo/bar") +app.Get("/", func(c fiber.Ctx) error { + // If request came from localhost, return true else return false + c.Req().IsFromLocal() + c.IsFromLocal() - return nil + // ... }) ``` -## Method +### Method Returns a string corresponding to the HTTP method of the request: `GET`, `POST`, `PUT`, and so on. Optionally, you could override the method by passing a string. ```go title="Signature" -func (c Ctx) Method(override ...string) string +func (r *Request) Method(override ...string) string ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { - c.Method() // "POST" + c.Req().Method() // "POST" - c.Method("GET") + c.Req().Method("GET") c.Method() // GET // ... }) ``` -## MultipartForm +### MultipartForm To access multipart form entries, you can parse the binary with `MultipartForm()`. This returns a `map[string][]string`, so given a key, the value will be a string slice. ```go title="Signature" -func (c Ctx) MultipartForm() (*multipart.Form, error) +func (r *Request) MultipartForm() (*multipart.Form, error) ``` ```go title="Example" app.Post("/", func(c fiber.Ctx) error { // Parse the multipart form: - if form, err := c.MultipartForm(); err == nil { + if form, err := c.Req().MultipartForm(); err == nil { // Note: c.MultipartForm() also works. // => *multipart.Form if token := form.Value["token"]; len(token) > 0 { @@ -1082,44 +980,21 @@ app.Post("/", func(c fiber.Ctx) error { }) ``` -## Next - -When **Next** is called, it executes the next method in the stack that matches the current route. You can pass an error struct within the method that will end the chaining and call the [error handler](https://docs.gofiber.io/guide/error-handling). - -```go title="Signature" -func (c Ctx) Next() error -``` - -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - fmt.Println("1st route!") - return c.Next() -}) - -app.Get("*", func(c fiber.Ctx) error { - fmt.Println("2nd route!") - return c.Next() -}) - -app.Get("/", func(c fiber.Ctx) error { - fmt.Println("3rd route!") - return c.SendString("Hello, World!") -}) -``` -## OriginalURL +### OriginalURL Returns the original request URL. ```go title="Signature" -func (c Ctx) OriginalURL() string +func (r *Request) OriginalURL() string ``` ```go title="Example" // GET http://example.com/search?q=something app.Get("/", func(c fiber.Ctx) error { - c.OriginalURL() // "/search?q=something" + c.Req().OriginalURL() // "/search?q=something" + c.OriginalURL() // "/search?q=something" // ... }) @@ -1132,7 +1007,7 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. ::: -## Params +### Params Method can be used to get the route parameters, you could pass an optional default value that will be returned if the param key does not exist. @@ -1141,21 +1016,23 @@ Defaults to empty string \(`""`\), if the param **doesn't** exist. ::: ```go title="Signature" -func (c Ctx) Params(key string, defaultValue ...string) string +func (r *Request) Params(key string, defaultValue ...string) string ``` ```go title="Example" // GET http://example.com/user/fenny app.Get("/user/:name", func(c fiber.Ctx) error { - c.Params("name") // "fenny" + c.Req().Params("name") // "fenny" + c.Params("name") // "fenny" // ... }) // GET http://example.com/user/fenny/123 app.Get("/user/*", func(c fiber.Ctx) error { - c.Params("*") // "fenny/123" - c.Params("*1") // "fenny/123" + c.Req().Params("*") // "fenny/123" + c.Req().Params("*1") // "fenny/123" + c.Params("*1") // "fenny/123" // ... }) @@ -1214,452 +1091,742 @@ The generic Params function supports returning the following data types based on - Byte array: []byte -## Path +### Path Contains the path part of the request URL. Optionally, you could override the path by passing a string. For internal redirects, you might want to call [RestartRouting](ctx.md#restartrouting) instead of [Next](ctx.md#next). ```go title="Signature" -func (c Ctx) Path(override ...string) string +func (r *Request) Path(override ...string) string ``` ```go title="Example" // GET http://example.com/users?sort=desc app.Get("/users", func(c fiber.Ctx) error { - c.Path() // "/users" + c.Req().Path() // "/users" - c.Path("/john") - c.Path() // "/john" + c.Req().Path("/john") + c.Req().Path() // "/john" + c.Path() // "/john" // ... }) ``` -## Port +### Port Returns the remote port of the request. ```go title="Signature" -func (c Ctx) Port() string +func (r *Request) Port() string ``` ```go title="Example" // GET http://example.com:8080 app.Get("/", func(c fiber.Ctx) error { - c.Port() // "8080" + c.Req().Port() // "8080" + c.Port() // "8080" // ... }) ``` -## Protocol +### Protocol Contains the request protocol string: `http` or `https` for **TLS** requests. ```go title="Signature" -func (c Ctx) Protocol() string +func (r *Request) Protocol() string ``` ```go title="Example" // GET http://example.com app.Get("/", func(c fiber.Ctx) error { - c.Protocol() // "http" + c.Req().Protocol() // "http" + c.Protocol() // "http" // ... }) ``` -## Queries +### Queries Queries is a function that returns an object containing a property for each query string parameter in the route. ```go title="Signature" -func (c Ctx) Queries() map[string]string +func (r *Request) Queries() map[string]string +``` + +```go title="Example" +// GET http://example.com/?name=alex&want_pizza=false&id= + +app.Get("/", func(c fiber.Ctx) error { + m := c.Req().Queries() + m["name"] // "alex" + m["want_pizza"] // "false" + m["id"] // "" + // ... +}) +``` + +```go title="Example" +// GET http://example.com/?field1=value1&field1=value2&field2=value3 + +app.Get("/", func (c fiber.Ctx) error { + m := c.Queries() + m["field1"] // "value2" + m["field2"] // value3 +}) +``` + +```go title="Example" +// GET http://example.com/?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 + +app.Get("/", func(c fiber.Ctx) error { + m := c.Req().Queries() + m["list_a"] // "3" + m["list_b[]"] // "3" + m["list_c"] // "1,2,3" +}) +``` + +```go title="Example" +// GET /api/posts?filters.author.name=John&filters.category.name=Technology + +app.Get("/", func(c fiber.Ctx) error { + m := c.Queries() + m["filters.author.name"] // John + m["filters.category.name"] // Technology +}) +``` + +```go title="Example" +// GET /api/posts?tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits + +app.Get("/", func(c fiber.Ctx) error { + m := c.Req().Queries() + m["tags"] // apple,orange,banana + m["filters[tags]"] // apple,orange,banana + m["filters[category][name]"] // fruits + m["filters.tags"] // apple,orange,banana + m["filters.category.name"] // fruits +}) +``` + +### Query + +This property is an object containing a property for each query string parameter in the route, you could pass an optional default value that will be returned if the query key does not exist. + +:::info +If there is **no** query string, it returns an **empty string**. +::: + +```go title="Signature" +func (c Ctx) Query(key string, defaultValue ...string) string +``` + +```go title="Example" +// GET http://example.com/?order=desc&brand=nike + +app.Get("/", func(c fiber.Ctx) error { + c.Req().Query("order") // "desc" + c.Query("brand") // "nike" + c.Req().Query("empty", "nike") // "nike" + + // ... +}) +``` + +:::info + +Returned value is only valid within the handler. Do not store any references. +Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) + +::: + +In certain scenarios, it can be useful to have an alternative approach to handle different types of query parameters, not +just strings. This can be achieved using a generic Query function known as `Query[V GenericType](c Ctx, key string, defaultValue ...V) V`. +This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V GenericType`. + +Here is the signature for the generic Query function: + +```go title="Signature" +func Query[V GenericType](c Ctx, key string, defaultValue ...V) V +``` + +Consider this example: + +```go title="Example" +// GET http://example.com/?page=1&brand=nike&new=true + +app.Get("/", func(c fiber.Ctx) error { + fiber.Query[int](c, "page") // 1 + fiber.Query[string](c, "brand") // "nike" + fiber.Query[bool](c, "new") // true + + // ... +}) +``` + +In this case, `Query[V GenericType](c Ctx, key string, defaultValue ...V) V` can retrieve 'page' as an integer, 'brand' +as a string, and 'new' as a boolean. The function uses the appropriate parsing function for each specified type to ensure +the correct type is returned. This simplifies the retrieval process of different types of query parameters, making your +controller actions cleaner. + +The generic Query function supports returning the following data types based on V GenericType: +- Integer: int, int8, int16, int32, int64 +- Unsigned integer: uint, uint8, uint16, uint32, uint64 +- Floating-point numbers: float32, float64 +- Boolean: bool +- String: string +- Byte array: []byte + +### Range + +A struct containing the type and a slice of ranges will be returned. + +```go title="Signature" +func (r *Request) Range(size int) (Range, error) +``` + +```go title="Example" +// Range: bytes=500-700, 700-900 +app.Get("/", func(c fiber.Ctx) error { + b := c.Req().Range(1000) // c.Range also works + if b.Type == "bytes" { + for r := range r.Ranges { + fmt.Println(r) + // [500, 700] + } + } +}) +``` + +### Route + +Returns the matched [Route](https://pkg.go.dev/github.com/gofiber/fiber?tab=doc#Route) struct. + +```go title="Signature" +func (r *Request) Route() *Route +``` + +```go title="Example" +// http://localhost:8080/hello + + +app.Get("/hello/:name", func(c fiber.Ctx) error { + r := c.Req().Route() + fmt.Println(r.Method, r.Path, r.Params, r.Handlers) + // GET /hello/:name handler [name] + + // ... +}) +``` + +:::caution +Do not rely on `c.Route()` in middlewares **before** calling `c.Next()` - `c.Route()` returns the **last executed route**. +::: + +```go title="Example" +func MyMiddleware() fiber.Handler { + return func(c fiber.Ctx) error { + beforeNext := c.Route().Path // Will be '/' + err := c.Next() + afterNext := c.Route().Path // Will be '/hello/:name' + return err + } +} +``` + +### Scheme + +Contains the request protocol string: http or https for TLS requests. + +:::info +Please use [`Config.EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) to prevent header spoofing, in case when your app is behind the proxy. +::: + +```go title="Signature" +func (r *Request) Scheme() string +``` + +```go title="Example" +// GET http://example.com +app.Get("/", func(c fiber.Ctx) error { + c.Req().Scheme() // "http" + c.Scheme() // "http" + + // ... +}) +``` + +### Secure + +A boolean property that is `true` , if a **TLS** connection is established. + +```go title="Signature" +func (r *Request) Secure() bool +``` + +```go title="Example" +// Secure() method is equivalent to: +c.Req().Protocol() == "https" +// or +c.Protocol() == "https" +``` + +### Stale + +[https://expressjs.com/en/4x/api.html\#req.stale](https://expressjs.com/en/4x/api.html#req.stale) + +```go title="Signature" +func (r *Request) Stale() bool ``` -```go title="Example" -// GET http://example.com/?name=alex&want_pizza=false&id= +### Subdomains -app.Get("/", func(c fiber.Ctx) error { - m := c.Queries() - m["name"] // "alex" - m["want_pizza"] // "false" - m["id"] // "" - // ... -}) -``` +Returns a string slice of subdomains in the domain name of the request. -```go title="Example" -// GET http://example.com/?field1=value1&field1=value2&field2=value3 +The application property subdomain offset, which defaults to `2`, is used for determining the beginning of the subdomain segments. -app.Get("/", func (c fiber.Ctx) error { - m := c.Queries() - m["field1"] // "value2" - m["field2"] // value3 -}) +```go title="Signature" +func (r *Request) Subdomains(offset ...int) []string ``` ```go title="Example" -// GET http://example.com/?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 +// Host: "tobi.ferrets.example.com" app.Get("/", func(c fiber.Ctx) error { - m := c.Queries() - m["list_a"] // "3" - m["list_b[]"] // "3" - m["list_c"] // "1,2,3" + c.Req().Subdomains() // ["ferrets", "tobi"] + c.Req().Subdomains(1) // ["tobi"] + c.Subdomains(1) // ["tobi"] + + // ... }) ``` -```go title="Example" -// GET /api/posts?filters.author.name=John&filters.category.name=Technology +### XHR -app.Get("/", func(c fiber.Ctx) error { - m := c.Queries() - m["filters.author.name"] // John - m["filters.category.name"] // Technology -}) +A Boolean property, that is `true`, if the request’s [X-Requested-With](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) header field is [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest), indicating that the request was issued by a client library \(such as [jQuery](https://api.jquery.com/jQuery.ajax/)\). + +```go title="Signature" +func (r *Request) XHR() bool ``` ```go title="Example" -// GET /api/posts?tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits +// X-Requested-With: XMLHttpRequest app.Get("/", func(c fiber.Ctx) error { - m := c.Queries() - m["tags"] // apple,orange,banana - m["filters[tags]"] // apple,orange,banana - m["filters[category][name]"] // fruits - m["filters.tags"] // apple,orange,banana - m["filters.category.name"] // fruits + c.Req().XHR() // true + c.XHR() // true + + // ... }) ``` -## Query +## 📨 Response {#response} -This property is an object containing a property for each query string parameter in the route, you could pass an optional default value that will be returned if the query key does not exist. +### Append -:::info -If there is **no** query string, it returns an **empty string**. +Appends the specified **value** to the HTTP response header field. + +:::caution +If the header is **not** already set, it creates the header with the specified value. ::: ```go title="Signature" -func (c Ctx) Query(key string, defaultValue ...string) string +func (r *Response) Append(field string, values ...string) ``` ```go title="Example" -// GET http://example.com/?order=desc&brand=nike - app.Get("/", func(c fiber.Ctx) error { - c.Query("order") // "desc" - c.Query("brand") // "nike" - c.Query("empty", "nike") // "nike" + c.Res().Append("Link", "http://google.com", "http://localhost") + // => Link: http://localhost, http://google.com + + c.Append("Link", "Test") + // => Link: http://localhost, http://google.com, Test // ... }) ``` -:::info - -Returned value is only valid within the handler. Do not store any references. -Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more...](../#zero-allocation) - -::: - -In certain scenarios, it can be useful to have an alternative approach to handle different types of query parameters, not -just strings. This can be achieved using a generic Query function known as `Query[V GenericType](c Ctx, key string, defaultValue ...V) V`. -This function is capable of parsing a query string and returning a value of a type that is assumed and specified by `V GenericType`. +### Attachment -Here is the signature for the generic Query function: +Sets the HTTP response [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header field to `attachment`. ```go title="Signature" -func Query[V GenericType](c Ctx, key string, defaultValue ...V) V +func (r *Response) Attachment(filename ...string) ``` -Consider this example: - ```go title="Example" -// GET http://example.com/?page=1&brand=nike&new=true - app.Get("/", func(c fiber.Ctx) error { - fiber.Query[int](c, "page") // 1 - fiber.Query[string](c, "brand") // "nike" - fiber.Query[bool](c, "new") // true + c.Res().Attachment() + // => Content-Disposition: attachment + + c.Attachment("./upload/images/logo.png") + // => Content-Disposition: attachment; filename="logo.png" + // => Content-Type: image/png // ... }) ``` -In this case, `Query[V GenericType](c Ctx, key string, defaultValue ...V) V` can retrieve 'page' as an integer, 'brand' -as a string, and 'new' as a boolean. The function uses the appropriate parsing function for each specified type to ensure -the correct type is returned. This simplifies the retrieval process of different types of query parameters, making your -controller actions cleaner. +### AutoFormat -The generic Query function supports returning the following data types based on V GenericType: -- Integer: int, int8, int16, int32, int64 -- Unsigned integer: uint, uint8, uint16, uint32, uint64 -- Floating-point numbers: float32, float64 -- Boolean: bool -- String: string -- Byte array: []byte +Performs content-negotiation on the [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. It uses [Accepts](ctx.md#accepts) to select a proper format. +The supported content types are `text/html`, `text/plain`, `application/json`, and `application/xml`. +For more flexible content negotiation, use [Format](ctx.md#format). -## Range -A struct containing the type and a slice of ranges will be returned. +:::info +If the header is **not** specified or there is **no** proper format, **text/plain** is used. +::: ```go title="Signature" -func (c Ctx) Range(size int) (Range, error) +func (r *Response) AutoFormat(body any) error ``` ```go title="Example" -// Range: bytes=500-700, 700-900 app.Get("/", func(c fiber.Ctx) error { - b := c.Range(1000) - if b.Type == "bytes" { - for r := range r.Ranges { - fmt.Println(r) - // [500, 700] - } + // Accept: text/plain + c.Res().AutoFormat("Hello, World!") + // => Hello, World! + + // Accept: text/html + c.AutoFormat("Hello, World!") + // =>

Hello, World!

+ + type User struct { + Name string } + user := User{"John Doe"} + + // Accept: application/json + c.Res().AutoFormat(user) + // => {"Name":"John Doe"} + + // Accept: application/xml + c.AutoFormat(user) + // => John Doe + // .. }) ``` -## Redirect - -Returns the Redirect reference. +### ClearCookie -For detailed information check the [Redirect](./redirect.md) documentation. +Expire a client cookie \(_or all cookies if left empty\)_ ```go title="Signature" -func (c Ctx) Redirect() *Redirect +func (r *Response) ClearCookie(key ...string) ``` ```go title="Example" -app.Get("/coffee", func(c fiber.Ctx) error { - return c.Redirect().To("/teapot") -}) +app.Get("/", func(c fiber.Ctx) error { + // Clears all cookies: + c.Res().ClearCookie() -app.Get("/teapot", func(c fiber.Ctx) error { - return c.Status(fiber.StatusTeapot).Send("🍵 short and stout 🍵") + // Expire specific cookie by name: + c.Res().ClearCookie("user") + + // Expire multiple cookies by names: + c.ClearCookie("token", "session", "track_id", "version") + // ... }) ``` +:::caution +Web browsers and other compliant clients will only clear the cookie if the given options are identical to those when creating the cookie, excluding expires and maxAge. ClearCookie will not set these values for you - a technique similar to the one shown below should be used to ensure your cookie is deleted. +::: + +```go title="Example" +app.Get("/set", func(c fiber.Ctx) error { + c.Cookie(&fiber.Cookie{ + Name: "token", + Value: "randomvalue", + Expires: time.Now().Add(24 * time.Hour), + HTTPOnly: true, + SameSite: "lax", + }) -## Render + // ... +}) -Renders a view with data and sends a `text/html` response. By default `Render` uses the default [**Go Template engine**](https://pkg.go.dev/html/template/). If you want to use another View engine, please take a look at our [**Template middleware**](https://docs.gofiber.io/template). +app.Get("/delete", func(c fiber.Ctx) error { + c.Cookie(&fiber.Cookie{ + Name: "token", + // Set expiry date to the past + Expires: time.Now().Add(-(time.Hour * 2)), + HTTPOnly: true, + SameSite: "lax", + }) -```go title="Signature" -func (c Ctx) Render(name string, bind Map, layouts ...string) error + // ... +}) ``` -## Request -Request return the [\*fasthttp.Request](https://godoc.org/github.com/valyala/fasthttp#Request) pointer +### Cookie + +Set cookie ```go title="Signature" -func (c Ctx) Request() *fasthttp.Request +func (r *Response) Cookie(cookie *Cookie) +``` + +```go +type Cookie struct { + Name string `json:"name"` + Value string `json:"value"` + Path string `json:"path"` + Domain string `json:"domain"` + MaxAge int `json:"max_age"` + Expires time.Time `json:"expires"` + Secure bool `json:"secure"` + HTTPOnly bool `json:"http_only"` + SameSite string `json:"same_site"` + SessionOnly bool `json:"session_only"` +} ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.Request().Header.Method() - // => []byte("GET") + // Create cookie + cookie := new(fiber.Cookie) + cookie.Name = "john" + cookie.Value = "doe" + cookie.Expires = time.Now().Add(24 * time.Hour) + + // Set cookie + c.Res().Cookie(cookie) // c.Cookie also works + // ... }) ``` -## Response -Response return the [\*fasthttp.Response](https://godoc.org/github.com/valyala/fasthttp#Response) pointer +### Download + +Transfers the file from path as an `attachment`. + +Typically, browsers will prompt the user to download. By default, the [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header `filename=` parameter is the file path \(_this typically appears in the browser dialog_\). + +Override this default with the **filename** parameter. ```go title="Signature" -func (c Ctx) Response() *fasthttp.Response +func (r *Response) Download(file string, filename ...string) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.Response().BodyWriter().Write([]byte("Hello, World!")) - // => "Hello, World!" - return nil -}) -``` - -## Reset - -Reset the context fields by given request when to use server handlers. + return c.Res().Download("./files/report-12345.pdf"); + // => Download report-12345.pdf -```go title="Signature" -func (c Ctx) Reset(fctx *fasthttp.RequestCtx) + return c.Download("./files/report-12345.pdf", "report.pdf"); + // => Download report.pdf +}) ``` -It is used outside of the Fiber Handlers to reset the context for the next request. +### Format -## RestartRouting +Performs content-negotiation on the [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) HTTP header. It uses [Accepts](ctx.md#accepts) to select a proper format from the supplied offers. A default handler can be provided by setting the `MediaType` to `"default"`. If no offers match and no default is provided, a 406 (Not Acceptable) response is sent. The Content-Type is automatically set when a handler is selected. -Instead of executing the next method when calling [Next](ctx.md#next), **RestartRouting** restarts execution from the first method that matches the current route. This may be helpful after overriding the path, i. e. an internal redirect. Note that handlers might be executed again which could result in an infinite loop. +:::info +If the Accept header is **not** specified, the first handler will be used. +::: ```go title="Signature" -func (c Ctx) RestartRouting() error +func (r *Response) Format(handlers ...ResFmt) error ``` ```go title="Example" -app.Get("/new", func(c fiber.Ctx) error { - return c.SendString("From /new") +// Accept: application/json => {"command":"eat","subject":"fruit"} +// Accept: text/plain => Eat Fruit! +// Accept: application/xml => Not Acceptable +app.Get("/no-default", func(c fiber.Ctx) error { + return c.Res().Format( + fiber.ResFmt{"application/json", func(c fiber.Ctx) error { + return c.JSON(fiber.Map{ + "command": "eat", + "subject": "fruit", + }) + }}, + fiber.ResFmt{"text/plain", func(c fiber.Ctx) error { + return c.SendString("Eat Fruit!") + }}, + ) }) -app.Get("/old", func(c fiber.Ctx) error { - c.Path("/new") - return c.RestartRouting() +// Accept: application/json => {"command":"eat","subject":"fruit"} +// Accept: text/plain => Eat Fruit! +// Accept: application/xml => Eat Fruit! +app.Get("/default", func(c fiber.Ctx) error { + textHandler := func(c fiber.Ctx) error { + return c.SendString("Eat Fruit!") + } + + handlers := []fiber.ResFmt{ + {"application/json", func(c fiber.Ctx) error { + return c.JSON(fiber.Map{ + "command": "eat", + "subject": "fruit", + }) + }}, + {"text/plain", textHandler}, + {"default", textHandler}, + } + + return c.Format(handlers...) }) ``` -## Route +### JSON -Returns the matched [Route](https://pkg.go.dev/github.com/gofiber/fiber?tab=doc#Route) struct. +Converts any **interface** or **string** to JSON using the [encoding/json](https://pkg.go.dev/encoding/json) package. + +:::info +JSON also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/json`. +::: ```go title="Signature" -func (c Ctx) Route() *Route +func (r *Request) JSON(data any, ctype ...string) error ``` ```go title="Example" -// http://localhost:8080/hello +type SomeStruct struct { + Name string + Age uint8 +} +app.Get("/json", func(c fiber.Ctx) error { + // Create data struct: + data := SomeStruct{ + Name: "Grame", + Age: 20, + } -app.Get("/hello/:name", func(c fiber.Ctx) error { - r := c.Route() - fmt.Println(r.Method, r.Path, r.Params, r.Handlers) - // GET /hello/:name handler [name] + return c.Res().JSON(data) + // => Content-Type: application/json + // => "{"Name": "Grame", "Age": 20}" - // ... + return c.Res().JSON(fiber.Map{ + "name": "Grame", + "age": 20, + }) + // => Content-Type: application/json + // => "{"name": "Grame", "age": 20}" + + return c.JSON(fiber.Map{ + "type": "https://example.com/probs/out-of-credit", + "title": "You do not have enough credit.", + "status": 403, + "detail": "Your current balance is 30, but that costs 50.", + "instance": "/account/12345/msgs/abc", + }, "application/problem+json") + // => Content-Type: application/problem+json + // => "{ + // => "type": "https://example.com/probs/out-of-credit", + // => "title": "You do not have enough credit.", + // => "status": 403, + // => "detail": "Your current balance is 30, but that costs 50.", + // => "instance": "/account/12345/msgs/abc", + // => }" }) ``` -:::caution -Do not rely on `c.Route()` in middlewares **before** calling `c.Next()` - `c.Route()` returns the **last executed route**. -::: - -```go title="Example" -func MyMiddleware() fiber.Handler { - return func(c fiber.Ctx) error { - beforeNext := c.Route().Path // Will be '/' - err := c.Next() - afterNext := c.Route().Path // Will be '/hello/:name' - return err - } -} -``` +### JSONP -## SaveFile +Sends a JSON response with JSONP support. This method is identical to [JSON](ctx.md#json), except that it opts-in to JSONP callback support. By default, the callback name is simply callback. -Method is used to save **any** multipart file to disk. +Override this by passing a **named string** in the method. ```go title="Signature" -func (c Ctx) SaveFile(fh *multipart.FileHeader, path string) error +func (r *Response) JSONP(data any, callback ...string) error ``` ```go title="Example" -app.Post("/", func(c fiber.Ctx) error { - // Parse the multipart form: - if form, err := c.MultipartForm(); err == nil { - // => *multipart.Form +type SomeStruct struct { + name string + age uint8 +} - // Get all files from "documents" key: - files := form.File["documents"] - // => []*multipart.FileHeader +app.Get("/", func(c fiber.Ctx) error { + // Create data struct: + data := SomeStruct{ + name: "Grame", + age: 20, + } - // Loop through files: - for _, file := range files { - fmt.Println(file.Filename, file.Size, file.Header["Content-Type"][0]) - // => "tutorial.pdf" 360641 "application/pdf" + return c.Res().JSONP(data) + // => callback({"name": "Grame", "age": 20}) - // Save the files to disk: - if err := c.SaveFile(file, fmt.Sprintf("./%s", file.Filename)); err != nil { - return err - } - } - return err - } + return c.JSONP(data, "customFunc") + // => customFunc({"name": "Grame", "age": 20}) }) ``` -## SaveFileToStorage +### Links -Method is used to save **any** multipart file to an external storage system. +Joins the links followed by the property to populate the response’s [Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) HTTP header field. ```go title="Signature" -func (c Ctx) SaveFileToStorage(fileheader *multipart.FileHeader, path string, storage Storage) error +func (r *Response) Links(link ...string) ``` ```go title="Example" -storage := memory.New() - -app.Post("/", func(c fiber.Ctx) error { - // Parse the multipart form: - if form, err := c.MultipartForm(); err == nil { - // => *multipart.Form - - // Get all files from "documents" key: - files := form.File["documents"] - // => []*multipart.FileHeader - - // Loop through files: - for _, file := range files { - fmt.Println(file.Filename, file.Size, file.Header["Content-Type"][0]) - // => "tutorial.pdf" 360641 "application/pdf" +app.Get("/", func(c fiber.Ctx) error { + c.Res().Links( + "http://api.example.com/users?page=2", "next", + "http://api.example.com/users?page=5", "last", + ) + c.Links( + "http://api.example.com/users?page=2", "next", + "http://api.example.com/users?page=5", "last", + ) + // Link: ; rel="next", + // ; rel="last" - // Save the files to storage: - if err := c.SaveFileToStorage(file, fmt.Sprintf("./%s", file.Filename), storage); err != nil { - return err - } - } - return err - } + // ... }) ``` -## Schema - -Contains the request protocol string: http or https for TLS requests. +### Location -:::info -Please use [`Config.EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) to prevent header spoofing, in case when your app is behind the proxy. -::: +Sets the response [Location](https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Location) HTTP header to the specified path parameter. ```go title="Signature" -func (c Ctx) Schema() string +func (r *Response) Location(path string) ``` ```go title="Example" -// GET http://example.com -app.Get("/", func(c fiber.Ctx) error { - c.Schema() // "http" +app.Post("/", func(c fiber.Ctx) error { + c.Res().Location("http://example.com") + c.Location("/foo/bar") - // ... + return nil }) ``` -## Secure -A boolean property that is `true` , if a **TLS** connection is established. +### Render -```go title="Signature" -func (c Ctx) Secure() bool -``` +Renders a view with data and sends a `text/html` response. By default `Render` uses the default [**Go Template engine**](https://pkg.go.dev/html/template/). If you want to use another View engine, please take a look at our [**Template middleware**](https://docs.gofiber.io/template). -```go title="Example" -// Secure() method is equivalent to: -c.Protocol() == "https" +```go title="Signature" +func (r *Response) Render(name string, bind Map, layouts ...string) error ``` -## Send +### Send Sets the HTTP response body. ```go title="Signature" -func (c Ctx) Send(body []byte) error +func (r *Response) Send(body []byte) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { + return c.Res().Send([]byte("Hello, World!")) // => "Hello, World!" + return c.Send([]byte("Hello, World!")) // => "Hello, World!" }) ``` @@ -1671,13 +1838,13 @@ Use this if you **don't need** type assertion, recommended for **faster** perfor ::: ```go title="Signature" -func (c Ctx) SendString(body string) error -func (c Ctx) SendStream(stream io.Reader, size ...int) error +func (r *Response) SendString(body string) error +func (r *Response) SendStream(stream io.Reader, size ...int) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - return c.SendString("Hello, World!") + return c.Res().SendString("Hello, World!") // => "Hello, World!" return c.SendStream(bytes.NewReader([]byte("Hello, World!"))) @@ -1685,7 +1852,7 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` -## SendFile +### SendFile Transfers the file from the given path. Sets the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) response HTTP header field based on the **filenames** extension. @@ -1694,12 +1861,12 @@ Method doesn´t use **gzipping** by default, set it to **true** to enable. ::: ```go title="Signature" title="Signature" -func (c Ctx) SendFile(file string, compress ...bool) error +func (r *Response) SendFile(file string, compress ...bool) error ``` ```go title="Example" app.Get("/not-found", func(c fiber.Ctx) error { - return c.SendFile("./public/404.html"); + return c.Res().SendFile("./public/404.html"); // Disable compression return c.SendFile("./static/index.html", false); @@ -1720,7 +1887,7 @@ app.Get("/file-with-url-chars", func(c fiber.Ctx) error { For sending files from embedded file system [this functionality](../middleware/filesystem.md#sendfile) can be used ::: -## SendStatus +### SendStatus Sets the status code and the correct status message in the body, if the response body is **empty**. @@ -1729,12 +1896,12 @@ You can find all used status codes and messages [here](https://github.com/gofibe ::: ```go title="Signature" -func (c Ctx) SendStatus(status int) error +func (r *Response) SendStatus(status int) error ``` ```go title="Example" app.Get("/not-found", func(c fiber.Ctx) error { - return c.SendStatus(415) + return c.Res().SendStatus(415) // => 415 "Unsupported Media Type" c.SendString("Hello, World!") @@ -1743,80 +1910,57 @@ app.Get("/not-found", func(c fiber.Ctx) error { }) ``` -## SendStream +### SendStream Sets response body to a stream of data and add optional body size. ```go title="Signature" -func (c Ctx) SendStream(stream io.Reader, size ...int) error +func (r *Response) SendStream(stream io.Reader, size ...int) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - return c.SendStream(bytes.NewReader([]byte("Hello, World!"))) + return c.Res().SendStream(bytes.NewReader([]byte("Hello, World!"))) // => "Hello, World!" }) ``` -## SendString +### SendString Sets the response body to a string. ```go title="Signature" -func (c Ctx) SendString(body string) error +func (r *Response) SendString(body string) error ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - return c.SendString("Hello, World!") + return c.Res().SendString("Hello, World!") // => "Hello, World!" }) ``` -## Set +### Set Sets the response’s HTTP header field to the specified `key`, `value`. ```go title="Signature" -func (c Ctx) Set(key string, val string) +func (r *Response) Set(key string, val string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.Set("Content-Type", "text/plain") - // => "Content-type: text/plain" - - // ... -}) -``` - -## SetUserContext - -Sets the user specified implementation for context interface. - -```go title="Signature" -func (c Ctx) SetUserContext(ctx context.Context) -``` + c.Res().Set("Content-Type", "text/plain") + // => "Content-Type: text/plain" -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - ctx := context.Background() - c.SetUserContext(ctx) - // Here ctx could be any context implementation + c.Set("Keep-Alive", "timeout=5") + // => "Keep-Alive: timeout=5" // ... }) ``` -## Stale - -[https://expressjs.com/en/4x/api.html\#req.stale](https://expressjs.com/en/4x/api.html#req.stale) - -```go title="Signature" -func (c Ctx) Stale() bool -``` - -## Status +### Status Sets the HTTP status for the response. @@ -1825,17 +1969,17 @@ Method is a **chainable**. ::: ```go title="Signature" -func (c Ctx) Status(status int) Ctx +func (r *Response) Status(status int) Ctx ``` ```go title="Example" app.Get("/fiber", func(c fiber.Ctx) error { - c.Status(fiber.StatusOK) + c.Res().Status(fiber.StatusOK) return nil } app.Get("/hello", func(c fiber.Ctx) error { - return c.Status(fiber.StatusBadRequest).SendString("Bad Request") + return c.Res().Status(fiber.StatusBadRequest).SendString("Bad Request") } app.Get("/world", func(c fiber.Ctx) error { @@ -1843,44 +1987,7 @@ app.Get("/world", func(c fiber.Ctx) error { }) ``` -## String - -Returns unique string representation of the ctx. - -```go title="Signature" -func (c Ctx) String() string -``` - -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - c.String() // => "#0000000100000001 - 127.0.0.1:3000 <-> 127.0.0.1:61516 - GET http://localhost:3000/" - - // ... -}) -``` - -## Subdomains - -Returns a string slice of subdomains in the domain name of the request. - -The application property subdomain offset, which defaults to `2`, is used for determining the beginning of the subdomain segments. - -```go title="Signature" -func (c Ctx) Subdomains(offset ...int) []string -``` - -```go title="Example" -// Host: "tobi.ferrets.example.com" - -app.Get("/", func(c fiber.Ctx) error { - c.Subdomains() // ["ferrets", "tobi"] - c.Subdomains(1) // ["tobi"] - - // ... -}) -``` - -## Type +### Type Sets the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) HTTP header to the MIME type listed [here](https://github.com/nginx/nginx/blob/master/conf/mime.types) specified by the file **extension**. @@ -1889,40 +1996,21 @@ Method is a **chainable**. ::: ```go title="Signature" -func (c Ctx) Type(ext string, charset ...string) Ctx +func (r *Response) Type(ext string, charset ...string) Ctx ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.Type(".html") // => "text/html" - c.Type("html") // => "text/html" - c.Type("png") // => "image/png" - + c.Res().Type(".html") // => "text/html" + c.Res().Type("html") // => "text/html" + c.Type("png") // => "image/png" c.Type("json", "utf-8") // => "application/json; charset=utf-8" // ... }) ``` -## UserContext - -UserContext returns a context implementation that was set by user earlier -or returns a non-nil, empty context, if it was not set earlier. - -```go title="Signature" -func (c Ctx) UserContext() context.Context -``` - -```go title="Example" -app.Get("/", func(c fiber.Ctx) error { - ctx := c.UserContext() - // ctx is context implementation set by user - - // ... -}) -``` - -## Vary +### Vary Adds the given header field to the [Vary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) response header. This will append the header, if not already listed, otherwise leaves it listed in the current location. @@ -1931,113 +2019,95 @@ Multiple fields are **allowed**. ::: ```go title="Signature" -func (c Ctx) Vary(fields ...string) +func (r *Response) Vary(fields ...string) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.Vary("Origin") // => Vary: Origin - c.Vary("User-Agent") // => Vary: Origin, User-Agent + c.Res().Vary("Origin") // => Vary: Origin + c.Vary("User-Agent") // => Vary: Origin, User-Agent // No duplicates - c.Vary("Origin") // => Vary: Origin, User-Agent + c.Res().Vary("Origin") // => Vary: Origin, User-Agent - c.Vary("Accept-Encoding", "Accept") + c.Res().Vary("Accept-Encoding", "Accept") // => Vary: Origin, User-Agent, Accept-Encoding, Accept // ... }) ``` -## ViewBind +### ViewBind Add vars to default view var map binding to template engine. Variables are read by the Render method and may be overwritten. ```go title="Signature" -func (c Ctx) ViewBind(vars Map) error +func (r *Response) ViewBind(vars Map) error ``` ```go title="Example" app.Use(func(c fiber.Ctx) error { - c.ViewBind(fiber.Map{ + c.Res().ViewBind(fiber.Map{ "Title": "Hello, World!", }) }) app.Get("/", func(c fiber.Ctx) error { - return c.Render("xxx.tmpl", fiber.Map{}) // Render will use Title variable + return c.Res().Render("xxx.tmpl", fiber.Map{}) // Render will use Title variable }) ``` -## Write +### Write Write adopts the Writer interface ```go title="Signature" -func (c Ctx) Write(p []byte) (n int, err error) +func (r *Response) Write(p []byte) (n int, err error) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.Write([]byte("Hello, World!")) // => "Hello, World!" + c.Res().Write([]byte("Hello, World!")) // => "Hello, World!" fmt.Fprintf(c, "%s\n", "Hello, World!") // "Hello, World!Hello, World!" }) ``` -## Writef +### Writef Writef adopts the string with variables ```go title="Signature" -func (c Ctx) Writef(f string, a ...any) (n int, err error) +func (r *Response) Writef(f string, a ...any) (n int, err error) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { world := "World!" - c.Writef("Hello, %s", world) // => "Hello, World!" + c.Res().Writef("Hello, %s", world) // => "Hello, World!" fmt.Fprintf(c, "%s\n", "Hello, World!") // "Hello, World!Hello, World!" }) ``` -## WriteString +### WriteString WriteString adopts the string ```go title="Signature" -func (c Ctx) WriteString(s string) (n int, err error) +func (r *Response) WriteString(s string) (n int, err error) ``` ```go title="Example" app.Get("/", func(c fiber.Ctx) error { - c.WriteString("Hello, World!") // => "Hello, World!" + c.Res().WriteString("Hello, World!") // => "Hello, World!" fmt.Fprintf(c, "%s\n", "Hello, World!") // "Hello, World!Hello, World!" }) ``` -## XHR - -A Boolean property, that is `true`, if the request’s [X-Requested-With](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) header field is [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest), indicating that the request was issued by a client library \(such as [jQuery](https://api.jquery.com/jQuery.ajax/)\). - -```go title="Signature" -func (c Ctx) XHR() bool -``` - -```go title="Example" -// X-Requested-With: XMLHttpRequest - -app.Get("/", func(c fiber.Ctx) error { - c.XHR() // true - - // ... -}) -``` - -## XML +### XML Converts any **interface** or **string** to XML using the standard `encoding/xml` package. @@ -2046,7 +2116,7 @@ XML also sets the content header to **application/xml**. ::: ```go title="Signature" -func (c Ctx) XML(data any) error +func (r *Response) XML(data any) error ``` ```go title="Example" @@ -2063,7 +2133,7 @@ app.Get("/", func(c fiber.Ctx) error { Age: 20, } - return c.XML(data) + return c.Res().XML(data) // c.XML also works // // Grame // 20 diff --git a/middleware/adaptor/adaptor.go b/middleware/adaptor/adaptor.go index 82059cf158..e2a1cfcacd 100644 --- a/middleware/adaptor/adaptor.go +++ b/middleware/adaptor/adaptor.go @@ -84,13 +84,13 @@ func HTTPMiddleware(mw func(http.Handler) http.Handler) fiber.Handler { nextHandler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { next = true // Convert again in case request may modify by middleware - c.Request().Header.SetMethod(r.Method) - c.Request().SetRequestURI(r.RequestURI) - c.Request().SetHost(r.Host) - c.Request().Header.SetHost(r.Host) + c.Context().Request.Header.SetMethod(r.Method) + c.Context().Request.SetRequestURI(r.RequestURI) + c.Context().Request.SetHost(r.Host) + c.Context().Request.Header.SetHost(r.Host) for key, val := range r.Header { for _, v := range val { - c.Request().Header.Set(key, v) + c.Context().Request.Header.Set(key, v) } } CopyContextToFiberContext(r.Context(), c.Context()) diff --git a/middleware/adaptor/adaptor_test.go b/middleware/adaptor/adaptor_test.go index a14ea60669..a7dd8f722d 100644 --- a/middleware/adaptor/adaptor_test.go +++ b/middleware/adaptor/adaptor_test.go @@ -242,7 +242,7 @@ func testFiberToHandlerFunc(t *testing.T, checkDefaultPort bool, app ...*fiber.A require.Equal(t, expectedRequestURI, string(c.Context().RequestURI()), "RequestURI") require.Equal(t, expectedContentLength, c.Context().Request.Header.ContentLength(), "ContentLength") require.Equal(t, expectedHost, c.Hostname(), "Host") - require.Equal(t, expectedHost, string(c.Request().Header.Host()), "Host") + require.Equal(t, expectedHost, c.Get(fiber.HeaderHost), "Host") require.Equal(t, "http://"+expectedHost, c.BaseURL(), "BaseURL") require.Equal(t, expectedRemoteAddr, c.Context().RemoteAddr().String(), "RemoteAddr") diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index 0ace704e35..adec84e629 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -135,14 +135,14 @@ func New(config ...Config) fiber.Handler { e.body = manager.getRaw(key + "_body") } // Set response headers from cache - c.Response().SetBodyRaw(e.body) - c.Response().SetStatusCode(e.status) - c.Response().Header.SetContentTypeBytes(e.ctype) + c.Context().Response.SetBodyRaw(e.body) + c.Context().Response.SetStatusCode(e.status) + c.Context().Response.Header.SetContentTypeBytes(e.ctype) if len(e.cencoding) > 0 { - c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding) + c.Context().Response.Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding) } for k, v := range e.headers { - c.Response().Header.SetBytesV(k, v) + c.Context().Response.Header.SetBytesV(k, v) } // Set Cache-Control header if enabled if cfg.CacheControl { @@ -177,7 +177,7 @@ func New(config ...Config) fiber.Handler { } // Don't try to cache if body won't fit into cache - bodySize := uint(len(c.Response().Body())) + bodySize := uint(len(c.Context().Response.Body())) if cfg.MaxBytes > 0 && bodySize > cfg.MaxBytes { c.Set(cfg.CacheHeader, cacheUnreachable) return nil @@ -193,16 +193,16 @@ func New(config ...Config) fiber.Handler { } // Cache response - e.body = utils.CopyBytes(c.Response().Body()) - e.status = c.Response().StatusCode() - e.ctype = utils.CopyBytes(c.Response().Header.ContentType()) - e.cencoding = utils.CopyBytes(c.Response().Header.Peek(fiber.HeaderContentEncoding)) + e.body = utils.CopyBytes(c.Context().Response.Body()) + e.status = c.Context().Response.StatusCode() + e.ctype = utils.CopyBytes(c.Context().Response.Header.ContentType()) + e.cencoding = utils.CopyBytes(c.Context().Response.Header.Peek(fiber.HeaderContentEncoding)) // Store all response headers // (more: https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1) if cfg.StoreResponseHeaders { e.headers = make(map[string][]byte) - c.Response().Header.VisitAll( + c.Context().Response.Header.VisitAll( func(key, value []byte) { // create real copy keyS := string(key) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 8966ec7ac2..1c122c5060 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -442,7 +442,7 @@ func Test_Cache_CustomNext(t *testing.T) { app.Use(New(Config{ Next: func(c fiber.Ctx) bool { - return c.Response().StatusCode() != fiber.StatusOK + return c.Context().Response.StatusCode() != fiber.StatusOK }, CacheControl: true, })) @@ -509,7 +509,7 @@ func Test_CustomExpiration(t *testing.T) { }})) app.Get("/", func(c fiber.Ctx) error { - c.Response().Header.Add("Cache-Time", "1") + c.Set("Cache-Time", "1") return c.SendString(strconv.FormatInt(time.Now().UnixNano(), 10)) }) @@ -553,7 +553,7 @@ func Test_AdditionalE2EResponseHeaders(t *testing.T) { })) app.Get("/", func(c fiber.Ctx) error { - c.Response().Header.Add("X-Foobar", "foobar") + c.Set("X-Foobar", "foobar") return c.SendString("hi") }) @@ -576,7 +576,7 @@ func Test_CacheHeader(t *testing.T) { app.Use(New(Config{ Expiration: 10 * time.Second, Next: func(c fiber.Ctx) bool { - return c.Response().StatusCode() != fiber.StatusOK + return c.Context().Response.StatusCode() != fiber.StatusOK }, })) @@ -843,7 +843,7 @@ func Benchmark_Cache_AdditionalHeaders(b *testing.B) { })) app.Get("/demo", func(c fiber.Ctx) error { - c.Response().Header.Add("X-Foobar", "foobar") + c.Set("X-Foobar", "foobar") return c.SendStatus(418) }) diff --git a/middleware/csrf/csrf_test.go b/middleware/csrf/csrf_test.go index 70f7f032ee..4efe1a13fa 100644 --- a/middleware/csrf/csrf_test.go +++ b/middleware/csrf/csrf_test.go @@ -88,7 +88,7 @@ func Test_CSRF_WithSession(t *testing.T) { // the session string is no longer be 123 newSessionIDString := sess.ID() - app.AcquireCtx(ctx).Request().Header.SetCookie("_session", newSessionIDString) + app.AcquireCtx(ctx).Context().Request.Header.SetCookie("_session", newSessionIDString) // middleware config config := Config{ @@ -221,7 +221,7 @@ func Test_CSRF_ExpiredToken_WithSession(t *testing.T) { // get session id newSessionIDString := sess.ID() - app.AcquireCtx(ctx).Request().Header.SetCookie("_session", newSessionIDString) + app.AcquireCtx(ctx).Context().Request.Header.SetCookie("_session", newSessionIDString) // middleware config config := Config{ @@ -1089,7 +1089,7 @@ func Test_CSRF_DeleteToken_WithSession(t *testing.T) { // the session string is no longer be 123 newSessionIDString := sess.ID() - app.AcquireCtx(ctx).Request().Header.SetCookie("_session", newSessionIDString) + app.AcquireCtx(ctx).Context().Request.Header.SetCookie("_session", newSessionIDString) // middleware config config := Config{ diff --git a/middleware/encryptcookie/encryptcookie.go b/middleware/encryptcookie/encryptcookie.go index 5faffcc69d..ba14208e8b 100644 --- a/middleware/encryptcookie/encryptcookie.go +++ b/middleware/encryptcookie/encryptcookie.go @@ -18,14 +18,14 @@ func New(config ...Config) fiber.Handler { } // Decrypt request cookies - c.Request().Header.VisitAllCookie(func(key, value []byte) { + c.Context().Request.Header.VisitAllCookie(func(key, value []byte) { keyString := string(key) if !isDisabled(keyString, cfg.Except) { decryptedValue, err := cfg.Decryptor(string(value), cfg.Key) if err != nil { - c.Request().Header.SetCookieBytesKV(key, nil) + c.Context().Request.Header.SetCookieBytesKV(key, nil) } else { - c.Request().Header.SetCookie(string(key), decryptedValue) + c.Context().Request.Header.SetCookie(string(key), decryptedValue) } } }) @@ -34,19 +34,19 @@ func New(config ...Config) fiber.Handler { err := c.Next() // Encrypt response cookies - c.Response().Header.VisitAllCookie(func(key, _ []byte) { + c.Context().Response.Header.VisitAllCookie(func(key, _ []byte) { keyString := string(key) if !isDisabled(keyString, cfg.Except) { cookieValue := fasthttp.Cookie{} cookieValue.SetKeyBytes(key) - if c.Response().Header.Cookie(&cookieValue) { + if c.Context().Response.Header.Cookie(&cookieValue) { encryptedValue, err := cfg.Encryptor(string(cookieValue.Value()), cfg.Key) if err != nil { panic(err) } cookieValue.SetValue(encryptedValue) - c.Response().Header.SetCookie(&cookieValue) + c.Context().Response.Header.SetCookie(&cookieValue) } } }) diff --git a/middleware/etag/etag.go b/middleware/etag/etag.go index 2a2c0ae161..5b2d54acfa 100644 --- a/middleware/etag/etag.go +++ b/middleware/etag/etag.go @@ -34,16 +34,16 @@ func New(config ...Config) fiber.Handler { } // Don't generate ETags for invalid responses - if c.Response().StatusCode() != fiber.StatusOK { + if c.Context().Response.StatusCode() != fiber.StatusOK { return nil } - body := c.Response().Body() + body := c.Context().Response.Body() // Skips ETag if no response body is present if len(body) == 0 { return nil } // Skip ETag if header is already present - if c.Response().Header.PeekBytes(normalizedHeaderETag) != nil { + if c.Context().Response.Header.PeekBytes(normalizedHeaderETag) != nil { return nil } @@ -65,7 +65,7 @@ func New(config ...Config) fiber.Handler { etag := bb.Bytes() // Get ETag header from request - clientEtag := c.Request().Header.Peek(fiber.HeaderIfNoneMatch) + clientEtag := c.Context().Request.Header.Peek(fiber.HeaderIfNoneMatch) // Check if client's ETag is weak if bytes.HasPrefix(clientEtag, weakPrefix) { @@ -77,7 +77,7 @@ func New(config ...Config) fiber.Handler { return c.SendStatus(fiber.StatusNotModified) } // W/1 != W/2 || W/1 != 2 - c.Response().Header.SetCanonical(normalizedHeaderETag, etag) + c.Context().Response.Header.SetCanonical(normalizedHeaderETag, etag) return nil } @@ -89,7 +89,7 @@ func New(config ...Config) fiber.Handler { return c.SendStatus(fiber.StatusNotModified) } // 1 != 2 - c.Response().Header.SetCanonical(normalizedHeaderETag, etag) + c.Context().Response.Header.SetCanonical(normalizedHeaderETag, etag) return nil } diff --git a/middleware/etag/etag_test.go b/middleware/etag/etag_test.go index 9769639cb1..74c6ac45d4 100644 --- a/middleware/etag/etag_test.go +++ b/middleware/etag/etag_test.go @@ -1,7 +1,6 @@ package etag import ( - "bytes" "io" "net/http/httptest" "testing" @@ -208,7 +207,7 @@ func testETagCustomEtag(t *testing.T, headerIfNoneMatch, matched bool) { //nolin app.Get("/", func(c fiber.Ctx) error { c.Set(fiber.HeaderETag, `"custom"`) - if bytes.Equal(c.Request().Header.Peek(fiber.HeaderIfNoneMatch), []byte(`"custom"`)) { + if c.Get(fiber.HeaderIfNoneMatch) == `"custom"` { return c.SendStatus(fiber.StatusNotModified) } return c.SendString("Hello, World!") @@ -249,7 +248,7 @@ func Test_ETag_CustomEtagPut(t *testing.T) { app.Put("/", func(c fiber.Ctx) error { c.Set(fiber.HeaderETag, `"custom"`) - if !bytes.Equal(c.Request().Header.Peek(fiber.HeaderIfMatch), []byte(`"custom"`)) { + if c.Get(fiber.HeaderIfMatch) != `"custom"` { return c.SendStatus(fiber.StatusPreconditionFailed) } return c.SendString("Hello, World!") diff --git a/middleware/filesystem/filesystem.go b/middleware/filesystem/filesystem.go index 62d4f4f6bb..883cdb0ad1 100644 --- a/middleware/filesystem/filesystem.go +++ b/middleware/filesystem/filesystem.go @@ -230,14 +230,14 @@ func New(config ...Config) fiber.Handler { if cfg.MaxAge > 0 { c.Set(fiber.HeaderCacheControl, cacheControlStr) } - c.Response().SetBodyStream(file, contentLength) + c.Context().Response.SetBodyStream(file, contentLength) return nil } if method == fiber.MethodHead { - c.Request().ResetBody() + c.Context().Request.ResetBody() // Fasthttp should skipbody by default if HEAD? - c.Response().SkipBody = true - c.Response().Header.SetContentLength(contentLength) + c.Context().Response.SkipBody = true + c.Context().Response.Header.SetContentLength(contentLength) if err := file.Close(); err != nil { return fmt.Errorf("failed to close: %w", err) } @@ -305,14 +305,14 @@ func SendFile(c fiber.Ctx, filesystem fs.FS, path string) error { method := c.Method() if method == fiber.MethodGet { - c.Response().SetBodyStream(file, contentLength) + c.Context().Response.SetBodyStream(file, contentLength) return nil } if method == fiber.MethodHead { - c.Request().ResetBody() + c.Context().Request.ResetBody() // Fasthttp should skipbody by default if HEAD? - c.Response().SkipBody = true - c.Response().Header.SetContentLength(contentLength) + c.Context().Response.SkipBody = true + c.Context().Response.Header.SetContentLength(contentLength) if err := file.Close(); err != nil { return fmt.Errorf("failed to close: %w", err) } diff --git a/middleware/idempotency/idempotency.go b/middleware/idempotency/idempotency.go index 923ce5ce9a..a24bde363e 100644 --- a/middleware/idempotency/idempotency.go +++ b/middleware/idempotency/idempotency.go @@ -117,9 +117,9 @@ func New(config ...Config) fiber.Handler { // Construct response res := &response{ - StatusCode: c.Response().StatusCode(), + StatusCode: c.Context().Response.StatusCode(), - Body: utils.CopyBytes(c.Response().Body()), + Body: utils.CopyBytes(c.Context().Response.Body()), } { headers := make(map[string][]string) diff --git a/middleware/limiter/limiter_fixed.go b/middleware/limiter/limiter_fixed.go index 1e2a1aa0e5..1ec0dad8c5 100644 --- a/middleware/limiter/limiter_fixed.go +++ b/middleware/limiter/limiter_fixed.go @@ -83,8 +83,8 @@ func (FixedWindow) New(cfg Config) fiber.Handler { err := c.Next() // Check for SkipFailedRequests and SkipSuccessfulRequests - if (cfg.SkipSuccessfulRequests && c.Response().StatusCode() < fiber.StatusBadRequest) || - (cfg.SkipFailedRequests && c.Response().StatusCode() >= fiber.StatusBadRequest) { + if (cfg.SkipSuccessfulRequests && c.Context().Response.StatusCode() < fiber.StatusBadRequest) || + (cfg.SkipFailedRequests && c.Context().Response.StatusCode() >= fiber.StatusBadRequest) { // Lock entry mux.Lock() e = manager.get(key) diff --git a/middleware/limiter/limiter_sliding.go b/middleware/limiter/limiter_sliding.go index a98593476e..091fef6d64 100644 --- a/middleware/limiter/limiter_sliding.go +++ b/middleware/limiter/limiter_sliding.go @@ -114,8 +114,8 @@ func (SlidingWindow) New(cfg Config) fiber.Handler { err := c.Next() // Check for SkipFailedRequests and SkipSuccessfulRequests - if (cfg.SkipSuccessfulRequests && c.Response().StatusCode() < fiber.StatusBadRequest) || - (cfg.SkipFailedRequests && c.Response().StatusCode() >= fiber.StatusBadRequest) { + if (cfg.SkipSuccessfulRequests && c.Context().Response.StatusCode() < fiber.StatusBadRequest) || + (cfg.SkipFailedRequests && c.Context().Response.StatusCode() >= fiber.StatusBadRequest) { // Lock entry mux.Lock() e = manager.get(key) diff --git a/middleware/logger/default_logger.go b/middleware/logger/default_logger.go index 5744fd1a5f..40cabde118 100644 --- a/middleware/logger/default_logger.go +++ b/middleware/logger/default_logger.go @@ -33,7 +33,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error { buf.WriteString( fmt.Sprintf("%s |%s %3d %s| %13v | %15s |%s %-7s %s| %-"+data.ErrPaddingStr+"s %s\n", data.Timestamp.Load().(string), //nolint:forcetypeassert // Timestamp is always a string - statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset, + statusColor(c.Context().Response.StatusCode(), colors), c.Context().Response.StatusCode(), colors.Reset, data.Stop.Sub(data.Start), c.IP(), methodColor(c.Method(), colors), c.Method(), colors.Reset, @@ -66,7 +66,7 @@ func defaultLoggerInstance(c fiber.Ctx, data *Data, cfg Config) error { buf.WriteString(" | ") // Status Code with 3 fixed width, right aligned - fixedWidth(strconv.Itoa(c.Response().StatusCode()), 3, true) + fixedWidth(strconv.Itoa(c.Context().Response.StatusCode()), 3, true) buf.WriteString(" | ") // Duration with 13 fixed width, right aligned diff --git a/middleware/logger/logger_test.go b/middleware/logger/logger_test.go index 0aa517bcf5..fea5359d55 100644 --- a/middleware/logger/logger_test.go +++ b/middleware/logger/logger_test.go @@ -115,7 +115,7 @@ func Test_Logger_Done(t *testing.T) { app.Use(New(Config{ Done: func(c fiber.Ctx, logString []byte) { - if c.Response().StatusCode() == fiber.StatusOK { + if c.Context().Response.StatusCode() == fiber.StatusOK { _, err := buf.Write(logString) require.NoError(t, err) } diff --git a/middleware/logger/tags.go b/middleware/logger/tags.go index 8d147fd05d..fc6b315f12 100644 --- a/middleware/logger/tags.go +++ b/middleware/logger/tags.go @@ -87,19 +87,19 @@ func createTagMap(cfg *Config) map[string]LogFunc { return output.Write(c.Body()) }, TagBytesReceived: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { - return appendInt(output, len(c.Request().Body())) + return appendInt(output, len(c.Req().Body())) }, TagBytesSent: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { - if c.Response().Header.ContentLength() < 0 { + if c.Context().Response.Header.ContentLength() < 0 { return appendInt(output, 0) } - return appendInt(output, len(c.Response().Body())) + return appendInt(output, len(c.Context().Response.Body())) }, TagRoute: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { return output.WriteString(c.Route().Path) }, TagResBody: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { - return output.Write(c.Response().Body()) + return output.Write(c.Context().Response.Body()) }, TagReqHeaders: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { out := make(map[string][]string, 0) @@ -114,7 +114,7 @@ func createTagMap(cfg *Config) map[string]LogFunc { return output.Write([]byte(strings.Join(reqHeaders, "&"))) }, TagQueryStringParams: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { - return output.WriteString(c.Request().URI().QueryArgs().String()) + return output.WriteString(c.Context().URI().QueryArgs().String()) }, TagBlack: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { @@ -184,9 +184,9 @@ func createTagMap(cfg *Config) map[string]LogFunc { TagStatus: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { if cfg.enableColors { colors := c.App().Config().ColorScheme - return output.WriteString(fmt.Sprintf("%s%3d%s", statusColor(c.Response().StatusCode(), colors), c.Response().StatusCode(), colors.Reset)) + return output.WriteString(fmt.Sprintf("%s%3d%s", statusColor(c.Context().Response.StatusCode(), colors), c.Context().Response.StatusCode(), colors.Reset)) } - return appendInt(output, c.Response().StatusCode()) + return appendInt(output, c.Context().Response.StatusCode()) }, TagMethod: func(output Buffer, c fiber.Ctx, _ *Data, _ string) (int, error) { if cfg.enableColors { diff --git a/middleware/proxy/proxy.go b/middleware/proxy/proxy.go index 9dad5d5523..2145f908a6 100644 --- a/middleware/proxy/proxy.go +++ b/middleware/proxy/proxy.go @@ -64,8 +64,8 @@ func Balancer(config Config) fiber.Handler { } // Set request and response - req := c.Request() - res := c.Response() + req := &c.Context().Request + res := &c.Context().Response // Don't proxy "Connection" header req.Header.Del(fiber.HeaderConnection) @@ -172,8 +172,8 @@ func doAction( lock.RUnlock() } - req := c.Request() - res := c.Response() + req := &c.Context().Request + res := &c.Context().Response originalURL := utils.CopyString(c.OriginalURL()) defer req.SetRequestURI(originalURL) @@ -205,7 +205,7 @@ func getScheme(uri []byte) []byte { // This method will return an fiber.Handler func DomainForward(hostname, addr string, clients ...*fasthttp.Client) fiber.Handler { return func(c fiber.Ctx) error { - host := string(c.Request().Host()) + host := string(c.Context().Host()) if host == hostname { return Do(c, addr+c.OriginalURL(), clients...) } @@ -246,7 +246,7 @@ func BalancerForward(servers []string, clients ...*fasthttp.Client) fiber.Handle if !strings.HasPrefix(server, "http") { server = "http://" + server } - c.Request().Header.Add("X-Real-IP", c.IP()) + c.Context().Request.Header.Add("X-Real-IP", c.IP()) return Do(c, server+c.OriginalURL(), clients...) } } diff --git a/middleware/proxy/proxy_test.go b/middleware/proxy/proxy_test.go index 713488a52b..78e43553d7 100644 --- a/middleware/proxy/proxy_test.go +++ b/middleware/proxy/proxy_test.go @@ -343,7 +343,7 @@ func Test_Proxy_Modify_Response(t *testing.T) { app.Use(Balancer(Config{ Servers: []string{addr}, ModifyResponse: func(c fiber.Ctx) error { - c.Response().SetStatusCode(fiber.StatusOK) + c.Res().Status(fiber.StatusOK) return c.SendString("modified response") }, })) @@ -362,7 +362,7 @@ func Test_Proxy_Modify_Request(t *testing.T) { t.Parallel() _, addr := createProxyTestServerIPv4(t, func(c fiber.Ctx) error { - b := c.Request().Body() + b := c.Req().Body() return c.SendString(string(b)) }) @@ -370,7 +370,7 @@ func Test_Proxy_Modify_Request(t *testing.T) { app.Use(Balancer(Config{ Servers: []string{addr}, ModifyRequest: func(c fiber.Ctx) error { - c.Request().SetBody([]byte("modified request")) + c.Context().Request.SetBody([]byte("modified request")) return nil }, })) @@ -645,7 +645,7 @@ func Test_Proxy_Do_HTTP_Prefix_URL(t *testing.T) { if err := Do(c, url); err != nil { return err } - c.Response().Header.Del(fiber.HeaderServer) + c.Context().Response.Header.Del(fiber.HeaderServer) return nil }) diff --git a/middleware/session/session.go b/middleware/session/session.go index c257343968..921d2816f1 100644 --- a/middleware/session/session.go +++ b/middleware/session/session.go @@ -215,8 +215,8 @@ func (s *Session) SetExpiry(exp time.Duration) { func (s *Session) setSession() { if s.config.source == SourceHeader { - s.ctx.Request().Header.SetBytesV(s.config.sessionName, []byte(s.id)) - s.ctx.Response().Header.SetBytesV(s.config.sessionName, []byte(s.id)) + s.ctx.Context().Request.Header.SetBytesV(s.config.sessionName, []byte(s.id)) + s.ctx.Context().Response.Header.SetBytesV(s.config.sessionName, []byte(s.id)) } else { fcookie := fasthttp.AcquireCookie() fcookie.SetKey(s.config.sessionName) @@ -240,18 +240,18 @@ func (s *Session) setSession() { default: fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode) } - s.ctx.Response().Header.SetCookie(fcookie) + s.ctx.Context().Response.Header.SetCookie(fcookie) fasthttp.ReleaseCookie(fcookie) } } func (s *Session) delSession() { if s.config.source == SourceHeader { - s.ctx.Request().Header.Del(s.config.sessionName) - s.ctx.Response().Header.Del(s.config.sessionName) + s.ctx.Context().Request.Header.Del(s.config.sessionName) + s.ctx.Context().Response.Header.Del(s.config.sessionName) } else { - s.ctx.Request().Header.DelCookie(s.config.sessionName) - s.ctx.Response().Header.DelCookie(s.config.sessionName) + s.ctx.Context().Request.Header.DelCookie(s.config.sessionName) + s.ctx.Context().Response.Header.DelCookie(s.config.sessionName) fcookie := fasthttp.AcquireCookie() fcookie.SetKey(s.config.sessionName) @@ -271,7 +271,7 @@ func (s *Session) delSession() { fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode) } - s.ctx.Response().Header.SetCookie(fcookie) + s.ctx.Context().Response.Header.SetCookie(fcookie) fasthttp.ReleaseCookie(fcookie) } } diff --git a/middleware/session/session_test.go b/middleware/session/session_test.go index 02bd52d4e2..30ac870015 100644 --- a/middleware/session/session_test.go +++ b/middleware/session/session_test.go @@ -25,7 +25,7 @@ func Test_Session(t *testing.T) { defer app.ReleaseCtx(ctx) // set session - ctx.Request().Header.SetCookie(store.sessionName, "123") + ctx.Context().Request.Header.SetCookie(store.sessionName, "123") // get session sess, err := store.Get(ctx) @@ -86,7 +86,7 @@ func Test_Session(t *testing.T) { defer app.ReleaseCtx(ctx) // request the server with the old session - ctx.Request().Header.SetCookie(store.sessionName, id) + ctx.Context().Request.Header.SetCookie(store.sessionName, id) sess, err = store.Get(ctx) require.NoError(t, err) require.False(t, sess.Fresh()) @@ -108,7 +108,7 @@ func Test_Session_Types(t *testing.T) { defer app.ReleaseCtx(ctx) // set cookie - ctx.Request().Header.SetCookie(store.sessionName, "123") + ctx.Context().Request.Header.SetCookie(store.sessionName, "123") // get session sess, err := store.Get(ctx) @@ -117,7 +117,7 @@ func Test_Session_Types(t *testing.T) { // the session string is no longer be 123 newSessionIDString := sess.ID() - ctx.Request().Header.SetCookie(store.sessionName, newSessionIDString) + ctx.Context().Request.Header.SetCookie(store.sessionName, newSessionIDString) type User struct { Name string @@ -279,7 +279,7 @@ func Test_Session_Store_Reset(t *testing.T) { require.True(t, sess.Fresh()) // set value & save sess.Set("hello", "world") - ctx.Request().Header.SetCookie(store.sessionName, sess.ID()) + ctx.Context().Request.Header.SetCookie(store.sessionName, sess.ID()) require.NoError(t, sess.Save()) // reset store @@ -337,8 +337,8 @@ func Test_Session_Save(t *testing.T) { // save session err = sess.Save() require.NoError(t, err) - require.Equal(t, store.getSessionID(ctx), string(ctx.Response().Header.Peek(store.sessionName))) - require.Equal(t, store.getSessionID(ctx), string(ctx.Request().Header.Peek(store.sessionName))) + require.Equal(t, store.getSessionID(ctx), ctx.Res().Get(store.sessionName)) + require.Equal(t, store.getSessionID(ctx), ctx.Req().Get(store.sessionName)) }) } @@ -434,8 +434,8 @@ func Test_Session_Destroy(t *testing.T) { err = sess.Destroy() require.NoError(t, err) - require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName))) - require.Equal(t, "", string(ctx.Request().Header.Peek(store.sessionName))) + require.Equal(t, "", ctx.Res().Get(store.sessionName)) + require.Equal(t, "", ctx.Req().Get(store.sessionName)) }) } @@ -468,7 +468,7 @@ func Test_Session_Cookie(t *testing.T) { require.NoError(t, sess.Save()) // cookie should be set on Save ( even if empty data ) - require.Len(t, ctx.Response().Header.PeekCookie(store.sessionName), 84) + require.Len(t, ctx.Context().Response.Header.PeekCookie(store.sessionName), 84) } // go test -run Test_Session_Cookie_In_Response @@ -509,7 +509,7 @@ func Test_Session_Deletes_Single_Key(t *testing.T) { sess, err := store.Get(ctx) require.NoError(t, err) - ctx.Request().Header.SetCookie(store.sessionName, sess.ID()) + ctx.Context().Request.Header.SetCookie(store.sessionName, sess.ID()) sess.Set("id", "1") require.NoError(t, sess.Save()) @@ -556,7 +556,7 @@ func Test_Session_Reset(t *testing.T) { require.NoError(t, err) // set cookie - ctx.Request().Header.SetCookie(store.sessionName, originalSessionUUIDString) + ctx.Context().Request.Header.SetCookie(store.sessionName, originalSessionUUIDString) // as the session is in the storage, session.fresh should be false acquiredSession, err := store.Get(ctx) @@ -585,8 +585,8 @@ func Test_Session_Reset(t *testing.T) { require.NoError(t, err) // Check that the session id is not in the header or cookie anymore - require.Equal(t, "", string(ctx.Response().Header.Peek(store.sessionName))) - require.Equal(t, "", string(ctx.Request().Header.Peek(store.sessionName))) + require.Equal(t, "", ctx.Res().Get(store.sessionName)) + require.Equal(t, "", ctx.Req().Get(store.sessionName)) }) } @@ -616,7 +616,7 @@ func Test_Session_Regenerate(t *testing.T) { require.NoError(t, err) // set cookie - ctx.Request().Header.SetCookie(store.sessionName, originalSessionUUIDString) + ctx.Context().Request.Header.SetCookie(store.sessionName, originalSessionUUIDString) // as the session is in the storage, session.fresh should be false acquiredSession, err := store.Get(ctx) @@ -639,7 +639,7 @@ func Benchmark_Session(b *testing.B) { app, store := fiber.New(), New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - c.Request().Header.SetCookie(store.sessionName, "12356789") + c.Context().Request.Header.SetCookie(store.sessionName, "12356789") b.ReportAllocs() b.ResetTimer() @@ -657,7 +657,7 @@ func Benchmark_Session(b *testing.B) { }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - c.Request().Header.SetCookie(store.sessionName, "12356789") + c.Context().Request.Header.SetCookie(store.sessionName, "12356789") b.ReportAllocs() b.ResetTimer() @@ -678,7 +678,7 @@ func Benchmark_Session_Parallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.SetCookie(store.sessionName, "12356789") + c.Context().Request.Header.SetCookie(store.sessionName, "12356789") sess, _ := store.Get(c) //nolint:errcheck // We're inside a benchmark sess.Set("john", "doe") @@ -698,7 +698,7 @@ func Benchmark_Session_Parallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.SetCookie(store.sessionName, "12356789") + c.Context().Request.Header.SetCookie(store.sessionName, "12356789") sess, _ := store.Get(c) //nolint:errcheck // We're inside a benchmark sess.Set("john", "doe") @@ -715,7 +715,7 @@ func Benchmark_Session_Asserted(b *testing.B) { app, store := fiber.New(), New() c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - c.Request().Header.SetCookie(store.sessionName, "12356789") + c.Context().Request.Header.SetCookie(store.sessionName, "12356789") b.ReportAllocs() b.ResetTimer() @@ -735,7 +735,7 @@ func Benchmark_Session_Asserted(b *testing.B) { }) c := app.AcquireCtx(&fasthttp.RequestCtx{}) defer app.ReleaseCtx(c) - c.Request().Header.SetCookie(store.sessionName, "12356789") + c.Context().Request.Header.SetCookie(store.sessionName, "12356789") b.ReportAllocs() b.ResetTimer() @@ -758,7 +758,7 @@ func Benchmark_Session_Asserted_Parallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.SetCookie(store.sessionName, "12356789") + c.Context().Request.Header.SetCookie(store.sessionName, "12356789") sess, err := store.Get(c) require.NoError(b, err) @@ -779,7 +779,7 @@ func Benchmark_Session_Asserted_Parallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.SetCookie(store.sessionName, "12356789") + c.Context().Request.Header.SetCookie(store.sessionName, "12356789") sess, err := store.Get(c) require.NoError(b, err) diff --git a/middleware/session/store.go b/middleware/session/store.go index dbca801808..74b0baade4 100644 --- a/middleware/session/store.go +++ b/middleware/session/store.go @@ -105,7 +105,7 @@ func (s *Store) getSessionID(c fiber.Ctx) string { } if s.source == SourceHeader { - id = string(c.Request().Header.Peek(s.sessionName)) + id = string(c.Context().Request.Header.Peek(s.sessionName)) if len(id) > 0 { return id } @@ -123,7 +123,7 @@ func (s *Store) getSessionID(c fiber.Ctx) string { func (s *Store) responseCookies(c fiber.Ctx) (string, error) { // Get key from response cookie - cookieValue := c.Response().Header.PeekCookie(s.sessionName) + cookieValue := c.Context().Response.Header.PeekCookie(s.sessionName) if len(cookieValue) == 0 { return "", nil } diff --git a/middleware/session/store_test.go b/middleware/session/store_test.go index 1373827899..b50ab36460 100644 --- a/middleware/session/store_test.go +++ b/middleware/session/store_test.go @@ -25,7 +25,7 @@ func Test_Store_getSessionID(t *testing.T) { ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) // set cookie - ctx.Request().Header.SetCookie(store.sessionName, expectedID) + ctx.Context().Request.Header.SetCookie(store.sessionName, expectedID) require.Equal(t, expectedID, store.getSessionID(ctx)) }) @@ -40,7 +40,7 @@ func Test_Store_getSessionID(t *testing.T) { ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) // set header - ctx.Request().Header.Set(store.sessionName, expectedID) + ctx.Context().Request.Header.Set(store.sessionName, expectedID) require.Equal(t, expectedID, store.getSessionID(ctx)) }) @@ -55,7 +55,7 @@ func Test_Store_getSessionID(t *testing.T) { ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) // set url parameter - ctx.Request().SetRequestURI(fmt.Sprintf("/path?%s=%s", store.sessionName, expectedID)) + ctx.Context().Request.SetRequestURI(fmt.Sprintf("/path?%s=%s", store.sessionName, expectedID)) require.Equal(t, expectedID, store.getSessionID(ctx)) }) @@ -76,7 +76,7 @@ func Test_Store_Get(t *testing.T) { ctx := app.AcquireCtx(&fasthttp.RequestCtx{}) // set cookie - ctx.Request().Header.SetCookie(store.sessionName, unexpectedID) + ctx.Context().Request.Header.SetCookie(store.sessionName, unexpectedID) acquiredSession, err := store.Get(ctx) require.NoError(t, err) diff --git a/redirect.go b/redirect.go index f03981b818..d66a2cb3e0 100644 --- a/redirect.go +++ b/redirect.go @@ -37,8 +37,9 @@ type Redirect struct { c *DefaultCtx // Embed ctx status int // Status code of redirection. Default: StatusFound - messages []string // Flash messages - oldInput map[string]string // Old input data + messages []string // Flash messages + redirectionMessages []string // Messages of the previous redirect + oldInput map[string]string // Old input data } // RedirectConfig A config to use with Redirect().Route() @@ -71,6 +72,7 @@ func ReleaseRedirect(r *Redirect) { func (r *Redirect) release() { r.status = 302 r.messages = r.messages[:0] + r.redirectionMessages = r.redirectionMessages[:0] // reset map for k := range r.oldInput { delete(r.oldInput, k) @@ -119,7 +121,7 @@ func (r *Redirect) WithInput() *Redirect { // Messages Get flash messages. func (r *Redirect) Messages() map[string]string { - msgs := r.c.redirectionMessages + msgs := r.redirectionMessages flashMessages := make(map[string]string, len(msgs)) for _, msg := range msgs { @@ -135,7 +137,7 @@ func (r *Redirect) Messages() map[string]string { // Message Get flash message by key. func (r *Redirect) Message(key string) string { - msgs := r.c.redirectionMessages + msgs := r.redirectionMessages for _, msg := range msgs { k, v := parseMessage(msg) @@ -149,7 +151,7 @@ func (r *Redirect) Message(key string) string { // OldInputs Get old input data. func (r *Redirect) OldInputs() map[string]string { - msgs := r.c.redirectionMessages + msgs := r.redirectionMessages oldInputs := make(map[string]string, len(msgs)) for _, msg := range msgs { @@ -165,7 +167,7 @@ func (r *Redirect) OldInputs() map[string]string { // OldInput Get old input data by key. func (r *Redirect) OldInput(key string) string { - msgs := r.c.redirectionMessages + msgs := r.redirectionMessages for _, msg := range msgs { k, v := parseMessage(msg) @@ -179,7 +181,7 @@ func (r *Redirect) OldInput(key string) string { // To redirect to the URL derived from the specified path, with specified status. func (r *Redirect) To(location string) error { - r.c.setCanonical(HeaderLocation, location) + r.c.res.setCanonical(HeaderLocation, location) r.c.Status(r.status) return nil @@ -279,10 +281,10 @@ func (r *Redirect) setFlash() { for { commaPos = findNextNonEscapedCharsetPosition(cookieValue, []byte(CookieDataSeparator)) if commaPos == -1 { - r.c.redirectionMessages = append(r.c.redirectionMessages, strings.Trim(cookieValue, " ")) + r.redirectionMessages = append(r.redirectionMessages, strings.Trim(cookieValue, " ")) break } - r.c.redirectionMessages = append(r.c.redirectionMessages, strings.Trim(cookieValue[:commaPos], " ")) + r.redirectionMessages = append(r.redirectionMessages, strings.Trim(cookieValue[:commaPos], " ")) cookieValue = cookieValue[commaPos+1:] } diff --git a/redirect_test.go b/redirect_test.go index dd5e4b2715..e95cd6ba64 100644 --- a/redirect_test.go +++ b/redirect_test.go @@ -25,13 +25,13 @@ func Test_Redirect_To(t *testing.T) { err := c.Redirect().To("http://default.com") require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) - require.Equal(t, "http://default.com", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, 302, c.Context().Response.StatusCode()) + require.Equal(t, "http://default.com", c.Res().Get(HeaderLocation)) err = c.Redirect().Status(301).To("http://example.com") require.NoError(t, err) - require.Equal(t, 301, c.Response().StatusCode()) - require.Equal(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, 301, c.Context().Response.StatusCode()) + require.Equal(t, "http://example.com", c.Res().Get(HeaderLocation)) } // go test -run Test_Redirect_Route_WithParams @@ -49,8 +49,8 @@ func Test_Redirect_Route_WithParams(t *testing.T) { }, }) require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) - require.Equal(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, 302, c.Context().Response.StatusCode()) + require.Equal(t, "/user/fiber", c.Res().Get(HeaderLocation)) } // go test -run Test_Redirect_Route_WithParams_WithQueries @@ -69,9 +69,9 @@ func Test_Redirect_Route_WithParams_WithQueries(t *testing.T) { Queries: map[string]string{"data[0][name]": "john", "data[0][age]": "10", "test": "doe"}, }) require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, 302, c.Context().Response.StatusCode()) // analysis of query parameters with url parsing, since a map pass is always randomly ordered - location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation))) + location, err := url.Parse(c.Res().Get(HeaderLocation)) require.NoError(t, err, "url.Parse(location)") require.Equal(t, "/user/fiber", location.Path) require.Equal(t, url.Values{"data[0][name]": []string{"john"}, "data[0][age]": []string{"10"}, "test": []string{"doe"}}, location.Query()) @@ -92,8 +92,8 @@ func Test_Redirect_Route_WithOptionalParams(t *testing.T) { }, }) require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) - require.Equal(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, 302, c.Context().Response.StatusCode()) + require.Equal(t, "/user/fiber", c.Res().Get(HeaderLocation)) } // go test -run Test_Redirect_Route_WithOptionalParamsWithoutValue @@ -107,8 +107,8 @@ func Test_Redirect_Route_WithOptionalParamsWithoutValue(t *testing.T) { err := c.Redirect().Route("user") require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) - require.Equal(t, "/user/", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, 302, c.Context().Response.StatusCode()) + require.Equal(t, "/user/", c.Res().Get(HeaderLocation)) } // go test -run Test_Redirect_Route_WithGreedyParameters @@ -126,8 +126,8 @@ func Test_Redirect_Route_WithGreedyParameters(t *testing.T) { }, }) require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) - require.Equal(t, "/user/test/routes", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, 302, c.Context().Response.StatusCode()) + require.Equal(t, "/user/test/routes", c.Res().Get(HeaderLocation)) } // go test -run Test_Redirect_Back @@ -141,11 +141,11 @@ func Test_Redirect_Back(t *testing.T) { err := c.Redirect().Back("/") require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) - require.Equal(t, "/", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, 302, c.Context().Response.StatusCode()) + require.Equal(t, "/", c.Res().Get(HeaderLocation)) err = c.Redirect().Back() - require.Equal(t, 500, c.Response().StatusCode()) + require.Equal(t, 500, c.Context().Response.StatusCode()) require.ErrorAs(t, err, &ErrRedirectBackNoFallback) } @@ -161,12 +161,12 @@ func Test_Redirect_Back_WithReferer(t *testing.T) { }).Name("back") c := app.AcquireCtx(&fasthttp.RequestCtx{}) - c.Request().Header.Set(HeaderReferer, "/back") + c.Context().Request.Header.Set(HeaderReferer, "/back") err := c.Redirect().Back("/") require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, 302, c.Context().Response.StatusCode()) require.Equal(t, "/back", c.Get(HeaderReferer)) - require.Equal(t, "/back", string(c.Response().Header.Peek(HeaderLocation))) + require.Equal(t, "/back", c.Res().Get(HeaderLocation)) } // go test -run Test_Redirect_Route_WithFlashMessages @@ -182,7 +182,7 @@ func Test_Redirect_Route_WithFlashMessages(t *testing.T) { err := c.Redirect().With("success", "1").With("message", "test").Route("user") require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, 302, c.Context().Response.StatusCode()) require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation))) equal := c.GetRespHeader(HeaderSetCookie) == "fiber_flash=success:1,message:test; path=/; SameSite=Lax" || c.GetRespHeader(HeaderSetCookie) == "fiber_flash=message:test,success:1; path=/; SameSite=Lax" @@ -202,11 +202,12 @@ func Test_Redirect_Route_WithOldInput(t *testing.T) { }).Name("user") c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed + defer app.ReleaseCtx(c) - c.Request().URI().SetQueryString("id=1&name=tom") + c.Context().URI().SetQueryString("id=1&name=tom") err := c.Redirect().With("success", "1").With("message", "test").WithInput().Route("user") require.NoError(t, err) - require.Equal(t, 302, c.Response().StatusCode()) + require.Equal(t, 302, c.Context().Response.StatusCode()) require.Equal(t, "/user", string(c.Response().Header.Peek(HeaderLocation))) require.Contains(t, c.GetRespHeader(HeaderSetCookie), "fiber_flash=") @@ -231,7 +232,7 @@ func Test_Redirect_setFlash(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Context().Request.Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") c.Redirect().setFlash() @@ -355,7 +356,7 @@ func Benchmark_Redirect_Route(b *testing.B) { } require.NoError(b, err) - require.Equal(b, 302, c.Response().StatusCode()) + require.Equal(b, 302, c.Context().Response.StatusCode()) require.Equal(b, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation))) } @@ -383,7 +384,7 @@ func Benchmark_Redirect_Route_WithQueries(b *testing.B) { } require.NoError(b, err) - require.Equal(b, 302, c.Response().StatusCode()) + require.Equal(b, 302, c.Context().Response.StatusCode()) // analysis of query parameters with url parsing, since a map pass is always randomly ordered location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation))) require.NoError(b, err, "url.Parse(location)") @@ -410,7 +411,7 @@ func Benchmark_Redirect_Route_WithFlashMessages(b *testing.B) { } require.NoError(b, err) - require.Equal(b, 302, c.Response().StatusCode()) + require.Equal(b, 302, c.Context().Response.StatusCode()) require.Equal(b, "/user", string(c.Response().Header.Peek(HeaderLocation))) equal := c.GetRespHeader(HeaderSetCookie) == "fiber_flash=success:1,message:test; path=/; SameSite=Lax" || c.GetRespHeader(HeaderSetCookie) == "fiber_flash=message:test,success:1; path=/; SameSite=Lax" @@ -429,7 +430,7 @@ func Benchmark_Redirect_setFlash(b *testing.B) { c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Context().Request.Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") b.ReportAllocs() b.ResetTimer() @@ -458,7 +459,7 @@ func Benchmark_Redirect_Messages(b *testing.B) { c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Context().Request.Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") c.Redirect().setFlash() var msgs map[string]string @@ -483,7 +484,7 @@ func Benchmark_Redirect_OldInputs(b *testing.B) { c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Context().Request.Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") c.Redirect().setFlash() var oldInputs map[string]string @@ -508,7 +509,7 @@ func Benchmark_Redirect_Message(b *testing.B) { c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Context().Request.Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") c.Redirect().setFlash() var msg string @@ -533,7 +534,7 @@ func Benchmark_Redirect_OldInput(b *testing.B) { c := app.AcquireCtx(&fasthttp.RequestCtx{}).(*DefaultCtx) //nolint:errcheck, forcetypeassert // not needed - c.Request().Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") + c.Context().Request.Header.Set(HeaderCookie, "fiber_flash=success:1,message:test,old_input_data_name:tom,old_input_data_id:1") c.Redirect().setFlash() var input string diff --git a/request.go b/request.go new file mode 100644 index 0000000000..a98c0f99ce --- /dev/null +++ b/request.go @@ -0,0 +1,741 @@ +package fiber + +import ( + "bytes" + "errors" + "mime/multipart" + "net" + "net/http" + "strconv" + "strings" + + "github.com/gofiber/utils/v2" + "github.com/valyala/fasthttp" +) + +type Request struct { + app *App // Reference to the parent App. + ctx Ctx // Reference to the parent Ctx. + fasthttp *fasthttp.Request // Reference to the underlying fasthttp.Request object. + route *Route // Reference to *Route + path string // HTTP path with the modifications by the configuration -> string copy from pathBuffer + pathBuffer []byte // HTTP path buffer + detectionPath string // Route detection path -> string copy from detectionPathBuffer + detectionPathBuffer []byte // HTTP detectionPath buffer + treePath string // Path for the search in the tree + pathOriginal string // Original HTTP path + values [maxParams]string // Route parameter values + baseURI string // Memoized base HTTP URI of the current request. + method string // HTTP method + methodINT int // HTTP method INT equivalent +} + +// Accepts checks if the specified extensions or content types are acceptable. +func (r *Request) Accepts(offers ...string) string { + return getOffer(r.fasthttp.Header.Peek(HeaderAccept), acceptsOfferType, offers...) +} + +// AcceptsCharsets checks if the specified charset is acceptable. +func (r *Request) AcceptsCharsets(offers ...string) string { + return getOffer(r.fasthttp.Header.Peek(HeaderAcceptCharset), acceptsOffer, offers...) +} + +// AcceptsEncodings checks if the specified encoding is acceptable. +func (r *Request) AcceptsEncodings(offers ...string) string { + return getOffer(r.fasthttp.Header.Peek(HeaderAcceptEncoding), acceptsOffer, offers...) +} + +// AcceptsLanguages checks if the specified language is acceptable. +func (r *Request) AcceptsLanguages(offers ...string) string { + return getOffer(r.fasthttp.Header.Peek(HeaderAcceptLanguage), acceptsOffer, offers...) +} + +func (r *Request) App() *App { + return r.app +} + +// Method returns the HTTP request method for the context, optionally overridden by the provided argument. +// If no override is given or if the provided override is not a valid HTTP method, it returns the current method from the context. +// Otherwise, it updates the context's method and returns the overridden method as a string. +func (r *Request) Method(override ...string) string { + if len(override) == 0 { + // Nothing to override, just return current method from context + return r.method + } + + method := utils.ToUpper(override[0]) + mINT := r.app.methodInt(method) + if mINT == -1 { + // Provided override does not valid HTTP method, no override, return current method + return r.method + } + + r.method = method + r.methodINT = mINT + return r.method +} + +// OriginalURL contains the original request URL. +// Returned value is only valid within the handler. Do not store any references. +// Make copies or use the Immutable setting to use the value outside the Handler. +func (r *Request) OriginalURL() string { + return r.app.getString(r.fasthttp.Header.RequestURI()) +} + +// BaseURL returns (protocol + host + base path). +func (r *Request) BaseURL() string { + // TODO: Could be improved: 53.8 ns/op 32 B/op 1 allocs/op + // Should work like https://codeigniter.com/user_guide/helpers/url_helper.html + if r.baseURI != "" { + return r.baseURI + } + r.baseURI = r.Scheme() + "://" + r.Host() + return r.baseURI +} + +// Path returns the path part of the request URL. +// Optionally, you could override the path. +func (r *Request) Path(override ...string) string { + if len(override) != 0 && r.path != override[0] { + // Set new path to context + r.pathOriginal = override[0] + + // Set new path to request context + r.fasthttp.URI().SetPath(r.pathOriginal) + // Prettify path + r.configDependentPaths() + } + return r.path +} + +// configDependentPaths set paths for route recognition and prepared paths for the user, +// here the features for caseSensitive, decoded paths, strict paths are evaluated +func (r *Request) configDependentPaths() { + r.pathBuffer = append(r.pathBuffer[0:0], r.pathOriginal...) + // If UnescapePath enabled, we decode the path and save it for the framework user + if r.app.config.UnescapePath { + r.pathBuffer = fasthttp.AppendUnquotedArg(r.pathBuffer[:0], r.pathBuffer) + } + r.path = r.app.getString(r.pathBuffer) + + // another path is specified which is for routing recognition only + // use the path that was changed by the previous configuration flags + r.detectionPathBuffer = append(r.detectionPathBuffer[0:0], r.pathBuffer...) + // If CaseSensitive is disabled, we lowercase the original path + if !r.app.config.CaseSensitive { + r.detectionPathBuffer = utils.ToLowerBytes(r.detectionPathBuffer) + } + // If StrictRouting is disabled, we strip all trailing slashes + if !r.app.config.StrictRouting && len(r.detectionPathBuffer) > 1 && r.detectionPathBuffer[len(r.detectionPathBuffer)-1] == '/' { + r.detectionPathBuffer = bytes.TrimRight(r.detectionPathBuffer, "/") + } + r.detectionPath = r.app.getString(r.detectionPathBuffer) + + // Define the path for dividing routes into areas for fast tree detection, so that fewer routes need to be traversed, + // since the first three characters area select a list of routes + r.treePath = r.treePath[0:0] + const maxDetectionPaths = 3 + if len(r.detectionPath) >= maxDetectionPaths { + r.treePath = r.detectionPath[:maxDetectionPaths] + } +} + +// Protocol returns the HTTP protocol of request: HTTP/1.1 and HTTP/2. +func (r *Request) Protocol() string { + return r.app.getString(r.fasthttp.Header.Protocol()) +} + +// Scheme contains the request protocol string: http or https for TLS requests. +// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +func (r *Request) Scheme() string { + if string(r.fasthttp.URI().Scheme()) == "https" { + return schemeHTTPS + } + if !r.ctx.IsProxyTrusted() { + return schemeHTTP + } + + scheme := schemeHTTP + const lenXHeaderName = 12 + r.fasthttp.Header.VisitAll(func(key, val []byte) { + if len(key) < lenXHeaderName { + return // Neither "X-Forwarded-" nor "X-Url-Scheme" + } + switch { + case bytes.HasPrefix(key, []byte("X-Forwarded-")): + if string(key) == HeaderXForwardedProto || + string(key) == HeaderXForwardedProtocol { + v := r.app.getString(val) + commaPos := strings.IndexByte(v, ',') + if commaPos != -1 { + scheme = v[:commaPos] + } else { + scheme = v + } + } else if string(key) == HeaderXForwardedSsl && string(val) == "on" { + scheme = schemeHTTPS + } + + case string(key) == HeaderXUrlScheme: + scheme = r.app.getString(val) + } + }) + return scheme +} + +// Host contains the host derived from the X-Forwarded-Host or Host HTTP header. +// Returned value is only valid within the handler. Do not store any references. +// In a network context, `Host` refers to the combination of a hostname and potentially a port number used for connecting, +// while `Hostname` refers specifically to the name assigned to a device on a network, excluding any port information. +// Example: URL: https://example.com:8080 -> Host: example.com:8080 +// Make copies or use the Immutable setting instead. +// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +func (r *Request) Host() string { + if r.ctx.IsProxyTrusted() { + if host := r.Get(HeaderXForwardedHost); len(host) > 0 { + commaPos := strings.Index(host, ",") + if commaPos != -1 { + return host[:commaPos] + } + return host + } + } + return r.app.getString(r.fasthttp.URI().Host()) +} + +// Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header using the r.Host() method. +// Returned value is only valid within the handler. Do not store any references. +// Example: URL: https://example.com:8080 -> Hostname: example.com +// Make copies or use the Immutable setting instead. +// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +func (r *Request) Hostname() string { + addr, _ := parseAddr(r.Host()) + + return addr +} + +// Port returns the remote port of the request. +func (r *Request) Port() string { + tcpaddr, ok := r.ctx.Context().RemoteAddr().(*net.TCPAddr) + if !ok { + panic(errors.New("failed to type-assert to *net.TCPAddr")) + } + return strconv.Itoa(tcpaddr.Port) +} + +// IP returns the remote IP address of the request. +// If ProxyHeader and IP Validation is configured, it will parse that header and return the first valid IP address. +// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy. +func (r *Request) IP() string { + if r.ctx.IsProxyTrusted() && len(r.app.config.ProxyHeader) > 0 { + return r.extractIPFromHeader(r.app.config.ProxyHeader) + } + + return r.ctx.Context().RemoteIP().String() +} + +// extractIPFromHeader will attempt to pull the real client IP from the given header when IP validation is enabled. +// currently, it will return the first valid IP address in header. +// when IP validation is disabled, it will simply return the value of the header without any inspection. +// Implementation is almost the same as in extractIPsFromHeader, but without allocation of []string. +func (r *Request) extractIPFromHeader(header string) string { + if r.app.config.EnableIPValidation { + headerValue := r.Get(header) + + i := 0 + j := -1 + + iploop: + for { + var v4, v6 bool + + // Manually splitting string without allocating slice, working with parts directly + i, j = j+1, j+2 + + if j > len(headerValue) { + break + } + + for j < len(headerValue) && headerValue[j] != ',' { + if headerValue[j] == ':' { + v6 = true + } else if headerValue[j] == '.' { + v4 = true + } + j++ + } + + for i < j && headerValue[i] == ' ' { + i++ + } + + s := strings.TrimRight(headerValue[i:j], " ") + + if r.app.config.EnableIPValidation { + if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) { + continue iploop + } + } + + return s + } + + return r.ctx.Context().RemoteIP().String() + } + + // default behavior if IP validation is not enabled is just to return whatever value is + // in the proxy header. Even if it is empty or invalid + return r.Get(r.app.config.ProxyHeader) +} + +// IPs returns a string slice of IP addresses specified in the X-Forwarded-For request header. +// When IP validation is enabled, only valid IPs are returned. +func (r *Request) IPs() []string { + return r.extractIPsFromHeader(HeaderXForwardedFor) +} + +// extractIPsFromHeader will return a slice of IPs it found given a header name in the order they appear. +// When IP validation is enabled, any invalid IPs will be omitted. +func (r *Request) extractIPsFromHeader(header string) []string { + // TODO: Reuse the c.extractIPFromHeader func somehow in here + + headerValue := r.Get(header) + + // We can't know how many IPs we will return, but we will try to guess with this constant division. + // Counting ',' makes function slower for about 50ns in general case. + const maxEstimatedCount = 8 + estimatedCount := len(headerValue) / maxEstimatedCount + if estimatedCount > maxEstimatedCount { + estimatedCount = maxEstimatedCount // Avoid big allocation on big header + } + + ipsFound := make([]string, 0, estimatedCount) + + i := 0 + j := -1 + +iploop: + for { + var v4, v6 bool + + // Manually splitting string without allocating slice, working with parts directly + i, j = j+1, j+2 + + if j > len(headerValue) { + break + } + + for j < len(headerValue) && headerValue[j] != ',' { + if headerValue[j] == ':' { + v6 = true + } else if headerValue[j] == '.' { + v4 = true + } + j++ + } + + for i < j && (headerValue[i] == ' ' || headerValue[i] == ',') { + i++ + } + + s := strings.TrimRight(headerValue[i:j], " ") + + if r.app.config.EnableIPValidation { + // Skip validation if IP is clearly not IPv4/IPv6, otherwise validate without allocations + if (!v6 && !v4) || (v6 && !utils.IsIPv6(s)) || (v4 && !utils.IsIPv4(s)) { + continue iploop + } + } + + ipsFound = append(ipsFound, s) + } + + return ipsFound +} + +// Is returns the matching content type, +// if the incoming request's Content-Type HTTP header field matches the MIME type specified by the type parameter +func (r *Request) Is(extension string) bool { + extensionHeader := utils.GetMIME(extension) + if extensionHeader == "" { + return false + } + + return strings.HasPrefix( + strings.TrimLeft(utils.UnsafeString(r.fasthttp.Header.ContentType()), " "), + extensionHeader, + ) +} + +var localHosts = [...]string{"127.0.0.1", "::1"} + +// IsLocalHost will return true if address is a localhost address. +func isLocalHost(address string) bool { + for _, h := range localHosts { + if address == h { + return true + } + } + return false +} + +// IsFromLocal will return true if request came from local. +func (r *Request) IsFromLocal() bool { + return isLocalHost(r.ctx.Context().RemoteIP().String()) +} + +// BodyRaw contains the raw body submitted in a POST request. +// Returned value is only valid within the handler. Do not store any references. +// Make copies or use the Immutable setting instead. +func (r *Request) BodyRaw() []byte { + if r.app.config.Immutable { + return utils.CopyBytes(r.fasthttp.Body()) + } + return r.fasthttp.Body() +} + +// Body contains the raw body submitted in a POST request. +// This method will decompress the body if the 'Content-Encoding' header is provided. +// It returns the original (or decompressed) body data which is valid only within the handler. +// Don't store direct references to the returned data. +// If you need to keep the body's data later, make a copy or use the Immutable option. +func (r *Request) Body() []byte { + var ( + err error + body, originalBody []byte + headerEncoding string + encodingOrder = []string{"", "", ""} + ) + + // faster than peek + r.fasthttp.Header.VisitAll(func(key, value []byte) { + if r.app.getString(key) == HeaderContentEncoding { + headerEncoding = r.app.getString(value) + } + }) + + // Split and get the encodings list, in order to attend the + // rule defined at: https://www.rfc-editor.org/rfc/rfc9110#section-8.4-5 + encodingOrder = getSplicedStrList(headerEncoding, encodingOrder) + if len(encodingOrder) == 0 { + if r.app.config.Immutable { + return utils.CopyBytes(r.fasthttp.Body()) + } + return r.fasthttp.Body() + } + + var decodesRealized uint8 + body, decodesRealized, err = r.tryDecodeBodyInOrder(&originalBody, encodingOrder) + + // Ensure that the body will be the original + if originalBody != nil && decodesRealized > 0 { + r.fasthttp.SetBodyRaw(originalBody) + } + if err != nil { + return []byte(err.Error()) + } + + if r.app.config.Immutable { + return utils.CopyBytes(body) + } + return body +} + +func (r *Request) tryDecodeBodyInOrder( + originalBody *[]byte, + encodings []string, +) ([]byte, uint8, error) { + var ( + err error + body []byte + decodesRealized uint8 + ) + + for index, encoding := range encodings { + decodesRealized++ + switch encoding { + case StrGzip: + body, err = r.fasthttp.BodyGunzip() + case StrBr, StrBrotli: + body, err = r.fasthttp.BodyUnbrotli() + case StrDeflate: + body, err = r.fasthttp.BodyInflate() + default: + decodesRealized-- + if len(encodings) == 1 { + body = r.fasthttp.Body() + } + return body, decodesRealized, nil + } + + if err != nil { + return nil, decodesRealized, err + } + + // Only execute body raw update if it has a next iteration to try to decode + if index < len(encodings)-1 && decodesRealized > 0 { + if index == 0 { + tempBody := r.fasthttp.Body() + *originalBody = make([]byte, len(tempBody)) + copy(*originalBody, tempBody) + } + r.fasthttp.SetBodyRaw(body) + } + } + + return body, decodesRealized, nil +} + +// Get returns the HTTP request header specified by field. +// Field names are case-insensitive +// Returned value is only valid within the handler. Do not store any references. +// Make copies or use the Immutable setting instead. +func (r *Request) Get(key string, defaultValue ...string) string { + return defaultString(r.app.getString(r.fasthttp.Header.Peek(key)), defaultValue) +} + +// Cookies are used for getting a cookie value by key. +// Defaults to the empty string "" if the cookie doesn't exist. +// If a default value is given, it will return that value if the cookie doesn't exist. +// The returned value is only valid within the handler. Do not store any references. +// Make copies or use the Immutable setting to use the value outside the Handler. +func (r *Request) Cookies(key string, defaultValue ...string) string { + return defaultString(r.app.getString(r.fasthttp.Header.Cookie(key)), defaultValue) +} + +// Fresh returns true when the response is still “fresh” in the client's cache, +// 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 (r *Request) Fresh() bool { + // fields + modifiedSince := r.Get(HeaderIfModifiedSince) + noneMatch := r.Get(HeaderIfNoneMatch) + + // unconditional request + if modifiedSince == "" && noneMatch == "" { + return false + } + + // Always return stale when Cache-Control: no-cache + // to support end-to-end reload requests + // https://tools.ietf.org/html/rfc2616#section-14.9.4 + cacheControl := r.Get(HeaderCacheControl) + if cacheControl != "" && isNoCache(cacheControl) { + return false + } + + // if-none-match + if noneMatch != "" && noneMatch != "*" { + etag := r.ctx.Res().Get(HeaderETag) + if etag == "" { + return false + } + if r.app.isEtagStale(etag, r.app.getBytes(noneMatch)) { + return false + } + + if modifiedSince != "" { + lastModified := r.ctx.Res().Get(HeaderLastModified) + if lastModified != "" { + lastModifiedTime, err := http.ParseTime(lastModified) + if err != nil { + return false + } + modifiedSinceTime, err := http.ParseTime(modifiedSince) + if err != nil { + return false + } + return lastModifiedTime.Before(modifiedSinceTime) + } + } + } + return true +} + +// Secure returns whether a secure connection was established. +func (r *Request) Secure() bool { + return r.Protocol() == schemeHTTPS +} + +// Stale is the opposite of [Request.Fresh] and returns true when the response +// to this request is no longer "fresh" in the client's cache. +func (r *Request) Stale() bool { + return !r.Fresh() +} + +// Subdomains returns a string slice of subdomains in the domain name of the request. +// The subdomain offset, which defaults to 2, is used for determining the beginning of the subdomain segments. +func (r *Request) Subdomains(offset ...int) []string { + o := 2 + if len(offset) > 0 { + o = offset[0] + } + subdomains := strings.Split(r.Host(), ".") + l := len(subdomains) - o + // Check index to avoid slice bounds out of range panic + if l < 0 { + l = len(subdomains) + } + subdomains = subdomains[:l] + return subdomains +} + +// XHR returns a Boolean property, that is true, if the request's X-Requested-With header field is XMLHttpRequest, +// indicating that the request was issued by a client library (such as jQuery). +func (r *Request) XHR() bool { + return utils.EqualFold(r.fasthttp.Header.Peek(HeaderXRequestedWith), []byte("xmlhttprequest")) +} + +// MultipartForm parse form entries from binary. +// This returns a map[string][]string, so given a key the value will be a string slice. +func (r *Request) MultipartForm() (*multipart.Form, error) { + return r.fasthttp.MultipartForm() +} + +// Params is used to get the route parameters. +// Defaults to empty string "" if the param doesn't exist. +// If a default value is given, it will return that value if the param doesn't exist. +// Returned value is only valid within the handler. Do not store any references. +// Make copies or use the Immutable setting to use the value outside the Handler. +func (r *Request) Params(key string, defaultValue ...string) string { + if key == "*" || key == "+" { + key += "1" + } + for i := range r.route.Params { + if len(key) != len(r.route.Params[i]) { + continue + } + if r.route.Params[i] == key || (!r.app.config.CaseSensitive && utils.EqualFold(r.route.Params[i], key)) { + // in case values are not here + if len(r.values) <= i || len(r.values[i]) == 0 { + break + } + return r.values[i] + } + } + return defaultString("", defaultValue) +} + +// Query returns the query string parameter in the url. +// Defaults to empty string "" if the query doesn't exist. +// If a default value is given, it will return that value if the query doesn't exist. +// Returned value is only valid within the handler. Do not store any references. +// Make copies or use the Immutable setting to use the value outside the Handler. +func (r *Request) Query(key string, defaultValue ...string) string { + query := r.app.getString(r.fasthttp.URI().QueryArgs().Peek(key)) + return defaultString(query, defaultValue) +} + +// Queries returns a map of query parameters and their values. +// +// GET /?name=alex&wanna_cake=2&id= +// Queries()["name"] == "alex" +// Queries()["wanna_cake"] == "2" +// Queries()["id"] == "" +// +// GET /?field1=value1&field1=value2&field2=value3 +// Queries()["field1"] == "value2" +// Queries()["field2"] == "value3" +// +// GET /?list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3 +// Queries()["list_a"] == "3" +// Queries()["list_b[]"] == "3" +// Queries()["list_c"] == "1,2,3" +// +// GET /api/search?filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending +// Queries()["filters.author.name"] == "John" +// Queries()["filters.category.name"] == "Technology" +// Queries()["filters[customer][name]"] == "Alice" +// Queries()["filters[status]"] == "pending" +func (r *Request) Queries() map[string]string { + m := make(map[string]string, r.fasthttp.URI().QueryArgs().Len()) + r.fasthttp.URI().QueryArgs().VisitAll(func(key, value []byte) { + m[r.app.getString(key)] = r.app.getString(value) + }) + return m +} + +// Range returns a struct containing the type and a slice of ranges. +func (r *Request) Range(size int) (Range, error) { + var ( + rangeData Range + ranges string + ) + rangeStr := r.Get(HeaderRange) + + i := strings.IndexByte(rangeStr, '=') + if i == -1 || strings.Contains(rangeStr[i+1:], "=") { + return rangeData, ErrRangeMalformed + } + rangeData.Type = rangeStr[:i] + ranges = rangeStr[i+1:] + + var ( + singleRange string + moreRanges = ranges + ) + for moreRanges != "" { + singleRange = moreRanges + if i := strings.IndexByte(moreRanges, ','); i >= 0 { + singleRange = moreRanges[:i] + moreRanges = moreRanges[i+1:] + } else { + moreRanges = "" + } + + var ( + startStr, endStr string + i int + ) + if i = strings.IndexByte(singleRange, '-'); i == -1 { + return rangeData, ErrRangeMalformed + } + startStr = singleRange[:i] + endStr = singleRange[i+1:] + + start, startErr := fasthttp.ParseUint(utils.UnsafeBytes(startStr)) + end, endErr := fasthttp.ParseUint(utils.UnsafeBytes(endStr)) + if startErr != nil { // -nnn + start = size - end + end = size - 1 + } else if endErr != nil { // nnn- + end = size - 1 + } + if end > size-1 { // limit last-byte-pos to current length + end = size - 1 + } + if start > end || start < 0 { + continue + } + rangeData.Ranges = append(rangeData.Ranges, struct { + Start int + End int + }{ + start, + end, + }) + } + if len(rangeData.Ranges) < 1 { + return rangeData, ErrRangeUnsatisfiable + } + + return rangeData, nil +} + +// Route returns the matched Route struct. +func (r *Request) Route() *Route { + if r.route == nil { + // Fallback for fasthttp error handler + return &Route{ + path: r.pathOriginal, + Path: r.pathOriginal, + Method: r.method, + Handlers: make([]Handler, 0), + Params: make([]string, 0), + } + } + return r.route +} diff --git a/response.go b/response.go new file mode 100644 index 0000000000..ca059751ef --- /dev/null +++ b/response.go @@ -0,0 +1,528 @@ +package fiber + +import ( + "fmt" + "path/filepath" + "strings" + "sync" + "text/template" + "time" + + "github.com/gofiber/utils/v2" + "github.com/valyala/bytebufferpool" + "github.com/valyala/fasthttp" +) + +var ( + sendFileOnce sync.Once + sendFileFS *fasthttp.FS + sendFileHandler fasthttp.RequestHandler +) + +type Response struct { + app *App + ctx Ctx + fasthttp *fasthttp.Response + viewBindMap sync.Map // Default view map to bind template engine +} + +// ResFmt associates a Content Type to a fiber.Handler for c.Format +type ResFmt struct { + MediaType string + Handler func(Ctx) error +} + +func (r *Response) App() *App { + return r.app +} + +// Append the specified value to the HTTP response header field. +// If the header is not already set, it creates the header with the specified value. +func (r *Response) Append(field string, values ...string) { + if len(values) == 0 { + return + } + h := r.app.getString(r.fasthttp.Header.Peek(field)) + originalH := h + for _, value := range values { + if len(h) == 0 { + h = value + } else if h != value && !strings.HasPrefix(h, value+",") && !strings.HasSuffix(h, " "+value) && + !strings.Contains(h, " "+value+",") { + h += ", " + value + } + } + if originalH != h { + r.Set(field, h) + } +} + +func (r *Response) Attachment(filename ...string) { + if len(filename) > 0 { + fname := filepath.Base(filename[0]) + r.Type(filepath.Ext(fname)) + + r.setCanonical(HeaderContentDisposition, `attachment; filename="`+r.app.quoteString(fname)+`"`) + return + } + r.setCanonical(HeaderContentDisposition, "attachment") +} + +// ViewBind adds vars to default view var map binding to template engine. +// Variables are read by the Render method and may be overwritten. +func (r *Response) ViewBind(vars Map) error { + // init viewBindMap - lazy map + for k, v := range vars { + r.viewBindMap.Store(k, v) + } + return nil +} + +// Cookie sets a cookie by passing a cookie struct. +func (r *Response) Cookie(cookie *Cookie) { + fcookie := fasthttp.AcquireCookie() + fcookie.SetKey(cookie.Name) + fcookie.SetValue(cookie.Value) + fcookie.SetPath(cookie.Path) + fcookie.SetDomain(cookie.Domain) + // only set max age and expiry when SessionOnly is false + // i.e. cookie supposed to last beyond browser session + // refer: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_the_lifetime_of_a_cookie + if !cookie.SessionOnly { + fcookie.SetMaxAge(cookie.MaxAge) + fcookie.SetExpire(cookie.Expires) + } + fcookie.SetSecure(cookie.Secure) + fcookie.SetHTTPOnly(cookie.HTTPOnly) + + switch utils.ToLower(cookie.SameSite) { + case CookieSameSiteStrictMode: + fcookie.SetSameSite(fasthttp.CookieSameSiteStrictMode) + case CookieSameSiteNoneMode: + fcookie.SetSameSite(fasthttp.CookieSameSiteNoneMode) + case CookieSameSiteDisabled: + fcookie.SetSameSite(fasthttp.CookieSameSiteDisabled) + default: + fcookie.SetSameSite(fasthttp.CookieSameSiteLaxMode) + } + + r.fasthttp.Header.SetCookie(fcookie) + fasthttp.ReleaseCookie(fcookie) +} + +// ClearCookie expires a specific cookie by key on the client side. +// If no key is provided it expires all cookies that came with the request. +func (r *Response) ClearCookie(key ...string) { + if len(key) > 0 { + for i := range key { + r.fasthttp.Header.DelClientCookie(key[i]) + } + return + } + r.ctx.Context().Request.Header.VisitAllCookie(func(k, _ []byte) { + r.fasthttp.Header.DelClientCookieBytes(k) + }) +} + +// 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). +// Override this default with the filename parameter. +func (r *Response) Download(file string, filename ...string) error { + var fname string + if len(filename) > 0 { + fname = filename[0] + } else { + fname = filepath.Base(file) + } + r.setCanonical(HeaderContentDisposition, `attachment; filename="`+r.app.quoteString(fname)+`"`) + return r.SendFile(file) +} + +// Format performs content-negotiation on the Accept HTTP header. +// It uses Accepts to select a proper format and calls the matching +// user-provided handler function. +// If no accepted format is found, and a format with MediaType "default" is given, +// that default handler is called. If no format is found and no default is given, +// StatusNotAcceptable is sent. +func (r *Response) Format(handlers ...ResFmt) error { + if len(handlers) == 0 { + return ErrNoHandlers + } + + r.Vary(HeaderAccept) + + if r.ctx.Get(HeaderAccept) == "" { + r.fasthttp.Header.SetContentType(handlers[0].MediaType) + return handlers[0].Handler(r.ctx) + } + + // Using an int literal as the slice capacity allows for the slice to be + // allocated on the stack. The number was chosen arbitrarily as an + // approximation of the maximum number of content types a user might handle. + // If the user goes over, it just causes allocations, so it's not a problem. + types := make([]string, 0, 8) + var defaultHandler Handler + for _, h := range handlers { + if h.MediaType == "default" { + defaultHandler = h.Handler + continue + } + types = append(types, h.MediaType) + } + accept := r.ctx.Accepts(types...) + + if accept == "" { + if defaultHandler == nil { + return r.SendStatus(StatusNotAcceptable) + } + return defaultHandler(r.ctx) + } + + for _, h := range handlers { + if h.MediaType == accept { + r.fasthttp.Header.SetContentType(h.MediaType) + return h.Handler(r.ctx) + } + } + + return fmt.Errorf("%w: format: an Accept was found but no handler was called", errUnreachable) +} + +// Get returns the HTTP response header specified by field. +// Field names are case-insensitive. +// Returned value is only valid within the handler. Do not store any references. +// Make copies or use the Immutable setting instead. +func (r *Response) Get(key string, defaultValue ...string) string { + return defaultString(r.app.getString(r.fasthttp.Header.Peek(key)), defaultValue) +} + +// JSON converts any interface or string to JSON. +// Array and slice values encode as JSON arrays, +// except that []byte encodes as a base64-encoded string, +// and a nil slice encodes as the null JSON value. +// If the ctype parameter is given, this method will set the +// Content-Type header equal to ctype. If ctype is not given, +// The Content-Type header will be set to application/json. +func (r *Response) JSON(data any, ctype ...string) error { + raw, err := r.app.config.JSONEncoder(data) + if err != nil { + return err + } + r.fasthttp.SetBodyRaw(raw) + if len(ctype) > 0 { + r.fasthttp.Header.SetContentType(ctype[0]) + } else { + r.fasthttp.Header.SetContentType(MIMEApplicationJSON) + } + return nil +} + +// JSONP sends a JSON response with JSONP support. +// This method is identical to JSON, except that it opts-in to JSONP callback support. +// By default, the callback name is simply callback. +func (r *Response) JSONP(data any, callback ...string) error { + raw, err := r.app.config.JSONEncoder(data) + if err != nil { + return err + } + + var result, cb string + + if len(callback) > 0 { + cb = callback[0] + } else { + cb = "callback" + } + + result = cb + "(" + r.app.getString(raw) + ");" + + r.setCanonical(HeaderXContentTypeOptions, "nosniff") + r.fasthttp.Header.SetContentType(MIMETextJavaScriptCharsetUTF8) + return r.SendString(result) +} + +// Links joins the links followed by the property to populate the response's Link HTTP header field. +func (r *Response) Links(link ...string) { + if len(link) == 0 { + return + } + bb := bytebufferpool.Get() + for i := range link { + if i%2 == 0 { + bb.WriteByte('<') + bb.WriteString(link[i]) + bb.WriteByte('>') + } else { + bb.WriteString(`; rel="` + link[i] + `",`) + } + } + r.setCanonical(HeaderLink, strings.TrimRight(r.app.getString(bb.Bytes()), ",")) + bytebufferpool.Put(bb) +} + +// Location sets the response Location HTTP header to the specified path parameter. +func (r *Response) Location(path string) { + r.setCanonical(HeaderLocation, path) +} + +// Render a template with data and sends a text/html response. +// We support the following engines: https://github.com/gofiber/template +func (r *Response) Render(name string, bind Map, layouts ...string) error { + // Get new buffer from pool + buf := bytebufferpool.Get() + defer bytebufferpool.Put(buf) + + // Initialize empty bind map if bind is nil + if bind == nil { + bind = make(Map) + } + + // Pass-locals-to-views, bind, appListKeys + r.renderExtensions(bind) + + var rendered bool + for i := len(r.app.mountFields.appListKeys) - 1; i >= 0; i-- { + prefix := r.app.mountFields.appListKeys[i] + app := r.app.mountFields.appList[prefix] + if prefix == "" || strings.Contains(r.ctx.OriginalURL(), prefix) { + if len(layouts) == 0 && app.config.ViewsLayout != "" { + layouts = []string{ + app.config.ViewsLayout, + } + } + + // Render template from Views + if app.config.Views != nil { + if err := app.config.Views.Render(buf, name, bind, layouts...); err != nil { + return fmt.Errorf("failed to render: %w", err) + } + + rendered = true + break + } + } + } + + if !rendered { + // Render raw template using 'name' as filepath if no engine is set + var tmpl *template.Template + if _, err := readContent(buf, name); err != nil { + return err + } + // Parse template + tmpl, err := template.New("").Parse(r.app.getString(buf.Bytes())) + if err != nil { + return fmt.Errorf("failed to parse: %w", err) + } + buf.Reset() + // Render template + if err := tmpl.Execute(buf, bind); err != nil { + return fmt.Errorf("failed to execute: %w", err) + } + } + + // Set Content-Type to text/html + r.fasthttp.Header.SetContentType(MIMETextHTMLCharsetUTF8) + // Set rendered template to body + r.fasthttp.SetBody(buf.Bytes()) + + return nil +} + +func (r *Response) renderExtensions(bind any) { + if bindMap, ok := bind.(Map); ok { + // Bind view map + r.viewBindMap.Range(func(key, value any) bool { + keyValue, ok := key.(string) + if !ok { + return true + } + if _, ok := bindMap[keyValue]; !ok { + bindMap[keyValue] = value + } + return true + }) + + // Check if the PassLocalsToViews option is enabled (by default it is disabled) + if r.app.config.PassLocalsToViews { + // Loop through each local and set it in the map + r.ctx.Context().VisitUserValues(func(key []byte, val any) { + // check if bindMap doesn't contain the key + if _, ok := bindMap[r.app.getString(key)]; !ok { + // Set the key and value in the bindMap + bindMap[r.app.getString(key)] = val + } + }) + } + } + + if len(r.app.mountFields.appListKeys) == 0 { + r.app.generateAppListKeys() + } +} + +// Send sets the HTTP response body without copying it. +// From this point onward the body argument must not be changed. +func (r *Response) Send(body []byte) error { + // Write response body + r.fasthttp.SetBodyRaw(body) + return nil +} + +// SendFile transfers the file from the given path. +// The file is not compressed by default, enable this by passing a 'true' argument +// Sets the Content-Type response HTTP header field based on the filenames extension. +func (r *Response) SendFile(file string, compress ...bool) error { + // Save the filename, we will need it in the error message if the file isn't found + filename := file + + // https://github.com/valyala/fasthttp/blob/c7576cc10cabfc9c993317a2d3f8355497bea156/fs.go#L129-L134 + sendFileOnce.Do(func() { + const cacheDuration = 10 * time.Second + sendFileFS = &fasthttp.FS{ + Root: "", + AllowEmptyRoot: true, + GenerateIndexPages: false, + AcceptByteRange: true, + Compress: true, + CompressedFileSuffix: r.app.config.CompressedFileSuffix, + CacheDuration: cacheDuration, + IndexNames: []string{"index.html"}, + PathNotFound: func(ctx *fasthttp.RequestCtx) { + ctx.Response.SetStatusCode(StatusNotFound) + }, + } + sendFileHandler = sendFileFS.NewRequestHandler() + }) + + // Keep original path for mutable params + r.ctx.Req().pathOriginal = utils.CopyString(r.ctx.Req().pathOriginal) + // Disable compression + if len(compress) == 0 || !compress[0] { + // https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L55 + r.ctx.Context().Request.Header.Del(HeaderAcceptEncoding) + } + // copy of https://github.com/valyala/fasthttp/blob/7cc6f4c513f9e0d3686142e0a1a5aa2f76b3194a/fs.go#L103-L121 with small adjustments + if len(file) == 0 || !filepath.IsAbs(file) { + // extend relative path to absolute path + hasTrailingSlash := len(file) > 0 && (file[len(file)-1] == '/' || file[len(file)-1] == '\\') + + var err error + file = filepath.FromSlash(file) + if file, err = filepath.Abs(file); err != nil { + return fmt.Errorf("failed to determine abs file path: %w", err) + } + if hasTrailingSlash { + file += "/" + } + } + // convert the path to forward slashes regardless the OS in order to set the URI properly + // the handler will convert back to OS path separator before opening the file + file = filepath.ToSlash(file) + + // Restore the original requested URL + originalURL := utils.CopyString(r.ctx.OriginalURL()) + defer r.ctx.Context().Request.SetRequestURI(originalURL) + // Set new URI for fileHandler + r.ctx.Context().Request.SetRequestURI(file) + // Save status code + status := r.fasthttp.StatusCode() + // Serve file + sendFileHandler(r.ctx.Context()) + // Get the status code which is set by fasthttp + fsStatus := r.fasthttp.StatusCode() + // Set the status code set by the user if it is different from the fasthttp status code and 200 + if status != fsStatus && status != StatusOK { + r.Status(status) + } + // Check for error + if status != StatusNotFound && fsStatus == StatusNotFound { + return NewError(StatusNotFound, fmt.Sprintf("sendfile: file %s not found", filename)) + } + return nil +} + +// SendStatus sets the HTTP status code and if the response body is empty, +// it sets the correct status message in the body. +func (r *Response) SendStatus(status int) error { + r.Status(status) + + // Only set status body when there is no response body + if len(r.fasthttp.Body()) == 0 { + return r.SendString(utils.StatusMessage(status)) + } + + return nil +} + +// SendString sets the HTTP response body for string types. +// This means no type assertion, recommended for faster performance +func (r *Response) SendString(body string) error { + r.fasthttp.SetBodyString(body) + return nil +} + +// Set sets the response's HTTP header field to the specified key, value. +func (r *Response) Set(key, val string) { + r.fasthttp.Header.Set(key, val) +} + +// setCanonical is the same as set, but it assumes key is already in canonical form, +// making it more efficient. +func (r *Response) setCanonical(key, val string) { + r.fasthttp.Header.SetCanonical(utils.UnsafeBytes(key), utils.UnsafeBytes(val)) +} + +// Status sets the HTTP status for the response. +// This method is chainable. +func (r *Response) Status(status int) *Response { + r.fasthttp.SetStatusCode(status) + return r +} + +// Type sets the Content-Type HTTP header to the MIME type specified by the file extension. +func (r *Response) Type(extension string, charset ...string) *Response { + if len(charset) > 0 { + r.fasthttp.Header.SetContentType(utils.GetMIME(extension) + "; charset=" + charset[0]) + } else { + r.fasthttp.Header.SetContentType(utils.GetMIME(extension)) + } + return r +} + +// Vary adds the given header field to the Vary response header. +// This will append the header, if not already listed, otherwise leaves it listed in the current location. +func (r *Response) Vary(fields ...string) { + r.Append(HeaderVary, fields...) +} + +// Write appends p into response body. +func (r *Response) Write(p []byte) (int, error) { + r.fasthttp.AppendBody(p) + return len(p), nil +} + +// Writef appends f & a into response body writer. +func (r *Response) Writef(f string, a ...any) (int, error) { + //nolint:wrapcheck // This must not be wrapped + return fmt.Fprintf(r.fasthttp.BodyWriter(), f, a...) +} + +// WriteString appends s to response body. +func (r *Response) WriteString(s string) (int, error) { + r.fasthttp.AppendBodyString(s) + return len(s), nil +} + +// XML converts any interface or string to XML. +// This method also sets the content header to application/xml. +func (r *Response) XML(data any) error { + raw, err := r.app.config.XMLEncoder(data) + if err != nil { + return err + } + r.fasthttp.SetBodyRaw(raw) + r.fasthttp.Header.SetContentType(MIMEApplicationXML) + return nil +} diff --git a/router.go b/router.go index f65b4ece90..3e31822ab2 100644 --- a/router.go +++ b/router.go @@ -150,9 +150,9 @@ func (app *App) nextCustom(c CustomCtx) (bool, error) { //nolint: unparam // boo func (app *App) next(c *DefaultCtx) (bool, error) { // Get stack length - tree, ok := app.treeStack[c.methodINT][c.treePath] + tree, ok := app.treeStack[c.req.methodINT][c.req.treePath] if !ok { - tree = app.treeStack[c.methodINT][""] + tree = app.treeStack[c.req.methodINT][""] } lenTree := len(tree) - 1 @@ -172,13 +172,13 @@ func (app *App) next(c *DefaultCtx) (bool, error) { } // Check if it matches the request path - match = route.match(c.detectionPath, c.path, &c.values) + match = route.match(c.req.detectionPath, c.req.path, &c.req.values) if !match { // No match, next route continue } // Pass route reference and param values - c.route = route + c.req.route = route // Non use handler matched if !c.matched && !route.use { @@ -194,7 +194,7 @@ func (app *App) next(c *DefaultCtx) (bool, error) { } // If c.Next() does not match, return 404 - err := NewError(StatusNotFound, "Cannot "+c.method+" "+html.EscapeString(c.pathOriginal)) + err := NewError(StatusNotFound, "Cannot "+c.req.method+" "+html.EscapeString(c.req.pathOriginal)) if !c.matched && app.methodExist(c) { // If no match, scan stack again if other methods match the request // Moved from app.handler because middleware may break the route chain @@ -227,7 +227,7 @@ func (app *App) requestHandler(rctx *fasthttp.RequestCtx) { } // check flash messages - if strings.Contains(utils.UnsafeString(c.Request().Header.RawHeaders()), FlashCookieName) { + if strings.Contains(utils.UnsafeString(c.Context().Request.Header.RawHeaders()), FlashCookieName) { c.Redirect().setFlash() }