Skip to content

Commit 95b1745

Browse files
authored
Merge branch 'main' into feature/add-buffered-streaming-support
2 parents 7b238d1 + f8b490f commit 95b1745

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+5238
-3181
lines changed

.github/README.md

Lines changed: 213 additions & 120 deletions
Large diffs are not rendered by default.

.github/testdata/fs/img/fiberpng

1.51 KB
Binary file not shown.
1.51 KB
Loading
1.51 KB
Binary file not shown.

.github/workflows/benchmark.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656

5757
# This will only run if we have Benchmark Results from main branch
5858
- name: Compare PR Benchmark Results with main branch
59-
uses: benchmark-action/[email protected].3
59+
uses: benchmark-action/[email protected].4
6060
if: steps.cache.outputs.cache-hit == 'true'
6161
with:
6262
tool: 'go'
@@ -72,7 +72,7 @@ jobs:
7272
alert-threshold: "150%"
7373

7474
- name: Store Benchmark Results for main branch
75-
uses: benchmark-action/[email protected].3
75+
uses: benchmark-action/[email protected].4
7676
if: ${{ github.ref_name == 'main' }}
7777
with:
7878
tool: 'go'
@@ -86,7 +86,7 @@ jobs:
8686
alert-threshold: "150%"
8787

8888
- name: Publish Benchmark Results to GitHub Pages
89-
uses: benchmark-action/[email protected].3
89+
uses: benchmark-action/[email protected].4
9090
if: ${{ github.ref_name == 'main' }}
9191
with:
9292
tool: 'go'

.github/workflows/linter.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,4 @@ jobs:
3737
uses: golangci/golangci-lint-action@v6
3838
with:
3939
# NOTE: Keep this in sync with the version from .golangci.yml
40-
version: v1.61.0
40+
version: v1.62.0

.github/workflows/markdown.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
uses: actions/checkout@v4
1616

