🔥 feat: Add support for CBOR encoding (#3173)

* feat(cbor): allow encoding response bodies in cbor

* fix(tests::cbor): encode struct instead of a randomly ordered hashmap

* docs(whats_new): add cbor in context section

* feat(binder): introduce CBOR

* feat(client): allow cbor in fiber client

* chore(tests): add more test

* chore(packages): go mod tidy

* fix(binder): update CBOR name and test

* improve test coverage

* improve test coverage

* update1

* add docs

* doc fixes

* update

* Fix markdown lint

* Add missing entry from binder README

* add/refresh documentation

---------

Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
Co-authored-by: M. Efe Çetin <efectn@protonmail.com>
Co-authored-by: RW <rene@gofiber.io>
pull/3153/head
Sumit Kumar 2024-12-01 15:33:50 +05:30 committed by GitHub
parent 89452fea32
commit 26cc477500
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 760 additions and 198 deletions

22
app.go
View File

@ -25,9 +25,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/fxamacker/cbor/v2"
"github.com/gofiber/fiber/v3/log" "github.com/gofiber/fiber/v3/log"
"github.com/gofiber/utils/v2" "github.com/gofiber/utils/v2"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
@ -320,6 +320,20 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
// Default: json.Unmarshal // Default: json.Unmarshal
JSONDecoder utils.JSONUnmarshal `json:"-"` 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 // XMLEncoder set by an external client of Fiber it will use the provided implementation of a
// XMLMarshal // XMLMarshal
// //
@ -537,6 +551,12 @@ func New(config ...Config) *App {
if app.config.JSONDecoder == nil { if app.config.JSONDecoder == nil {
app.config.JSONDecoder = json.Unmarshal 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 { if app.config.XMLEncoder == nil {
app.config.XMLEncoder = xml.Marshal app.config.XMLEncoder = xml.Marshal
} }

14
bind.go
View File

@ -23,7 +23,7 @@ type Bind struct {
dontHandleErrs bool 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. // It's default behavior of binder.
func (b *Bind) WithoutAutoHandling() *Bind { func (b *Bind) WithoutAutoHandling() *Bind {
b.dontHandleErrs = true b.dontHandleErrs = true
@ -31,7 +31,7 @@ func (b *Bind) WithoutAutoHandling() *Bind {
return b 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`. // 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 // You must still return on error explicitly
func (b *Bind) WithAutoHandling() *Bind { func (b *Bind) WithAutoHandling() *Bind {
@ -121,6 +121,14 @@ func (b *Bind) JSON(out any) error {
return b.validateStruct(out) 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
}
return b.validateStruct(out)
}
// XML binds the body string into the struct. // XML binds the body string into the struct.
func (b *Bind) XML(out any) error { func (b *Bind) XML(out any) error {
if err := b.returnErr(binder.XMLBinder.Bind(b.ctx.Body(), out)); err != nil { if err := b.returnErr(binder.XMLBinder.Bind(b.ctx.Body(), out)); err != nil {
@ -183,6 +191,8 @@ func (b *Bind) Body(out any) error {
return b.JSON(out) return b.JSON(out)
case MIMETextXML, MIMEApplicationXML: case MIMETextXML, MIMEApplicationXML:
return b.XML(out) return b.XML(out)
case MIMEApplicationCBOR:
return b.CBOR(out)
case MIMEApplicationForm: case MIMEApplicationForm:
return b.Form(out) return b.Form(out)
case MIMEMultipartForm: case MIMEMultipartForm:

View File

@ -12,6 +12,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/fxamacker/cbor/v2"
"github.com/gofiber/fiber/v3/binder" "github.com/gofiber/fiber/v3/binder"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
@ -157,7 +158,7 @@ func Test_Bind_Query_WithSetParserDecoder(t *testing.T) {
} }
nonRFCTime := binder.ParserType{ nonRFCTime := binder.ParserType{
Customtype: NonRFCTime{}, CustomType: NonRFCTime{},
Converter: nonRFCConverter, Converter: nonRFCConverter,
} }
@ -411,7 +412,7 @@ func Test_Bind_Header_WithSetParserDecoder(t *testing.T) {
} }
nonRFCTime := binder.ParserType{ nonRFCTime := binder.ParserType{
Customtype: NonRFCTime{}, CustomType: NonRFCTime{},
Converter: nonRFCConverter, Converter: nonRFCConverter,
} }
@ -922,11 +923,11 @@ func Test_Bind_Body(t *testing.T) {
testCompressedBody(t, compressedBody, "zstd") testCompressedBody(t, compressedBody, "zstd")
}) })
testDecodeParser := func(t *testing.T, contentType, body string) { testDecodeParser := func(t *testing.T, contentType string, body []byte) {
t.Helper() t.Helper()
c := app.AcquireCtx(&fasthttp.RequestCtx{}) c := app.AcquireCtx(&fasthttp.RequestCtx{})
c.Request().Header.SetContentType(contentType) c.Request().Header.SetContentType(contentType)
c.Request().SetBody([]byte(body)) c.Request().SetBody(body)
c.Request().Header.SetContentLength(len(body)) c.Request().Header.SetContentLength(len(body))
d := new(Demo) d := new(Demo)
require.NoError(t, c.Bind().Body(d)) require.NoError(t, c.Bind().Body(d))
@ -934,19 +935,36 @@ func Test_Bind_Body(t *testing.T) {
} }
t.Run("JSON", func(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)
}
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) { t.Run("XML", func(t *testing.T) {
testDecodeParser(t, MIMEApplicationXML, `<Demo><name>john</name></Demo>`) testDecodeParser(t, MIMEApplicationXML, []byte(`<Demo><name>john</name></Demo>`))
}) })
t.Run("Form", func(t *testing.T) { 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) { 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) { testDecodeParserError := func(t *testing.T, contentType, body string) {
@ -1009,7 +1027,7 @@ func Test_Bind_Body_WithSetParserDecoder(t *testing.T) {
} }
customTime := binder.ParserType{ customTime := binder.ParserType{
Customtype: CustomTime{}, CustomType: CustomTime{},
Converter: timeConverter, Converter: timeConverter,
} }
@ -1100,6 +1118,35 @@ func Benchmark_Bind_Body_XML(b *testing.B) {
require.Equal(b, "john", d.Name) 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, 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))
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 // go test -v -run=^$ -bench=Benchmark_Bind_Body_Form -benchmem -count=4
func Benchmark_Bind_Body_Form(b *testing.B) { func Benchmark_Bind_Body_Form(b *testing.B) {
var err error var err error
@ -1404,7 +1451,7 @@ func Test_Bind_Cookie_WithSetParserDecoder(t *testing.T) {
} }
nonRFCTime := binder.ParserType{ nonRFCTime := binder.ParserType{
Customtype: NonRFCTime{}, CustomType: NonRFCTime{},
Converter: nonRFCConverter, Converter: nonRFCConverter,
} }
@ -1720,8 +1767,12 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) {
require.Equal(t, "body_param", r.BodyParam) require.Equal(t, "body_param", r.BodyParam)
} }
cb, err := cbor.Marshal(&Request{BodyParam: "body_param"})
require.NoError(t, err, "Failed to marshal CBOR data")
testDecodeParser(MIMEApplicationJSON, `{"body_param":"body_param"}`) testDecodeParser(MIMEApplicationJSON, `{"body_param":"body_param"}`)
testDecodeParser(MIMEApplicationXML, `<Demo><body_param>body_param</body_param></Demo>`) testDecodeParser(MIMEApplicationXML, `<Demo><body_param>body_param</body_param></Demo>`)
testDecodeParser(MIMEApplicationCBOR, string(cb))
testDecodeParser(MIMEApplicationForm, "body_param=body_param") 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--") testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"body_param\"\r\n\r\nbody_param\r\n--b--")
} }

View File

@ -22,6 +22,7 @@ Fiber provides several default binders out of the box:
- [Cookie](cookie.go) - [Cookie](cookie.go)
- [JSON](json.go) - [JSON](json.go)
- [XML](xml.go) - [XML](xml.go)
- [CBOR](cbor.go)
## Guides ## Guides

View File

@ -20,4 +20,5 @@ var (
URIBinder = &uriBinding{} URIBinder = &uriBinding{}
XMLBinder = &xmlBinding{} XMLBinder = &xmlBinding{}
JSONBinder = &jsonBinding{} JSONBinder = &jsonBinding{}
CBORBinder = &cborBinding{}
) )

18
binder/cbor.go Normal file
View File

@ -0,0 +1,18 @@
package binder
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)
}

View File

@ -8,12 +8,15 @@ import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
// cookieBinding is the cookie binder for cookie request body.
type cookieBinding struct{} type cookieBinding struct{}
// Name returns the binding name.
func (*cookieBinding) Name() string { func (*cookieBinding) Name() string {
return "cookie" return "cookie"
} }
// Bind parses the request cookie and returns the result.
func (b *cookieBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { func (b *cookieBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error {
data := make(map[string][]string) data := make(map[string][]string)
var err error var err error

View File

@ -8,12 +8,15 @@ import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
// formBinding is the form binder for form request body.
type formBinding struct{} type formBinding struct{}
// Name returns the binding name.
func (*formBinding) Name() string { func (*formBinding) Name() string {
return "form" return "form"
} }
// Bind parses the request body and returns the result.
func (b *formBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { func (b *formBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error {
data := make(map[string][]string) data := make(map[string][]string)
var err error var err error
@ -47,6 +50,7 @@ func (b *formBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error {
return parse(b.Name(), out, data) 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 { func (b *formBinding) BindMultipart(reqCtx *fasthttp.RequestCtx, out any) error {
data, err := reqCtx.MultipartForm() data, err := reqCtx.MultipartForm()
if err != nil { if err != nil {

View File

@ -8,12 +8,15 @@ import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
// headerBinding is the header binder for header request body.
type headerBinding struct{} type headerBinding struct{}
// Name returns the binding name.
func (*headerBinding) Name() string { func (*headerBinding) Name() string {
return "header" return "header"
} }
// Bind parses the request header and returns the result.
func (b *headerBinding) Bind(req *fasthttp.Request, out any) error { func (b *headerBinding) Bind(req *fasthttp.Request, out any) error {
data := make(map[string][]string) data := make(map[string][]string)
req.Header.VisitAll(func(key, val []byte) { req.Header.VisitAll(func(key, val []byte) {

View File

@ -4,12 +4,15 @@ import (
"github.com/gofiber/utils/v2" "github.com/gofiber/utils/v2"
) )
// jsonBinding is the JSON binder for JSON request body.
type jsonBinding struct{} type jsonBinding struct{}
// Name returns the binding name.
func (*jsonBinding) Name() string { func (*jsonBinding) Name() string {
return "json" return "json"
} }
// Bind parses the request body as JSON and returns the result.
func (*jsonBinding) Bind(body []byte, jsonDecoder utils.JSONUnmarshal, out any) error { func (*jsonBinding) Bind(body []byte, jsonDecoder utils.JSONUnmarshal, out any) error {
return jsonDecoder(body, out) return jsonDecoder(body, out)
} }

View File

@ -24,7 +24,7 @@ type ParserConfig struct {
// ParserType require two element, type and converter for register. // ParserType require two element, type and converter for register.
// Use ParserType with BodyParser for parsing custom type in form data. // Use ParserType with BodyParser for parsing custom type in form data.
type ParserType struct { type ParserType struct {
Customtype any CustomType any
Converter func(string) reflect.Value Converter func(string) reflect.Value
} }
@ -51,7 +51,7 @@ func decoderBuilder(parserConfig ParserConfig) any {
decoder.SetAliasTag(parserConfig.SetAliasTag) decoder.SetAliasTag(parserConfig.SetAliasTag)
} }
for _, v := range parserConfig.ParserType { 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) decoder.ZeroEmpty(parserConfig.ZeroEmpty)
return decoder return decoder

View File

@ -8,12 +8,15 @@ import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
// queryBinding is the query binder for query request body.
type queryBinding struct{} type queryBinding struct{}
// Name returns the binding name.
func (*queryBinding) Name() string { func (*queryBinding) Name() string {
return "query" return "query"
} }
// Bind parses the request query and returns the result.
func (b *queryBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error { func (b *queryBinding) Bind(reqCtx *fasthttp.RequestCtx, out any) error {
data := make(map[string][]string) data := make(map[string][]string)
var err error var err error

View File

@ -8,12 +8,15 @@ import (
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
// respHeaderBinding is the respHeader binder for response header.
type respHeaderBinding struct{} type respHeaderBinding struct{}
// Name returns the binding name.
func (*respHeaderBinding) Name() string { func (*respHeaderBinding) Name() string {
return "respHeader" return "respHeader"
} }
// Bind parses the response header and returns the result.
func (b *respHeaderBinding) Bind(resp *fasthttp.Response, out any) error { func (b *respHeaderBinding) Bind(resp *fasthttp.Response, out any) error {
data := make(map[string][]string) data := make(map[string][]string)
resp.Header.VisitAll(func(key, val []byte) { resp.Header.VisitAll(func(key, val []byte) {

View File

@ -1,11 +1,14 @@
package binder package binder
// uriBinding is the URI binder for URI parameters.
type uriBinding struct{} type uriBinding struct{}
// Name returns the binding name.
func (*uriBinding) Name() string { func (*uriBinding) Name() string {
return "uri" 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 { func (b *uriBinding) Bind(params []string, paramsFunc func(key string, defaultValue ...string) string, out any) error {
data := make(map[string][]string, len(params)) data := make(map[string][]string, len(params))
for _, param := range params { for _, param := range params {

View File

@ -5,12 +5,15 @@ import (
"fmt" "fmt"
) )
// xmlBinding is the XML binder for XML request body.
type xmlBinding struct{} type xmlBinding struct{}
// Name returns the binding name.
func (*xmlBinding) Name() string { func (*xmlBinding) Name() string {
return "xml" return "xml"
} }
// Bind parses the request body as XML and returns the result.
func (*xmlBinding) Bind(body []byte, out any) error { func (*xmlBinding) Bind(body []byte, out any) error {
if err := xml.Unmarshal(body, out); err != nil { if err := xml.Unmarshal(body, out); err != nil {
return fmt.Errorf("failed to unmarshal xml: %w", err) return fmt.Errorf("failed to unmarshal xml: %w", err)

View File

@ -13,6 +13,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/fxamacker/cbor/v2"
"github.com/gofiber/fiber/v3/log" "github.com/gofiber/fiber/v3/log"
"github.com/gofiber/utils/v2" "github.com/gofiber/utils/v2"
@ -44,6 +45,8 @@ type Client struct {
jsonUnmarshal utils.JSONUnmarshal jsonUnmarshal utils.JSONUnmarshal
xmlMarshal utils.XMLMarshal xmlMarshal utils.XMLMarshal
xmlUnmarshal utils.XMLUnmarshal xmlUnmarshal utils.XMLUnmarshal
cborMarshal utils.CBORMarshal
cborUnmarshal utils.CBORUnmarshal
cookieJar *CookieJar cookieJar *CookieJar
@ -150,6 +153,28 @@ func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client {
return c return c
} }
// CBORMarshal returns CBOR marshal function in Core.
func (c *Client) CBORMarshal() utils.CBORMarshal {
return c.cborMarshal
}
// SetCBORMarshal sets CBOR encoder.
func (c *Client) SetCBORMarshal(f utils.CBORMarshal) *Client {
c.cborMarshal = f
return c
}
// CBORUnmarshal returns CBOR unmarshal function in Core.
func (c *Client) CBORUnmarshal() utils.CBORUnmarshal {
return c.cborUnmarshal
}
// SetCBORUnmarshal sets CBOR decoder.
func (c *Client) SetCBORUnmarshal(f utils.CBORUnmarshal) *Client {
c.cborUnmarshal = f
return c
}
// TLSConfig returns tlsConfig in client. // TLSConfig returns tlsConfig in client.
// If client don't have tlsConfig, this function will init it. // If client don't have tlsConfig, this function will init it.
func (c *Client) TLSConfig() *tls.Config { func (c *Client) TLSConfig() *tls.Config {
@ -706,6 +731,8 @@ func NewWithClient(c *fasthttp.Client) *Client {
jsonMarshal: json.Marshal, jsonMarshal: json.Marshal,
jsonUnmarshal: json.Unmarshal, jsonUnmarshal: json.Unmarshal,
xmlMarshal: xml.Marshal, xmlMarshal: xml.Marshal,
cborMarshal: cbor.Marshal,
cborUnmarshal: cbor.Unmarshal,
xmlUnmarshal: xml.Unmarshal, xmlUnmarshal: xml.Unmarshal,
logger: log.DefaultLogger(), logger: log.DefaultLogger(),
} }

View File

@ -3,6 +3,7 @@ package client
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/hex"
"errors" "errors"
"io" "io"
"net" "net"
@ -225,6 +226,33 @@ func Test_Client_Marshal(t *testing.T) {
require.Equal(t, errors.New("empty xml"), err) require.Equal(t, errors.New("empty xml"), err)
}) })
t.Run("set cbor marshal", func(t *testing.T) {
t.Parallel()
bs, err := hex.DecodeString("f6")
if err != nil {
t.Error(err)
}
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(_ 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.Run("set xml unmarshal", func(t *testing.T) {
t.Parallel() t.Parallel()
client := New(). client := New().
@ -1443,11 +1471,12 @@ func Test_Set_Config_To_Request(t *testing.T) {
t.Run("set ctx", func(t *testing.T) { t.Run("set ctx", func(t *testing.T) {
t.Parallel() t.Parallel()
key := struct{}{}
type ctxKey struct{}
var key ctxKey = struct{}{}
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, key, "v1") //nolint: staticcheck // not needed for tests ctx = context.WithValue(ctx, key, "v1")
req := AcquireRequest() req := AcquireRequest()
setConfigToRequest(req, Config{Ctx: ctx}) setConfigToRequest(req, Config{Ctx: ctx})

View File

@ -23,6 +23,7 @@ var (
headerAccept = "Accept" headerAccept = "Accept"
applicationJSON = "application/json" applicationJSON = "application/json"
applicationCBOR = "application/cbor"
applicationXML = "application/xml" applicationXML = "application/xml"
applicationForm = "application/x-www-form-urlencoded" applicationForm = "application/x-www-form-urlencoded"
multipartFormData = "multipart/form-data" multipartFormData = "multipart/form-data"
@ -129,6 +130,8 @@ func parserRequestHeader(c *Client, req *Request) error {
req.RawRequest.Header.Set(headerAccept, applicationJSON) req.RawRequest.Header.Set(headerAccept, applicationJSON)
case xmlBody: case xmlBody:
req.RawRequest.Header.SetContentType(applicationXML) req.RawRequest.Header.SetContentType(applicationXML)
case cborBody:
req.RawRequest.Header.SetContentType(applicationCBOR)
case formBody: case formBody:
req.RawRequest.Header.SetContentType(applicationForm) req.RawRequest.Header.SetContentType(applicationForm)
case filesBody: case filesBody:
@ -189,6 +192,12 @@ func parserRequestBody(c *Client, req *Request) error {
return err return err
} }
req.RawRequest.SetBody(body) req.RawRequest.SetBody(body)
case cborBody:
body, err := c.cborMarshal(req.body)
if err != nil {
return err
}
req.RawRequest.SetBody(body)
case formBody: case formBody:
req.RawRequest.SetBody(req.formData.QueryString()) req.RawRequest.SetBody(req.formData.QueryString())
case filesBody: case filesBody:

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/fxamacker/cbor/v2"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -456,6 +457,30 @@ func Test_Parser_Request_Body(t *testing.T) {
require.Equal(t, []byte("<body><name>foo</name></body>"), req.RawRequest.Body()) require.Equal(t, []byte("<body><name>foo</name></body>"), 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.Run("form data body", func(t *testing.T) {
t.Parallel() t.Parallel()
client := New() client := New()

View File

@ -34,6 +34,7 @@ const (
formBody formBody
filesBody filesBody
rawBody rawBody
cborBody
) )
var ErrClientNil = errors.New("client can not be nil") var ErrClientNil = errors.New("client can not be nil")
@ -337,6 +338,13 @@ func (r *Request) SetXML(v any) *Request {
return r return r
} }
// SetCBOR method sets CBOR body in request.
func (r *Request) SetCBOR(v any) *Request {
r.body = v
r.bodyType = cborBody
return r
}
// SetRawBody method sets body with raw data in request. // SetRawBody method sets body with raw data in request.
func (r *Request) SetRawBody(v []byte) *Request { func (r *Request) SetRawBody(v []byte) *Request {
r.body = v r.body = v

View File

@ -80,11 +80,12 @@ func Test_Request_Context(t *testing.T) {
req := AcquireRequest() req := AcquireRequest()
ctx := req.Context() ctx := req.Context()
key := struct{}{} type ctxKey struct{}
var key ctxKey = struct{}{}
require.Nil(t, ctx.Value(key)) 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) req.SetContext(ctx)
ctx = req.Context() ctx = req.Context()
@ -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.Run("formdata", func(t *testing.T) {
t.Parallel() t.Parallel()
testRequest(t, testRequest(t,

View File

@ -75,6 +75,11 @@ func (r *Response) JSON(v any) error {
return r.client.jsonUnmarshal(r.Body(), v) 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. // XML method will unmarshal body to xml.
func (r *Response) XML(v any) error { func (r *Response) XML(v any) error {
return r.client.xmlUnmarshal(r.Body(), v) return r.client.xmlUnmarshal(r.Body(), v)

View File

@ -240,6 +240,18 @@ func Test_Response_Body(t *testing.T) {
app.Get("/xml", func(c fiber.Ctx) error { app.Get("/xml", func(c fiber.Ctx) error {
return c.SendString("<status><name>success</name></status>") return c.SendString("<status><name>success</name></status>")
}) })
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 return server
@ -327,6 +339,36 @@ func Test_Response_Body(t *testing.T) {
require.Equal(t, "success", tmp.Status) require.Equal(t, "success", tmp.Status)
resp.Close() 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) { func Test_Response_Save(t *testing.T) {

View File

@ -23,6 +23,7 @@ const (
MIMETextCSS = "text/css" MIMETextCSS = "text/css"
MIMEApplicationXML = "application/xml" MIMEApplicationXML = "application/xml"
MIMEApplicationJSON = "application/json" MIMEApplicationJSON = "application/json"
MIMEApplicationCBOR = "application/cbor"
// Deprecated: use MIMETextJavaScript instead // Deprecated: use MIMETextJavaScript instead
MIMEApplicationJavaScript = "application/javascript" MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationForm = "application/x-www-form-urlencoded" MIMEApplicationForm = "application/x-www-form-urlencoded"

18
ctx.go
View File

@ -884,6 +884,24 @@ func (c *DefaultCtx) JSON(data any, ctype ...string) error {
return nil 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. // JSONP sends a JSON response with JSONP support.
// This method is identical to JSON, except that it opts-in to JSONP callback support. // This method is identical to JSON, except that it opts-in to JSONP callback support.
// By default, the callback name is simply callback. // By default, the callback name is simply callback.

View File

@ -164,6 +164,11 @@ type Ctx interface {
// Content-Type header equal to ctype. If ctype is not given, // Content-Type header equal to ctype. If ctype is not given,
// The Content-Type header will be set to application/json. // The Content-Type header will be set to application/json.
JSON(data any, ctype ...string) error 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. // JSONP sends a JSON response with JSONP support.
// This method is identical to JSON, except that it opts-in to JSONP callback support. // This method is identical to JSON, except that it opts-in to JSONP callback support.
// By default, the callback name is simply callback. // By default, the callback name is simply callback.

View File

@ -12,6 +12,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"embed" "embed"
"encoding/hex"
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt" "fmt"
@ -3618,6 +3619,98 @@ func Benchmark_Ctx_JSON(b *testing.B) {
require.JSONEq(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body())) require.JSONEq(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)))
type dummyStruct struct {
Name string
Age int
}
// Test without ctype
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(dummyStruct{ // map has no order
Name: "Grame",
Age: 20,
}, "application/problem+cbor")
require.NoError(t, err)
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) {
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")
// 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()
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 // go test -run=^$ -bench=Benchmark_Ctx_JSON_Ctype -benchmem -count=4
func Benchmark_Ctx_JSON_Ctype(b *testing.B) { func Benchmark_Ctx_JSON_Ctype(b *testing.B) {
app := New() app := New()

View File

@ -20,6 +20,7 @@ Make copies or use the [**`Immutable`**](./ctx.md) setting instead. [Read more..
- [JSON](#json) - [JSON](#json)
- [MultipartForm](#multipartform) - [MultipartForm](#multipartform)
- [XML](#xml) - [XML](#xml)
- [CBOR](#cbor)
- [Cookie](#cookie) - [Cookie](#cookie)
- [Header](#header) - [Header](#header)
- [Query](#query) - [Query](#query)
@ -226,6 +227,43 @@ Run tests with the following `curl` command:
curl -X POST -H "Content-Type: application/xml" --data "<login><name>john</name><pass>doe</pass></login>" localhost:3000 curl -X POST -H "Content-Type: application/xml" --data "<login><name>john</name><pass>doe</pass></login>" 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 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
```
```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 ### Cookie
This method is similar to [Body Binding](#body), but for cookie parameters. This method is similar to [Body Binding](#body), but for cookie parameters.

View File

@ -26,25 +26,26 @@ const (
```go ```go
const ( const (
MIMETextXML = "text/xml" MIMETextXML = "text/xml"
MIMETextHTML = "text/html" MIMETextHTML = "text/html"
MIMETextPlain = "text/plain" MIMETextPlain = "text/plain"
MIMETextJavaScript = "text/javascript" MIMETextJavaScript = "text/javascript"
MIMETextCSS = "text/css" MIMETextCSS = "text/css"
MIMEApplicationXML = "application/xml" MIMEApplicationXML = "application/xml"
MIMEApplicationJSON = "application/json" MIMEApplicationJSON = "application/json"
MIMEApplicationJavaScript = "application/javascript" MIMEApplicationCBOR = "application/cbor"
MIMEApplicationForm = "application/x-www-form-urlencoded" MIMEApplicationJavaScript = "application/javascript"
MIMEOctetStream = "application/octet-stream" MIMEApplicationForm = "application/x-www-form-urlencoded"
MIMEMultipartForm = "multipart/form-data" MIMEOctetStream = "application/octet-stream"
MIMEMultipartForm = "multipart/form-data"
MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8" MIMETextXMLCharsetUTF8 = "text/xml; charset=utf-8"
MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8" MIMETextHTMLCharsetUTF8 = "text/html; charset=utf-8"
MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8" MIMETextPlainCharsetUTF8 = "text/plain; charset=utf-8"
MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8" MIMETextJavaScriptCharsetUTF8 = "text/javascript; charset=utf-8"
MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8" MIMETextCSSCharsetUTF8 = "text/css; charset=utf-8"
MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8" MIMEApplicationXMLCharsetUTF8 = "application/xml; charset=utf-8"
MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8" MIMEApplicationJSONCharsetUTF8 = "application/json; charset=utf-8"
MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8" MIMEApplicationJavaScriptCharsetUTF8 = "application/javascript; charset=utf-8"
)``` )```
@ -72,6 +73,7 @@ const (
StatusSeeOther = 303 // RFC 7231, 6.4.4 StatusSeeOther = 303 // RFC 7231, 6.4.4
StatusNotModified = 304 // RFC 7232, 4.1 StatusNotModified = 304 // RFC 7232, 4.1
StatusUseProxy = 305 // RFC 7231, 6.4.5 StatusUseProxy = 305 // RFC 7231, 6.4.5
StatusSwitchProxy = 306 // RFC 9110, 15.4.7 (Unused)
StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
StatusPermanentRedirect = 308 // RFC 7538, 3 StatusPermanentRedirect = 308 // RFC 7538, 3
StatusBadRequest = 400 // RFC 7231, 6.5.1 StatusBadRequest = 400 // RFC 7231, 6.5.1
@ -168,127 +170,129 @@ HTTP Headers were copied from net/http.
```go ```go
const ( const (
HeaderAuthorization = "Authorization" HeaderAuthorization = "Authorization"
HeaderProxyAuthenticate = "Proxy-Authenticate" HeaderProxyAuthenticate = "Proxy-Authenticate"
HeaderProxyAuthorization = "Proxy-Authorization" HeaderProxyAuthorization = "Proxy-Authorization"
HeaderWWWAuthenticate = "WWW-Authenticate" HeaderWWWAuthenticate = "WWW-Authenticate"
HeaderAge = "Age" HeaderAge = "Age"
HeaderCacheControl = "Cache-Control" HeaderCacheControl = "Cache-Control"
HeaderClearSiteData = "Clear-Site-Data" HeaderClearSiteData = "Clear-Site-Data"
HeaderExpires = "Expires" HeaderExpires = "Expires"
HeaderPragma = "Pragma" HeaderPragma = "Pragma"
HeaderWarning = "Warning" HeaderWarning = "Warning"
HeaderAcceptCH = "Accept-CH" HeaderAcceptCH = "Accept-CH"
HeaderAcceptCHLifetime = "Accept-CH-Lifetime" HeaderAcceptCHLifetime = "Accept-CH-Lifetime"
HeaderContentDPR = "Content-DPR" HeaderContentDPR = "Content-DPR"
HeaderDPR = "DPR" HeaderDPR = "DPR"
HeaderEarlyData = "Early-Data" HeaderEarlyData = "Early-Data"
HeaderSaveData = "Save-Data" HeaderSaveData = "Save-Data"
HeaderViewportWidth = "Viewport-Width" HeaderViewportWidth = "Viewport-Width"
HeaderWidth = "Width" HeaderWidth = "Width"
HeaderETag = "ETag" HeaderETag = "ETag"
HeaderIfMatch = "If-Match" HeaderIfMatch = "If-Match"
HeaderIfModifiedSince = "If-Modified-Since" HeaderIfModifiedSince = "If-Modified-Since"
HeaderIfNoneMatch = "If-None-Match" HeaderIfNoneMatch = "If-None-Match"
HeaderIfUnmodifiedSince = "If-Unmodified-Since" HeaderIfUnmodifiedSince = "If-Unmodified-Since"
HeaderLastModified = "Last-Modified" HeaderLastModified = "Last-Modified"
HeaderVary = "Vary" HeaderVary = "Vary"
HeaderConnection = "Connection" HeaderConnection = "Connection"
HeaderKeepAlive = "Keep-Alive" HeaderKeepAlive = "Keep-Alive"
HeaderAccept = "Accept" HeaderAccept = "Accept"
HeaderAcceptCharset = "Accept-Charset" HeaderAcceptCharset = "Accept-Charset"
HeaderAcceptEncoding = "Accept-Encoding" HeaderAcceptEncoding = "Accept-Encoding"
HeaderAcceptLanguage = "Accept-Language" HeaderAcceptLanguage = "Accept-Language"
HeaderCookie = "Cookie" HeaderCookie = "Cookie"
HeaderExpect = "Expect" HeaderExpect = "Expect"
HeaderMaxForwards = "Max-Forwards" HeaderMaxForwards = "Max-Forwards"
HeaderSetCookie = "Set-Cookie" HeaderSetCookie = "Set-Cookie"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers" HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods" HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin" HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age" HeaderAccessControlMaxAge = "Access-Control-Max-Age"
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers" HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
HeaderAccessControlRequestMethod = "Access-Control-Request-Method" HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderOrigin = "Origin" HeaderOrigin = "Origin"
HeaderTimingAllowOrigin = "Timing-Allow-Origin" HeaderTimingAllowOrigin = "Timing-Allow-Origin"
HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies" HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies"
HeaderDNT = "DNT" HeaderDNT = "DNT"
HeaderTk = "Tk" HeaderTk = "Tk"
HeaderContentDisposition = "Content-Disposition" HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding" HeaderContentEncoding = "Content-Encoding"
HeaderContentLanguage = "Content-Language" HeaderContentLanguage = "Content-Language"
HeaderContentLength = "Content-Length" HeaderContentLength = "Content-Length"
HeaderContentLocation = "Content-Location" HeaderContentLocation = "Content-Location"
HeaderContentType = "Content-Type" HeaderContentType = "Content-Type"
HeaderForwarded = "Forwarded" HeaderForwarded = "Forwarded"
HeaderVia = "Via" HeaderVia = "Via"
HeaderXForwardedFor = "X-Forwarded-For" HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedHost = "X-Forwarded-Host" HeaderXForwardedHost = "X-Forwarded-Host"
HeaderXForwardedProto = "X-Forwarded-Proto" HeaderXForwardedProto = "X-Forwarded-Proto"
HeaderXForwardedProtocol = "X-Forwarded-Protocol" HeaderXForwardedProtocol = "X-Forwarded-Protocol"
HeaderXForwardedSsl = "X-Forwarded-Ssl" HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme" HeaderXUrlScheme = "X-Url-Scheme"
HeaderLocation = "Location" HeaderLocation = "Location"
HeaderFrom = "From" HeaderFrom = "From"
HeaderHost = "Host" HeaderHost = "Host"
HeaderReferer = "Referer" HeaderReferer = "Referer"
HeaderReferrerPolicy = "Referrer-Policy" HeaderReferrerPolicy = "Referrer-Policy"
HeaderUserAgent = "User-Agent" HeaderUserAgent = "User-Agent"
HeaderAllow = "Allow" HeaderAllow = "Allow"
HeaderServer = "Server" HeaderServer = "Server"
HeaderAcceptRanges = "Accept-Ranges" HeaderAcceptRanges = "Accept-Ranges"
HeaderContentRange = "Content-Range" HeaderContentRange = "Content-Range"
HeaderIfRange = "If-Range" HeaderIfRange = "If-Range"
HeaderRange = "Range" HeaderRange = "Range"
HeaderContentSecurityPolicy = "Content-Security-Policy" HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy" HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy"
HeaderExpectCT = "Expect-CT" HeaderExpectCT = "Expect-CT"
HeaderFeaturePolicy = "Feature-Policy" HeaderFeaturePolicy = "Feature-Policy"
HeaderPublicKeyPins = "Public-Key-Pins" HeaderPublicKeyPins = "Public-Key-Pins"
HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only" HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only"
HeaderStrictTransportSecurity = "Strict-Transport-Security" HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests" HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests"
HeaderXContentTypeOptions = "X-Content-Type-Options" HeaderXContentTypeOptions = "X-Content-Type-Options"
HeaderXDownloadOptions = "X-Download-Options" HeaderXDownloadOptions = "X-Download-Options"
HeaderXFrameOptions = "X-Frame-Options" HeaderXFrameOptions = "X-Frame-Options"
HeaderXPoweredBy = "X-Powered-By" HeaderXPoweredBy = "X-Powered-By"
HeaderXXSSProtection = "X-XSS-Protection" HeaderXXSSProtection = "X-XSS-Protection"
HeaderLastEventID = "Last-Event-ID" HeaderLastEventID = "Last-Event-ID"
HeaderNEL = "NEL" HeaderNEL = "NEL"
HeaderPingFrom = "Ping-From" HeaderPingFrom = "Ping-From"
HeaderPingTo = "Ping-To" HeaderPingTo = "Ping-To"
HeaderReportTo = "Report-To" HeaderReportTo = "Report-To"
HeaderTE = "TE" HeaderTE = "TE"
HeaderTrailer = "Trailer" HeaderTrailer = "Trailer"
HeaderTransferEncoding = "Transfer-Encoding" HeaderTransferEncoding = "Transfer-Encoding"
HeaderSecWebSocketAccept = "Sec-WebSocket-Accept" HeaderSecWebSocketAccept = "Sec-WebSocket-Accept"
HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions" HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions"
HeaderSecWebSocketKey = "Sec-WebSocket-Key" HeaderSecWebSocketKey = "Sec-WebSocket-Key"
HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol" HeaderSecWebSocketProtocol = "Sec-WebSocket-Protocol"
HeaderSecWebSocketVersion = "Sec-WebSocket-Version" HeaderSecWebSocketVersion = "Sec-WebSocket-Version"
HeaderAcceptPatch = "Accept-Patch" HeaderAcceptPatch = "Accept-Patch"
HeaderAcceptPushPolicy = "Accept-Push-Policy" HeaderAcceptPushPolicy = "Accept-Push-Policy"
HeaderAcceptSignature = "Accept-Signature" HeaderAcceptSignature = "Accept-Signature"
HeaderAltSvc = "Alt-Svc" HeaderAltSvc = "Alt-Svc"
HeaderDate = "Date" HeaderDate = "Date"
HeaderIndex = "Index" HeaderIndex = "Index"
HeaderLargeAllocation = "Large-Allocation" HeaderLargeAllocation = "Large-Allocation"
HeaderLink = "Link" HeaderLink = "Link"
HeaderPushPolicy = "Push-Policy" HeaderPushPolicy = "Push-Policy"
HeaderRetryAfter = "Retry-After" HeaderRetryAfter = "Retry-After"
HeaderServerTiming = "Server-Timing" HeaderServerTiming = "Server-Timing"
HeaderSignature = "Signature" HeaderSignature = "Signature"
HeaderSignedHeaders = "Signed-Headers" HeaderSignedHeaders = "Signed-Headers"
HeaderSourceMap = "SourceMap" HeaderSourceMap = "SourceMap"
HeaderUpgrade = "Upgrade" HeaderUpgrade = "Upgrade"
HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control" HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control"
HeaderXPingback = "X-Pingback" HeaderXPingback = "X-Pingback"
HeaderXRequestID = "X-Request-ID" HeaderXRequestID = "X-Request-ID"
HeaderXRequestedWith = "X-Requested-With" HeaderXRequestedWith = "X-Requested-With"
HeaderXRobotsTag = "X-Robots-Tag" HeaderXRobotsTag = "X-Robots-Tag"
HeaderXUACompatible = "X-UA-Compatible" HeaderXUACompatible = "X-UA-Compatible"
HeaderAccessControlAllowPrivateNetwork = "Access-Control-Allow-Private-Network"
HeaderAccessControlRequestPrivateNetwork = "Access-Control-Request-Private-Network"
) )
``` ```

View File

@ -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/cbor`.
:::
```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 ## Links
Joins the links followed by the property to populate the responses [Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) HTTP header field. Joins the links followed by the property to populate the responses [Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) HTTP header field.

View File

@ -42,45 +42,47 @@ app := fiber.New(fiber.Config{
#### Config fields #### Config fields
| Property | Type | Description | Default | | Property | Type | Description | Default |
|---------------------------------------------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| |---------------------------------------------------------------------------------------|-------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
| <Reference id="appname">AppName</Reference> | `string` | This allows to setup app name for the app | `""` | | <Reference id="appname">AppName</Reference> | `string` | This allows to setup app name for the app | `""` |
| <Reference id="bodylimit">BodyLimit</Reference> | `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` | | <Reference id="bodylimit">BodyLimit</Reference> | `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` |
| <Reference id="casesensitive">CaseSensitive</Reference> | `bool` | When enabled, `/Foo` and `/foo` are different routes. When disabled, `/Foo`and `/foo` are treated the same. | `false` | | <Reference id="casesensitive">CaseSensitive</Reference> | `bool` | When enabled, `/Foo` and `/foo` are different routes. When disabled, `/Foo`and `/foo` are treated the same. | `false` |
| <Reference id="colorscheme">ColorScheme</Reference> | [`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) | | <Reference id="colorscheme">ColorScheme</Reference> | [`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) |
| <Reference id="compressedfilesuffixes">CompressedFileSuffixes</Reference> | `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"}` | | <Reference id="compressedfilesuffixes">CompressedFileSuffixes</Reference> | `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"}` |
| <Reference id="concurrency">Concurrency</Reference> | `int` | Maximum number of concurrent connections. | `256 * 1024` | | <Reference id="concurrency">Concurrency</Reference> | `int` | Maximum number of concurrent connections. | `256 * 1024` |
| <Reference id="disabledefaultcontenttype">DisableDefaultContentType</Reference> | `bool` | When set to true, causes the default Content-Type header to be excluded from the Response. | `false` | | <Reference id="disabledefaultcontenttype">DisableDefaultContentType</Reference> | `bool` | When set to true, causes the default Content-Type header to be excluded from the Response. | `false` |
| <Reference id="disabledefaultdate">DisableDefaultDate</Reference> | `bool` | When set to true causes the default date header to be excluded from the response. | `false` | | <Reference id="disabledefaultdate">DisableDefaultDate</Reference> | `bool` | When set to true causes the default date header to be excluded from the response. | `false` |
| <Reference id="disableheadernormalizing">DisableHeaderNormalizing</Reference> | `bool` | By default all header names are normalized: conteNT-tYPE -&gt; Content-Type | `false` | | <Reference id="disableheadernormalizing">DisableHeaderNormalizing</Reference> | `bool` | By default all header names are normalized: conteNT-tYPE -&gt; Content-Type | `false` |
| <Reference id="disablekeepalive">DisableKeepalive</Reference> | `bool` | Disable keep-alive connections, the server will close incoming connections after sending the first response to the client | `false` | | <Reference id="disablekeepalive">DisableKeepalive</Reference> | `bool` | Disable keep-alive connections, the server will close incoming connections after sending the first response to the client | `false` |
| <Reference id="disablepreparsemultipartform">DisablePreParseMultipartForm</Reference> | `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` | | <Reference id="disablepreparsemultipartform">DisablePreParseMultipartForm</Reference> | `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` |
| <Reference id="enableipvalidation">EnableIPValidation</Reference> | `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.<br /><br />**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` | | <Reference id="enableipvalidation">EnableIPValidation</Reference> | `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.<br /><br />**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` |
| <Reference id="enablesplittingonparsers">EnableSplittingOnParsers</Reference> | `bool` | EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true. <br /> <br /> 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` | | <Reference id="enablesplittingonparsers">EnableSplittingOnParsers</Reference> | `bool` | EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true. <br /> <br /> 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` |
| <Reference id="trustproxy">TrustProxy</Reference> | `bool` | When set to true, fiber will check whether proxy is trusted, using TrustProxyConfig.Proxies list. <br /><br />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. <br /> 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` | | <Reference id="trustproxy">TrustProxy</Reference> | `bool` | When set to true, fiber will check whether proxy is trusted, using TrustProxyConfig.Proxies list. <br /><br />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. <br /> 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` |
| <Reference id="errorhandler">ErrorHandler</Reference> | `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` | | <Reference id="errorhandler">ErrorHandler</Reference> | `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` |
| <Reference id="getonly">GETOnly</Reference> | `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` | | <Reference id="getonly">GETOnly</Reference> | `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` |
| <Reference id="idletimeout">IdleTimeout</Reference> | `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` | | <Reference id="idletimeout">IdleTimeout</Reference> | `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` |
| <Reference id="immutable">Immutable</Reference> | `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` | | <Reference id="immutable">Immutable</Reference> | `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` |
| <Reference id="jsondecoder">JSONDecoder</Reference> | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` | | <Reference id="jsonencoder">JSONEncoder</Reference> | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` |
| <Reference id="jsonencoder">JSONEncoder</Reference> | `utils.JSONMarshal` | Allowing for flexibility in using another json library for encoding. | `json.Marshal` | | <Reference id="jsondecoder">JSONDecoder</Reference> | `utils.JSONUnmarshal` | Allowing for flexibility in using another json library for decoding. | `json.Unmarshal` |
| <Reference id="passlocalstoviews">PassLocalsToViews</Reference> | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` | | <Reference id="cborencoder">CBOREncoder</Reference> | `utils.CBORMarshal` | Allowing for flexibility in using another cbor library for encoding. | `cbor.Marshal` |
| <Reference id="proxyheader">ProxyHeader</Reference> | `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-\*_. | `""` | | <Reference id="cbordecoder">CBORDecoder</Reference> | `utils.CBORUnmarshal` | Allowing for flexibility in using another cbor library for decoding. | `cbor.Unmarshal` |
| <Reference id="readbuffersize">ReadBufferSize</Reference> | `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` | | <Reference id="passlocalstoviews">PassLocalsToViews</Reference> | `bool` | PassLocalsToViews Enables passing of the locals set on a fiber.Ctx to the template engine. See our **Template Middleware** for supported engines. | `false` |
| <Reference id="readtimeout">ReadTimeout</Reference> | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `nil` | | <Reference id="proxyheader">ProxyHeader</Reference> | `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-\*_. | `""` |
| <Reference id="reducememoryusage">ReduceMemoryUsage</Reference> | `bool` | Aggressively reduces memory usage at the cost of higher CPU usage if set to true. | `false` | | <Reference id="readbuffersize">ReadBufferSize</Reference> | `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` |
| <Reference id="requestmethods">RequestMethods</Reference> | `[]string` | RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish. | `DefaultMethods` | | <Reference id="readtimeout">ReadTimeout</Reference> | `time.Duration` | The amount of time allowed to read the full request, including the body. The default timeout is unlimited. | `nil` |
| <Reference id="serverheader">ServerHeader</Reference> | `string` | Enables the `Server` HTTP header with the given value. | `""` | | <Reference id="reducememoryusage">ReduceMemoryUsage</Reference> | `bool` | Aggressively reduces memory usage at the cost of higher CPU usage if set to true. | `false` |
| <Reference id="streamrequestbody">StreamRequestBody</Reference> | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` | | <Reference id="requestmethods">RequestMethods</Reference> | `[]string` | RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish. | `DefaultMethods` |
| <Reference id="strictrouting">StrictRouting</Reference> | `bool` | When enabled, the router treats `/foo` and `/foo/` as different. Otherwise, the router treats `/foo` and `/foo/` as the same. | `false` | | <Reference id="serverheader">ServerHeader</Reference> | `string` | Enables the `Server` HTTP header with the given value. | `""` |
| <Reference id="structvalidator">StructValidator</Reference> | `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` | | <Reference id="streamrequestbody">StreamRequestBody</Reference> | `bool` | StreamRequestBody enables request body streaming, and calls the handler sooner when given body is larger than the current limit. | `false` |
| <Reference id="trustproxyconfig">TrustProxyConfig</Reference> | `TrustProxyConfig` | Configure trusted proxy IP's. Look at `TrustProxy` doc. <br /> <br /> `TrustProxyConfig.Proxies` can take IP or IP range addresses. | `nil` | | <Reference id="strictrouting">StrictRouting</Reference> | `bool` | When enabled, the router treats `/foo` and `/foo/` as different. Otherwise, the router treats `/foo` and `/foo/` as the same. | `false` |
| <Reference id="unescapepath">UnescapePath</Reference> | `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` | | <Reference id="structvalidator">StructValidator</Reference> | `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` |
| <Reference id="views">Views</Reference> | `Views` | Views is the interface that wraps the Render function. See our **Template Middleware** for supported engines. | `nil` | | <Reference id="trustproxyconfig">TrustProxyConfig</Reference> | `TrustProxyConfig` | Configure trusted proxy IP's. Look at `TrustProxy` doc. <br /> <br /> `TrustProxyConfig.Proxies` can take IP or IP range addresses. | `nil` |
| <Reference id="viewslayout">ViewsLayout</Reference> | `string` | Views Layout is the global layout for all template render until override on Render function. See our **Template Middleware** for supported engines. | `""` | | <Reference id="unescapepath">UnescapePath</Reference> | `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` |
| <Reference id="writebuffersize">WriteBufferSize</Reference> | `int` | Per-connection buffer size for responses' writing. | `4096` | | <Reference id="views">Views</Reference> | `Views` | Views is the interface that wraps the Render function. See our **Template Middleware** for supported engines. | `nil` |
| <Reference id="writetimeout">WriteTimeout</Reference> | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` | | <Reference id="viewslayout">ViewsLayout</Reference> | `string` | Views Layout is the global layout for all template render until override on Render function. See our **Template Middleware** for supported engines. | `""` |
| <Reference id="xmlencoder">XMLEncoder</Reference> | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` | | <Reference id="writebuffersize">WriteBufferSize</Reference> | `int` | Per-connection buffer size for responses' writing. | `4096` |
| <Reference id="writetimeout">WriteTimeout</Reference> | `time.Duration` | The maximum duration before timing out writes of the response. The default timeout is unlimited. | `nil` |
| <Reference id="xmlencoder">XMLEncoder</Reference> | `utils.XMLMarshal` | Allowing for flexibility in using another XML library for encoding. | `xml.Marshal` |
## Server listening ## Server listening

View File

@ -657,6 +657,15 @@ SetXML method sets XML body in request.
func (r *Request) SetXML(v any) *Request 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"`.
```go title="Signature"
func (r *Request) SetCBOR(v any) *Request
```
## SetRawBody ## SetRawBody
SetRawBody method sets body with raw data in request. SetRawBody method sets body with raw data in request.

View File

@ -187,6 +187,14 @@ XML method will unmarshal body to xml.
func (r *Response) XML(v any) error 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
Save method will save the body to a file or io.Writer. Save method will save the body to a file or io.Writer.

View File

@ -81,6 +81,8 @@ type Client struct {
jsonUnmarshal utils.JSONUnmarshal jsonUnmarshal utils.JSONUnmarshal
xmlMarshal utils.XMLMarshal xmlMarshal utils.XMLMarshal
xmlUnmarshal utils.XMLUnmarshal xmlUnmarshal utils.XMLUnmarshal
cborMarshal utils.CBORMarshal
cborUnmarshal utils.CBORUnmarshal
cookieJar *CookieJar cookieJar *CookieJar
@ -314,6 +316,40 @@ SetXMLUnmarshal sets the XML decoder.
func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client 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 ### TLS
#### TLSConfig #### TLSConfig

View File

@ -310,6 +310,7 @@ testConfig := fiber.TestConfig{
- **SendString**: Similar to Express.js, sends a string as the response. - **SendString**: Similar to Express.js, sends a string as the response.
- **String**: Similar to Express.js, converts a value to a string. - **String**: Similar to Express.js, converts a value to a string.
- **ViewBind**: Binds data to a view, replacing the old `Bind` method. - **ViewBind**: Binds data to a view, replacing the old `Bind` method.
- **CBOR**: Introducing [CBOR](https://cbor.io/) binary encoding format for both request & response body. CBOR is a binary data serialization format which is both compact and efficient, making it ideal for use in web applications.
### Removed Methods ### Removed Methods

2
go.mod
View File

@ -17,10 +17,12 @@ require (
require ( require (
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/davecgh/go-spew 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.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/tcplisten 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.30.0 // indirect golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect

View File

@ -52,6 +52,9 @@ func (z *item) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "headers", za0001) err = msgp.WrapError(err, "headers", za0001)
return return
} }
if za0002 == nil {
za0002 = make([]byte, 0)
}
z.headers[za0001] = za0002 z.headers[za0001] = za0002
} }
case "body": case "body":
@ -267,6 +270,9 @@ func (z *item) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "headers", za0001) err = msgp.WrapError(err, "headers", za0001)
return return
} }
if za0002 == nil {
za0002 = make([]byte, 0)
}
z.headers[za0001] = za0002 z.headers[za0001] = za0002
} }
case "body": case "body":