From 0949f421ee5bac2df85ffc01be260cc6e02ef49b Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Sun, 1 Dec 2024 17:33:02 +0300 Subject: [PATCH 01/12] chore: simplify parserRequestBodyFile logic --- client/hooks.go | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/client/hooks.go b/client/hooks.go index ec3987938e2..eebf8f6b55e 100644 --- a/client/hooks.go +++ b/client/hooks.go @@ -1,7 +1,6 @@ package client import ( - "errors" "fmt" "io" "math/rand" @@ -241,8 +240,8 @@ func parserRequestBodyFile(req *Request) error { return fmt.Errorf("write formdata error: %w", err) } - // add file - b := make([]byte, 512) + // add files + fileBuf := make([]byte, 1<<20) // Allocate 1MB buffer for i, v := range req.files { if v.name == "" && v.path == "" { return ErrFileNoName @@ -273,24 +272,10 @@ func parserRequestBodyFile(req *Request) error { return fmt.Errorf("create file error: %w", err) } - for { - n, err := v.reader.Read(b) - if err != nil && !errors.Is(err, io.EOF) { - return fmt.Errorf("read file error: %w", err) - } - - if errors.Is(err, io.EOF) { - break - } - - _, err = w.Write(b[:n]) - if err != nil { - return fmt.Errorf("write file error: %w", err) - } - } + // Copy the file from reader to multipart writer + io.CopyBuffer(w, v.reader, fileBuf) - err = v.reader.Close() - if err != nil { + if err := v.reader.Close(); err != nil { return fmt.Errorf("close file error: %w", err) } } From 7578ae3b199aa2fff383be12916f758894e570ed Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Mon, 2 Dec 2024 12:04:53 +0300 Subject: [PATCH 02/12] client: add support for go1.23 iterators --- client/request.go | 131 ++++++++++++++++++++++++++ client/request_test.go | 203 ++++++++++++++++++++++++++++++++++++++++ client/response.go | 27 ++++++ client/response_test.go | 79 ++++++++++++++++ 4 files changed, 440 insertions(+) diff --git a/client/request.go b/client/request.go index a86d927c4e8..92f1558d535 100644 --- a/client/request.go +++ b/client/request.go @@ -5,8 +5,10 @@ import ( "context" "errors" "io" + "iter" "path/filepath" "reflect" + "slices" "strconv" "sync" "time" @@ -129,6 +131,29 @@ func (r *Request) Header(key string) []string { return r.header.PeekMultiple(key) } +// Headers returns all headers in the request using an iterator. +// You can use maps.Collect() to collect all headers into a map. +// +// The returned value is valid until the request object is released. +// Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Request) Headers() iter.Seq2[string, []string] { + return func(yield func(string, []string) bool) { + keys := r.header.PeekKeys() + + for _, key := range keys { + vals := r.header.PeekAll(utils.UnsafeString(key)) + valsStr := make([]string, len(vals)) + for i, v := range vals { + valsStr[i] = utils.UnsafeString(v) + } + + if !yield(utils.UnsafeString(key), valsStr) { + return + } + } + } +} + // AddHeader method adds a single header field and its value in the request instance. func (r *Request) AddHeader(key, val string) *Request { r.header.Add(key, val) @@ -168,6 +193,33 @@ func (r *Request) Param(key string) []string { return res } +// Params returns all params in the request using an iterator. +// You can use maps.Collect() to collect all params into a map. +// +// The returned value is valid until the request object is released. +// Any future calls to Params method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Request) Params() iter.Seq2[string, []string] { + return func(yield func(string, []string) bool) { + keys := r.params.Keys() + + for _, key := range keys { + if key == "" { + continue + } + + vals := r.params.PeekMulti(key) + valsStr := make([]string, len(vals)) + for i, v := range vals { + valsStr[i] = utils.UnsafeString(v) + } + + if !yield(key, valsStr) { + return + } + } + } +} + // AddParam method adds a single param field and its value in the request instance. func (r *Request) AddParam(key, val string) *Request { r.params.Add(key, val) @@ -254,6 +306,18 @@ func (r *Request) Cookie(key string) string { return "" } +// Cookies returns all cookies in the cookies using an iterator. +// You can use maps.Collect() to collect all cookies into a map. +func (r *Request) Cookies() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + r.cookies.VisitAll(func(key, val string) { + if !yield(key, val) { + return + } + }) + } +} + // SetCookie method sets a single cookie field and its value in the request instance. // It will override cookie which set in client instance. func (r *Request) SetCookie(key, val string) *Request { @@ -291,6 +355,18 @@ func (r *Request) PathParam(key string) string { return "" } +// PathParams returns all path params in request instance. +// You can use maps.Collect() to collect all cookies into a map. +func (r *Request) PathParams() iter.Seq2[string, string] { + return func(yield func(string, string) bool) { + r.path.VisitAll(func(key, val string) { + if !yield(key, val) { + return + } + }) + } +} + // SetPathParam method sets a single path param field and its value in the request instance. // It will override path param which set in client instance. func (r *Request) SetPathParam(key, val string) *Request { @@ -376,6 +452,33 @@ func (r *Request) FormData(key string) []string { return res } +// FormDatas method returns all form datas in request instance. +// You can use maps.Collect() to collect all cookies into a map. +// +// The returned value is valid until the request object is released. +// Any future calls to FormDatas method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Request) FormDatas() iter.Seq2[string, []string] { + return func(yield func(string, []string) bool) { + keys := r.formData.Keys() + + for _, key := range keys { + if key == "" { + continue + } + + vals := r.formData.PeekMulti(key) + valsStr := make([]string, len(vals)) + for i, v := range vals { + valsStr[i] = utils.UnsafeString(v) + } + + if !yield(key, valsStr) { + return + } + } + } +} + // AddFormData method adds a single form data field and its value in the request instance. func (r *Request) AddFormData(key, val string) *Request { r.formData.AddData(key, val) @@ -435,6 +538,14 @@ func (r *Request) File(name string) *File { return nil } +// Files method returns all files in request instance. +// +// The returned value is valid until the request object is released. +// Any future calls to Files method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Request) Files() []*File { + return r.files +} + // FileByPath returns file ptr store in request obj by path. func (r *Request) FileByPath(path string) *File { for _, v := range r.files { @@ -617,6 +728,16 @@ type QueryParam struct { *fasthttp.Args } +// Keys method returns all keys in the query params. +func (f *QueryParam) Keys() []string { + keys := make([]string, f.Len()) + f.VisitAll(func(key, value []byte) { + keys = append(keys, utils.UnsafeString(key)) + }) + + return slices.Compact(keys) +} + // AddParams receive a map and add each value to param. func (p *QueryParam) AddParams(r map[string][]string) { for k, v := range r { @@ -747,6 +868,16 @@ type FormData struct { *fasthttp.Args } +// Keys method returns all keys in the form data. +func (f *FormData) Keys() []string { + keys := make([]string, f.Len()) + f.VisitAll(func(key, value []byte) { + keys = append(keys, utils.UnsafeString(key)) + }) + + return slices.Compact(keys) +} + // AddData method is a wrapper of Args's Add method. func (f *FormData) AddData(key, val string) { f.Add(key, val) diff --git a/client/request_test.go b/client/request_test.go index f62865a3428..0b5c8663955 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -5,6 +5,7 @@ import ( "context" "errors" "io" + "maps" "mime/multipart" "net" "os" @@ -157,6 +158,40 @@ func Test_Request_Header(t *testing.T) { }) } +func Test_Request_Headers(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.AddHeaders(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + headers := maps.Collect(req.Headers()) + + require.Contains(t, headers["Foo"], "fiber") + require.Contains(t, headers["Foo"], "bar") + require.Contains(t, headers["Bar"], "foo") + + require.Len(t, headers, 2) +} + +func Benchmark_Request_Headers(b *testing.B) { + req := AcquireRequest() + req.AddHeaders(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.Headers() { + } + } +} + func Test_Request_QueryParam(t *testing.T) { t.Parallel() @@ -282,6 +317,40 @@ func Test_Request_QueryParam(t *testing.T) { }) } +func Test_Request_Params(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.AddParams(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + pathParams := maps.Collect(req.Params()) + + require.Contains(t, pathParams["foo"], "bar") + require.Contains(t, pathParams["foo"], "fiber") + require.Contains(t, pathParams["bar"], "foo") + + require.Len(t, pathParams, 2) +} + +func Benchmark_Request_Params(b *testing.B) { + req := AcquireRequest() + req.AddParams(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.Params() { + } + } +} + func Test_Request_UA(t *testing.T) { t.Parallel() @@ -364,6 +433,39 @@ func Test_Request_Cookie(t *testing.T) { }) } +func Test_Request_Cookies(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.SetCookies(map[string]string{ + "foo": "bar", + "bar": "foo", + }) + + cookies := maps.Collect(req.Cookies()) + + require.Equal(t, "bar", cookies["foo"]) + require.Equal(t, "foo", cookies["bar"]) + + require.Len(t, cookies, 2) +} + +func Benchmark_Request_Cookies(b *testing.B) { + req := AcquireRequest() + req.SetCookies(map[string]string{ + "foo": "bar", + "bar": "foo", + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.Cookies() { + } + } +} + func Test_Request_PathParam(t *testing.T) { t.Parallel() @@ -441,6 +543,39 @@ func Test_Request_PathParam(t *testing.T) { }) } +func Test_Request_PathParams(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.SetPathParams(map[string]string{ + "foo": "bar", + "bar": "foo", + }) + + pathParams := maps.Collect(req.PathParams()) + + require.Equal(t, "bar", pathParams["foo"]) + require.Equal(t, "foo", pathParams["bar"]) + + require.Len(t, pathParams, 2) +} + +func Benchmark_Request_PathParams(b *testing.B) { + req := AcquireRequest() + req.SetPathParams(map[string]string{ + "foo": "bar", + "bar": "foo", + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.PathParams() { + } + } +} + func Test_Request_FormData(t *testing.T) { t.Parallel() @@ -610,6 +745,40 @@ func Test_Request_File(t *testing.T) { }) } +func Test_Request_Files(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.AddFile("../.github/index.html") + req.AddFiles(AcquireFile(SetFileName("tmp.txt"))) + + files := req.Files() + + require.Equal(t, "../.github/index.html", files[0].path) + require.Nil(t, files[0].reader) + + require.Equal(t, "tmp.txt", files[1].name) + require.Nil(t, files[1].reader) + + require.Len(t, files, 2) +} + +func Benchmark_Request_Files(b *testing.B) { + req := AcquireRequest() + req.AddFile("../.github/index.html") + req.AddFiles(AcquireFile(SetFileName("tmp.txt"))) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for k, v := range req.Files() { + _ = k + _ = v + } + } +} + func Test_Request_Timeout(t *testing.T) { t.Parallel() @@ -1181,6 +1350,40 @@ func Test_Request_Body_With_Server(t *testing.T) { }) } +func Test_Request_FormDatas(t *testing.T) { + t.Parallel() + + req := AcquireRequest() + req.AddFormDatas(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + pathParams := maps.Collect(req.FormDatas()) + + require.Contains(t, pathParams["foo"], "bar") + require.Contains(t, pathParams["foo"], "fiber") + require.Contains(t, pathParams["bar"], "foo") + + require.Len(t, pathParams, 2) +} + +func Benchmark_Request_FormDatas(b *testing.B) { + req := AcquireRequest() + req.AddFormDatas(map[string][]string{ + "foo": {"bar", "fiber"}, + "bar": {"foo"}, + }) + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + for range req.FormDatas() { + } + } +} + func Test_Request_Error_Body_With_Server(t *testing.T) { t.Parallel() t.Run("json error", func(t *testing.T) { diff --git a/client/response.go b/client/response.go index e60c6bd0fb9..8d213297747 100644 --- a/client/response.go +++ b/client/response.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/fs" + "iter" "os" "path/filepath" "sync" @@ -55,7 +56,33 @@ func (r *Response) Header(key string) string { return utils.UnsafeString(r.RawResponse.Header.Peek(key)) } +// Headers returns all headers in the response using an iterator. +// You can use maps.Collect() to collect all headers into a map. +// +// The returned value is valid until the response object is released. +// Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. +func (r *Response) Headers() iter.Seq2[string, []string] { + return func(yield func(string, []string) bool) { + keys := r.RawResponse.Header.PeekKeys() + + for _, key := range keys { + vals := r.RawResponse.Header.PeekAll(utils.UnsafeString(key)) + valsStr := make([]string, len(vals)) + for i, v := range vals { + valsStr[i] = utils.UnsafeString(v) + } + + if !yield(utils.UnsafeString(key), valsStr) { + return + } + } + } +} + // Cookies method to access all the response cookies. +// +// The returned value is valid until the response object is released. +// Any future calls to Cookies method will return the modified value. Do not store references to returned value. Make copies instead. func (r *Response) Cookies() []*fasthttp.Cookie { return r.cookie } diff --git a/client/response_test.go b/client/response_test.go index bf12e751618..340f1d8a2fc 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/tls" "encoding/xml" + "fmt" "io" "net" "os" @@ -199,6 +200,84 @@ func Test_Response_Header(t *testing.T) { resp.Close() } +func Test_Response_Headers(t *testing.T) { + t.Parallel() + + server := startTestServer(t, func(app *fiber.App) { + app.Get("/", func(c fiber.Ctx) error { + c.Response().Header.Add("foo", "bar") + c.Response().Header.Add("foo", "bar2") + c.Response().Header.Add("foo2", "bar") + + return c.SendString("helo world") + }) + }) + defer server.stop() + + client := New().SetDial(server.dial()) + + resp, err := AcquireRequest(). + SetClient(client). + Get("http://example.com") + + require.NoError(t, err) + + headers := make(map[string][]string) + for k, v := range resp.Headers() { + headers[k] = make([]string, 0) + for _, value := range v { + fmt.Print(string(value)) + headers[k] = append(headers[k], string(value)) + } + } + + require.Contains(t, headers["Foo"], "bar") + require.Contains(t, headers["Foo"], "bar2") + require.Contains(t, headers["Foo2"], "bar") + + resp.Close() +} + +func Benchmark_Headers(b *testing.B) { + server := startTestServer( + b, + func(app *fiber.App) { + app.Get("/", func(c fiber.Ctx) error { + c.Response().Header.Add("foo", "bar") + c.Response().Header.Add("foo", "bar2") + c.Response().Header.Add("foo", "bar3") + + c.Response().Header.Add("foo2", "bar") + c.Response().Header.Add("foo2", "bar2") + c.Response().Header.Add("foo2", "bar3") + + return c.SendString("helo world") + }) + }, + ) + + defer server.stop() + + client := New().SetDial(server.dial()) + + b.ResetTimer() + b.ReportAllocs() + + var err error + var resp *Response + for i := 0; i < b.N; i++ { + resp, err = AcquireRequest(). + SetClient(client). + Get("http://example.com") + + for range resp.Headers() { + } + + resp.Close() + } + require.NoError(b, err) +} + func Test_Response_Cookie(t *testing.T) { t.Parallel() From 26a34399068263a180e18daa0da6cda1f3fcd0e9 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:59:17 -0500 Subject: [PATCH 03/12] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- client/hooks.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/hooks.go b/client/hooks.go index eebf8f6b55e..4c36145f2f7 100644 --- a/client/hooks.go +++ b/client/hooks.go @@ -273,7 +273,9 @@ func parserRequestBodyFile(req *Request) error { } // Copy the file from reader to multipart writer - io.CopyBuffer(w, v.reader, fileBuf) + if _, err := io.CopyBuffer(w, v.reader, fileBuf); err != nil { + return fmt.Errorf("failed to copy file data: %w", err) + } if err := v.reader.Close(); err != nil { return fmt.Errorf("close file error: %w", err) From b57b7c25719a60ce43e4a3a00de6cff7df4b7276 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 17:05:28 +0300 Subject: [PATCH 04/12] fix linter --- client/request.go | 10 +++++----- client/request_test.go | 20 +++++++++++++++----- client/response_test.go | 11 ++++------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/client/request.go b/client/request.go index 92f1558d535..d1b6d2369a5 100644 --- a/client/request.go +++ b/client/request.go @@ -729,9 +729,9 @@ type QueryParam struct { } // Keys method returns all keys in the query params. -func (f *QueryParam) Keys() []string { - keys := make([]string, f.Len()) - f.VisitAll(func(key, value []byte) { +func (p *QueryParam) Keys() []string { + keys := make([]string, 0, p.Len()) + p.VisitAll(func(key, _ []byte) { keys = append(keys, utils.UnsafeString(key)) }) @@ -870,8 +870,8 @@ type FormData struct { // Keys method returns all keys in the form data. func (f *FormData) Keys() []string { - keys := make([]string, f.Len()) - f.VisitAll(func(key, value []byte) { + keys := make([]string, 0, f.Len()) + f.VisitAll(func(key, _ []byte) { keys = append(keys, utils.UnsafeString(key)) }) diff --git a/client/request_test.go b/client/request_test.go index 0b5c8663955..c875c5ffd10 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -187,7 +187,9 @@ func Benchmark_Request_Headers(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.Headers() { + for k, v := range req.Headers() { + _ = k + _ = v } } } @@ -346,7 +348,9 @@ func Benchmark_Request_Params(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.Params() { + for k, v := range req.Params() { + _ = k + _ = v } } } @@ -461,7 +465,9 @@ func Benchmark_Request_Cookies(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.Cookies() { + for k, v := range req.Cookies() { + _ = k + _ = v } } } @@ -571,7 +577,9 @@ func Benchmark_Request_PathParams(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.PathParams() { + for k, v := range req.PathParams() { + _ = k + _ = v } } } @@ -1379,7 +1387,9 @@ func Benchmark_Request_FormDatas(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for range req.FormDatas() { + for k, v := range req.FormDatas() { + _ = k + _ = v } } } diff --git a/client/response_test.go b/client/response_test.go index 340f1d8a2fc..ac1863c1b48 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/tls" "encoding/xml" - "fmt" "io" "net" "os" @@ -224,11 +223,7 @@ func Test_Response_Headers(t *testing.T) { headers := make(map[string][]string) for k, v := range resp.Headers() { - headers[k] = make([]string, 0) - for _, value := range v { - fmt.Print(string(value)) - headers[k] = append(headers[k], string(value)) - } + headers[k] = append(headers[k], v...) } require.Contains(t, headers["Foo"], "bar") @@ -270,7 +265,9 @@ func Benchmark_Headers(b *testing.B) { SetClient(client). Get("http://example.com") - for range resp.Headers() { + for k, v := range resp.Headers() { + _ = k + _ = v } resp.Close() From b4c7c5489b61fd32fba4580bb3a7b8b854d3bacd Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 17:49:13 +0300 Subject: [PATCH 05/12] fix tests --- client/request.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/request.go b/client/request.go index d1b6d2369a5..982a1390d9f 100644 --- a/client/request.go +++ b/client/request.go @@ -138,7 +138,9 @@ func (r *Request) Header(key string) []string { // Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. func (r *Request) Headers() iter.Seq2[string, []string] { return func(yield func(string, []string) bool) { - keys := r.header.PeekKeys() + peekKeys := r.header.PeekKeys() + keys := make([][]byte, len(peekKeys)) + copy(keys, peekKeys) // It is necessary to have immutable byte slice. for _, key := range keys { vals := r.header.PeekAll(utils.UnsafeString(key)) From 86e93b176f0b00c289a71dfea4b22b38d08ee64a Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 17:57:02 +0300 Subject: [PATCH 06/12] correct benchmark --- client/response_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/client/response_test.go b/client/response_test.go index ac1863c1b48..b15a5243568 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -251,28 +251,28 @@ func Benchmark_Headers(b *testing.B) { }, ) - defer server.stop() - client := New().SetDial(server.dial()) + resp, err := AcquireRequest(). + SetClient(client). + Get("http://example.com") + require.NoError(b, err) + + b.Cleanup(func() { + resp.Close() + server.stop() + }) + b.ResetTimer() b.ReportAllocs() - var err error - var resp *Response for i := 0; i < b.N; i++ { - resp, err = AcquireRequest(). - SetClient(client). - Get("http://example.com") - for k, v := range resp.Headers() { _ = k _ = v } - resp.Close() } - require.NoError(b, err) } func Test_Response_Cookie(t *testing.T) { From 7488d533402321a504af41019dde23124b7f9f05 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 18:02:10 +0300 Subject: [PATCH 07/12] fix linter --- client/response_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/client/response_test.go b/client/response_test.go index b15a5243568..dfc85c54851 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -271,7 +271,6 @@ func Benchmark_Headers(b *testing.B) { _ = k _ = v } - } } From 0343f060844af7d894de023907a05c71f0fd071f Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 18:38:40 +0300 Subject: [PATCH 08/12] create docs --- Makefile | 2 +- client/response_test.go | 6 ++- docs/client/request.md | 90 +++++++++++++++++++++++++++++++++++++++++ docs/client/response.md | 34 ++++++++++++++++ 4 files changed, 130 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 87d4b50db5d..4b348cd5744 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ coverage: format: go run mvdan.cc/gofumpt@latest -w -l . -## markdown: 🎨 Find markdown format issues (Requires markdownlint-cli) +## markdown: 🎨 Find markdown format issues (Requires markdownlint-cli2) .PHONY: markdown markdown: markdownlint-cli2 "**/*.md" "#vendor" diff --git a/client/response_test.go b/client/response_test.go index dfc85c54851..60b87bd155d 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -208,7 +208,7 @@ func Test_Response_Headers(t *testing.T) { c.Response().Header.Add("foo", "bar2") c.Response().Header.Add("foo2", "bar") - return c.SendString("helo world") + return c.SendString("hello world") }) }) defer server.stop() @@ -226,10 +226,14 @@ func Test_Response_Headers(t *testing.T) { headers[k] = append(headers[k], v...) } + require.Equal(t, "hello world", resp.String()) + require.Contains(t, headers["Foo"], "bar") require.Contains(t, headers["Foo"], "bar2") require.Contains(t, headers["Foo2"], "bar") + require.Len(t, headers, 3) // Foo + Foo2 + Date + resp.Close() } diff --git a/docs/client/request.md b/docs/client/request.md index 08a04e65e0a..38f0cb760e3 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -211,6 +211,38 @@ Header method returns header value via key, this method will visit all field in func (r *Request) Header(key string) []string ``` +### Headers + +Headers returns all headers in the request using an iterator. You can use `maps.Collect()` to collect all headers into a map. + +The returned value is valid until the request object is released. Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. + +```go title="Signature" +func (r *Request) Headers() iter.Seq2[string, []string] +``` + +```go title="Example" +req := client.AcquireRequest() + +req.AddHeader("Golang", "Fiber") +req.AddHeader("Test", "123456") +req.AddHeader("Test", "654321") + +for k, v := range req.Headers() { + fmt.Printf("Header Key: %s, Header Value: %v\n", k, v) +} +``` + +
+Click here to see the result + +```sh +Header Key: Golang, Header Value: [Fiber] +Header Key: Test, Header Value: [123456 654321] +``` + +
+ ### AddHeader AddHeader method adds a single header field and its value in the request instance. @@ -320,6 +352,15 @@ Param method returns params value via key, this method will visit all field in t func (r *Request) Param(key string) []string ``` +### Params + +Params returns all params in the request using an iterator. You can use `maps.Collect()` to collect all params into a map. +The returned value is valid until the request object is released. Any future calls to Params method will return the modified value. Do not store references to returned value. Make copies instead. + +```go title="Signature" +func (r *Request) Params() iter.Seq2[string, []string] +``` + ### AddParam AddParam method adds a single param field and its value in the request instance. @@ -502,6 +543,14 @@ Cookie returns the cookie set in the request instance. If the cookie doesn't exi func (r *Request) Cookie(key string) string ``` +### Cookies + +Cookies returns all cookies in the request using an iterator. You can use `maps.Collect()` to collect all cookies into a map. + +```go title="Signature" +func (r *Request) Cookies() iter.Seq2[string, string] +``` + ### SetCookie SetCookie method sets a single cookie field and its value in the request instance. @@ -575,6 +624,14 @@ PathParam returns the path param set in the request instance. If the path param func (r *Request) PathParam(key string) string ``` +### PathParams + +PathParams returns all path params in the request using an iterator. You can use `maps.Collect()` to collect all path params into a map. + +```go title="Signature" +func (r *Request) PathParams() iter.Seq2[string, string] +``` + ### SetPathParam SetPathParam method sets a single path param field and its value in the request instance. @@ -682,6 +739,14 @@ FormData method returns form data value via key, this method will visit all fiel func (r *Request) FormData(key string) []string ``` +### FormDatas + +FormDatas returns all form data in the request using an iterator. You can use `maps.Collect()` to collect all form data into a map. + +```go title="Signature" +func (r *Request) FormDatas() iter.Seq2[string, []string] +``` + ### AddFormData AddFormData method adds a single form data field and its value in the request instance. @@ -817,6 +882,15 @@ If the name field is empty, it will try to match path. func (r *Request) File(name string) *File ``` +### Files + +Files method returns all files in request instance. +The returned value is valid until the request object is released. Any future calls to Files method will return the modified value. Do not store references to returned value. Make copies instead. + +```go title="Signature" +func (r *Request) Files() []*File +``` + ### FileByPath FileByPath returns file ptr store in request obj by path. @@ -1063,6 +1137,14 @@ type QueryParam struct { } ``` +### Keys + +Keys method returns all keys in the query params. + +```go title="Signature" +func (p *QueryParam) Keys() []string +``` + ### AddParams AddParams receive a map and add each value to param. @@ -1242,6 +1324,14 @@ type FormData struct { } ``` +### Keys + +Keys method returns all keys in the form data. + +```go title="Signature" +func (f *FormData) Keys() []string +``` + ### AddData AddData method is a wrapper of Args's Add method. diff --git a/docs/client/response.md b/docs/client/response.md index 0dba3c6b483..99b48988d95 100644 --- a/docs/client/response.md +++ b/docs/client/response.md @@ -94,9 +94,43 @@ Header method returns the response headers. func (r *Response) Header(key string) string ``` +## Headers + +Headers returns all headers in the response using an iterator. You can use `maps.Collect()` to collect all headers into a map. +The returned value is valid until the response object is released. Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. + +```go title="Signature" +func (r *Response) Headers() iter.Seq2[string, []string] +``` + +```go title="Example" +resp, err := client.Get("https://httpbin.org/get") +if err != nil { + panic(err) +} + +for key, values := range resp.Headers() { + fmt.Printf("%s => %s\n", key, strings.Join(values, ", ")) +} +``` + +
+ +Click here to see the result + +```text +Date => Wed, 04 Dec 2024 15:28:29 GMT +Connection => keep-alive +Access-Control-Allow-Origin => * +Access-Control-Allow-Credentials => true +``` + +
+ ## Cookies Cookies method to access all the response cookies. +The returned value is valid until the response object is released. Any future calls to Cookies method will return the modified value. Do not store references to returned value. Make copies instead. ```go title="Signature" func (r *Response) Cookies() []*fasthttp.Cookie From bb43f285293116cc1e791dad34c17215c03496d6 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Wed, 4 Dec 2024 18:50:49 +0300 Subject: [PATCH 09/12] update --- docs/client/request.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/client/request.md b/docs/client/request.md index 38f0cb760e3..d2c7091f8e5 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -214,7 +214,6 @@ func (r *Request) Header(key string) []string ### Headers Headers returns all headers in the request using an iterator. You can use `maps.Collect()` to collect all headers into a map. - The returned value is valid until the request object is released. Any future calls to Headers method will return the modified value. Do not store references to returned value. Make copies instead. ```go title="Signature" From cd9b6c9889a4377e5e3486fe6651fd3323b113ed Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Mon, 9 Dec 2024 10:36:11 +0300 Subject: [PATCH 10/12] rename FormDatas -> AllFormData --- client/request.go | 4 ++-- client/request_test.go | 8 ++++---- docs/client/request.md | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/request.go b/client/request.go index 982a1390d9f..f303fb74901 100644 --- a/client/request.go +++ b/client/request.go @@ -454,12 +454,12 @@ func (r *Request) FormData(key string) []string { return res } -// FormDatas method returns all form datas in request instance. +// AllFormData method returns all form datas in request instance. // You can use maps.Collect() to collect all cookies into a map. // // The returned value is valid until the request object is released. // Any future calls to FormDatas method will return the modified value. Do not store references to returned value. Make copies instead. -func (r *Request) FormDatas() iter.Seq2[string, []string] { +func (r *Request) AllFormData() iter.Seq2[string, []string] { return func(yield func(string, []string) bool) { keys := r.formData.Keys() diff --git a/client/request_test.go b/client/request_test.go index c875c5ffd10..e663f2feeba 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -1358,7 +1358,7 @@ func Test_Request_Body_With_Server(t *testing.T) { }) } -func Test_Request_FormDatas(t *testing.T) { +func Test_Request_AllFormData(t *testing.T) { t.Parallel() req := AcquireRequest() @@ -1367,7 +1367,7 @@ func Test_Request_FormDatas(t *testing.T) { "bar": {"foo"}, }) - pathParams := maps.Collect(req.FormDatas()) + pathParams := maps.Collect(req.AllFormData()) require.Contains(t, pathParams["foo"], "bar") require.Contains(t, pathParams["foo"], "fiber") @@ -1376,7 +1376,7 @@ func Test_Request_FormDatas(t *testing.T) { require.Len(t, pathParams, 2) } -func Benchmark_Request_FormDatas(b *testing.B) { +func Benchmark_Request_AllFormData(b *testing.B) { req := AcquireRequest() req.AddFormDatas(map[string][]string{ "foo": {"bar", "fiber"}, @@ -1387,7 +1387,7 @@ func Benchmark_Request_FormDatas(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - for k, v := range req.FormDatas() { + for k, v := range req.AllFormData() { _ = k _ = v } diff --git a/docs/client/request.md b/docs/client/request.md index d2c7091f8e5..f25a9bfad69 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -738,12 +738,12 @@ FormData method returns form data value via key, this method will visit all fiel func (r *Request) FormData(key string) []string ``` -### FormDatas +### AllFormData -FormDatas returns all form data in the request using an iterator. You can use `maps.Collect()` to collect all form data into a map. +AllFormData returns all form data in the request using an iterator. You can use `maps.Collect()` to collect all form data into a map. ```go title="Signature" -func (r *Request) FormDatas() iter.Seq2[string, []string] +func (r *Request) AllFormData() iter.Seq2[string, []string] ``` ### AddFormData From 0b71163cbf90359fce4b78840e5ff807768aa454 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Mon, 9 Dec 2024 10:54:17 +0300 Subject: [PATCH 11/12] add examples for maps.Collect() --- docs/client/request.md | 23 +++++++++++++++++++++++ docs/client/response.md | 25 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/docs/client/request.md b/docs/client/request.md index f25a9bfad69..beb29d385b2 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -242,6 +242,29 @@ Header Key: Test, Header Value: [123456 654321] +```go title="Example with maps.Collect()" +req := client.AcquireRequest() + +req.AddHeader("Golang", "Fiber") +req.AddHeader("Test", "123456") +req.AddHeader("Test", "654321") + +headers := maps.Collect(req.Headers()) // Collect all headers into a map +for k, v := range headers { + fmt.Printf("Header Key: %s, Header Value: %v\n", k, v) +} +``` + +
+Click here to see the result + +```sh +Header Key: Golang, Header Value: [Fiber] +Header Key: Test, Header Value: [123456 654321] +``` + +
+ ### AddHeader AddHeader method adds a single header field and its value in the request instance. diff --git a/docs/client/response.md b/docs/client/response.md index 99b48988d95..c5f04ffad57 100644 --- a/docs/client/response.md +++ b/docs/client/response.md @@ -127,6 +127,31 @@ Access-Control-Allow-Credentials => true +```go title="Example with maps.Collect()" +resp, err := client.Get("https://httpbin.org/get") +if err != nil { + panic(err) +} + +headers := maps.Collect(resp.Headers()) // Collect all headers into a map +for key, values := range headers { + fmt.Printf("%s => %s\n", key, strings.Join(values, ", ")) +} +``` + +
+ +Click here to see the result + +```text +Date => Wed, 04 Dec 2024 15:28:29 GMT +Connection => keep-alive +Access-Control-Allow-Origin => * +Access-Control-Allow-Credentials => true +``` + +
+ ## Cookies Cookies method to access all the response cookies. From fa4c54a4751f4d736ed4094a86b3c30c2f8c24c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Tue, 10 Dec 2024 10:22:58 +0100 Subject: [PATCH 12/12] change request/response markdown examples --- docs/client/request.md | 84 ++++++++++++++++++++--------------------- docs/client/response.md | 32 ++++++++-------- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/docs/client/request.md b/docs/client/request.md index beb29d385b2..9789ea0d9a2 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -220,6 +220,9 @@ The returned value is valid until the request object is released. Any future cal func (r *Request) Headers() iter.Seq2[string, []string] ``` +
+Example + ```go title="Example" req := client.AcquireRequest() @@ -232,9 +235,6 @@ for k, v := range req.Headers() { } ``` -
-Click here to see the result - ```sh Header Key: Golang, Header Value: [Fiber] Header Key: Test, Header Value: [123456 654321] @@ -242,6 +242,9 @@ Header Key: Test, Header Value: [123456 654321]
+
+Example with maps.Collect() + ```go title="Example with maps.Collect()" req := client.AcquireRequest() @@ -255,9 +258,6 @@ for k, v := range headers { } ``` -
-Click here to see the result - ```sh Header Key: Golang, Header Value: [Fiber] Header Key: Test, Header Value: [123456 654321] @@ -273,6 +273,9 @@ AddHeader method adds a single header field and its value in the request instanc func (r *Request) AddHeader(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -289,9 +292,6 @@ if err != nil { fmt.Println(resp.String()) ``` -
-Click here to see the result - ```json { "headers": { @@ -316,6 +316,9 @@ It will override the header which has been set in the client instance. func (r *Request) SetHeader(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -331,9 +334,6 @@ if err != nil { fmt.Println(resp.String()) ``` -
-Click here to see the result - ```json { "headers": { @@ -391,6 +391,9 @@ AddParam method adds a single param field and its value in the request instance. func (r *Request) AddParam(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -407,9 +410,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "Content-Length": "145", @@ -459,6 +459,9 @@ It will override param, which has been set in client instance. func (r *Request) SetParamsWithStruct(v any) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -482,9 +485,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "Content-Length": "147", @@ -591,6 +591,9 @@ It will override the cookie which is set in the client instance. func (r *Request) SetCookies(m map[string]string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -608,9 +611,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "cookies": { @@ -663,6 +663,9 @@ It will override path param which set in client instance. func (r *Request) SetPathParam(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -677,9 +680,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```plaintext Gofiber ``` @@ -777,6 +777,9 @@ AddFormData method adds a single form data field and its value in the request in func (r *Request) AddFormData(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -793,9 +796,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -822,6 +822,9 @@ SetFormData method sets a single form data field and its value in the request in func (r *Request) SetFormData(key, val string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -837,9 +840,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -929,6 +929,9 @@ AddFile method adds a single file field and its value in the request instance vi func (r *Request) AddFile(path string) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -943,9 +946,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -968,6 +968,9 @@ AddFileWithReader method adds a single field and its value in the request instan func (r *Request) AddFileWithReader(name string, reader io.ReadCloser) *Request ``` +
+Example + ```go title="Example" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -983,9 +986,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -1025,6 +1025,9 @@ It will override timeout which set in client instance. func (r *Request) SetTimeout(t time.Duration) *Request ``` +
+Example 1 + ```go title="Example 1" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -1039,9 +1042,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```json { "args": {}, @@ -1054,6 +1054,9 @@ fmt.Println(string(resp.Body()))
+
+Example 2 + ```go title="Example 2" req := client.AcquireRequest() defer client.ReleaseRequest(req) @@ -1068,9 +1071,6 @@ if err != nil { fmt.Println(string(resp.Body())) ``` -
-Click here to see the result - ```shell panic: timeout or cancel diff --git a/docs/client/response.md b/docs/client/response.md index c5f04ffad57..135970069ca 100644 --- a/docs/client/response.md +++ b/docs/client/response.md @@ -68,6 +68,9 @@ Protocol method returns the HTTP response protocol used for the request. func (r *Response) Protocol() string ``` +
+Example + ```go title="Example" resp, err := client.Get("https://httpbin.org/get") if err != nil { @@ -77,9 +80,6 @@ if err != nil { fmt.Println(resp.Protocol()) ``` -
-Click here to see the result - ```text HTTP/1.1 ``` @@ -103,6 +103,9 @@ The returned value is valid until the response object is released. Any future ca func (r *Response) Headers() iter.Seq2[string, []string] ``` +
+Example + ```go title="Example" resp, err := client.Get("https://httpbin.org/get") if err != nil { @@ -114,10 +117,6 @@ for key, values := range resp.Headers() { } ``` -
- -Click here to see the result - ```text Date => Wed, 04 Dec 2024 15:28:29 GMT Connection => keep-alive @@ -127,6 +126,9 @@ Access-Control-Allow-Credentials => true
+
+Example with maps.Collect() + ```go title="Example with maps.Collect()" resp, err := client.Get("https://httpbin.org/get") if err != nil { @@ -139,10 +141,6 @@ for key, values := range headers { } ``` -
- -Click here to see the result - ```text Date => Wed, 04 Dec 2024 15:28:29 GMT Connection => keep-alive @@ -161,6 +159,9 @@ The returned value is valid until the response object is released. Any future ca func (r *Response) Cookies() []*fasthttp.Cookie ``` +
+Example + ```go title="Example" resp, err := client.Get("https://httpbin.org/cookies/set/go/fiber") if err != nil { @@ -173,9 +174,6 @@ for _, cookie := range cookies { } ``` -
-Click here to see the result - ```text go => fiber ``` @@ -206,6 +204,9 @@ JSON method will unmarshal body to json. func (r *Response) JSON(v any) error ``` +
+Example + ```go title="Example" type Body struct { Slideshow struct { @@ -229,9 +230,6 @@ if err != nil { fmt.Printf("%+v\n", out) ``` -
-Click here to see the result - ```text {Slideshow:{Author:Yours Truly Date:date of publication Title:Sample Slide Show}} ```