Compare commits

...

11 Commits

Author SHA1 Message Date
Ben Turner
e9fa4ce620
Merge c17ae54e64177e6e340f475a40a2eb1a7a876e89 into c3d5a28ed6d3849da820195b6774d212bcc038a9 2025-11-08 09:53:39 +01:00
Name
c3d5a28ed6
fix(gin): close os.File in RunFd to prevent resource leak (#4422)
Co-authored-by: 1911860538 <alxps1911@gmail.com>
2025-11-07 12:01:19 +08:00
Name
acc55e049e
feat(context): add Protocol Buffers support to content negotiation (#4423)
Co-authored-by: 1911860538 <alxps1911@gmail.com>
2025-11-07 11:59:58 +08:00
dependabot[bot]
0c0e99d253
chore(deps): bump github/codeql-action from 3 to 4 in the actions group (#4425)
Bumps the actions group with 1 update: [github/codeql-action](https://github.com/github/codeql-action).


Updates `github/codeql-action` from 3 to 4
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  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-07 11:57:41 +08:00
Bo-Yi Wu
dceb61e6e7
docs(README): add a Trivy security scan badge (#4426)
- Add a Trivy security scan badge to the documentation
- Import the log package in the example code
- Improve error handling for server startup in the example code

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-11-07 11:57:12 +08:00
Bo-Yi Wu
5e5ff3ace4
ci: replace vulnerability scanning workflow with Trivy integration (#4421)
- Remove the vulnerability-scanning job from the gin workflow
- Add a dedicated Trivy security scan workflow with scheduled, push, pull request, and manual triggers
- Improve Trivy scan output by uploading SARIF results to the GitHub Security tab and logging table output

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-11-06 14:15:50 +08:00
Name
2e22e50859
perf(tree): optimize path parsing using strings.Count (#4246)
Co-authored-by: 1911860538 <alxps1911@gmail.com>
2025-10-31 22:09:07 +08:00
dependabot[bot]
52f70cf18a
chore(deps): bump github.com/ugorji/go/codec from 1.3.0 to 1.3.1 (#4409)
Bumps [github.com/ugorji/go/codec](https://github.com/ugorji/go) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/ugorji/go/releases)
- [Commits](https://github.com/ugorji/go/compare/codec/v1.3.0...codec/v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/ugorji/go/codec
  dependency-version: 1.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 22:03:29 +08:00
dependabot[bot]
87c207a140
chore(deps): bump github.com/bytedance/sonic from 1.14.0 to 1.14.2 (#4410)
Bumps [github.com/bytedance/sonic](https://github.com/bytedance/sonic) from 1.14.0 to 1.14.2.
- [Release notes](https://github.com/bytedance/sonic/releases)
- [Commits](https://github.com/bytedance/sonic/compare/v1.14.0...v1.14.2)

---
updated-dependencies:
- dependency-name: github.com/bytedance/sonic
  dependency-version: 1.14.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-31 22:02:56 +08:00
3ocene
c17ae54e64 add method for handling contexts without resetting
HandleContext resets contexts completely, overwriting any modifications since GetContext was called. This is undermines the purpose of being able to GetContexts separately, so this commit adds a new method which only resets the index, but leaves everything else intact.
2022-10-29 16:11:47 -07:00
3ocene
9c6f92b16b add GetContext and ReleaseContext methods
- Add a method for retrieving a new *Context given an http.ResponseWriter and *http.Request.
- Add a method for returning to the pool *Contexts which were provided by *Engine.GetRequest to the pool

This allows contexts to be manipulated prior to handling whereas ServeHTTP does not provide the caller with access to the context.
2022-10-29 15:24:43 -07:00
9 changed files with 160 additions and 55 deletions

View File

@ -81,19 +81,3 @@ jobs:
uses: codecov/codecov-action@v5
with:
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
vulnerability-scanning:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@0.33.1
with:
scan-type: "fs"
ignore-unfixed: true
format: "table"
exit-code: "1"
severity: "CRITICAL,HIGH,MEDIUM"

57
.github/workflows/trivy-scan.yml vendored Normal file
View File

@ -0,0 +1,57 @@
name: Trivy Security Scan
on:
push:
branches:
- master
pull_request:
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 *'
workflow_dispatch: # Allow manual trigger
permissions:
contents: read
security-events: write # Required for uploading SARIF results
jobs:
trivy-scan:
name: Trivy Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Run Trivy vulnerability scanner (source code)
uses: aquasecurity/trivy-action@0.33.1
with:
scan-type: 'fs'
scan-ref: '.'
scanners: 'vuln,secret,misconfig'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH,MEDIUM'
ignore-unfixed: true
- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Run Trivy scanner (table output for logs)
uses: aquasecurity/trivy-action@0.33.1
if: always()
with:
scan-type: 'fs'
scan-ref: '.'
scanners: 'vuln,secret,misconfig'
format: 'table'
severity: 'CRITICAL,HIGH,MEDIUM'
ignore-unfixed: true
exit-code: '1'

View File

@ -3,6 +3,7 @@
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
[![Build Status](https://github.com/gin-gonic/gin/actions/workflows/gin.yml/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions/workflows/gin.yml)
[![Trivy Security Scan](https://github.com/gin-gonic/gin/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/gin-gonic/gin/actions/workflows/trivy-scan.yml)
[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
[![Go Reference](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
@ -62,6 +63,7 @@ Here's a complete example that demonstrates Gin's simplicity:
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
@ -70,7 +72,7 @@ import (
func main() {
// Create a Gin router with default middleware (logger and recovery)
r := gin.Default()
// Define a simple GET endpoint
r.GET("/ping", func(c *gin.Context) {
// Return JSON response
@ -78,10 +80,12 @@ func main() {
"message": "pong",
})
})
// Start server on port 8080 (default)
// Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows)
r.Run()
if err := r.Run(); err != nil {
log.Fatalf("failed to run server: %v", err)
}
}
```
@ -190,7 +194,6 @@ Gin has a rich ecosystem of middleware for common web development needs. Explore
- CORS, Rate limiting, Compression
- Logging, Metrics, Tracing
- Static file serving, Template engines
- **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware
## 🏢 Production Usage

View File

@ -39,6 +39,7 @@ const (
MIMEYAML = binding.MIMEYAML
MIMEYAML2 = binding.MIMEYAML2
MIMETOML = binding.MIMETOML
MIMEPROTOBUF = binding.MIMEPROTOBUF
)
// BodyBytesKey indicates a default body bytes key.
@ -1280,14 +1281,15 @@ func (c *Context) Stream(step func(w io.Writer) bool) bool {
// Negotiate contains all negotiations data.
type Negotiate struct {
Offered []string
HTMLName string
HTMLData any
JSONData any
XMLData any
YAMLData any
Data any
TOMLData any
Offered []string
HTMLName string
HTMLData any
JSONData any
XMLData any
YAMLData any
Data any
TOMLData any
PROTOBUFData any
}
// Negotiate calls different Render according to acceptable Accept format.
@ -1313,6 +1315,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
data := chooseData(config.TOMLData, config.Data)
c.TOML(code, data)
case binding.MIMEPROTOBUF:
data := chooseData(config.PROTOBUFData, config.Data)
c.ProtoBuf(code, data)
default:
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck
}

View File

@ -1628,6 +1628,32 @@ func TestContextNegotiationWithHTML(t *testing.T) {
assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
}
func TestContextNegotiationWithPROTOBUF(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodPost, "/", nil)
reps := []int64{int64(1), int64(2)}
label := "test"
data := &testdata.Test{
Label: &label,
Reps: reps,
}
c.Negotiate(http.StatusCreated, Negotiate{
Offered: []string{MIMEPROTOBUF, MIMEJSON, MIMEXML},
Data: data,
})
// Marshal original data for comparison
protoData, err := proto.Marshal(data)
require.NoError(t, err)
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, string(protoData), w.Body.String())
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
}
func TestContextNegotiationNotSupport(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)

40
gin.go
View File

@ -593,6 +593,7 @@ func (engine *Engine) RunFd(fd int) (err error) {
}
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
defer f.Close()
listener, err := net.FileListener(f)
if err != nil {
return
@ -633,16 +634,49 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
return
}
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// GetContext transforms a http.ResponseWriter and *http.Request into a new
// *Context without handling the request. This new context can then be passed
// to *Engine.HandleContext for handling.
//
// Any contexts created with this method are not automatically returned to the
// pool, so it is recommended that you call *Engine.ReleaseContext when you are
// done with it, though this is not required.
func (engine *Engine) GetContext(w http.ResponseWriter, req *http.Request) *Context {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
return c
}
// ReleaseContext releases a context created with *Engine.GetContext back into
// the pool. Releasing a context marks it as available for reuse and therefore
// must only be called once the context is no longer in use. Releasing a context
// is not required, but it is recommended to improve performance. Calling this
// method on a context that is not created with *Engine.GetContext will result
// in undefined behavior.
func (engine *Engine) ReleaseContext(c *Context) {
engine.pool.Put(c)
}
// HandleContextAsIs handles the context as is without resetting it. This means
// that all key/values, middleware, etc. will be retained. This is useful for
// handling a context that has been created with *Engine.GetContext.
func (engine *Engine) HandleContextAsIs(c *Context) {
oldIndex := c.index
c.index = -1
engine.handleHTTPRequest(c)
c.index = oldIndex
}
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.GetContext(w, req)
engine.handleHTTPRequest(c)
engine.pool.Put(c)
engine.ReleaseContext(c)
}
// HandleContext re-enters a context that has been rewritten.

7
go.mod
View File

@ -3,7 +3,7 @@ module github.com/gin-gonic/gin
go 1.24.0
require (
github.com/bytedance/sonic v1.14.0
github.com/bytedance/sonic v1.14.2
github.com/gin-contrib/sse v1.1.0
github.com/go-playground/validator/v10 v10.28.0
github.com/goccy/go-json v0.10.2
@ -14,13 +14,14 @@ require (
github.com/pelletier/go-toml/v2 v2.2.4
github.com/quic-go/quic-go v0.55.0
github.com/stretchr/testify v1.11.1
github.com/ugorji/go/codec v1.3.0
github.com/ugorji/go/codec v1.3.1
golang.org/x/net v0.46.0
google.golang.org/protobuf v1.36.10
)
require (
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.4.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect

18
go.sum
View File

@ -1,7 +1,9 @@
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -49,16 +51,18 @@ github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8
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=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
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=

18
tree.go
View File

@ -5,7 +5,6 @@
package gin
import (
"bytes"
"net/url"
"strings"
"unicode"
@ -14,12 +13,6 @@ import (
"github.com/gin-gonic/gin/internal/bytesconv"
)
var (
strColon = []byte(":")
strStar = []byte("*")
strSlash = []byte("/")
)
// Param is a single URL parameter, consisting of a key and a value.
type Param struct {
Key string
@ -85,16 +78,13 @@ func (n *node) addChild(child *node) {
}
func countParams(path string) uint16 {
var n uint16
s := bytesconv.StringToBytes(path)
n += uint16(bytes.Count(s, strColon))
n += uint16(bytes.Count(s, strStar))
return n
colons := strings.Count(path, ":")
stars := strings.Count(path, "*")
return uint16(colons + stars)
}
func countSections(path string) uint16 {
s := bytesconv.StringToBytes(path)
return uint16(bytes.Count(s, strSlash))
return uint16(strings.Count(path, "/"))
}
type nodeType uint8