From 7f7fb38cd69b52a4cbecd86be58f03920be2468a Mon Sep 17 00:00:00 2001 From: imsk17 Date: Mon, 21 Oct 2024 03:34:14 +0530 Subject: [PATCH 01/17] feat(cbor): allow encoding response bodies in cbor --- app.go | 22 ++++++++- constants.go | 1 + ctx.go | 18 +++++++ ctx_interface_gen.go | 5 ++ ctx_test.go | 81 ++++++++++++++++++++++++++++++++ go.mod | 4 +- go.sum | 8 +++- middleware/cache/manager_msgp.go | 6 +++ 8 files changed, 141 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 38a3d17319..3a734ecc23 100644 --- a/app.go +++ b/app.go @@ -23,9 +23,9 @@ import ( "sync" "time" + "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3/log" "github.com/gofiber/utils/v2" - "github.com/valyala/fasthttp" ) @@ -318,6 +318,20 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa // Default: json.Unmarshal JSONDecoder utils.JSONUnmarshal `json:"-"` + // When set by an external client of Fiber it will use the provided implementation of a + // CBORMarshal + // + // Allowing for flexibility in using another cbor library for encoding + // Default: cbor.Marshal + CBOREncoder utils.CBORMarshal `json:"-"` + + // When set by an external client of Fiber it will use the provided implementation of a + // CBORUnmarshal + // + // Allowing for flexibility in using another cbor library for decoding + // Default: cbor.Unmarshal + CBORDecoder utils.CBORUnmarshal `json:"-"` + // XMLEncoder set by an external client of Fiber it will use the provided implementation of a // XMLMarshal // @@ -535,6 +549,12 @@ func New(config ...Config) *App { if app.config.JSONDecoder == nil { app.config.JSONDecoder = json.Unmarshal } + if app.config.CBOREncoder == nil { + app.config.CBOREncoder = cbor.Marshal + } + if app.config.CBORDecoder == nil { + app.config.CBORDecoder = cbor.Unmarshal + } if app.config.XMLEncoder == nil { app.config.XMLEncoder = xml.Marshal } diff --git a/constants.go b/constants.go index 4717204094..782195a2c0 100644 --- a/constants.go +++ b/constants.go @@ -23,6 +23,7 @@ const ( MIMETextCSS = "text/css" MIMEApplicationXML = "application/xml" MIMEApplicationJSON = "application/json" + MIMEApplicationCBOR = "application/cbor" // Deprecated: use MIMETextJavaScript instead MIMEApplicationJavaScript = "application/javascript" MIMEApplicationForm = "application/x-www-form-urlencoded" diff --git a/ctx.go b/ctx.go index 9e61d0903e..b80c8e3ca9 100644 --- a/ctx.go +++ b/ctx.go @@ -883,6 +883,24 @@ func (c *DefaultCtx) JSON(data any, ctype ...string) error { return nil } +// CBOR converts any interface or string to cbor encoded bytes. +// 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/cbor. +func (c *DefaultCtx) CBOR(data any, ctype ...string) error { + raw, err := c.app.config.CBOREncoder(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(MIMEApplicationCBOR) + } + 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. diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go index aa317ecb00..f9b5976e81 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -163,6 +163,11 @@ type Ctx interface { // Content-Type header equal to ctype. If ctype is not given, // The Content-Type header will be set to application/json. JSON(data any, ctype ...string) error + // CBOR converts any interface or string to cbor encoded bytes. + // 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/cbor. + CBOR(data any, ctype ...string) error // 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. diff --git a/ctx_test.go b/ctx_test.go index eef29a97ad..624d4983f5 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -12,6 +12,7 @@ import ( "context" "crypto/tls" "embed" + "encoding/hex" "encoding/xml" "errors" "fmt" @@ -3572,6 +3573,86 @@ func Benchmark_Ctx_JSON(b *testing.B) { require.Equal(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body())) } +// go test -run Test_Ctx_CBOR +func Test_Ctx_CBOR(t *testing.T) { + t.Parallel() + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + require.Error(t, c.CBOR(complex(1, 1))) + + // Test without ctype + err := c.CBOR(Map{ // map has no order + "Name": "Grame", + "Age": 20, + }) + require.NoError(t, err) + require.Equal(t, `a2644e616d65654772616d656341676514`, hex.EncodeToString(c.Response().Body())) + require.Equal(t, "application/cbor", string(c.Response().Header.Peek("content-type"))) + + // Test with ctype + err = c.CBOR(Map{ // map has no order + "Name": "Grame", + "Age": 20, + }, "application/problem+cbor") + require.NoError(t, err) + require.Equal(t, `a26341676514644e616d65654772616d65`, hex.EncodeToString(c.Response().Body())) + require.Equal(t, "application/problem+cbor", string(c.Response().Header.Peek("content-type"))) + + testEmpty := func(v any, r string) { + err := c.CBOR(v) + require.NoError(t, err) + require.Equal(t, r, hex.EncodeToString(c.Response().Body())) + } + + testEmpty(nil, "f6") + testEmpty("", `60`) + testEmpty(0, "00") + testEmpty([]int{}, "80") + + t.Run("custom cbor encoder", func(t *testing.T) { + t.Parallel() + + app := New(Config{ + CBOREncoder: func(_ any) ([]byte, error) { + return []byte(hex.EncodeToString([]byte("random"))), nil + }, + }) + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + err := c.CBOR(Map{ // map has no order + "Name": "Grame", + "Age": 20, + }) + require.NoError(t, err) + require.Equal(t, `72616e646f6d`, string(c.Response().Body())) + require.Equal(t, "application/cbor", string(c.Response().Header.Peek("content-type"))) + }) +} + +// go test -run=^$ -bench=Benchmark_Ctx_CBOR -benchmem -count=4 +func Benchmark_Ctx_CBOR(b *testing.B) { + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + type SomeStruct struct { + Name string + Age uint8 + } + data := SomeStruct{ + Name: "Grame", + Age: 20, + } + var err error + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + err = c.CBOR(data) + } + require.NoError(b, err) + require.Equal(b, `a2644e616d65654772616d656341676514`, hex.EncodeToString(c.Response().Body())) +} + // go test -run=^$ -bench=Benchmark_Ctx_JSON_Ctype -benchmem -count=4 func Benchmark_Ctx_JSON_Ctype(b *testing.B) { app := New() diff --git a/go.mod b/go.mod index 8f3a2a43d3..ccd9a10a89 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22 require ( github.com/gofiber/schema v1.2.0 - github.com/gofiber/utils/v2 v2.0.0-beta.6 + github.com/gofiber/utils/v2 v2.0.0-beta.7 github.com/google/uuid v1.6.0 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.20 @@ -17,10 +17,12 @@ require ( require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // direct github.com/klauspost/compress v1.17.9 // indirect github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + github.com/x448/float16 v0.8.4 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect diff --git a/go.sum b/go.sum index 1d53c4560b..42768451af 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,12 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gofiber/schema v1.2.0 h1:j+ZRrNnUa/0ZuWrn/6kAtAufEr4jCJ+JuTURAMxNSZg= github.com/gofiber/schema v1.2.0/go.mod h1:YYwj01w3hVfaNjhtJzaqetymL56VW642YS3qZPhuE6c= -github.com/gofiber/utils/v2 v2.0.0-beta.6 h1:ED62bOmpRXdgviPlfTmf0Q+AXzhaTUAFtdWjgx+XkYI= -github.com/gofiber/utils/v2 v2.0.0-beta.6/go.mod h1:3Kz8Px3jInKFvqxDzDeoSygwEOO+3uyubTmUa6PqY+0= +github.com/gofiber/utils/v2 v2.0.0-beta.7 h1:NnHFrRHvhrufPABdWajcKZejz9HnCWmT/asoxRsiEbQ= +github.com/gofiber/utils/v2 v2.0.0-beta.7/go.mod h1:J/M03s+HMdZdvhAeyh76xT72IfVqBzuz/OJkrMa7cwU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= @@ -29,6 +31,8 @@ github.com/valyala/fasthttp v1.56.0 h1:bEZdJev/6LCBlpdORfrLu/WOZXXxvrUQSiyniuaoW github.com/valyala/fasthttp v1.56.0/go.mod h1:sReBt3XZVnudxuLOx4J/fMrJVorWRiWY2koQKgABiVI= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/middleware/cache/manager_msgp.go b/middleware/cache/manager_msgp.go index bf5d615200..492e9a88bd 100644 --- a/middleware/cache/manager_msgp.go +++ b/middleware/cache/manager_msgp.go @@ -52,6 +52,9 @@ func (z *item) DecodeMsg(dc *msgp.Reader) (err error) { err = msgp.WrapError(err, "headers", za0001) return } + if za0002 == nil { + za0002 = make([]byte, 0) + } z.headers[za0001] = za0002 } case "body": @@ -267,6 +270,9 @@ func (z *item) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "headers", za0001) return } + if za0002 == nil { + za0002 = make([]byte, 0) + } z.headers[za0001] = za0002 } case "body": From ea592678ac4c18738b60fdae7d931e0d99700808 Mon Sep 17 00:00:00 2001 From: imsk17 Date: Mon, 21 Oct 2024 03:54:32 +0530 Subject: [PATCH 02/17] fix(tests::cbor): encode struct instead of a randomly ordered hashmap --- ctx_test.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ctx_test.go b/ctx_test.go index 624d4983f5..2d180a8988 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -3581,22 +3581,27 @@ func Test_Ctx_CBOR(t *testing.T) { require.Error(t, c.CBOR(complex(1, 1))) + type dummyStruct struct { + Name string + Age int + } + // Test without ctype - err := c.CBOR(Map{ // map has no order - "Name": "Grame", - "Age": 20, + err := c.CBOR(dummyStruct{ // map has no order + Name: "Grame", + Age: 20, }) require.NoError(t, err) require.Equal(t, `a2644e616d65654772616d656341676514`, hex.EncodeToString(c.Response().Body())) require.Equal(t, "application/cbor", string(c.Response().Header.Peek("content-type"))) // Test with ctype - err = c.CBOR(Map{ // map has no order - "Name": "Grame", - "Age": 20, + err = c.CBOR(dummyStruct{ // map has no order + Name: "Grame", + Age: 20, }, "application/problem+cbor") require.NoError(t, err) - require.Equal(t, `a26341676514644e616d65654772616d65`, hex.EncodeToString(c.Response().Body())) + require.Equal(t, `a2644e616d65654772616d656341676514`, hex.EncodeToString(c.Response().Body())) require.Equal(t, "application/problem+cbor", string(c.Response().Header.Peek("content-type"))) testEmpty := func(v any, r string) { From db004689ba88a752ce28517d102d0c58c02ddf80 Mon Sep 17 00:00:00 2001 From: imsk17 Date: Mon, 21 Oct 2024 04:25:29 +0530 Subject: [PATCH 03/17] docs(whats_new): add cbor in context section --- docs/whats_new.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/whats_new.md b/docs/whats_new.md index e040f367d5..07f0b65024 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -196,6 +196,8 @@ DRAFT section - Cookie now allows Partitioned cookies for [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips) support. CHIPS (Cookies Having Independent Partitioned State) is a feature that improves privacy by allowing cookies to be partitioned by top-level site, mitigating cross-site tracking. +- Introducing [CBOR](https://cbor.io/) binary encoding format for response body. CBOR is a binary data serialization format that is both compact and efficient, making it ideal for use in web applications. + ### new methods - AutoFormat -> ExpressJs like @@ -208,6 +210,7 @@ DRAFT section - SendString -> ExpressJs like - String -> ExpressJs like - ViewBind -> instead of Bind +- CBOR -> for CBOR encoding ### removed methods From ba2330517a775831a0484f4b7022f142072a97b2 Mon Sep 17 00:00:00 2001 From: imsk17 Date: Mon, 21 Oct 2024 05:15:14 +0530 Subject: [PATCH 04/17] feat(binder): introduce CBOR --- bind.go | 9 +++++++++ bind_test.go | 36 ++++++++++++++++++++++++++++++++++++ binder/binder.go | 1 + binder/cbor.go | 15 +++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 binder/cbor.go diff --git a/bind.go b/bind.go index e202cd85e0..a02e16c117 100644 --- a/bind.go +++ b/bind.go @@ -120,6 +120,13 @@ func (b *Bind) JSON(out any) error { return b.validateStruct(out) } +func (b *Bind) CBOR(out any) error { + if err := b.returnErr(binder.CBORBinder.Bind(b.ctx.Body(), b.ctx.App().Config().CBORDecoder, out)); err != nil { + return err + } + return b.validateStruct(out) +} + // XML binds the body string into the struct. func (b *Bind) XML(out any) error { if err := b.returnErr(binder.XMLBinder.Bind(b.ctx.Body(), out)); err != nil { @@ -182,6 +189,8 @@ func (b *Bind) Body(out any) error { return b.JSON(out) case MIMETextXML, MIMEApplicationXML: return b.XML(out) + case MIMEApplicationCBOR: + return b.CBOR(out) case MIMEApplicationForm: return b.Form(out) case MIMEMultipartForm: diff --git a/bind_test.go b/bind_test.go index aa00e191ca..b3ec8f2723 100644 --- a/bind_test.go +++ b/bind_test.go @@ -12,6 +12,7 @@ import ( "testing" "time" + "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3/binder" "github.com/stretchr/testify/require" "github.com/valyala/fasthttp" @@ -927,6 +928,11 @@ func Test_Bind_Body(t *testing.T) { t.Run("JSON", func(t *testing.T) { testDecodeParser(t, MIMEApplicationJSON, `{"name":"john"}`) }) + t.Run("CBOR", func(t *testing.T) { + enc, _ := cbor.Marshal(&Demo{Name: "john"}) + str := string(enc) + testDecodeParser(t, MIMEApplicationCBOR, str) + }) t.Run("XML", func(t *testing.T) { testDecodeParser(t, MIMEApplicationXML, `john`) @@ -1091,6 +1097,32 @@ func Benchmark_Bind_Body_XML(b *testing.B) { require.Equal(b, "john", d.Name) } +// go test -v -run=^$ -bench=Benchmark_Bind_Body_CBOR -benchmem -count=4 +func Benchmark_Bind_Body_CBOR(b *testing.B) { + var err error + + app := New() + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + + type Demo struct { + Name string `json:"name"` + } + body, _ := cbor.Marshal(&Demo{Name: "john"}) + c.Request().SetBody(body) + c.Request().Header.SetContentType(MIMEApplicationCBOR) + c.Request().Header.SetContentLength(len(body)) + d := new(Demo) + + b.ReportAllocs() + b.ResetTimer() + + for n := 0; n < b.N; n++ { + err = c.Bind().Body(d) + } + require.NoError(b, err) + require.Equal(b, "john", d.Name) +} + // go test -v -run=^$ -bench=Benchmark_Bind_Body_Form -benchmem -count=4 func Benchmark_Bind_Body_Form(b *testing.B) { var err error @@ -1710,9 +1742,13 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) { require.NoError(t, c.Bind().Body(r)) require.Equal(t, "body_param", r.BodyParam) } + cb, _ := cbor.Marshal(&Request{ + BodyParam: "body_param", + }) testDecodeParser(MIMEApplicationJSON, `{"body_param":"body_param"}`) testDecodeParser(MIMEApplicationXML, `body_param`) + testDecodeParser(MIMEApplicationCBOR, string(cb)) testDecodeParser(MIMEApplicationForm, "body_param=body_param") testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"body_param\"\r\n\r\nbody_param\r\n--b--") } diff --git a/binder/binder.go b/binder/binder.go index fb7ac12dab..bb3fc2b394 100644 --- a/binder/binder.go +++ b/binder/binder.go @@ -20,4 +20,5 @@ var ( URIBinder = &uriBinding{} XMLBinder = &xmlBinding{} JSONBinder = &jsonBinding{} + CBORBinder = &cborBinding{} ) diff --git a/binder/cbor.go b/binder/cbor.go new file mode 100644 index 0000000000..197d2fc9b5 --- /dev/null +++ b/binder/cbor.go @@ -0,0 +1,15 @@ +package binder + +import ( + "github.com/gofiber/utils/v2" +) + +type cborBinding struct{} + +func (*cborBinding) Name() string { + return "json" +} + +func (*cborBinding) Bind(body []byte, cborDecoder utils.CBORUnmarshal, out any) error { + return cborDecoder(body, out) +} From 7ee95484c89302dea7fa7abf2faf94c1209ff941 Mon Sep 17 00:00:00 2001 From: imsk17 Date: Wed, 23 Oct 2024 00:20:04 +0530 Subject: [PATCH 05/17] feat(client): allow cbor in fiber client --- client/client.go | 24 ++++++++++++++++++++++++ client/client_test.go | 25 +++++++++++++++++++++++++ client/hooks.go | 3 +++ client/request.go | 7 +++++++ client/response.go | 5 +++++ 5 files changed, 64 insertions(+) diff --git a/client/client.go b/client/client.go index d9b9c84c63..51fe0b989d 100644 --- a/client/client.go +++ b/client/client.go @@ -44,6 +44,8 @@ type Client struct { jsonUnmarshal utils.JSONUnmarshal xmlMarshal utils.XMLMarshal xmlUnmarshal utils.XMLUnmarshal + cborMarshal utils.CBORMarshal + cborUnmarshal utils.CBORUnmarshal cookieJar *CookieJar @@ -150,6 +152,28 @@ func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client { return c } +// CBORMarshal returns xml marshal function in Core. +func (c *Client) CBORMarshal() utils.CBORMarshal { + return c.cborMarshal +} + +// SetCBORMarshal Set xml encoder. +func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client { + c.cborMarshal = f + return c +} + +// CBORUnmarshal returns xml unmarshal function in Core. +func (c *Client) CBORUnmarshal() utils.CBORUnmarshal { + return c.cborUnmarshal +} + +// SetCBORUnmarshal Set xml decoder. +func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client { + c.cborUnmarshal = f + return c +} + // TLSConfig returns tlsConfig in client. // If client don't have tlsConfig, this function will init it. func (c *Client) TLSConfig() *tls.Config { diff --git a/client/client_test.go b/client/client_test.go index b8dd39bbf0..676d4a668d 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -3,6 +3,7 @@ package client import ( "context" "crypto/tls" + "encoding/hex" "errors" "io" "net" @@ -202,6 +203,30 @@ func Test_Client_Marshal(t *testing.T) { require.Equal(t, errors.New("empty xml"), err) }) + t.Run("set cbor marshal", func(t *testing.T) { + t.Parallel() + bs, _ := hex.DecodeString("f6") + client := New(). + SetCBORMarshal(func(_ any) ([]byte, error) { + return bs, nil + }) + val, err := client.CBORMarshal()(nil) + + require.NoError(t, err) + require.Equal(t, bs, val) + }) + + t.Run("set cbor marshal error", func(t *testing.T) { + t.Parallel() + client := New().SetCBORMarshal(func(v any) ([]byte, error) { + return nil, errors.New("invalid struct") + }) + + val, err := client.CBORMarshal()(nil) + require.Nil(t, val) + require.Equal(t, errors.New("invalid struct"), err) + }) + t.Run("set xml unmarshal", func(t *testing.T) { t.Parallel() client := New(). diff --git a/client/hooks.go b/client/hooks.go index f11f9865f3..1d856eaff5 100644 --- a/client/hooks.go +++ b/client/hooks.go @@ -23,6 +23,7 @@ var ( headerAccept = "Accept" applicationJSON = "application/json" + applicationCBOR = "application/cbor" applicationXML = "application/xml" applicationForm = "application/x-www-form-urlencoded" multipartFormData = "multipart/form-data" @@ -129,6 +130,8 @@ func parserRequestHeader(c *Client, req *Request) error { req.RawRequest.Header.Set(headerAccept, applicationJSON) case xmlBody: req.RawRequest.Header.SetContentType(applicationXML) + case cborBody: + req.RawRequest.Header.SetContentType(applicationCBOR) case formBody: req.RawRequest.Header.SetContentType(applicationForm) case filesBody: diff --git a/client/request.go b/client/request.go index 61b5798c57..724358600a 100644 --- a/client/request.go +++ b/client/request.go @@ -34,6 +34,7 @@ const ( formBody filesBody rawBody + cborBody ) var ErrClientNil = errors.New("client can not be nil") @@ -337,6 +338,12 @@ func (r *Request) SetXML(v any) *Request { return r } +func (r *Request) SetCBOR(v any) *Request { + r.body = v + r.bodyType = cborBody + return r +} + // SetRawBody method sets body with raw data in request. func (r *Request) SetRawBody(v []byte) *Request { r.body = v diff --git a/client/response.go b/client/response.go index a8a032b6c5..f21290f764 100644 --- a/client/response.go +++ b/client/response.go @@ -75,6 +75,11 @@ func (r *Response) JSON(v any) error { return r.client.jsonUnmarshal(r.Body(), v) } +// CBOR method will unmarshal body to cbor. +func (r *Response) CBOR(v any) error { + return r.client.cborUnmarshal(r.Body(), v) +} + // XML method will unmarshal body to xml. func (r *Response) XML(v any) error { return r.client.xmlUnmarshal(r.Body(), v) From ae8407c8f0eb4e5d2ccf8e55041d7b7b47c11a29 Mon Sep 17 00:00:00 2001 From: imsk17 Date: Wed, 6 Nov 2024 21:09:09 +0530 Subject: [PATCH 06/17] chore(tests): add more test --- bind_test.go | 15 ++++++++++++--- client/client_test.go | 7 +++++-- client/hooks.go | 6 ++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/bind_test.go b/bind_test.go index b3ec8f2723..c916475078 100644 --- a/bind_test.go +++ b/bind_test.go @@ -929,7 +929,10 @@ func Test_Bind_Body(t *testing.T) { testDecodeParser(t, MIMEApplicationJSON, `{"name":"john"}`) }) t.Run("CBOR", func(t *testing.T) { - enc, _ := cbor.Marshal(&Demo{Name: "john"}) + enc, err := cbor.Marshal(&Demo{Name: "john"}) + if err != nil { + t.Error(err) + } str := string(enc) testDecodeParser(t, MIMEApplicationCBOR, str) }) @@ -1107,7 +1110,10 @@ func Benchmark_Bind_Body_CBOR(b *testing.B) { type Demo struct { Name string `json:"name"` } - body, _ := cbor.Marshal(&Demo{Name: "john"}) + body, err := cbor.Marshal(&Demo{Name: "john"}) + if err != nil { + b.Error(err) + } c.Request().SetBody(body) c.Request().Header.SetContentType(MIMEApplicationCBOR) c.Request().Header.SetContentLength(len(body)) @@ -1742,9 +1748,12 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) { require.NoError(t, c.Bind().Body(r)) require.Equal(t, "body_param", r.BodyParam) } - cb, _ := cbor.Marshal(&Request{ + cb, err := cbor.Marshal(&Request{ BodyParam: "body_param", }) + if err != nil { + t.Error(err) + } testDecodeParser(MIMEApplicationJSON, `{"body_param":"body_param"}`) testDecodeParser(MIMEApplicationXML, `body_param`) diff --git a/client/client_test.go b/client/client_test.go index 676d4a668d..17a58adb54 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -205,7 +205,10 @@ func Test_Client_Marshal(t *testing.T) { t.Run("set cbor marshal", func(t *testing.T) { t.Parallel() - bs, _ := hex.DecodeString("f6") + bs, err := hex.DecodeString("f6") + if err != nil { + t.Error(err) + } client := New(). SetCBORMarshal(func(_ any) ([]byte, error) { return bs, nil @@ -218,7 +221,7 @@ func Test_Client_Marshal(t *testing.T) { t.Run("set cbor marshal error", func(t *testing.T) { t.Parallel() - client := New().SetCBORMarshal(func(v any) ([]byte, error) { + client := New().SetCBORMarshal(func(_ any) ([]byte, error) { return nil, errors.New("invalid struct") }) diff --git a/client/hooks.go b/client/hooks.go index 1d856eaff5..ec3987938e 100644 --- a/client/hooks.go +++ b/client/hooks.go @@ -192,6 +192,12 @@ func parserRequestBody(c *Client, req *Request) error { return err } req.RawRequest.SetBody(body) + case cborBody: + body, err := c.cborMarshal(req.body) + if err != nil { + return err + } + req.RawRequest.SetBody(body) case formBody: req.RawRequest.SetBody(req.formData.QueryString()) case filesBody: From b316b36231fb8de49e58f00cbbbd18eb41647ce5 Mon Sep 17 00:00:00 2001 From: imsk17 Date: Fri, 8 Nov 2024 00:05:38 +0530 Subject: [PATCH 07/17] chore(packages): go mod tidy --- go.mod | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index e9aaf2f02c..559556367f 100644 --- a/go.mod +++ b/go.mod @@ -18,13 +18,13 @@ require ( github.com/andybalholm/brotli v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // direct - github.com/klauspost/compress v1.17.9 // indirect - github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) From a8192d194945063c6c675769323c24a9d6fdd0d5 Mon Sep 17 00:00:00 2001 From: imsk17 Date: Fri, 8 Nov 2024 00:12:47 +0530 Subject: [PATCH 08/17] fix(binder): update CBOR name and test --- bind_test.go | 15 +++++++-------- binder/cbor.go | 2 +- client/client.go | 3 +++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bind_test.go b/bind_test.go index c916475078..f13238918d 100644 --- a/bind_test.go +++ b/bind_test.go @@ -914,11 +914,11 @@ func Test_Bind_Body(t *testing.T) { testCompressedBody(t, compressedBody, "zstd") }) - testDecodeParser := func(t *testing.T, contentType, body string) { + testDecodeParser := func(t *testing.T, contentType string, body []byte) { t.Helper() c := app.AcquireCtx(&fasthttp.RequestCtx{}) c.Request().Header.SetContentType(contentType) - c.Request().SetBody([]byte(body)) + c.Request().SetBody(body) c.Request().Header.SetContentLength(len(body)) d := new(Demo) require.NoError(t, c.Bind().Body(d)) @@ -926,27 +926,26 @@ func Test_Bind_Body(t *testing.T) { } t.Run("JSON", func(t *testing.T) { - testDecodeParser(t, MIMEApplicationJSON, `{"name":"john"}`) + testDecodeParser(t, MIMEApplicationJSON, []byte(`{"name":"john"}`)) }) t.Run("CBOR", func(t *testing.T) { enc, err := cbor.Marshal(&Demo{Name: "john"}) if err != nil { t.Error(err) } - str := string(enc) - testDecodeParser(t, MIMEApplicationCBOR, str) + testDecodeParser(t, MIMEApplicationCBOR, enc) }) t.Run("XML", func(t *testing.T) { - testDecodeParser(t, MIMEApplicationXML, `john`) + testDecodeParser(t, MIMEApplicationXML, []byte(`john`)) }) t.Run("Form", func(t *testing.T) { - testDecodeParser(t, MIMEApplicationForm, "name=john") + testDecodeParser(t, MIMEApplicationForm, []byte("name=john")) }) t.Run("MultipartForm", func(t *testing.T) { - testDecodeParser(t, MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--") + testDecodeParser(t, MIMEMultipartForm+`;boundary="b"`, []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")) }) testDecodeParserError := func(t *testing.T, contentType, body string) { diff --git a/binder/cbor.go b/binder/cbor.go index 197d2fc9b5..12ed13715c 100644 --- a/binder/cbor.go +++ b/binder/cbor.go @@ -7,7 +7,7 @@ import ( type cborBinding struct{} func (*cborBinding) Name() string { - return "json" + return "cbor" } func (*cborBinding) Bind(body []byte, cborDecoder utils.CBORUnmarshal, out any) error { diff --git a/client/client.go b/client/client.go index 51fe0b989d..3665ff21e6 100644 --- a/client/client.go +++ b/client/client.go @@ -13,6 +13,7 @@ import ( "sync" "time" + "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3/log" "github.com/gofiber/utils/v2" @@ -722,6 +723,8 @@ func New() *Client { jsonMarshal: json.Marshal, jsonUnmarshal: json.Unmarshal, xmlMarshal: xml.Marshal, + cborMarshal: cbor.Marshal, + cborUnmarshal: cbor.Unmarshal, xmlUnmarshal: xml.Unmarshal, logger: log.DefaultLogger(), } From 697243bbe10023e9b1f2c70564af601b78fcfef0 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 29 Nov 2024 10:52:30 +0300 Subject: [PATCH 09/17] improve test coverage --- bind_test.go | 1 + client/hooks_test.go | 25 ++++++++++++++++++++++++ client/response_test.go | 42 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/bind_test.go b/bind_test.go index f13238918d..581c8bb486 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1750,6 +1750,7 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) { cb, err := cbor.Marshal(&Request{ BodyParam: "body_param", }) + if err != nil { t.Error(err) } diff --git a/client/hooks_test.go b/client/hooks_test.go index 8e91392930..8a02c7ab49 100644 --- a/client/hooks_test.go +++ b/client/hooks_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -456,6 +457,30 @@ func Test_Parser_Request_Body(t *testing.T) { require.Equal(t, []byte("foo"), req.RawRequest.Body()) }) + t.Run("CBOR body", func(t *testing.T) { + t.Parallel() + type cborData struct { + Name string `cbor:"name"` + Age int `cbor:"age"` + } + + data := cborData{ + Name: "foo", + Age: 12, + } + + client := New() + req := AcquireRequest(). + SetCBOR(data) + + err := parserRequestBody(client, req) + require.NoError(t, err) + + encoded, err := cbor.Marshal(data) + require.NoError(t, err) + require.Equal(t, encoded, req.RawRequest.Body()) + }) + t.Run("form data body", func(t *testing.T) { t.Parallel() client := New() diff --git a/client/response_test.go b/client/response_test.go index 0d27ee7ed4..bf12e75161 100644 --- a/client/response_test.go +++ b/client/response_test.go @@ -240,6 +240,18 @@ func Test_Response_Body(t *testing.T) { app.Get("/xml", func(c fiber.Ctx) error { return c.SendString("success") }) + + app.Get("/cbor", func(c fiber.Ctx) error { + type cborData struct { + Name string `cbor:"name"` + Age int `cbor:"age"` + } + + return c.CBOR(cborData{ + Name: "foo", + Age: 12, + }) + }) }) return server @@ -327,6 +339,36 @@ func Test_Response_Body(t *testing.T) { require.Equal(t, "success", tmp.Status) resp.Close() }) + + t.Run("cbor body", func(t *testing.T) { + t.Parallel() + type cborData struct { + Name string `cbor:"name"` + Age int `cbor:"age"` + } + + data := cborData{ + Name: "foo", + Age: 12, + } + + server := setupApp() + defer server.stop() + + client := New().SetDial(server.dial()) + + resp, err := AcquireRequest(). + SetClient(client). + Get("http://example.com/cbor") + + require.NoError(t, err) + + tmp := &cborData{} + err = resp.CBOR(tmp) + require.NoError(t, err) + require.Equal(t, data, *tmp) + resp.Close() + }) } func Test_Response_Save(t *testing.T) { From 6dfc59fde7a1c599222aa1fa2167b30df8388dba Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 29 Nov 2024 11:02:31 +0300 Subject: [PATCH 10/17] improve test coverage --- client/client_test.go | 4 +++- client/request_test.go | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index c91c1da5db..81fa2a90c8 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1448,7 +1448,9 @@ func Test_Set_Config_To_Request(t *testing.T) { t.Run("set ctx", func(t *testing.T) { t.Parallel() - key := struct{}{} + + type ctxKey struct{} + var key ctxKey = struct{}{} ctx := context.Background() ctx = context.WithValue(ctx, key, "v1") //nolint: staticcheck // not needed for tests diff --git a/client/request_test.go b/client/request_test.go index 0593d04824..eb690f8fff 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -80,7 +80,8 @@ func Test_Request_Context(t *testing.T) { req := AcquireRequest() ctx := req.Context() - key := struct{}{} + type ctxKey struct{} + var key ctxKey = struct{}{} require.Nil(t, ctx.Value(key)) @@ -1006,6 +1007,25 @@ func Test_Request_Body_With_Server(t *testing.T) { ) }) + t.Run("cbor body", func(t *testing.T) { + t.Parallel() + testRequest(t, + func(c fiber.Ctx) error { + require.Equal(t, "application/cbor", string(c.Request().Header.ContentType())) + return c.SendString(string(c.Request().Body())) + }, + func(agent *Request) { + type args struct { + Content string `cbor:"content"` + } + agent.SetCBOR(args{ + Content: "hello", + }) + }, + "\xa1gcontentehello", + ) + }) + t.Run("formdata", func(t *testing.T) { t.Parallel() testRequest(t, From 23ef5798bfb48b8d70535dc7f2b7f02e58e1bdc5 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 29 Nov 2024 11:05:02 +0300 Subject: [PATCH 11/17] update1 --- ctx_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ctx_test.go b/ctx_test.go index cdab326396..1a9d291b39 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -3658,6 +3658,13 @@ func Test_Ctx_CBOR(t *testing.T) { testEmpty(0, "00") testEmpty([]int{}, "80") + // Test invalid types + err = c.CBOR(make(chan int)) + require.Error(t, err) + + err = c.CBOR(func() {}) + require.Error(t, err) + t.Run("custom cbor encoder", func(t *testing.T) { t.Parallel() From 190704a90fe6bcf6396abf9403db2b064838c959 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 29 Nov 2024 15:29:17 +0300 Subject: [PATCH 12/17] add docs --- client/client.go | 8 +++---- ctx_test.go | 14 ++++++++++++ docs/api/bind.md | 38 ++++++++++++++++++++++++++++++++ docs/api/ctx.md | 48 +++++++++++++++++++++++++++++++++++++++++ docs/client/request.md | 8 +++++++ docs/client/response.md | 8 +++++++ docs/client/rest.md | 36 +++++++++++++++++++++++++++++++ 7 files changed, 156 insertions(+), 4 deletions(-) diff --git a/client/client.go b/client/client.go index a774fff4af..2c4086aed3 100644 --- a/client/client.go +++ b/client/client.go @@ -153,23 +153,23 @@ func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client { return c } -// CBORMarshal returns xml marshal function in Core. +// CBORMarshal returns CBOR marshal function in Core. func (c *Client) CBORMarshal() utils.CBORMarshal { return c.cborMarshal } -// SetCBORMarshal Set xml encoder. +// SetCBORMarshal sets CBOR encoder. func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client { c.cborMarshal = f return c } -// CBORUnmarshal returns xml unmarshal function in Core. +// CBORUnmarshal returns CBOR unmarshal function in Core. func (c *Client) CBORUnmarshal() utils.CBORUnmarshal { return c.cborUnmarshal } -// SetCBORUnmarshal Set xml decoder. +// SetCBORUnmarshal sets CBOR decoder. func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client { c.cborUnmarshal = f return c diff --git a/ctx_test.go b/ctx_test.go index 72c415a9ca..fe901658b5 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -29,6 +29,7 @@ import ( "text/template" "time" + "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3/internal/storage/memory" "github.com/gofiber/utils/v2" "github.com/stretchr/testify/require" @@ -3668,6 +3669,19 @@ func Test_Ctx_CBOR(t *testing.T) { err = c.CBOR(func() {}) require.Error(t, err) + type SomeStruct struct { + Name string `cbor:"name"` + Pass string `cbor:"pass"` + } + + data := SomeStruct{ + Name: "john", + Pass: "doe", + } + + a, _ := cbor.Marshal(data) + require.Equal(t, "", string(a)) + t.Run("custom cbor encoder", func(t *testing.T) { t.Parallel() diff --git a/docs/api/bind.md b/docs/api/bind.md index a1cbedd18d..313a7af614 100644 --- a/docs/api/bind.md +++ b/docs/api/bind.md @@ -20,6 +20,7 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more.. - [JSON](#json) - [MultipartForm](#multipartform) - [XML](#xml) + - [CBOR](#cbor) - [Cookie](#cookie) - [Header](#header) - [Query](#query) @@ -226,6 +227,43 @@ Run tests with the following `curl` command: curl -X POST -H "Content-Type: application/xml" --data "johndoe" localhost:3000 ``` +### CBOR + +Binds the request CBOR body to a struct. + +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse an CBOR body with a field called `Pass`, you would use a struct field with `cbor:"pass"`. + +```go title="Signature" +func (b *Bind) CBOR(out any) error +``` + +```go title="Example" +// Field names should start with an uppercase letter +type Person struct { + Name string `cbor:"name"` + Pass string `cbor:"pass"` +} + +app.Post("/", func(c fiber.Ctx) error { + p := new(Person) + + if err := c.Bind().CBOR(p); err != nil { + return err + } + + log.Println(p.Name) // john + log.Println(p.Pass) // doe + + // ... +}) +``` + +Run tests with the following `curl` command: + +```bash +curl -X POST -H "Content-Type: application/cbor" --data "\xa2dnamedjohndpasscdoe" localhost:3000 +``` + ### Cookie This method is similar to [Body Binding](#body), but for cookie parameters. diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 2171a0ba80..102d594986 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -924,6 +924,54 @@ app.Get("/", func(c fiber.Ctx) error { }) ``` +## CBOR + +CBOR converts any interface or string to cbor encoded bytes. + +:::info +CBOR also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/cblor`. +::: + +```go title="Signature" +func (c fiber.Ctx) CBOR(data any, ctype ...string) error +``` + +```go title="Example" +type SomeStruct struct { + Name string `cbor:"name"` + Age uint8 `cbor:"age"` +} + +app.Get("/cbor", func(c fiber.Ctx) error { + // Create data struct: + data := SomeStruct{ + Name: "Grame", + Age: 20, + } + + return c.CBOR(data) + // => Content-Type: application/cbor + // => \xa2dnameeGramecage\x14 + + return c.CBOR(fiber.Map{ + "name": "Grame", + "age": 20, + }) + // => Content-Type: application/cbor + // => \xa2dnameeGramecage\x14 + + return c.CBOR(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", + }) + // => Content-Type: application/cbor + // => \xa5dtypex'https://example.com/probs/out-of-creditetitlex\x1eYou do not have enough credit.fstatus\x19\x01\x93fdetailx.Your current balance is 30, but that costs 50.hinstancew/account/12345/msgs/abc +}) +``` + ## 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. diff --git a/docs/client/request.md b/docs/client/request.md index d1126dae0e..c124aafd19 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -657,6 +657,14 @@ SetXML method sets XML body in request. func (r *Request) SetXML(v any) *Request ``` +## SetCBOR + +SetCBOR method sets CBOR body in request. + +```go title="Signature" +func (r *Request) SetCBOR(v any) *Request +``` + ## SetRawBody SetRawBody method sets body with raw data in request. diff --git a/docs/client/response.md b/docs/client/response.md index 2256d400e3..eb854d5ca1 100644 --- a/docs/client/response.md +++ b/docs/client/response.md @@ -187,6 +187,14 @@ XML method will unmarshal body to xml. func (r *Response) XML(v any) error ``` +## CBOR + +CBOR method will unmarshal body to cbor. + +```go title="Signature" +func (r *Response) CBOR(v any) error +``` + ## Save Save method will save the body to a file or io.Writer. diff --git a/docs/client/rest.md b/docs/client/rest.md index 9718131120..88dbdd2031 100644 --- a/docs/client/rest.md +++ b/docs/client/rest.md @@ -81,6 +81,8 @@ type Client struct { jsonUnmarshal utils.JSONUnmarshal xmlMarshal utils.XMLMarshal xmlUnmarshal utils.XMLUnmarshal + cborMarshal utils.CBORMarshal + cborUnmarshal utils.CBORUnmarshal cookieJar *CookieJar @@ -314,6 +316,40 @@ SetXMLUnmarshal sets the XML decoder. func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client ``` +### CBOR + +#### CBORMarshal + +CBORMarshal returns CBOR marshal function in Core. + +```go title="Signature" +func (c *Client) CBORMarshal() utils.CBORMarshal +``` + +#### CBORUnmarshal + +CBORUnmarshal returns CBOR unmarshal function in Core. + +```go title="Signature" +func (c *Client) CBORUnmarshal() utils.CBORUnmarshal +``` + +#### SetCBORMarshal + +SetCBORMarshal sets CBOR encoder. + +```go title="Signature" +func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client +``` + +#### SetCBORUnmarshal + +SetCBORUnmarshal sets CBOR decoder. + +```go title="Signature" +func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client +``` + ### TLS #### TLSConfig From e397dc25f2400dfce29429997c2ad2261e4ba4a3 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 29 Nov 2024 15:43:09 +0300 Subject: [PATCH 13/17] doc fixes --- bind_test.go | 8 ++------ ctx_test.go | 9 --------- docs/api/ctx.md | 2 +- docs/client/request.md | 3 ++- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/bind_test.go b/bind_test.go index 249f919a82..1bed0c25f4 100644 --- a/bind_test.go +++ b/bind_test.go @@ -1756,13 +1756,9 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) { require.NoError(t, c.Bind().Body(r)) require.Equal(t, "body_param", r.BodyParam) } - cb, err := cbor.Marshal(&Request{ - BodyParam: "body_param", - }) - if err != nil { - t.Error(err) - } + cb, err := cbor.Marshal(&Request{BodyParam: "body_param"}) + require.NoError(t, err, "Failed to marshal CBOR data") testDecodeParser(MIMEApplicationJSON, `{"body_param":"body_param"}`) testDecodeParser(MIMEApplicationXML, `body_param`) diff --git a/ctx_test.go b/ctx_test.go index fe901658b5..4921aa3aae 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -29,7 +29,6 @@ import ( "text/template" "time" - "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3/internal/storage/memory" "github.com/gofiber/utils/v2" "github.com/stretchr/testify/require" @@ -3674,14 +3673,6 @@ func Test_Ctx_CBOR(t *testing.T) { Pass string `cbor:"pass"` } - data := SomeStruct{ - Name: "john", - Pass: "doe", - } - - a, _ := cbor.Marshal(data) - require.Equal(t, "", string(a)) - t.Run("custom cbor encoder", func(t *testing.T) { t.Parallel() diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 102d594986..07530e28b2 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -929,7 +929,7 @@ app.Get("/", func(c fiber.Ctx) error { CBOR converts any interface or string to cbor encoded bytes. :::info -CBOR also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/cblor`. +CBOR also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/cbor`. ::: ```go title="Signature" diff --git a/docs/client/request.md b/docs/client/request.md index c124aafd19..6845db0ba3 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -659,7 +659,8 @@ func (r *Request) SetXML(v any) *Request ## SetCBOR -SetCBOR method sets CBOR body in request. +SetCBOR method sets the request body using CBOR (Concise Binary Object Representation) encoding format. +It automatically sets the Content-Type header to "application/cbor". ```go title="Signature" func (r *Request) SetCBOR(v any) *Request From a12cad3037369aa7acc3c0664943e009de681c83 Mon Sep 17 00:00:00 2001 From: Muhammed Efe Cetin Date: Fri, 29 Nov 2024 21:17:36 +0300 Subject: [PATCH 14/17] update --- bind_test.go | 10 ++++++++++ client/client_test.go | 3 +-- client/request_test.go | 2 +- client/response.go | 2 +- ctx.go | 2 +- ctx_interface_gen.go | 2 +- ctx_test.go | 5 ----- docs/api/bind.md | 2 +- docs/api/ctx.md | 2 +- docs/client/request.md | 2 +- docs/client/response.md | 2 +- docs/whats_new.md | 4 ++-- 12 files changed, 21 insertions(+), 17 deletions(-) diff --git a/bind_test.go b/bind_test.go index 1bed0c25f4..befb17870c 100644 --- a/bind_test.go +++ b/bind_test.go @@ -943,6 +943,16 @@ func Test_Bind_Body(t *testing.T) { t.Error(err) } testDecodeParser(t, MIMEApplicationCBOR, enc) + + // Test invalid CBOR data + t.Run("Invalid", func(t *testing.T) { + invalidData := []byte{0xFF, 0xFF} // Invalid CBOR data + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + c.Request().Header.SetContentType(MIMEApplicationCBOR) + c.Request().SetBody(invalidData) + d := new(Demo) + require.Error(t, c.Bind().Body(d)) + }) }) t.Run("XML", func(t *testing.T) { diff --git a/client/client_test.go b/client/client_test.go index 29635480f9..f6722114f9 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1476,8 +1476,7 @@ func Test_Set_Config_To_Request(t *testing.T) { var key ctxKey = struct{}{} ctx := context.Background() - ctx = context.WithValue(ctx, key, "v1") //nolint: staticcheck // not needed for tests - + ctx = context.WithValue(ctx, key, "v1") req := AcquireRequest() setConfigToRequest(req, Config{Ctx: ctx}) diff --git a/client/request_test.go b/client/request_test.go index eb690f8fff..f62865a342 100644 --- a/client/request_test.go +++ b/client/request_test.go @@ -85,7 +85,7 @@ func Test_Request_Context(t *testing.T) { require.Nil(t, ctx.Value(key)) - ctx = context.WithValue(ctx, key, "string") //nolint: staticcheck // not needed for tests + ctx = context.WithValue(ctx, key, "string") req.SetContext(ctx) ctx = req.Context() diff --git a/client/response.go b/client/response.go index f21290f764..e60c6bd0fb 100644 --- a/client/response.go +++ b/client/response.go @@ -75,7 +75,7 @@ func (r *Response) JSON(v any) error { return r.client.jsonUnmarshal(r.Body(), v) } -// CBOR method will unmarshal body to cbor. +// CBOR method will unmarshal body to CBOR. func (r *Response) CBOR(v any) error { return r.client.cborUnmarshal(r.Body(), v) } diff --git a/ctx.go b/ctx.go index 19a41b5067..5dde2f89d0 100644 --- a/ctx.go +++ b/ctx.go @@ -884,7 +884,7 @@ func (c *DefaultCtx) JSON(data any, ctype ...string) error { return nil } -// CBOR converts any interface or string to cbor encoded bytes. +// CBOR converts any interface or string to CBOR encoded bytes. // 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/cbor. diff --git a/ctx_interface_gen.go b/ctx_interface_gen.go index 012e32664e..859563f638 100644 --- a/ctx_interface_gen.go +++ b/ctx_interface_gen.go @@ -164,7 +164,7 @@ type Ctx interface { // Content-Type header equal to ctype. If ctype is not given, // The Content-Type header will be set to application/json. JSON(data any, ctype ...string) error - // CBOR converts any interface or string to cbor encoded bytes. + // CBOR converts any interface or string to CBOR encoded bytes. // 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/cbor. diff --git a/ctx_test.go b/ctx_test.go index 4921aa3aae..72c415a9ca 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -3668,11 +3668,6 @@ func Test_Ctx_CBOR(t *testing.T) { err = c.CBOR(func() {}) require.Error(t, err) - type SomeStruct struct { - Name string `cbor:"name"` - Pass string `cbor:"pass"` - } - t.Run("custom cbor encoder", func(t *testing.T) { t.Parallel() diff --git a/docs/api/bind.md b/docs/api/bind.md index 313a7af614..e91fed4273 100644 --- a/docs/api/bind.md +++ b/docs/api/bind.md @@ -231,7 +231,7 @@ curl -X POST -H "Content-Type: application/xml" --data "john Binds the request CBOR body to a struct. -It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse an CBOR body with a field called `Pass`, you would use a struct field with `cbor:"pass"`. +It is important to specify the correct struct tag based on the content type to be parsed. For example, if you want to parse a CBOR body with a field called `Pass`, you would use a struct field with `cbor:"pass"`. ```go title="Signature" func (b *Bind) CBOR(out any) error diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 07530e28b2..6f4b1ae4c0 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -926,7 +926,7 @@ app.Get("/", func(c fiber.Ctx) error { ## CBOR -CBOR converts any interface or string to cbor encoded bytes. +CBOR converts any interface or string to CBOR encoded bytes. :::info CBOR also sets the content header to the `ctype` parameter. If no `ctype` is passed in, the header is set to `application/cbor`. diff --git a/docs/client/request.md b/docs/client/request.md index 6845db0ba3..e14e722976 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -659,7 +659,7 @@ func (r *Request) SetXML(v any) *Request ## SetCBOR -SetCBOR method sets the request body using CBOR (Concise Binary Object Representation) encoding format. +SetCBOR method sets the request body using [CBOR](https://cbor.io/) encoding format. It automatically sets the Content-Type header to "application/cbor". ```go title="Signature" diff --git a/docs/client/response.md b/docs/client/response.md index eb854d5ca1..0dba3c6b48 100644 --- a/docs/client/response.md +++ b/docs/client/response.md @@ -189,7 +189,7 @@ func (r *Response) XML(v any) error ## CBOR -CBOR method will unmarshal body to cbor. +CBOR method will unmarshal body to CBOR. ```go title="Signature" func (r *Response) CBOR(v any) error diff --git a/docs/whats_new.md b/docs/whats_new.md index 9a08795a7f..ea7a3d2e17 100644 --- a/docs/whats_new.md +++ b/docs/whats_new.md @@ -259,7 +259,7 @@ DRAFT section - Cookie now allows Partitioned cookies for [CHIPS](https://developers.google.com/privacy-sandbox/3pcd/chips) support. CHIPS (Cookies Having Independent Partitioned State) is a feature that improves privacy by allowing cookies to be partitioned by top-level site, mitigating cross-site tracking. -- Introducing [CBOR](https://cbor.io/) binary encoding format for response body. CBOR is a binary data serialization format that is both compact and efficient, making it ideal for use in web applications. +- Introducing [CBOR](https://cbor.io/) binary encoding format for both request & response body. CBOR is a binary data serialization format that is both compact and efficient, making it ideal for use in web applications. ### new methods @@ -274,7 +274,7 @@ DRAFT section - SendString -> ExpressJs like - String -> ExpressJs like - ViewBind -> instead of Bind -- CBOR -> for CBOR encoding +- CBOR -> for CBOR encoding and decoding ### removed methods From ef215f37a6d3fce14bfc399ed7b0cc58c4a958ec Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Fri, 29 Nov 2024 14:32:11 -0500 Subject: [PATCH 15/17] Fix markdown lint --- docs/client/request.md | 2 +- docs/client/rest.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/client/request.md b/docs/client/request.md index e14e722976..08a04e65e0 100644 --- a/docs/client/request.md +++ b/docs/client/request.md @@ -660,7 +660,7 @@ func (r *Request) SetXML(v any) *Request ## SetCBOR SetCBOR method sets the request body using [CBOR](https://cbor.io/) encoding format. -It automatically sets the Content-Type header to "application/cbor". +It automatically sets the Content-Type header to `"application/cbor"`. ```go title="Signature" func (r *Request) SetCBOR(v any) *Request diff --git a/docs/client/rest.md b/docs/client/rest.md index 88dbdd2031..04c7a5c44c 100644 --- a/docs/client/rest.md +++ b/docs/client/rest.md @@ -82,7 +82,7 @@ type Client struct { xmlMarshal utils.XMLMarshal xmlUnmarshal utils.XMLUnmarshal cborMarshal utils.CBORMarshal - cborUnmarshal utils.CBORUnmarshal + cborUnmarshal utils.CBORUnmarshal cookieJar *CookieJar From 602ae7afda5daf53661d5fd7a75bdc60717c8c62 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Fri, 29 Nov 2024 15:22:12 -0500 Subject: [PATCH 16/17] Add missing entry from binder README --- binder/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/binder/README.md b/binder/README.md index 6794a3b93b..4d75fd1b90 100644 --- a/binder/README.md +++ b/binder/README.md @@ -22,6 +22,7 @@ Fiber provides several default binders out of the box: - [Cookie](cookie.go) - [JSON](json.go) - [XML](xml.go) +- [CBOR](cbor.go) ## Guides From fc1779b6df4602e0acd42e5695fe9cda7d439880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= Date: Sun, 1 Dec 2024 10:52:33 +0100 Subject: [PATCH 17/17] add/refresh documentation --- bind.go | 5 +- bind_test.go | 8 +- binder/cbor.go | 3 + binder/cookie.go | 3 + binder/form.go | 4 + binder/header.go | 3 + binder/json.go | 3 + binder/mapping.go | 4 +- binder/query.go | 3 + binder/resp_header.go | 3 + binder/uri.go | 3 + binder/xml.go | 3 + client/request.go | 1 + docs/api/constants.md | 284 +++++++++++++++++++++--------------------- docs/api/fiber.md | 78 ++++++------ 15 files changed, 222 insertions(+), 186 deletions(-) diff --git a/bind.go b/bind.go index 3d5a8d323f..5af83743a0 100644 --- a/bind.go +++ b/bind.go @@ -23,7 +23,7 @@ type Bind struct { dontHandleErrs bool } -// If you want to handle binder errors manually, you can use `WithoutAutoHandling`. +// WithoutAutoHandling If you want to handle binder errors manually, you can use `WithoutAutoHandling`. // It's default behavior of binder. func (b *Bind) WithoutAutoHandling() *Bind { b.dontHandleErrs = true @@ -31,7 +31,7 @@ func (b *Bind) WithoutAutoHandling() *Bind { return b } -// If you want to handle binder errors automatically, you can use `WithAutoHandling`. +// WithAutoHandling If you want to handle binder errors automatically, you can use `WithAutoHandling`. // If there's an error, it will return the error and set HTTP status to `400 Bad Request`. // You must still return on error explicitly func (b *Bind) WithAutoHandling() *Bind { @@ -121,6 +121,7 @@ func (b *Bind) JSON(out any) error { return b.validateStruct(out) } +// CBOR binds the body string into the struct. func (b *Bind) CBOR(out any) error { if err := b.returnErr(binder.CBORBinder.Bind(b.ctx.Body(), b.ctx.App().Config().CBORDecoder, out)); err != nil { return err diff --git a/bind_test.go b/bind_test.go index befb17870c..55d2dd75e9 100644 --- a/bind_test.go +++ b/bind_test.go @@ -158,7 +158,7 @@ func Test_Bind_Query_WithSetParserDecoder(t *testing.T) { } nonRFCTime := binder.ParserType{ - Customtype: NonRFCTime{}, + CustomType: NonRFCTime{}, Converter: nonRFCConverter, } @@ -412,7 +412,7 @@ func Test_Bind_Header_WithSetParserDecoder(t *testing.T) { } nonRFCTime := binder.ParserType{ - Customtype: NonRFCTime{}, + CustomType: NonRFCTime{}, Converter: nonRFCConverter, } @@ -1027,7 +1027,7 @@ func Test_Bind_Body_WithSetParserDecoder(t *testing.T) { } customTime := binder.ParserType{ - Customtype: CustomTime{}, + CustomType: CustomTime{}, Converter: timeConverter, } @@ -1451,7 +1451,7 @@ func Test_Bind_Cookie_WithSetParserDecoder(t *testing.T) { } nonRFCTime := binder.ParserType{ - Customtype: NonRFCTime{}, + CustomType: NonRFCTime{}, Converter: nonRFCConverter, } diff --git a/binder/cbor.go b/binder/cbor.go index 12ed13715c..6f47893531 100644 --- a/binder/cbor.go +++ b/binder/cbor.go @@ -4,12 +4,15 @@ import ( "github.com/gofiber/utils/v2" ) +// cborBinding is the CBOR binder for CBOR request body. type cborBinding struct{} +// Name returns the binding name. func (*cborBinding) Name() string { return "cbor" } +// Bind parses the request body as CBOR and returns the result. func (*cborBinding) Bind(body []byte, cborDecoder utils.CBORUnmarshal, out any) error { return cborDecoder(body, out) } diff --git a/binder/cookie.go b/binder/cookie.go index 0f5c650c33..62271c8e38 100644 --- a/binder/cookie.go +++ b/binder/cookie.go @@ -8,12 +8,15 @@ import ( "github.com/valyala/fasthttp" ) +// cookieBinding is the cookie binder for cookie request body. type cookieBinding struct{} +// Name returns the binding name. func (*cookieBinding) Name() string { return "cookie" } +// Bind parses the request cookie and returns the result. func (b *cookieBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { data := make(map[string][]string) var err error diff --git a/binder/form.go b/binder/form.go index f45407fe93..e0f1acd302 100644 --- a/binder/form.go +++ b/binder/form.go @@ -8,12 +8,15 @@ import ( "github.com/valyala/fasthttp" ) +// formBinding is the form binder for form request body. type formBinding struct{} +// Name returns the binding name. func (*formBinding) Name() string { return "form" } +// Bind parses the request body and returns the result. func (b *formBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { data := make(map[string][]string) var err error @@ -47,6 +50,7 @@ func (b *formBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { return parse(b.Name(), out, data) } +// BindMultipart parses the request body and returns the result. func (b *formBinding) BindMultipart(reqCtx *fasthttp.RequestCtx, out any) error { data, err := reqCtx.MultipartForm() if err != nil { diff --git a/binder/header.go b/binder/header.go index 196163694d..258a0b2229 100644 --- a/binder/header.go +++ b/binder/header.go @@ -8,12 +8,15 @@ import ( "github.com/valyala/fasthttp" ) +// headerBinding is the header binder for header request body. type headerBinding struct{} +// Name returns the binding name. func (*headerBinding) Name() string { return "header" } +// Bind parses the request header and returns the result. func (b *headerBinding) Bind(req *fasthttp.Request, out any) error { data := make(map[string][]string) req.Header.VisitAll(func(key, val []byte) { diff --git a/binder/json.go b/binder/json.go index 6c0d80c89b..7889aee8a2 100644 --- a/binder/json.go +++ b/binder/json.go @@ -4,12 +4,15 @@ import ( "github.com/gofiber/utils/v2" ) +// jsonBinding is the JSON binder for JSON request body. type jsonBinding struct{} +// Name returns the binding name. func (*jsonBinding) Name() string { return "json" } +// Bind parses the request body as JSON and returns the result. func (*jsonBinding) Bind(body []byte, jsonDecoder utils.JSONUnmarshal, out any) error { return jsonDecoder(body, out) } diff --git a/binder/mapping.go b/binder/mapping.go index ea67ace200..055345fe26 100644 --- a/binder/mapping.go +++ b/binder/mapping.go @@ -24,7 +24,7 @@ type ParserConfig struct { // ParserType require two element, type and converter for register. // Use ParserType with BodyParser for parsing custom type in form data. type ParserType struct { - Customtype any + CustomType any Converter func(string) reflect.Value } @@ -51,7 +51,7 @@ func decoderBuilder(parserConfig ParserConfig) any { decoder.SetAliasTag(parserConfig.SetAliasTag) } for _, v := range parserConfig.ParserType { - decoder.RegisterConverter(reflect.ValueOf(v.Customtype).Interface(), v.Converter) + decoder.RegisterConverter(reflect.ValueOf(v.CustomType).Interface(), v.Converter) } decoder.ZeroEmpty(parserConfig.ZeroEmpty) return decoder diff --git a/binder/query.go b/binder/query.go index 25b69f5bc3..8f029d30c4 100644 --- a/binder/query.go +++ b/binder/query.go @@ -8,12 +8,15 @@ import ( "github.com/valyala/fasthttp" ) +// queryBinding is the query binder for query request body. type queryBinding struct{} +// Name returns the binding name. func (*queryBinding) Name() string { return "query" } +// Bind parses the request query and returns the result. func (b *queryBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { data := make(map[string][]string) var err error diff --git a/binder/resp_header.go b/binder/resp_header.go index 0455185ba1..ef14255315 100644 --- a/binder/resp_header.go +++ b/binder/resp_header.go @@ -8,12 +8,15 @@ import ( "github.com/valyala/fasthttp" ) +// respHeaderBinding is the respHeader binder for response header. type respHeaderBinding struct{} +// Name returns the binding name. func (*respHeaderBinding) Name() string { return "respHeader" } +// Bind parses the response header and returns the result. func (b *respHeaderBinding) Bind(resp *fasthttp.Response, out any) error { data := make(map[string][]string) resp.Header.VisitAll(func(key, val []byte) { diff --git a/binder/uri.go b/binder/uri.go index 2759f7b464..b58d9d49c4 100644 --- a/binder/uri.go +++ b/binder/uri.go @@ -1,11 +1,14 @@ package binder +// uriBinding is the URI binder for URI parameters. type uriBinding struct{} +// Name returns the binding name. func (*uriBinding) Name() string { return "uri" } +// Bind parses the URI parameters and returns the result. func (b *uriBinding) Bind(params []string, paramsFunc func(key string, defaultValue ...string) string, out any) error { data := make(map[string][]string, len(params)) for _, param := range params { diff --git a/binder/xml.go b/binder/xml.go index 35d1de86bc..58da2b9b07 100644 --- a/binder/xml.go +++ b/binder/xml.go @@ -5,12 +5,15 @@ import ( "fmt" ) +// xmlBinding is the XML binder for XML request body. type xmlBinding struct{} +// Name returns the binding name. func (*xmlBinding) Name() string { return "xml" } +// Bind parses the request body as XML and returns the result. func (*xmlBinding) Bind(body []byte, out any) error { if err := xml.Unmarshal(body, out); err != nil { return fmt.Errorf("failed to unmarshal xml: %w", err) diff --git a/client/request.go b/client/request.go index 724358600a..a86d927c4e 100644 --- a/client/request.go +++ b/client/request.go @@ -338,6 +338,7 @@ func (r *Request) SetXML(v any) *Request { return r } +// SetCBOR method sets CBOR body in request. func (r *Request) SetCBOR(v any) *Request { r.body = v r.bodyType = cborBody diff --git a/docs/api/constants.md b/docs/api/constants.md index 9fbff533cb..a1c80e9546 100644 --- a/docs/api/constants.md +++ b/docs/api/constants.md @@ -26,25 +26,26 @@ const ( ```go const ( - MIMETextXML = "text/xml" - MIMETextHTML = "text/html" - MIMETextPlain = "text/plain" - MIMETextJavaScript = "text/javascript" - MIMETextCSS = "text/css" - MIMEApplicationXML = "application/xml" - MIMEApplicationJSON = "application/json" - MIMEApplicationJavaScript = "application/javascript" - MIMEApplicationForm = "application/x-www-form-urlencoded" - MIMEOctetStream = "application/octet-stream" - MIMEMultipartForm = "multipart/form-data" + MIMETextXML = "text/xml" + MIMETextHTML = "text/html" + MIMETextPlain = "text/plain" + MIMETextJavaScript = "text/javascript" + MIMETextCSS = "text/css" + MIMEApplicationXML = "application/xml" + MIMEApplicationJSON = "application/json" + MIMEApplicationCBOR = "application/cbor" + MIMEApplicationJavaScript = "application/javascript" + MIMEApplicationForm = "application/x-www-form-urlencoded" + MIMEOctetStream = "application/octet-stream" + MIMEMultipartForm = "multipart/form-data" - MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" - MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" - MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" - MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8" - MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8" - MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" - MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" + MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" + MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" + MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" + MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8" + MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8" + MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" + MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8" )``` @@ -72,6 +73,7 @@ const ( StatusSeeOther = 303 // RFC 7231, 6.4.4 StatusNotModified = 304 // RFC 7232, 4.1 StatusUseProxy = 305 // RFC 7231, 6.4.5 + StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused) StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 StatusPermanentRedirect = 308 // RFC 7538, 3 StatusBadRequest = 400 // RFC 7231, 6.5.1 @@ -168,127 +170,129 @@ HTTP Headers were copied from net/http. ```go const ( - HeaderAuthorization = "Authorization" - HeaderProxyAuthenticate = "Proxy-Authenticate" - HeaderProxyAuthorization = "Proxy-Authorization" - HeaderWWWAuthenticate = "WWW-Authenticate" - HeaderAge = "Age" - HeaderCacheControl = "Cache-Control" - HeaderClearSiteData = "Clear-Site-Data" - HeaderExpires = "Expires" - HeaderPragma = "Pragma" - HeaderWarning = "Warning" - HeaderAcceptCH = "Accept-CH" - HeaderAcceptCHLifetime = "Accept-CH-Lifetime" - HeaderContentDPR = "Content-DPR" - HeaderDPR = "DPR" - HeaderEarlyData = "Early-Data" - HeaderSaveData = "Save-Data" - HeaderViewportWidth = "Viewport-Width" - HeaderWidth = "Width" - HeaderETag = "ETag" - HeaderIfMatch = "If-Match" - HeaderIfModifiedSince = "If-Modified-Since" - HeaderIfNoneMatch = "If-None-Match" - HeaderIfUnmodifiedSince = "If-Unmodified-Since" - HeaderLastModified = "Last-Modified" - HeaderVary = "Vary" - HeaderConnection = "Connection" - HeaderKeepAlive = "Keep-Alive" - HeaderAccept = "Accept" - HeaderAcceptCharset = "Accept-Charset" - HeaderAcceptEncoding = "Accept-Encoding" - HeaderAcceptLanguage = "Accept-Language" - HeaderCookie = "Cookie" - HeaderExpect = "Expect" - HeaderMaxForwards = "Max-Forwards" - HeaderSetCookie = "Set-Cookie" - HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" - HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" - HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" - HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" - HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" - HeaderAccessControlMaxAge = "Access-Control-Max-Age" - HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" - HeaderAccessControlRequestMethod = "Access-Control-Request-Method" - HeaderOrigin = "Origin" - HeaderTimingAllowOrigin = "Timing-Allow-Origin" - HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" - HeaderDNT = "DNT" - HeaderTk = "Tk" - HeaderContentDisposition = "Content-Disposition" - HeaderContentEncoding = "Content-Encoding" - HeaderContentLanguage = "Content-Language" - HeaderContentLength = "Content-Length" - HeaderContentLocation = "Content-Location" - HeaderContentType = "Content-Type" - HeaderForwarded = "Forwarded" - HeaderVia = "Via" - HeaderXForwardedFor = "X-Forwarded-For" - HeaderXForwardedHost = "X-Forwarded-Host" - HeaderXForwardedProto = "X-Forwarded-Proto" - HeaderXForwardedProtocol = "X-Forwarded-Protocol" - HeaderXForwardedSsl = "X-Forwarded-Ssl" - HeaderXUrlScheme = "X-Url-Scheme" - HeaderLocation = "Location" - HeaderFrom = "From" - HeaderHost = "Host" - HeaderReferer = "Referer" - HeaderReferrerPolicy = "Referrer-Policy" - HeaderUserAgent = "User-Agent" - HeaderAllow = "Allow" - HeaderServer = "Server" - HeaderAcceptRanges = "Accept-Ranges" - HeaderContentRange = "Content-Range" - HeaderIfRange = "If-Range" - HeaderRange = "Range" - HeaderContentSecurityPolicy = "Content-Security-Policy" - HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" - HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" - HeaderExpectCT = "Expect-CT" - HeaderFeaturePolicy = "Feature-Policy" - HeaderPublicKeyPins = "Public-Key-Pins" - HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" - HeaderStrictTransportSecurity = "Strict-Transport-Security" - HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" - HeaderXContentTypeOptions = "X-Content-Type-Options" - HeaderXDownloadOptions = "X-Download-Options" - HeaderXFrameOptions = "X-Frame-Options" - HeaderXPoweredBy = "X-Powered-By" - HeaderXXSSProtection = "X-XSS-Protection" - HeaderLastEventID = "Last-Event-ID" - HeaderNEL = "NEL" - HeaderPingFrom = "Ping-From" - HeaderPingTo = "Ping-To" - HeaderReportTo = "Report-To" - HeaderTE = "TE" - HeaderTrailer = "Trailer" - HeaderTransferEncoding = "Transfer-Encoding" - HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" - HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" - HeaderSecWebSocketKey = "Sec-WebSocket-Key" - HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" - HeaderSecWebSocketVersion = "Sec-WebSocket-Version" - HeaderAcceptPatch = "Accept-Patch" - HeaderAcceptPushPolicy = "Accept-Push-Policy" - HeaderAcceptSignature = "Accept-Signature" - HeaderAltSvc = "Alt-Svc" - HeaderDate = "Date" - HeaderIndex = "Index" - HeaderLargeAllocation = "Large-Allocation" - HeaderLink = "Link" - HeaderPushPolicy = "Push-Policy" - HeaderRetryAfter = "Retry-After" - HeaderServerTiming = "Server-Timing" - HeaderSignature = "Signature" - HeaderSignedHeaders = "Signed-Headers" - HeaderSourceMap = "SourceMap" - HeaderUpgrade = "Upgrade" - HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" - HeaderXPingback = "X-Pingback" - HeaderXRequestID = "X-Request-ID" - HeaderXRequestedWith = "X-Requested-With" - HeaderXRobotsTag = "X-Robots-Tag" - HeaderXUACompatible = "X-UA-Compatible" + HeaderAuthorization = "Authorization" + HeaderProxyAuthenticate = "Proxy-Authenticate" + HeaderProxyAuthorization = "Proxy-Authorization" + HeaderWWWAuthenticate = "WWW-Authenticate" + HeaderAge = "Age" + HeaderCacheControl = "Cache-Control" + HeaderClearSiteData = "Clear-Site-Data" + HeaderExpires = "Expires" + HeaderPragma = "Pragma" + HeaderWarning = "Warning" + HeaderAcceptCH = "Accept-CH" + HeaderAcceptCHLifetime = "Accept-CH-Lifetime" + HeaderContentDPR = "Content-DPR" + HeaderDPR = "DPR" + HeaderEarlyData = "Early-Data" + HeaderSaveData = "Save-Data" + HeaderViewportWidth = "Viewport-Width" + HeaderWidth = "Width" + HeaderETag = "ETag" + HeaderIfMatch = "If-Match" + HeaderIfModifiedSince = "If-Modified-Since" + HeaderIfNoneMatch = "If-None-Match" + HeaderIfUnmodifiedSince = "If-Unmodified-Since" + HeaderLastModified = "Last-Modified" + HeaderVary = "Vary" + HeaderConnection = "Connection" + HeaderKeepAlive = "Keep-Alive" + HeaderAccept = "Accept" + HeaderAcceptCharset = "Accept-Charset" + HeaderAcceptEncoding = "Accept-Encoding" + HeaderAcceptLanguage = "Accept-Language" + HeaderCookie = "Cookie" + HeaderExpect = "Expect" + HeaderMaxForwards = "Max-Forwards" + HeaderSetCookie = "Set-Cookie" + HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" + HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" + HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" + HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" + HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" + HeaderAccessControlMaxAge = "Access-Control-Max-Age" + HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" + HeaderAccessControlRequestMethod = "Access-Control-Request-Method" + HeaderOrigin = "Origin" + HeaderTimingAllowOrigin = "Timing-Allow-Origin" + HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" + HeaderDNT = "DNT" + HeaderTk = "Tk" + HeaderContentDisposition = "Content-Disposition" + HeaderContentEncoding = "Content-Encoding" + HeaderContentLanguage = "Content-Language" + HeaderContentLength = "Content-Length" + HeaderContentLocation = "Content-Location" + HeaderContentType = "Content-Type" + HeaderForwarded = "Forwarded" + HeaderVia = "Via" + HeaderXForwardedFor = "X-Forwarded-For" + HeaderXForwardedHost = "X-Forwarded-Host" + HeaderXForwardedProto = "X-Forwarded-Proto" + HeaderXForwardedProtocol = "X-Forwarded-Protocol" + HeaderXForwardedSsl = "X-Forwarded-Ssl" + HeaderXUrlScheme = "X-Url-Scheme" + HeaderLocation = "Location" + HeaderFrom = "From" + HeaderHost = "Host" + HeaderReferer = "Referer" + HeaderReferrerPolicy = "Referrer-Policy" + HeaderUserAgent = "User-Agent" + HeaderAllow = "Allow" + HeaderServer = "Server" + HeaderAcceptRanges = "Accept-Ranges" + HeaderContentRange = "Content-Range" + HeaderIfRange = "If-Range" + HeaderRange = "Range" + HeaderContentSecurityPolicy = "Content-Security-Policy" + HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" + HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" + HeaderExpectCT = "Expect-CT" + HeaderFeaturePolicy = "Feature-Policy" + HeaderPublicKeyPins = "Public-Key-Pins" + HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" + HeaderStrictTransportSecurity = "Strict-Transport-Security" + HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" + HeaderXContentTypeOptions = "X-Content-Type-Options" + HeaderXDownloadOptions = "X-Download-Options" + HeaderXFrameOptions = "X-Frame-Options" + HeaderXPoweredBy = "X-Powered-By" + HeaderXXSSProtection = "X-XSS-Protection" + HeaderLastEventID = "Last-Event-ID" + HeaderNEL = "NEL" + HeaderPingFrom = "Ping-From" + HeaderPingTo = "Ping-To" + HeaderReportTo = "Report-To" + HeaderTE = "TE" + HeaderTrailer = "Trailer" + HeaderTransferEncoding = "Transfer-Encoding" + HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" + HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" + HeaderSecWebSocketKey = "Sec-WebSocket-Key" + HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" + HeaderSecWebSocketVersion = "Sec-WebSocket-Version" + HeaderAcceptPatch = "Accept-Patch" + HeaderAcceptPushPolicy = "Accept-Push-Policy" + HeaderAcceptSignature = "Accept-Signature" + HeaderAltSvc = "Alt-Svc" + HeaderDate = "Date" + HeaderIndex = "Index" + HeaderLargeAllocation = "Large-Allocation" + HeaderLink = "Link" + HeaderPushPolicy = "Push-Policy" + HeaderRetryAfter = "Retry-After" + HeaderServerTiming = "Server-Timing" + HeaderSignature = "Signature" + HeaderSignedHeaders = "Signed-Headers" + HeaderSourceMap = "SourceMap" + HeaderUpgrade = "Upgrade" + HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" + HeaderXPingback = "X-Pingback" + HeaderXRequestID = "X-Request-ID" + HeaderXRequestedWith = "X-Requested-With" + HeaderXRobotsTag = "X-Robots-Tag" + HeaderXUACompatible = "X-UA-Compatible" + HeaderAccessControlAllowPrivateNetwork = "Access-Control-Allow-Private-Network" + HeaderAccessControlRequestPrivateNetwork = "Access-Control-Request-Private-Network" ) ``` diff --git a/docs/api/fiber.md b/docs/api/fiber.md index 6892225e11..07d24dcc58 100644 --- a/docs/api/fiber.md +++ b/docs/api/fiber.md @@ -42,45 +42,47 @@ app := fiber.New(fiber.Config{ #### Config fields -| Property | Type | Description | Default | -|---------------------------------------------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| -| AppName | `string` | This allows to setup app name for the app | `""` | -| BodyLimit | `int` | Sets the maximum allowed size for a request body, if the size exceeds the configured limit, it sends `413 - Request Entity Too Large` response. | `4 * 1024 * 1024` | -| CaseSensitive | `bool` | When enabled, `/Foo` and `/foo` are different routes. When disabled, `/Foo`and `/foo` are treated the same. | `false` | -| ColorScheme | [`Colors`](https://github.com/gofiber/fiber/blob/master/color.go) | You can define custom color scheme. They'll be used for startup message, route list and some middlewares. | [`DefaultColors`](https://github.com/gofiber/fiber/blob/master/color.go) | -| CompressedFileSuffixes | `map[string]string` | Adds a suffix to the original file name and tries saving the resulting compressed file under the new file name. | `{"gzip": ".fiber.gz", "br": ".fiber.br", "zstd": ".fiber.zst"}` | -| Concurrency | `int` | Maximum number of concurrent connections. | `256 * 1024` | -| DisableDefaultContentType | `bool` | When set to true, causes the default Content-Type header to be excluded from the Response. | `false` | -| DisableDefaultDate | `bool` | When set to true causes the default date header to be excluded from the response. | `false` | -| DisableHeaderNormalizing | `bool` | By default all header names are normalized: conteNT-tYPE -> Content-Type | `false` | -| DisableKeepalive | `bool` | Disable keep-alive connections, the server will close incoming connections after sending the first response to the client | `false` | -| DisablePreParseMultipartForm | `bool` | Will not pre parse Multipart Form data if set to true. This option is useful for servers that desire to treat multipart form data as a binary blob, or choose when to parse the data. | `false` | -| EnableIPValidation | `bool` | If set to true, `c.IP()` and `c.IPs()` will validate IP addresses before returning them. Also, `c.IP()` will return only the first valid IP rather than just the raw header value that may be a comma separated string.

