diff --git a/app.go b/app.go
index c4a26e7772..7f9193a1a1 100644
--- a/app.go
+++ b/app.go
@@ -25,9 +25,9 @@ import (
"sync"
"time"
+ "github.com/fxamacker/cbor/v2"
"github.com/gofiber/fiber/v3/log"
"github.com/gofiber/utils/v2"
-
"github.com/valyala/fasthttp"
)
@@ -320,6 +320,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
//
@@ -537,6 +551,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/bind.go b/bind.go
index ad76594265..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,14 @@ 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
+ }
+ 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 {
@@ -183,6 +191,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 a75722b3b4..55d2dd75e9 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"
@@ -157,7 +158,7 @@ func Test_Bind_Query_WithSetParserDecoder(t *testing.T) {
}
nonRFCTime := binder.ParserType{
- Customtype: NonRFCTime{},
+ CustomType: NonRFCTime{},
Converter: nonRFCConverter,
}
@@ -411,7 +412,7 @@ func Test_Bind_Header_WithSetParserDecoder(t *testing.T) {
}
nonRFCTime := binder.ParserType{
- Customtype: NonRFCTime{},
+ CustomType: NonRFCTime{},
Converter: nonRFCConverter,
}
@@ -922,11 +923,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))
@@ -934,19 +935,36 @@ 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)
+ }
+ 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) {
- 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) {
@@ -1009,7 +1027,7 @@ func Test_Bind_Body_WithSetParserDecoder(t *testing.T) {
}
customTime := binder.ParserType{
- Customtype: CustomTime{},
+ CustomType: CustomTime{},
Converter: timeConverter,
}
@@ -1100,6 +1118,35 @@ 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, 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
func Benchmark_Bind_Body_Form(b *testing.B) {
var err error
@@ -1404,7 +1451,7 @@ func Test_Bind_Cookie_WithSetParserDecoder(t *testing.T) {
}
nonRFCTime := binder.ParserType{
- Customtype: NonRFCTime{},
+ CustomType: NonRFCTime{},
Converter: nonRFCConverter,
}
@@ -1720,8 +1767,12 @@ func Test_Bind_RepeatParserWithSameStruct(t *testing.T) {
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(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/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
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..6f47893531
--- /dev/null
+++ b/binder/cbor.go
@@ -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)
+}
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/client.go b/client/client.go
index 820be4ee41..2c4086aed3 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"
@@ -44,6 +45,8 @@ type Client struct {
jsonUnmarshal utils.JSONUnmarshal
xmlMarshal utils.XMLMarshal
xmlUnmarshal utils.XMLUnmarshal
+ cborMarshal utils.CBORMarshal
+ cborUnmarshal utils.CBORUnmarshal
cookieJar *CookieJar
@@ -150,6 +153,28 @@ func (c *Client) SetXMLUnmarshal(f utils.XMLUnmarshal) *Client {
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.
// If client don't have tlsConfig, this function will init it.
func (c *Client) TLSConfig() *tls.Config {
@@ -706,6 +731,8 @@ func NewWithClient(c *fasthttp.Client) *Client {
jsonMarshal: json.Marshal,
jsonUnmarshal: json.Unmarshal,
xmlMarshal: xml.Marshal,
+ cborMarshal: cbor.Marshal,
+ cborUnmarshal: cbor.Unmarshal,
xmlUnmarshal: xml.Unmarshal,
logger: log.DefaultLogger(),
}
diff --git a/client/client_test.go b/client/client_test.go
index 0a1ad14ab5..f6722114f9 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"
@@ -225,6 +226,33 @@ 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, 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.Parallel()
client := New().
@@ -1443,11 +1471,12 @@ func Test_Set_Config_To_Request(t *testing.T) {
t.Run("set ctx", func(t *testing.T) {
t.Parallel()
- key := struct{}{}
- ctx := context.Background()
- ctx = context.WithValue(ctx, key, "v1") //nolint: staticcheck // not needed for tests
+ type ctxKey struct{}
+ var key ctxKey = struct{}{}
+ ctx := context.Background()
+ ctx = context.WithValue(ctx, key, "v1")
req := AcquireRequest()
setConfigToRequest(req, Config{Ctx: ctx})
diff --git a/client/hooks.go b/client/hooks.go
index f11f9865f3..ec3987938e 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:
@@ -189,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:
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/request.go b/client/request.go
index 61b5798c57..a86d927c4e 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,13 @@ 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
+ 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/request_test.go b/client/request_test.go
index 0593d04824..f62865a342 100644
--- a/client/request_test.go
+++ b/client/request_test.go
@@ -80,11 +80,12 @@ 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))
- ctx = context.WithValue(ctx, key, "string") //nolint: staticcheck // not needed for tests
+ ctx = context.WithValue(ctx, key, "string")
req.SetContext(ctx)
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.Parallel()
testRequest(t,
diff --git a/client/response.go b/client/response.go
index a8a032b6c5..e60c6bd0fb 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)
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) {
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 0e5d732b1f..5dde2f89d0 100644
--- a/ctx.go
+++ b/ctx.go
@@ -884,6 +884,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 f5d0919113..859563f638 100644
--- a/ctx_interface_gen.go
+++ b/ctx_interface_gen.go
@@ -164,6 +164,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 71b9e86221..72c415a9ca 100644
--- a/ctx_test.go
+++ b/ctx_test.go
@@ -12,6 +12,7 @@ import (
"context"
"crypto/tls"
"embed"
+ "encoding/hex"
"encoding/xml"
"errors"
"fmt"
@@ -3618,6 +3619,98 @@ func Benchmark_Ctx_JSON(b *testing.B) {
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
func Benchmark_Ctx_JSON_Ctype(b *testing.B) {
app := New()
diff --git a/docs/api/bind.md b/docs/api/bind.md
index a1cbedd18d..e91fed4273 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 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
This method is similar to [Body Binding](#body), but for cookie parameters.
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/ctx.md b/docs/api/ctx.md
index 2171a0ba80..6f4b1ae4c0 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/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
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/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` |
-| | `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` |
+| | `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` |
-| | `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` |
-| | `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` |
+| | `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` |
+| | `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
diff --git a/docs/client/request.md b/docs/client/request.md
index d1126dae0e..08a04e65e0 100644
--- a/docs/client/request.md
+++ b/docs/client/request.md
@@ -657,6 +657,15 @@ SetXML method sets XML body in 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 method sets body with raw data in request.
diff --git a/docs/client/response.md b/docs/client/response.md
index 2256d400e3..0dba3c6b48 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..04c7a5c44c 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
diff --git a/docs/whats_new.md b/docs/whats_new.md
index 274a7799c8..6b3a459256 100644
--- a/docs/whats_new.md
+++ b/docs/whats_new.md
@@ -310,6 +310,7 @@ testConfig := fiber.TestConfig{
- **SendString**: Similar to Express.js, sends a string as the response.
- **String**: Similar to Express.js, converts a value to a string.
- **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
diff --git a/go.mod b/go.mod
index a94fadd1a8..87580a950d 100644
--- a/go.mod
+++ b/go.mod
@@ -17,10 +17,12 @@ require (
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.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.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
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":