1717
- name: Run markdownlint-cli2
18-
uses: DavidAnson/markdownlint-cli2-action@v17
18+
uses: DavidAnson/markdownlint-cli2-action@v18
1919
with:
2020
globs: |
2121
**/*.md

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232

3333
- name: Upload coverage reports to Codecov
3434
if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.23.x' }}
35-
uses: codecov/codecov-action@v4.5.0
35+
uses: codecov/codecov-action@v5.0.7
3636
with:
3737
token: ${{ secrets.CODECOV_TOKEN }}
3838
file: ./coverage.txt

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ markdown:
3535
## lint: 🚨 Run lint checks
3636
.PHONY: lint
3737
lint:
38-
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 run ./...
38+
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 run ./...
3939

4040
## test: 🚦 Execute all tests
4141
.PHONY: test

app.go

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import (
1414
"encoding/xml"
1515
"errors"
1616
"fmt"
17+
"io"
1718
"net"
1819
"net/http"
1920
"net/http/httputil"
21+
"os"
2022
"reflect"
2123
"strconv"
2224
"strings"
@@ -142,7 +144,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
142144
// Default: false
143145
StrictRouting bool `json:"strict_routing"`
144146

145-
// When set to true, enables case sensitive routing.
147+
// When set to true, enables case-sensitive routing.
146148
// E.g. "/FoO" and "/foo" are treated as different routes.
147149
// By default this is disabled and both "/FoO" and "/foo" will execute the same handler.
148150
//
@@ -330,29 +332,31 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
330332
// For example, the Host HTTP header is usually used to return the requested host.
331333
// But when you’re behind a proxy, the actual host may be stored in an X-Forwarded-Host header.
332334
//
333-
// If you are behind a proxy, you should enable TrustedProxyCheck to prevent header spoofing.
334-
// If you enable EnableTrustedProxyCheck and leave TrustedProxies empty Fiber will skip
335+
// If you are behind a proxy, you should enable TrustProxy to prevent header spoofing.
336+
// If you enable TrustProxy and do not provide a TrustProxyConfig, Fiber will skip
335337
// all headers that could be spoofed.
336-
// If request ip in TrustedProxies whitelist then:
338+
// If the request IP is in the TrustProxyConfig.Proxies allowlist, then:
337339
// 1. c.Scheme() get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header
338340
// 2. c.IP() get value from ProxyHeader header.
339341
// 3. c.Host() and c.Hostname() get value from X-Forwarded-Host header
340-
// But if request ip NOT in Trusted Proxies whitelist then:
341-
// 1. c.Scheme() WON't get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header,
342-
// will return https in case when tls connection is handled by the app, of http otherwise
342+
// But if the request IP is NOT in the TrustProxyConfig.Proxies allowlist, then:
343+
// 1. c.Scheme() WON'T get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header,
344+
// will return https when a TLS connection is handled by the app, or http otherwise.
343345
// 2. c.IP() WON'T get value from ProxyHeader header, will return RemoteIP() from fasthttp context
344346
// 3. c.Host() and c.Hostname() WON'T get value from X-Forwarded-Host header, fasthttp.Request.URI().Host()
345347
// will be used to get the hostname.
346348
//
349+
// To automatically trust all loopback, link-local, or private IP addresses,
350+
// without manually adding them to the TrustProxyConfig.Proxies allowlist,
351+
// you can set TrustProxyConfig.Loopback, TrustProxyConfig.LinkLocal, or TrustProxyConfig.Private to true.
352+
//
347353
// Default: false
348-
EnableTrustedProxyCheck bool `json:"enable_trusted_proxy_check"`
354+
TrustProxy bool `json:"trust_proxy"`
349355

350-
// Read EnableTrustedProxyCheck doc.
356+
// Read TrustProxy doc.
351357
//
352-
// Default: []string
353-
TrustedProxies []string `json:"trusted_proxies"`
354-
trustedProxiesMap map[string]struct{}
355-
trustedProxyRanges []*net.IPNet
358+
// Default: DefaultTrustProxyConfig
359+
TrustProxyConfig TrustProxyConfig `json:"trust_proxy_config"`
356360

357361
// If set to true, c.IP() and c.IPs() will validate IP addresses before returning them.
358362
// Also, c.IP() will return only the first valid IP rather than just the raw header
@@ -372,7 +376,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
372376
// Default: nil
373377
StructValidator StructValidator
374378

375-
// RequestMethods provides customizibility for HTTP methods. You can add/remove methods as you wish.
379+
// RequestMethods provides customizability for HTTP methods. You can add/remove methods as you wish.
376380
//
377381
// Optional. Default: DefaultMethods
378382
RequestMethods []string
@@ -385,6 +389,36 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
385389
EnableSplittingOnParsers bool `json:"enable_splitting_on_parsers"`
386390
}
387391

392+
// Default TrustProxyConfig
393+
var DefaultTrustProxyConfig = TrustProxyConfig{}
394+
395+
// TrustProxyConfig is a struct for configuring trusted proxies if Config.TrustProxy is true.
396+
type TrustProxyConfig struct {
397+
ips map[string]struct{}
398+
399+
// Proxies is a list of trusted proxy IP addresses or CIDR ranges.
400+
//
401+
// Default: []string
402+
Proxies []string `json:"proxies"`
403+
404+
ranges []*net.IPNet
405+
406+
// LinkLocal enables trusting all link-local IP ranges (e.g., 169.254.0.0/16, fe80::/10).
407+
//
408+
// Default: false
409+
LinkLocal bool `json:"link_local"`
410+
411+
// Loopback enables trusting all loopback IP ranges (e.g., 127.0.0.0/8, ::1/128).
412+
//
413+
// Default: false
414+
Loopback bool `json:"loopback"`
415+
416+
// Private enables trusting all private IP ranges (e.g., 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7).
417+
//
418+
// Default: false
419+
Private bool `json:"private"`
420+
}
421+
388422
// RouteMessage is some message need to be print when server starts
389423
type RouteMessage struct {
390424
name string
@@ -510,8 +544,8 @@ func New(config ...Config) *App {
510544
app.config.RequestMethods = DefaultMethods
511545
}
512546

513-
app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies))
514-
for _, ipAddress := range app.config.TrustedProxies {
547+
app.config.TrustProxyConfig.ips = make(map[string]struct{}, len(app.config.TrustProxyConfig.Proxies))
548+
for _, ipAddress := range app.config.TrustProxyConfig.Proxies {
515549
app.handleTrustedProxy(ipAddress)
516550
}
517551

@@ -529,17 +563,22 @@ func New(config ...Config) *App {
529563
return app
530564
}
531565

532-
// Adds an ip address to trustedProxyRanges or trustedProxiesMap based on whether it is an IP range or not
566+
// Adds an ip address to TrustProxyConfig.ranges or TrustProxyConfig.ips based on whether it is an IP range or not
533567
func (app *App) handleTrustedProxy(ipAddress string) {
534568
if strings.Contains(ipAddress, "/") {
535569
_, ipNet, err := net.ParseCIDR(ipAddress)
536570
if err != nil {
537571
log.Warnf("IP range %q could not be parsed: %v", ipAddress, err)
538572
} else {
539-
app.config.trustedProxyRanges = append(app.config.trustedProxyRanges, ipNet)
573+
app.config.TrustProxyConfig.ranges = append(app.config.TrustProxyConfig.ranges, ipNet)
540574
}
541575
} else {
542-
app.config.trustedProxiesMap[ipAddress] = struct{}{}
576+
ip := net.ParseIP(ipAddress)
577+
if ip == nil {
578+
log.Warnf("IP address %q could not be parsed", ipAddress)
579+
} else {
580+
app.config.TrustProxyConfig.ips[ipAddress] = struct{}{}
581+
}
543582
}
544583
}
545584

@@ -864,13 +903,33 @@ func (app *App) Hooks() *Hooks {
864903
return app.hooks
865904
}
866905

906+
// TestConfig is a struct holding Test settings
907+
type TestConfig struct {
908+
// Timeout defines the maximum duration a
909+
// test can run before timing out.
910+
// Default: time.Second
911+
Timeout time.Duration
912+
913+
// FailOnTimeout specifies whether the test
914+
// should return a timeout error if the HTTP response
915+
// exceeds the Timeout duration.
916+
// Default: true
917+
FailOnTimeout bool
918+
}
919+
867920
// Test is used for internal debugging by passing a *http.Request.
868-
// Timeout is optional and defaults to 1s, -1 will disable it completely.
869-
func (app *App) Test(req *http.Request, timeout ...time.Duration) (*http.Response, error) {
870-
// Set timeout
871-
to := 1 * time.Second
872-
if len(timeout) > 0 {
873-
to = timeout[0]
921+
// Config is optional and defaults to a 1s error on timeout,
922+
// 0 timeout will disable it completely.
923+
func (app *App) Test(req *http.Request, config ...TestConfig) (*http.Response, error) {
924+
// Default config
925+
cfg := TestConfig{
926+
Timeout: time.Second,
927+
FailOnTimeout: true,
928+
}
929+
930+
// Override config if provided
931+
if len(config) > 0 {
932+
cfg = config[0]
874933
}
875934

876935
// Add Content-Length if not provided with body
@@ -909,12 +968,15 @@ func (app *App) Test(req *http.Request, timeout ...time.Duration) (*http.Respons
909968
}()
910969

911970
// Wait for callback
912-
if to >= 0 {
971+
if cfg.Timeout > 0 {
913972
// With timeout
914973
select {
915974
case err = <-channel:
916-
case <-time.After(to):
917-
return nil, fmt.Errorf("test: timeout error after %s", to)
975+
case <-time.After(cfg.Timeout):
976+
conn.Close() //nolint:errcheck, revive // It is fine to ignore the error here
977+
if cfg.FailOnTimeout {
978+
return nil, os.ErrDeadlineExceeded
979+
}
918980
}
919981
} else {
920982
// Without timeout
@@ -932,6 +994,9 @@ func (app *App) Test(req *http.Request, timeout ...time.Duration) (*http.Respons
932994
// Convert raw http response to *http.Response
933995
res, err := http.ReadResponse(buffer, req)
934996
if err != nil {
997+
if errors.Is(err, io.ErrUnexpectedEOF) {
998+
return nil, errors.New("test: got empty response")
999+
}
9351000
return nil, fmt.Errorf("failed to read response: %w", err)
9361001
}
9371002

0 commit comments

Comments
 (0)