Skip to content

Commit

Permalink
Merge branch 'main' into feature/add-buffered-streaming-support
Browse files Browse the repository at this point in the history
  • Loading branch information
efectn authored Nov 22, 2024
2 parents 7b238d1 + f8b490f commit 95b1745
Show file tree
Hide file tree
Showing 98 changed files with 5,238 additions and 3,181 deletions.
333 changes: 213 additions & 120 deletions .github/README.md

Large diffs are not rendered by default.

Binary file added .github/testdata/fs/img/fiberpng
Binary file not shown.
Binary file added .github/testdata/fs/img/fiberpng.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/testdata/fs/img/fiberpng.notvalidext
Binary file not shown.
6 changes: 3 additions & 3 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:

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

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

- name: Publish Benchmark Results to GitHub Pages
uses: benchmark-action/[email protected].3
uses: benchmark-action/[email protected].4
if: ${{ github.ref_name == 'main' }}
with:
tool: 'go'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ jobs:
uses: golangci/golangci-lint-action@v6
with:
# NOTE: Keep this in sync with the version from .golangci.yml
version: v1.61.0
version: v1.62.0
2 changes: 1 addition & 1 deletion .github/workflows/markdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v4

- name: Run markdownlint-cli2
uses: DavidAnson/markdownlint-cli2-action@v17
uses: DavidAnson/markdownlint-cli2-action@v18
with:
globs: |
**/*.md
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

- name: Upload coverage reports to Codecov
if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.23.x' }}
uses: codecov/codecov-action@v4.5.0
uses: codecov/codecov-action@v5.0.7
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.txt
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ markdown:
## lint: 🚨 Run lint checks
.PHONY: lint
lint:
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.61.0 run ./...
go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 run ./...

## test: 🚦 Execute all tests
.PHONY: test
Expand Down
121 changes: 93 additions & 28 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import (
"encoding/xml"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"os"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -142,7 +144,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
// Default: false
StrictRouting bool `json:"strict_routing"`

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

// Read EnableTrustedProxyCheck doc.
// Read TrustProxy doc.
//
// Default: []string
TrustedProxies []string `json:"trusted_proxies"`
trustedProxiesMap map[string]struct{}
trustedProxyRanges []*net.IPNet
// Default: DefaultTrustProxyConfig
TrustProxyConfig TrustProxyConfig `json:"trust_proxy_config"`

// 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
Expand All @@ -372,7 +376,7 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa
// Default: nil
StructValidator StructValidator

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

// Default TrustProxyConfig
var DefaultTrustProxyConfig = TrustProxyConfig{}

// TrustProxyConfig is a struct for configuring trusted proxies if Config.TrustProxy is true.
type TrustProxyConfig struct {
ips map[string]struct{}

// Proxies is a list of trusted proxy IP addresses or CIDR ranges.
//
// Default: []string
Proxies []string `json:"proxies"`

ranges []*net.IPNet

// LinkLocal enables trusting all link-local IP ranges (e.g., 169.254.0.0/16, fe80::/10).
//
// Default: false
LinkLocal bool `json:"link_local"`

// Loopback enables trusting all loopback IP ranges (e.g., 127.0.0.0/8, ::1/128).
//
// Default: false
Loopback bool `json:"loopback"`

// 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).
//
// Default: false
Private bool `json:"private"`
}

// RouteMessage is some message need to be print when server starts
type RouteMessage struct {
name string
Expand Down Expand Up @@ -510,8 +544,8 @@ func New(config ...Config) *App {
app.config.RequestMethods = DefaultMethods
}

app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies))
for _, ipAddress := range app.config.TrustedProxies {
app.config.TrustProxyConfig.ips = make(map[string]struct{}, len(app.config.TrustProxyConfig.Proxies))
for _, ipAddress := range app.config.TrustProxyConfig.Proxies {
app.handleTrustedProxy(ipAddress)
}

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

// Adds an ip address to trustedProxyRanges or trustedProxiesMap based on whether it is an IP range or not
// Adds an ip address to TrustProxyConfig.ranges or TrustProxyConfig.ips based on whether it is an IP range or not
func (app *App) handleTrustedProxy(ipAddress string) {
if strings.Contains(ipAddress, "/") {
_, ipNet, err := net.ParseCIDR(ipAddress)
if err != nil {
log.Warnf("IP range %q could not be parsed: %v", ipAddress, err)
} else {
app.config.trustedProxyRanges = append(app.config.trustedProxyRanges, ipNet)
app.config.TrustProxyConfig.ranges = append(app.config.TrustProxyConfig.ranges, ipNet)
}
} else {
app.config.trustedProxiesMap[ipAddress] = struct{}{}
ip := net.ParseIP(ipAddress)
if ip == nil {
log.Warnf("IP address %q could not be parsed", ipAddress)
} else {
app.config.TrustProxyConfig.ips[ipAddress] = struct{}{}
}
}
}

Expand Down Expand Up @@ -864,13 +903,33 @@ func (app *App) Hooks() *Hooks {
return app.hooks
}

// TestConfig is a struct holding Test settings
type TestConfig struct {
// Timeout defines the maximum duration a
// test can run before timing out.
// Default: time.Second
Timeout time.Duration

// FailOnTimeout specifies whether the test
// should return a timeout error if the HTTP response
// exceeds the Timeout duration.
// Default: true
FailOnTimeout bool
}

// Test is used for internal debugging by passing a *http.Request.
// Timeout is optional and defaults to 1s, -1 will disable it completely.
func (app *App) Test(req *http.Request, timeout ...time.Duration) (*http.Response, error) {
// Set timeout
to := 1 * time.Second
if len(timeout) > 0 {
to = timeout[0]
// Config is optional and defaults to a 1s error on timeout,
// 0 timeout will disable it completely.
func (app *App) Test(req *http.Request, config ...TestConfig) (*http.Response, error) {
// Default config
cfg := TestConfig{
Timeout: time.Second,
FailOnTimeout: true,
}

// Override config if provided
if len(config) > 0 {
cfg = config[0]
}

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

// Wait for callback
if to >= 0 {
if cfg.Timeout > 0 {
// With timeout
select {
case err = <-channel:
case <-time.After(to):
return nil, fmt.Errorf("test: timeout error after %s", to)
case <-time.After(cfg.Timeout):
conn.Close() //nolint:errcheck, revive // It is fine to ignore the error here
if cfg.FailOnTimeout {
return nil, os.ErrDeadlineExceeded
}
}
} else {
// Without timeout
Expand All @@ -932,6 +994,9 @@ func (app *App) Test(req *http.Request, timeout ...time.Duration) (*http.Respons
// Convert raw http response to *http.Response
res, err := http.ReadResponse(buffer, req)
if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
return nil, errors.New("test: got empty response")
}
return nil, fmt.Errorf("failed to read response: %w", err)
}

Expand Down
41 changes: 34 additions & 7 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"net"
"net/http"
"net/http/httptest"
"os"
"reflect"
"regexp"
"runtime"
Expand Down Expand Up @@ -451,9 +452,9 @@ func Test_App_Use_CaseSensitive(t *testing.T) {
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")

// check the detected path when the case insensitive recognition is activated
// check the detected path when the case-insensitive recognition is activated
app.config.CaseSensitive = false
// check the case sensitive feature
// check the case-sensitive feature
resp, err = app.Test(httptest.NewRequest(MethodGet, "/AbC", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, StatusOK, resp.StatusCode, "Status code")
Expand Down Expand Up @@ -1124,7 +1125,9 @@ func Test_Test_Timeout(t *testing.T) {

app.Get("/", testEmptyHandler)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil), -1)
resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil), TestConfig{
Timeout: 0,
})
require.NoError(t, err, "app.Test(req)")
require.Equal(t, 200, resp.StatusCode, "Status code")

Expand All @@ -1133,7 +1136,10 @@ func Test_Test_Timeout(t *testing.T) {
return nil
})

_, err = app.Test(httptest.NewRequest(MethodGet, "/timeout", nil), 20*time.Millisecond)
_, err = app.Test(httptest.NewRequest(MethodGet, "/timeout", nil), TestConfig{
Timeout: 20 * time.Millisecond,
FailOnTimeout: true,
})
require.Error(t, err, "app.Test(req)")
}

Expand Down Expand Up @@ -1432,7 +1438,9 @@ func Test_App_Test_no_timeout_infinitely(t *testing.T) {
})

req := httptest.NewRequest(MethodGet, "/", nil)
_, err = app.Test(req, -1)
_, err = app.Test(req, TestConfig{
Timeout: 0,
})
}()

tk := time.NewTimer(5 * time.Second)
Expand Down Expand Up @@ -1460,8 +1468,27 @@ func Test_App_Test_timeout(t *testing.T) {
return nil
})

_, err := app.Test(httptest.NewRequest(MethodGet, "/", nil), 100*time.Millisecond)
require.Equal(t, errors.New("test: timeout error after 100ms"), err)
_, err := app.Test(httptest.NewRequest(MethodGet, "/", nil), TestConfig{
Timeout: 100 * time.Millisecond,
FailOnTimeout: true,
})
require.Equal(t, os.ErrDeadlineExceeded, err)
}

func Test_App_Test_timeout_empty_response(t *testing.T) {
t.Parallel()

app := New()
app.Get("/", func(_ Ctx) error {
time.Sleep(1 * time.Second)
return nil
})

_, err := app.Test(httptest.NewRequest(MethodGet, "/", nil), TestConfig{
Timeout: 100 * time.Millisecond,
FailOnTimeout: false,
})
require.Equal(t, errors.New("test: got empty response"), err)
}

func Test_App_SetTLSHandler(t *testing.T) {
Expand Down
Loading

0 comments on commit 95b1745

Please sign in to comment.