**WARNING:** There is a small performance cost to doing this validation. Keep disabled if speed is your only concern and your application is behind a trusted proxy that already validates this header. | `false` | -| EnableSplittingOnParsers | `bool` | EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true.

For example, you can use it to parse multiple values from a query parameter like this: `/api?foo=bar,baz == foo[]=bar&foo[]=baz` | `false` | +| Property | Type | Description | Default | +|---------------------------------------------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| AppName | `string` | This allows to setup app name for the app | `""` | +| BodyLimit | `int` | Sets the maximum allowed size for a request body, if the size exceeds the configured limit, it sends `413 - Request Entity Too Large` response. | `4 * 1024 * 1024` | +| CaseSensitive | `bool` | When enabled, `/Foo` and `/foo` are different routes. When disabled, `/Foo`and `/foo` are treated the same. | `false` | +| ColorScheme | [`Colors`](https://github.com/gofiber/fiber/blob/master/color.go) | You can define custom color scheme. They'll be used for startup message, route list and some middlewares. | [`DefaultColors`](https://github.com/gofiber/fiber/blob/master/color.go) | +| CompressedFileSuffixes | `map[string]string` | Adds a suffix to the original file name and tries saving the resulting compressed file under the new file name. | `{"gzip": ".fiber.gz", "br": ".fiber.br", "zstd": ".fiber.zst"}` | +| Concurrency | `int` | Maximum number of concurrent connections. | `256 * 1024` | +| DisableDefaultContentType | `bool` | When set to true, causes the default Content-Type header to be excluded from the Response. | `false` | +| DisableDefaultDate | `bool` | When set to true causes the default date header to be excluded from the response. | `false` | +| DisableHeaderNormalizing | `bool` | By default all header names are normalized: conteNT-tYPE -> Content-Type | `false` | +| DisableKeepalive | `bool` | Disable keep-alive connections, the server will close incoming connections after sending the first response to the client | `false` | +| DisablePreParseMultipartForm | `bool` | Will not pre parse Multipart Form data if set to true. This option is useful for servers that desire to treat multipart form data as a binary blob, or choose when to parse the data. | `false` | +| EnableIPValidation | `bool` | If set to true, `c.IP()` and `c.IPs()` will validate IP addresses before returning them. Also, `c.IP()` will return only the first valid IP rather than just the raw header value that may be a comma separated string.

**WARNING:** There is a small performance cost to doing this validation. Keep disabled if speed is your only concern and your application is behind a trusted proxy that already validates this header. | `false` | +| EnableSplittingOnParsers | `bool` | EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true.

For example, you can use it to parse multiple values from a query parameter like this: `/api?foo=bar,baz == foo[]=bar&foo[]=baz` | `false` | | TrustProxy | `bool` | When set to true, fiber will check whether proxy is trusted, using TrustProxyConfig.Proxies list.

By default `c.Protocol()` will get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header, `c.IP()` will get value from `ProxyHeader` header, `c.Hostname()` will get value from X-Forwarded-Host header.
If `TrustProxy` is true, and `RemoteIP` is in the list of `TrustProxyConfig.Proxies` `c.Protocol()`, `c.IP()`, and `c.Hostname()` will have the same behaviour when `TrustProxy` disabled, if `RemoteIP` isn't in the list, `c.Protocol()` will return https when a TLS connection is handled by the app, or http otherwise, `c.IP()` will return RemoteIP() from fasthttp context, `c.Hostname()` will return `fasthttp.Request.URI().Host()` | `false` | -| ErrorHandler | `ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler. Mounted fiber error handlers are retained by the top-level app and applied on prefix associated requests. | `DefaultErrorHandler` | -| GETOnly | `bool` | Rejects all non-GET requests if set to true. This option is useful as anti-DoS protection for servers accepting only GET requests. The request size is limited by ReadBufferSize if GETOnly is set. | `false` | -| IdleTimeout | `time.Duration` | The maximum amount of time to wait for the next request when keep-alive is enabled. If IdleTimeout is zero, the value of ReadTimeout is used. | `nil` | -| Immutable | `bool` | When enabled, all values returned by context methods are immutable. By default, they are valid until you return from the handler; see issue [\#185](https://github.com/gofiber/fiber/issues/185). | `false` | -| JSONDecoder | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` | -| JSONEncoder | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` | -| PassLocalsToViews | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` | -| ProxyHeader | `string` | This will enable `c.IP()` to return the value of the given header key. By default `c.IP()`will return the Remote IP from the TCP connection, this property can be useful if you are behind a load balancer e.g. _X-Forwarded-\*_. | `""` | -| ReadBufferSize | `int` | per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers \(for example, BIG cookies\). | `4096` | -| ReadTimeout | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `nil` | -| ReduceMemoryUsage | `bool` | Aggressively reduces memory usage at the cost of higher CPU usage if set to true. | `false` | -| RequestMethods | `[]string` | RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish. | `DefaultMethods` | -| ServerHeader | `string` | Enables the `Server` HTTP header with the given value. | `""` | -| StreamRequestBody | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` | -| StrictRouting | `bool` | When enabled, the router treats `/foo` and `/foo/` as different. Otherwise, the router treats `/foo` and `/foo/` as the same. | `false` | -| StructValidator | `StructValidator` | If you want to validate header/form/query... automatically when to bind, you can define struct validator. Fiber doesn't have default validator, so it'll skip validator step if you don't use any validator. | `nil` | -| TrustProxyConfig | `TrustProxyConfig` | Configure trusted proxy IP's. Look at `TrustProxy` doc.

`TrustProxyConfig.Proxies` can take IP or IP range addresses. | `nil` | -| UnescapePath | `bool` | Converts all encoded characters in the route back before setting the path for the context, so that the routing can also work with URL encoded special characters | `false` | -| Views | `Views` | Views is the interface that wraps the Render function. See our **Template Middleware** for supported engines. | `nil` | -| ViewsLayout | `string` | Views Layout is the global layout for all template render until override on Render function. See our **Template Middleware** for supported engines. | `""` | -| WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | `4096` | -| WriteTimeout | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` | -| XMLEncoder | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` | +| ErrorHandler | `ErrorHandler` | ErrorHandler is executed when an error is returned from fiber.Handler. Mounted fiber error handlers are retained by the top-level app and applied on prefix associated requests. | `DefaultErrorHandler` | +| GETOnly | `bool` | Rejects all non-GET requests if set to true. This option is useful as anti-DoS protection for servers accepting only GET requests. The request size is limited by ReadBufferSize if GETOnly is set. | `false` | +| IdleTimeout | `time.Duration` | The maximum amount of time to wait for the next request when keep-alive is enabled. If IdleTimeout is zero, the value of ReadTimeout is used. | `nil` | +| Immutable | `bool` | When enabled, all values returned by context methods are immutable. By default, they are valid until you return from the handler; see issue [\#185](https://github.com/gofiber/fiber/issues/185). | `false` | +| JSONEncoder | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` | +| JSONDecoder | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` | +| CBOREncoder | `utils.CBORMarshal` | Allowing for flexibility in using another cbor library for encoding. | `cbor.Marshal` | +| CBORDecoder | `utils.CBORUnmarshal` | Allowing for flexibility in using another cbor library for decoding. | `cbor.Unmarshal` | +| PassLocalsToViews | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` | +| ProxyHeader | `string` | This will enable `c.IP()` to return the value of the given header key. By default `c.IP()`will return the Remote IP from the TCP connection, this property can be useful if you are behind a load balancer e.g. _X-Forwarded-\*_. | `""` | +| ReadBufferSize | `int` | per-connection buffer size for requests' reading. This also limits the maximum header size. Increase this buffer if your clients send multi-KB RequestURIs and/or multi-KB headers \(for example, BIG cookies\). | `4096` | +| ReadTimeout | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `nil` | +| ReduceMemoryUsage | `bool` | Aggressively reduces memory usage at the cost of higher CPU usage if set to true. | `false` | +| RequestMethods | `[]string` | RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish. | `DefaultMethods` | +| ServerHeader | `string` | Enables the `Server` HTTP header with the given value. | `""` | +| StreamRequestBody | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` | +| StrictRouting | `bool` | When enabled, the router treats `/foo` and `/foo/` as different. Otherwise, the router treats `/foo` and `/foo/` as the same. | `false` | +| StructValidator | `StructValidator` | If you want to validate header/form/query... automatically when to bind, you can define struct validator. Fiber doesn't have default validator, so it'll skip validator step if you don't use any validator. | `nil` | +| TrustProxyConfig | `TrustProxyConfig` | Configure trusted proxy IP's. Look at `TrustProxy` doc.

`TrustProxyConfig.Proxies` can take IP or IP range addresses. | `nil` | +| UnescapePath | `bool` | Converts all encoded characters in the route back before setting the path for the context, so that the routing can also work with URL encoded special characters | `false` | +| Views | `Views` | Views is the interface that wraps the Render function. See our **Template Middleware** for supported engines. | `nil` | +| ViewsLayout | `string` | Views Layout is the global layout for all template render until override on Render function. See our **Template Middleware** for supported engines. | `""` | +| WriteBufferSize | `int` | Per-connection buffer size for responses' writing. | `4096` | +| WriteTimeout | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` | +| XMLEncoder | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` | ## Server listening