Skip to content

Commit

Permalink
Merge branch 'main' into feat-listen-gracefulshutdown-timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
gaby authored Dec 3, 2024
2 parents dd0b528 + 9a2ceb7 commit a15845d
Show file tree
Hide file tree
Showing 49 changed files with 1,761 additions and 320 deletions.
6 changes: 3 additions & 3 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Fiber v3 is currently in beta and under active development. While it offers exci

## ⚙️ Installation

Fiber requires **Go version `1.22` or higher** to run. If you need to install or upgrade Go, visit the [official Go download page](https://go.dev/dl/). To start setting up your project, create a new directory for your project and navigate into it. Then, initialize your project with Go modules by executing the following command in your terminal:
Fiber requires **Go version `1.23` or higher** to run. If you need to install or upgrade Go, visit the [official Go download page](https://go.dev/dl/). To start setting up your project, create a new directory for your project and navigate into it. Then, initialize your project with Go modules by executing the following command in your terminal:

```bash
go mod init github.com/your/repo
Expand Down Expand Up @@ -124,7 +124,7 @@ We **listen** to our users in [issues](https://github.com/gofiber/fiber/issues),

## ⚠️ Limitations

- Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber v3 has been tested with Go versions 1.22 and 1.23.
- Due to Fiber's usage of unsafe, the library may not always be compatible with the latest Go version. Fiber v3 has been tested with Go version 1.23.
- Fiber is not compatible with net/http interfaces. This means you will not be able to use projects like gqlgen, go-swagger, or any others which are part of the net/http ecosystem.

## 👀 Examples
Expand Down Expand Up @@ -708,7 +708,7 @@ List of externally hosted middleware modules and maintained by the [Fiber team](
| :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------- |
| [contrib](https://github.com/gofiber/contrib) | Third-party middlewares |
| [storage](https://github.com/gofiber/storage) | Premade storage drivers that implement the Storage interface, designed to be used with various Fiber middlewares. |
| [template](https://github.com/gofiber/template) | This package contains 9 template engines that can be used with Fiber `v3`. Go version 1.22 or higher is required. |
| [template](https://github.com/gofiber/template) | This package contains 9 template engines that can be used with Fiber `v3`. Go version 1.23 or higher is required. |

## 🕶️ Awesome List

Expand Down
32 changes: 31 additions & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,40 @@ jobs:
uses: actions/setup-go@v5
with:
# NOTE: Keep this in sync with the version from go.mod
go-version: "1.22.x"
go-version: "1.23.x"

- name: Run Benchmark
run: set -o pipefail; go test ./... -benchmem -run=^$ -bench . | tee output.txt
### hack because of the problem with duplicated benchmark names - https://github.com/benchmark-action/github-action-benchmark/issues/264
- name: Extract Module Name
id: extract-module
run: |
MODULE_NAME=$(awk '/^module / {print $2}' go.mod)
echo "MODULE_NAME=$MODULE_NAME" >> $GITHUB_ENV
- name: Identify Duplicate Benchmark Names
run: |
awk '/^Benchmark/ {print $1}' output.txt | sort | uniq -d > duplicate_benchmarks.txt
- name: Add Normalized Package Prefix to Duplicate Benchmark Names
run: |
awk -v MODULE_NAME="$MODULE_NAME" '
FNR==NR {duplicates[$1]; next}
/^pkg: / { package=$2 }
/^Benchmark/ {
if ($1 in duplicates) {
sub("^" MODULE_NAME "/?", "", package)
gsub("/", "_", package)
print $1 "_" package substr($0, length($1) + 1)
} else {
print $0
}
next
}
{ print }
' duplicate_benchmarks.txt output.txt > output_prefixed.txt
mv output_prefixed.txt output.txt
### end

# NOTE: Benchmarks could change with different CPU types
- name: Get GitHub Runner System Information
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- uses: actions/setup-go@v5
with:
# NOTE: Keep this in sync with the version from go.mod
go-version: "1.22.x"
go-version: "1.23.x"
cache: false

- name: golangci-lint
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
unit:
strategy:
matrix:
go-version: [1.22.x, 1.23.x]
go-version: [1.23.x]
platform: [ubuntu-latest, windows-latest, macos-latest, macos-13]
runs-on: ${{ matrix.platform }}
steps:
Expand Down
22 changes: 21 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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
//
Expand Down Expand Up @@ -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
}
Expand Down
14 changes: 12 additions & 2 deletions bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ 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

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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down
71 changes: 61 additions & 10 deletions bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -157,7 +158,7 @@ func Test_Bind_Query_WithSetParserDecoder(t *testing.T) {
}

nonRFCTime := binder.ParserType{
Customtype: NonRFCTime{},
CustomType: NonRFCTime{},
Converter: nonRFCConverter,
}

Expand Down Expand Up @@ -411,7 +412,7 @@ func Test_Bind_Header_WithSetParserDecoder(t *testing.T) {
}

nonRFCTime := binder.ParserType{
Customtype: NonRFCTime{},
CustomType: NonRFCTime{},
Converter: nonRFCConverter,
}

Expand Down Expand Up @@ -922,31 +923,48 @@ 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))
require.Equal(t, "john", d.Name)
}

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, `<Demo><name>john</name></Demo>`)
testDecodeParser(t, MIMEApplicationXML, []byte(`<Demo><name>john</name></Demo>`))
})

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) {
Expand Down Expand Up @@ -1009,7 +1027,7 @@ func Test_Bind_Body_WithSetParserDecoder(t *testing.T) {
}

customTime := binder.ParserType{
Customtype: CustomTime{},
CustomType: CustomTime{},
Converter: timeConverter,
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1404,7 +1451,7 @@ func Test_Bind_Cookie_WithSetParserDecoder(t *testing.T) {
}

nonRFCTime := binder.ParserType{
Customtype: NonRFCTime{},
CustomType: NonRFCTime{},
Converter: nonRFCConverter,
}

Expand Down Expand Up @@ -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, `<Demo><body_param>body_param</body_param></Demo>`)
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--")
}
1 change: 1 addition & 0 deletions binder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ var (
URIBinder = &uriBinding{}
XMLBinder = &xmlBinding{}
JSONBinder = &jsonBinding{}
CBORBinder = &cborBinding{}
)
18 changes: 18 additions & 0 deletions binder/cbor.go
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 3 additions & 0 deletions binder/cookie.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions binder/form.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit a15845d

Please sign in to comment.