Compare commits

...

13 Commits

Author SHA1 Message Date
ljluestc
d3adef23e6
Merge 0cebb2da0a9921d5080255df5c18c2699eb58590 into e88fc8927a52b74f55bec0351604a56ac0aa1c51 2025-11-21 10:22:25 +01:00
Bo-Yi Wu
e88fc8927a
ci(sec): schedule Trivy security scans to run daily at midnight UTC (#4439)
- Change Trivy scan schedule from quarterly to daily runs at 00:00 UTC

Signed-off-by: appleboy <appleboy.tw@gmail.com>
2025-11-18 23:05:54 +08:00
Pawan Kalyan
5fad976b37
fix(gin): literal colon routes not working with engine.Handler() (#4415)
* fix: call updateRouteTrees in ServeHTTP using sync.Once to support literal colon routes in all usage scenarios (#4413)

* chore: fixed golangci-lint issue in test cases for literal colon

* fix: gofumpt formatting issue

* fix: gofumpt issue in gin.go

* chore: updated routeTreesUpdated comments

* chore: removed unused variable and updated TestUpdateRouteTreesCalledOnce testcase

* chore: moved tests from literal_colon_test.go into gin_test.go

---------

Co-authored-by: pawannn <pawan@zenz.tech>
2025-11-16 09:22:07 +08:00
Bo-Yi Wu
93ff771e6d
ci(sec): improve type safety and server organization in HTTP middleware (#4437)
- Update linting configuration to exclude G115 gosec check instead of including specific checks
- Add the safeInt8 helper for safer type conversions and use it to prevent int8 overflow in middleware handler execution
- Group related constants and variables together for better organization in gin.go
- Refactor HTTP server instantiation to use a dedicated http.Server object for all Run methods
- Add the safeUint16 helper and use it to safely handle conversions in tree node functions to prevent uint16 overflow

Signed-off-by: appleboy <appleboy.tw@gmail.com>
2025-11-15 23:03:32 +08:00
AtoriUzawa
58135f06cf
docs(context): add example comments for ShouldBind* methods (#4428)
- Added detailed example for ShouldBindJSON
- Added consistent descriptive comments for ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindTOML, ShouldBindPlain, ShouldBindHeader, ShouldBindUri
- Makes binding method usage clearer for new users
2025-11-15 19:46:45 +08:00
efcking
a85ef5ce4d
refactor: use b.Loop() to simplify the code and improve performance (#4432)
Signed-off-by: efcking <efcking@outlook.com>
2025-11-15 19:22:18 +08:00
Bo-Yi Wu
fb27ef26c2
ci(lint): refactor test assertions and linter configuration (#4436)
- Update golangci-lint GitHub Action version from v2.1.6 to v2.6
- Remove the gci formatter and exclusions for third_party, builtin, and examples from the linter config
- Fix argument order for assert.EqualValues and assert.Exactly in context tests for clarity
- Refactor integration tests to build response strings using strings.Builder instead of direct concatenation for improved performance and readability

Signed-off-by: appleboy <appleboy.tw@gmail.com>
2025-11-15 19:21:42 +08:00
dependabot[bot]
19c2d5c0d1
chore(deps): bump golang.org/x/net from 0.46.0 to 0.47.0 (#4433)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.46.0 to 0.47.0.
- [Commits](https://github.com/golang/net/compare/v0.46.0...v0.47.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-15 12:42:25 +08:00
dependabot[bot]
a9401cd238
chore(deps): bump github.com/quic-go/quic-go from 0.55.0 to 0.56.0 (#4430)
Bumps [github.com/quic-go/quic-go](https://github.com/quic-go/quic-go) from 0.55.0 to 0.56.0.
- [Release notes](https://github.com/quic-go/quic-go/releases)
- [Commits](https://github.com/quic-go/quic-go/compare/v0.55.0...v0.56.0)

---
updated-dependencies:
- dependency-name: github.com/quic-go/quic-go
  dependency-version: 0.56.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-15 12:32:48 +08:00
dependabot[bot]
d1bcabc7ee
chore(deps): bump golangci/golangci-lint-action in the actions group (#4431)
Bumps the actions group with 1 update: [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action).


Updates `golangci/golangci-lint-action` from 8 to 9
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v8...v9)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-15 12:32:23 +08:00
ljluestc
0cebb2da0a fix context copy 2025-03-23 22:40:36 -07:00
ljluestc
e5d837948a fix context error 2025-03-23 22:10:34 -07:00
ljluestc
043b245931 fix context error 2025-03-23 20:49:53 -07:00
15 changed files with 281 additions and 72 deletions

View File

@ -24,9 +24,9 @@ jobs:
with:
go-version: "^1"
- name: Setup golangci-lint
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@v9
with:
version: v2.1.6
version: v2.6
args: --verbose
test:
needs: lint

View File

@ -8,9 +8,8 @@ on:
branches:
- master
schedule:
# Run every 3 months (quarterly) on the 1st day at 00:00 UTC
# Months: January (1), April (4), July (7), October (10)
- cron: '0 0 1 1,4,7,10 *'
# Run daily at 00:00 UTC
- cron: '0 0 * * *'
workflow_dispatch: # Allow manual trigger
permissions:

View File

@ -18,15 +18,8 @@ linters:
- wastedassign
settings:
gosec:
includes:
- G102
- G106
- G108
- G109
- G111
- G112
- G201
- G203
excludes:
- G115
perfsprint:
int-conversion: true
err-error: true
@ -68,7 +61,6 @@ linters:
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
@ -80,7 +72,4 @@ formatters:
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
- gin.go

View File

@ -18,9 +18,8 @@ func BenchmarkSliceValidationError(b *testing.B) {
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
if len(e.Error()) == 0 {
b.Errorf("error")
}

View File

@ -31,7 +31,7 @@ type structFull struct {
func BenchmarkMapFormFull(b *testing.B) {
var s structFull
for i := 0; i < b.N; i++ {
for b.Loop() {
err := mapForm(&s, form)
if err != nil {
b.Fatalf("Error on a form mapping")
@ -54,7 +54,7 @@ type structName struct {
func BenchmarkMapFormName(b *testing.B) {
var s structName
for i := 0; i < b.N; i++ {
for b.Loop() {
err := mapForm(&s, form)
if err != nil {
b.Fatalf("Error on a form mapping")

View File

@ -55,6 +55,14 @@ const ContextRequestKey ContextKeyType = 0
// abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math.MaxInt8 >> 1
// safeInt8 converts int to int8 safely, capping at math.MaxInt8
func safeInt8(n int) int8 {
if n > math.MaxInt8 {
return math.MaxInt8
}
return int8(n)
}
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
@ -140,6 +148,15 @@ func (c *Context) Copy() *Context {
cp.Params = make([]Param, len(cParams))
copy(cp.Params, cParams)
cErrors := c.Errors
cp.Errors = make(errorMsgs, len(cErrors))
for i, e := range cErrors {
cp.Errors[i] = &Error{
Err: e.Err,
Type: e.Type,
Meta: e.Meta,
}
}
return &cp
}
@ -186,7 +203,7 @@ func (c *Context) FullPath() string {
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
for c.index < safeInt8(len(c.handlers)) {
if c.handlers[c.index] != nil {
c.handlers[c.index](c)
}
@ -830,41 +847,71 @@ func (c *Context) ShouldBind(obj any) error {
}
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
//
// Example:
//
// POST /user
// Content-Type: application/json
//
// Request Body:
// {
// "name": "Manu",
// "age": 20
// }
//
// type User struct {
// Name string `json:"name"`
// Age int `json:"age"`
// }
//
// var user User
// if err := c.ShouldBindJSON(&user); err != nil {
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// return
// }
// c.JSON(http.StatusOK, user)
func (c *Context) ShouldBindJSON(obj any) error {
return c.ShouldBindWith(obj, binding.JSON)
}
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
// It works like ShouldBindJSON but binds the request body as XML data.
func (c *Context) ShouldBindXML(obj any) error {
return c.ShouldBindWith(obj, binding.XML)
}
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
// It works like ShouldBindJSON but binds query parameters from the URL.
func (c *Context) ShouldBindQuery(obj any) error {
return c.ShouldBindWith(obj, binding.Query)
}
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
// It works like ShouldBindJSON but binds the request body as YAML data.
func (c *Context) ShouldBindYAML(obj any) error {
return c.ShouldBindWith(obj, binding.YAML)
}
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
// It works like ShouldBindJSON but binds the request body as TOML data.
func (c *Context) ShouldBindTOML(obj any) error {
return c.ShouldBindWith(obj, binding.TOML)
}
// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain).
// It works like ShouldBindJSON but binds plain text data from the request body.
func (c *Context) ShouldBindPlain(obj any) error {
return c.ShouldBindWith(obj, binding.Plain)
}
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
// It works like ShouldBindJSON but binds values from HTTP headers.
func (c *Context) ShouldBindHeader(obj any) error {
return c.ShouldBindWith(obj, binding.Header)
}
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
// It works like ShouldBindJSON but binds parameters from the URI.
func (c *Context) ShouldBindUri(obj any) error {
m := make(map[string][]string, len(c.Params))
for _, v := range c.Params {

View File

@ -292,7 +292,7 @@ func TestContextReset(t *testing.T) {
assert.Empty(t, c.Errors.Errors())
assert.Empty(t, c.Errors.ByType(ErrorTypeAny))
assert.Empty(t, c.Params)
assert.EqualValues(t, c.index, -1)
assert.EqualValues(t, -1, c.index)
assert.Equal(t, c.Writer.(*responseWriter), &c.writermem)
}
@ -384,7 +384,7 @@ func TestContextSetGetValues(t *testing.T) {
c.Set("intInterface", a)
assert.Exactly(t, "this is a string", c.MustGet("string").(string))
assert.Exactly(t, c.MustGet("int32").(int32), int32(-42))
assert.Exactly(t, int32(-42), c.MustGet("int32").(int32))
assert.Exactly(t, int64(42424242424242), c.MustGet("int64").(int64))
assert.Exactly(t, uint64(42), c.MustGet("uint64").(uint64))
assert.InDelta(t, float32(4.2), c.MustGet("float32").(float32), 0.01)
@ -671,6 +671,41 @@ func TestContextCopy(t *testing.T) {
assert.Equal(t, cp.fullPath, c.fullPath)
}
func TestContextCopyErrors(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
// Add errors to the original context
c.Error(fmt.Errorf("first error")).SetType(ErrorTypePublic).SetMeta("meta1") // nolint: errcheck
c.Error(fmt.Errorf("second error")).SetType(ErrorTypePrivate).SetMeta(42) // nolint: errcheck
// Copy the context
cp := c.Copy()
// Verify the copied context has the same number of errors
assert.Equal(t, len(c.Errors), len(cp.Errors), "Copied context should have the same number of errors")
// Verify that the slices are distinct (deep copy) by checking contents and ensuring independence
assert.True(t, reflect.DeepEqual(c.Errors, cp.Errors), "Copied errors should have the same content initially")
// Since we cant compare slices with ==, we rely on content equality and test isolation below
// Check each error in the copied context matches the original
for i, origErr := range c.Errors {
copiedErr := cp.Errors[i]
assert.Equal(t, origErr.Err, copiedErr.Err, "Error message should match")
assert.Equal(t, origErr.Type, copiedErr.Type, "Error type should match")
assert.Equal(t, origErr.Meta, copiedErr.Meta, "Error metadata should match")
// Ensure pointers are different (deep copy)
assert.NotSame(t, origErr, copiedErr, "Each error should be a distinct instance")
}
// Modify original context errors and ensure copy remains unchanged
c.Error(fmt.Errorf("third error")) // nolint: errcheck
assert.Equal(t, 2, len(cp.Errors), "Copied context errors should not reflect changes to original")
assert.Equal(t, 3, len(c.Errors), "Original context should have new error")
assert.False(t, reflect.DeepEqual(c.Errors, cp.Errors), "Copied errors should differ after modification")
}
func TestContextHandlerName(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder())
c.handlers = HandlersChain{func(c *Context) {}, handlerNameTest}

46
gin.go
View File

@ -23,10 +23,12 @@ import (
"golang.org/x/net/http2/h2c"
)
const defaultMultipartMemory = 32 << 20 // 32 MB
const escapedColon = "\\:"
const colon = ":"
const backslash = "\\"
const (
defaultMultipartMemory = 32 << 20 // 32 MB
escapedColon = "\\:"
colon = ":"
backslash = "\\"
)
var (
default404Body = []byte("404 page not found")
@ -46,8 +48,10 @@ var defaultTrustedCIDRs = []*net.IPNet{
},
}
var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
var regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
var (
regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
)
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
@ -94,6 +98,10 @@ const (
type Engine struct {
RouterGroup
// routeTreesUpdated ensures that the initialization or update of the route trees
// (used for routing HTTP requests) happens only once, even if called multiple times concurrently.
routeTreesUpdated sync.Once
// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists.
// For example if /foo/ is requested but a route only exists for /foo, the
@ -537,7 +545,11 @@ func (engine *Engine) Run(addr ...string) (err error) {
engine.updateRouteTrees()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
server := &http.Server{ // #nosec G112
Addr: address,
Handler: engine.Handler(),
}
err = server.ListenAndServe()
return
}
@ -553,7 +565,11 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
}
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
server := &http.Server{ // #nosec G112
Addr: addr,
Handler: engine.Handler(),
}
err = server.ListenAndServeTLS(certFile, keyFile)
return
}
@ -576,7 +592,10 @@ func (engine *Engine) RunUnix(file string) (err error) {
defer listener.Close()
defer os.Remove(file)
err = http.Serve(listener, engine.Handler())
server := &http.Server{ // #nosec G112
Handler: engine.Handler(),
}
err = server.Serve(listener)
return
}
@ -630,12 +649,19 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
}
err = http.Serve(listener, engine.Handler())
server := &http.Server{ // #nosec G112
Handler: engine.Handler(),
}
err = server.Serve(listener)
return
}
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
engine.routeTreesUpdated.Do(func() {
engine.updateRouteTrees()
})
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req

View File

@ -16,6 +16,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
"time"
@ -261,10 +262,11 @@ func TestUnixSocket(t *testing.T) {
fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n")
scanner := bufio.NewScanner(c)
var response string
var responseBuilder strings.Builder
for scanner.Scan() {
response += scanner.Text()
responseBuilder.WriteString(scanner.Text())
}
response := responseBuilder.String()
assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
assert.Contains(t, response, "it worked", "resp body should match")
}
@ -322,10 +324,11 @@ func TestFileDescriptor(t *testing.T) {
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
scanner := bufio.NewScanner(c)
var response string
var responseBuilder strings.Builder
for scanner.Scan() {
response += scanner.Text()
responseBuilder.WriteString(scanner.Text())
}
response := responseBuilder.String()
assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
assert.Contains(t, response, "it worked", "resp body should match")
}
@ -354,10 +357,11 @@ func TestListener(t *testing.T) {
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
scanner := bufio.NewScanner(c)
var response string
var responseBuilder strings.Builder
for scanner.Scan() {
response += scanner.Text()
responseBuilder.WriteString(scanner.Text())
}
response := responseBuilder.String()
assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
assert.Contains(t, response, "it worked", "resp body should match")
}

View File

@ -913,3 +913,102 @@ func TestMethodNotAllowedNoRoute(t *testing.T) {
assert.NotPanics(t, func() { g.ServeHTTP(resp, req) })
assert.Equal(t, http.StatusNotFound, resp.Code)
}
// Test the fix for https://github.com/gin-gonic/gin/pull/4415
func TestLiteralColonWithRun(t *testing.T) {
SetMode(TestMode)
router := New()
router.GET(`/test\:action`, func(c *Context) {
c.JSON(http.StatusOK, H{"path": "literal_colon"})
})
router.updateRouteTrees()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "literal_colon")
}
func TestLiteralColonWithDirectServeHTTP(t *testing.T) {
SetMode(TestMode)
router := New()
router.GET(`/test\:action`, func(c *Context) {
c.JSON(http.StatusOK, H{"path": "literal_colon"})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "literal_colon")
}
func TestLiteralColonWithHandler(t *testing.T) {
SetMode(TestMode)
router := New()
router.GET(`/test\:action`, func(c *Context) {
c.JSON(http.StatusOK, H{"path": "literal_colon"})
})
handler := router.Handler()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
handler.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "literal_colon")
}
func TestLiteralColonWithHTTPServer(t *testing.T) {
SetMode(TestMode)
router := New()
router.GET(`/test\:action`, func(c *Context) {
c.JSON(http.StatusOK, H{"path": "literal_colon"})
})
router.GET("/test/:param", func(c *Context) {
c.JSON(http.StatusOK, H{"param": c.Param("param")})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), "literal_colon")
w2 := httptest.NewRecorder()
req2, _ := http.NewRequest(http.MethodGet, "/test/foo", nil)
router.ServeHTTP(w2, req2)
assert.Equal(t, http.StatusOK, w2.Code)
assert.Contains(t, w2.Body.String(), "foo")
}
// Test that updateRouteTrees is called only once
func TestUpdateRouteTreesCalledOnce(t *testing.T) {
SetMode(TestMode)
router := New()
router.GET(`/test\:action`, func(c *Context) {
c.String(http.StatusOK, "ok")
})
for range 5 {
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/test:action", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "ok", w.Body.String())
}
}

14
go.mod
View File

@ -12,10 +12,10 @@ require (
github.com/mattn/go-isatty v0.0.20
github.com/modern-go/reflect2 v1.0.2
github.com/pelletier/go-toml/v2 v2.2.4
github.com/quic-go/quic-go v0.55.0
github.com/quic-go/quic-go v0.56.0
github.com/stretchr/testify v1.11.1
github.com/ugorji/go/codec v1.3.1
golang.org/x/net v0.46.0
golang.org/x/net v0.47.0
google.golang.org/protobuf v1.36.10
)
@ -28,17 +28,15 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

38
go.sum
View File

@ -6,6 +6,7 @@ github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2N
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -32,6 +33,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@ -46,8 +51,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY=
github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -67,25 +74,22 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -94,7 +94,7 @@ func TestPathCleanMallocs(t *testing.T) {
func BenchmarkPathClean(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
for _, test := range cleanTests {
cleanPath(test.path)
}
@ -134,10 +134,10 @@ func TestPathCleanLong(t *testing.T) {
func BenchmarkPathCleanLong(b *testing.B) {
cleanTests := genLongPaths()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
for _, test := range cleanTests {
cleanPath(test.path)
}

13
tree.go
View File

@ -5,6 +5,7 @@
package gin
import (
"math"
"net/url"
"strings"
"unicode"
@ -77,14 +78,22 @@ func (n *node) addChild(child *node) {
}
}
// safeUint16 converts int to uint16 safely, capping at math.MaxUint16
func safeUint16(n int) uint16 {
if n > math.MaxUint16 {
return math.MaxUint16
}
return uint16(n)
}
func countParams(path string) uint16 {
colons := strings.Count(path, ":")
stars := strings.Count(path, "*")
return uint16(colons + stars)
return safeUint16(colons + stars)
}
func countSections(path string) uint16 {
return uint16(strings.Count(path, "/"))
return safeUint16(strings.Count(path, "/"))
}
type nodeType uint8

View File

@ -19,7 +19,7 @@ func init() {
}
func BenchmarkParseAccept(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
}
}