mirror of
				https://github.com/gin-gonic/gin.git
				synced 2025-11-04 01:02:14 +08:00 
			
		
		
		
	Compare commits
	
		
			29 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2e22e50859 | ||
| 
						 | 
					52f70cf18a | ||
| 
						 | 
					87c207a140 | ||
| 
						 | 
					c0048f645e | ||
| 
						 | 
					38e7651192 | ||
| 
						 | 
					c221133ee8 | ||
| 
						 | 
					c3d1092b3b | ||
| 
						 | 
					9968c4bf9d | ||
| 
						 | 
					053e5765fd | ||
| 
						 | 
					0d085ed9fe | ||
| 
						 | 
					5dd833f1f2 | ||
| 
						 | 
					48a5dca087 | ||
| 
						 | 
					0bd10a84f9 | ||
| 
						 | 
					4dec17afdf | ||
| 
						 | 
					731374fb36 | ||
| 
						 | 
					8ca975441f | ||
| 
						 | 
					39858a0859 | ||
| 
						 | 
					ed150e7254 | ||
| 
						 | 
					234a6d4c00 | ||
| 
						 | 
					df2753778e | ||
| 
						 | 
					048f6fb884 | ||
| 
						 | 
					61b67de522 | ||
| 
						 | 
					f3a5e78719 | ||
| 
						 | 
					414de60574 | ||
| 
						 | 
					59e9d4a794 | ||
| 
						 | 
					6a1d1218c3 | ||
| 
						 | 
					7925414704 | ||
| 
						 | 
					1bbbec0baf | ||
| 
						 | 
					4dd00f81b1 | 
							
								
								
									
										49
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										49
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@ -1,49 +0,0 @@
 | 
			
		||||
- With issues:
 | 
			
		||||
  - Use the search tool before opening a new issue.
 | 
			
		||||
  - Please provide source code and commit sha if you found a bug.
 | 
			
		||||
  - Review existing issues and provide feedback or react to them.
 | 
			
		||||
 | 
			
		||||
## Description
 | 
			
		||||
 | 
			
		||||
<!-- Description of a problem -->
 | 
			
		||||
 | 
			
		||||
## How to reproduce
 | 
			
		||||
 | 
			
		||||
<!-- The smallest possible code example to show the problem that can be compiled, like -->
 | 
			
		||||
```
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	g := gin.Default()
 | 
			
		||||
	g.GET("/hello/:name", func(c *gin.Context) {
 | 
			
		||||
		c.String(200, "Hello %s", c.Param("name"))
 | 
			
		||||
	})
 | 
			
		||||
	g.Run(":9000")
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Expectations
 | 
			
		||||
 | 
			
		||||
<!-- Your expectation result of 'curl' command, like -->
 | 
			
		||||
```
 | 
			
		||||
$ curl http://localhost:9000/hello/world
 | 
			
		||||
Hello world
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Actual result
 | 
			
		||||
 | 
			
		||||
<!-- Actual result showing the problem -->
 | 
			
		||||
```
 | 
			
		||||
$ curl -i http://localhost:9000/hello/world
 | 
			
		||||
<YOUR RESULT>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Environment
 | 
			
		||||
 | 
			
		||||
- go version:
 | 
			
		||||
- gin version (or commit ref):
 | 
			
		||||
- operating system:
 | 
			
		||||
							
								
								
									
										60
									
								
								.github/ISSUE_TEMPLATE/bug-report.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								.github/ISSUE_TEMPLATE/bug-report.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,60 @@
 | 
			
		||||
name: Bug Report
 | 
			
		||||
description: Found something you weren't expecting? Report it here!
 | 
			
		||||
labels: ["type/bug"]
 | 
			
		||||
body:
 | 
			
		||||
  - type: markdown
 | 
			
		||||
    attributes:
 | 
			
		||||
      value: |
 | 
			
		||||
        NOTE: If your issue is a security concern, please send an email to appleboy.tw@gmail.com instead of opening a public issue.
 | 
			
		||||
  - type: markdown
 | 
			
		||||
    attributes:
 | 
			
		||||
      value: |
 | 
			
		||||
        1. Please speak English, this is the language all maintainers can speak and write.
 | 
			
		||||
        2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions).
 | 
			
		||||
        3. Make sure you are using the latest release and
 | 
			
		||||
           take a moment to check that your issue hasn't been reported before.
 | 
			
		||||
  - type: textarea
 | 
			
		||||
    id: description
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: Description
 | 
			
		||||
      description: |
 | 
			
		||||
        Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below)
 | 
			
		||||
  - type: input
 | 
			
		||||
    id: gin-ver
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: Gin Version
 | 
			
		||||
      description: Gin version (or commit reference) of your instance
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
  - type: dropdown
 | 
			
		||||
    id: can-reproduce
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: Can you reproduce the bug?
 | 
			
		||||
      description: |
 | 
			
		||||
        If so, please write the steps to reproduce the bug.
 | 
			
		||||
      options:
 | 
			
		||||
        - "Yes"
 | 
			
		||||
        - "No"
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
  - type: markdown
 | 
			
		||||
    attributes:
 | 
			
		||||
      value: |
 | 
			
		||||
        It's really important to provide pertinent logs
 | 
			
		||||
        Please read https://docs.gitea.com/administration/logging-config#collecting-logs-for-help
 | 
			
		||||
        In addition, if your problem relates to git commands set `RUN_MODE=dev` at the top of app.ini
 | 
			
		||||
  - type: textarea
 | 
			
		||||
    id: source-code
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: Source Code
 | 
			
		||||
      description: If this issue involves source code, please provide a minimal reproducible example
 | 
			
		||||
  - type: input
 | 
			
		||||
    id: go-ver
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: Go Version
 | 
			
		||||
      description: The version of Go running on the server
 | 
			
		||||
  - type: input
 | 
			
		||||
    id: os-ver
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: Operating System
 | 
			
		||||
      description: The operating system you are using to run Gin
 | 
			
		||||
							
								
								
									
										11
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
blank_issues_enabled: false
 | 
			
		||||
contact_links:
 | 
			
		||||
  - name: Go.dev API Documentation
 | 
			
		||||
    url: https://pkg.go.dev/github.com/gin-gonic/gin
 | 
			
		||||
    about: Comprehensive API documentation for Gin.
 | 
			
		||||
  - name: Gin User Guides
 | 
			
		||||
    url: https://gin-gonic.com/
 | 
			
		||||
    about: In-depth user guides and tutorials for using Gin.
 | 
			
		||||
  - name: Discussions Forum
 | 
			
		||||
    url: https://github.com/gin-gonic/gin/discussions
 | 
			
		||||
    about: Questions and configuration or deployment problems can also be discussed.
 | 
			
		||||
							
								
								
									
										18
									
								
								.github/ISSUE_TEMPLATE/feature-request.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								.github/ISSUE_TEMPLATE/feature-request.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
name: Feature Request
 | 
			
		||||
description: Got an idea for a feature that Gin doesn't have currently?  Submit your idea here!
 | 
			
		||||
labels: ["type/proposal"]
 | 
			
		||||
body:
 | 
			
		||||
  - type: markdown
 | 
			
		||||
    attributes:
 | 
			
		||||
      value: |
 | 
			
		||||
        1. Please speak English, this is the language all maintainers can speak and write.
 | 
			
		||||
        2. Please ask questions problems on our Discussions Forum (https://github.com/gin-gonic/gin/discussions).
 | 
			
		||||
        3. Please take a moment to check that your feature hasn't already been suggested.
 | 
			
		||||
  - type: textarea
 | 
			
		||||
    id: description
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: Feature Description
 | 
			
		||||
      placeholder: |
 | 
			
		||||
        I think it would be great if Gin had...
 | 
			
		||||
    validations:
 | 
			
		||||
      required: true
 | 
			
		||||
							
								
								
									
										15
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.github/PULL_REQUEST_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							@ -1,7 +1,10 @@
 | 
			
		||||
- With pull requests:
 | 
			
		||||
  - Open your pull request against `master`
 | 
			
		||||
  - Your pull request should have no more than two commits, if not you should squash them.
 | 
			
		||||
  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
 | 
			
		||||
  - You should add/modify tests to cover your proposed code changes.
 | 
			
		||||
  - If your pull request contains a new feature, please document it on the README.
 | 
			
		||||
# Pull Request Checklist
 | 
			
		||||
 | 
			
		||||
Please ensure your pull request meets the following requirements:
 | 
			
		||||
 | 
			
		||||
- [ ] Open your pull request against the `master` branch.
 | 
			
		||||
- [ ] All tests pass in available continuous integration systems (e.g., GitHub Actions).
 | 
			
		||||
- [ ] Tests are added or modified as needed to cover code changes.
 | 
			
		||||
- [ ] If the pull request introduces a new feature, the feature is documented in the `docs/doc.md`.
 | 
			
		||||
 | 
			
		||||
Thank you for contributing!
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							@ -1,10 +1,14 @@
 | 
			
		||||
version: 2
 | 
			
		||||
updates:
 | 
			
		||||
  - package-ecosystem: github-actions
 | 
			
		||||
    directory: /
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: weekly
 | 
			
		||||
  - package-ecosystem: gomod
 | 
			
		||||
    directory: /
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: weekly
 | 
			
		||||
      interval: daily
 | 
			
		||||
  - package-ecosystem: github-actions
 | 
			
		||||
    directory: /
 | 
			
		||||
    groups:
 | 
			
		||||
      actions:
 | 
			
		||||
        patterns:
 | 
			
		||||
          - "*"
 | 
			
		||||
    schedule:
 | 
			
		||||
      interval: daily
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							@ -33,11 +33,11 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout repository
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        uses: actions/checkout@v5
 | 
			
		||||
 | 
			
		||||
      # Initializes the CodeQL tools for scanning.
 | 
			
		||||
      - name: Initialize CodeQL
 | 
			
		||||
        uses: github/codeql-action/init@v3
 | 
			
		||||
        uses: github/codeql-action/init@v4
 | 
			
		||||
        with:
 | 
			
		||||
          languages: ${{ matrix.language }}
 | 
			
		||||
          # If you wish to specify custom queries, you can do so here or in a config file.
 | 
			
		||||
@ -46,4 +46,4 @@ jobs:
 | 
			
		||||
          # queries: ./path/to/local/query, your-org/your-repo/queries@main
 | 
			
		||||
 | 
			
		||||
      - name: Perform CodeQL Analysis
 | 
			
		||||
        uses: github/codeql-action/analyze@v3
 | 
			
		||||
        uses: github/codeql-action/analyze@v4
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								.github/workflows/gin.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.github/workflows/gin.yml
									
									
									
									
										vendored
									
									
								
							@ -16,7 +16,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        uses: actions/checkout@v5
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
      - name: Set up Go
 | 
			
		||||
@ -33,7 +33,7 @@ jobs:
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        os: [ubuntu-latest, macos-latest]
 | 
			
		||||
        go: ["1.23", "1.24", "1.25"]
 | 
			
		||||
        go: ["1.24", "1.25"]
 | 
			
		||||
        test-tags:
 | 
			
		||||
          [
 | 
			
		||||
            "",
 | 
			
		||||
@ -61,7 +61,7 @@ jobs:
 | 
			
		||||
          cache: false
 | 
			
		||||
 | 
			
		||||
      - name: Checkout Code
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        uses: actions/checkout@v5
 | 
			
		||||
        with:
 | 
			
		||||
          ref: ${{ github.ref }}
 | 
			
		||||
 | 
			
		||||
@ -78,7 +78,7 @@ jobs:
 | 
			
		||||
        run: make test
 | 
			
		||||
 | 
			
		||||
      - name: Upload coverage to Codecov
 | 
			
		||||
        uses: codecov/codecov-action@v4
 | 
			
		||||
        uses: codecov/codecov-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}
 | 
			
		||||
 | 
			
		||||
@ -92,8 +92,8 @@ jobs:
 | 
			
		||||
      - name: Run Trivy vulnerability scanner in repo mode
 | 
			
		||||
        uses: aquasecurity/trivy-action@0.33.1
 | 
			
		||||
        with:
 | 
			
		||||
          scan-type: 'fs'
 | 
			
		||||
          scan-type: "fs"
 | 
			
		||||
          ignore-unfixed: true
 | 
			
		||||
          format: 'table'
 | 
			
		||||
          exit-code: '1'
 | 
			
		||||
          severity: 'CRITICAL,HIGH,MEDIUM'
 | 
			
		||||
          format: "table"
 | 
			
		||||
          exit-code: "1"
 | 
			
		||||
          severity: "CRITICAL,HIGH,MEDIUM"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
								
							@ -13,7 +13,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        uses: actions/checkout@v5
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
      - name: Set up Go
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,41 @@
 | 
			
		||||
## Contributing
 | 
			
		||||
# Contributing
 | 
			
		||||
 | 
			
		||||
- With issues:
 | 
			
		||||
  - Use the search tool before opening a new issue.
 | 
			
		||||
  - Please provide source code and commit sha if you found a bug.
 | 
			
		||||
We welcome both issue reports and pull requests! Please follow these guidelines to help maintainers respond effectively.
 | 
			
		||||
 | 
			
		||||
## Issues
 | 
			
		||||
 | 
			
		||||
- **Before opening a new issue:**
 | 
			
		||||
  - Use the search tool to check for existing issues or feature requests.
 | 
			
		||||
  - Review existing issues and provide feedback or react to them.
 | 
			
		||||
  - Use English for all communications — it is the language all maintainers read and write.
 | 
			
		||||
  - For questions, configuration or deployment problems, please use the [Discussions Forum](https://github.com/gin-gonic/gin/discussions).
 | 
			
		||||
  - For bug reports involving sensitive security issues, email <appleboy.tw@gmail.com> instead of posting publicly.
 | 
			
		||||
 | 
			
		||||
- With pull requests:
 | 
			
		||||
  - Open your pull request against `master`
 | 
			
		||||
  - Your pull request should have no more than two commits, if not you should squash them.
 | 
			
		||||
  - It should pass all tests in the available continuous integration systems such as GitHub Actions.
 | 
			
		||||
  - You should add/modify tests to cover your proposed code changes.
 | 
			
		||||
  - If your pull request contains a new feature, please document it on the README.
 | 
			
		||||
- **Reporting a bug:**
 | 
			
		||||
  - Please provide a clear description of your issue, and a minimal reproducible code example if possible.
 | 
			
		||||
  - Include the Gin version (or commit reference), Go version, and operating system.
 | 
			
		||||
  - Indicate whether you can reproduce the bug and describe steps to do so.
 | 
			
		||||
  - Attach relevant logs per [Logging Documentation](https://docs.gitea.com/administration/logging-config#collecting-logs-for-help).
 | 
			
		||||
 | 
			
		||||
- **Feature requests:**
 | 
			
		||||
  - Before opening a request, check that a similar idea hasn’t already been suggested.
 | 
			
		||||
  - Clearly describe your proposed feature and its benefits.
 | 
			
		||||
 | 
			
		||||
_For API Documentation, User Guides, and more, see:_
 | 
			
		||||
 | 
			
		||||
- [Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin)
 | 
			
		||||
- [Gin User Guides](https://gin-gonic.com/)
 | 
			
		||||
- [Discussions Forum](https://github.com/gin-gonic/gin/discussions)
 | 
			
		||||
 | 
			
		||||
## Pull Requests
 | 
			
		||||
 | 
			
		||||
Please ensure your pull request meets the following requirements:
 | 
			
		||||
 | 
			
		||||
- Open your pull request against the `master` branch.
 | 
			
		||||
- Your pull request should have no more than two commits — squash them if necessary.
 | 
			
		||||
- All tests pass in available continuous integration systems (e.g., GitHub Actions).
 | 
			
		||||
- Add or modify tests to cover your code changes.
 | 
			
		||||
- If your pull request introduces a new feature, document it in [`docs/doc.md`](docs/doc.md), not in the README.
 | 
			
		||||
- Follow the checklist in the [Pull Request Template](.github/PULL_REQUEST_TEMPLATE.md:1).
 | 
			
		||||
 | 
			
		||||
Thank you for contributing!
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										182
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										182
									
								
								README.md
									
									
									
									
									
								
							@ -9,46 +9,54 @@
 | 
			
		||||
[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
 | 
			
		||||
[](https://www.codetriage.com/gin-gonic/gin)
 | 
			
		||||
[](https://github.com/gin-gonic/gin/releases)
 | 
			
		||||
[](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin)
 | 
			
		||||
 | 
			
		||||
Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter).
 | 
			
		||||
If you need performance and good productivity, you will love Gin.
 | 
			
		||||
## 📰 [Announcing Gin 1.11.0!](https://gin-gonic.com/en/blog/news/gin-1-11-0-release-announcement/)
 | 
			
		||||
 | 
			
		||||
**Gin's key features are:**
 | 
			
		||||
Read about the latest features and improvements in Gin 1.11.0 on our official blog.
 | 
			
		||||
 | 
			
		||||
- Zero allocation router
 | 
			
		||||
- Speed
 | 
			
		||||
- Middleware support
 | 
			
		||||
- Crash-free
 | 
			
		||||
- JSON validation
 | 
			
		||||
- Route grouping
 | 
			
		||||
- Error management
 | 
			
		||||
- Built-in rendering
 | 
			
		||||
- Extensible
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Getting started
 | 
			
		||||
Gin is a high-performance HTTP web framework written in [Go](https://go.dev/). It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to [httprouter](https://github.com/julienschmidt/httprouter). Gin is designed for building REST APIs, web applications, and microservices where speed and developer productivity are essential.
 | 
			
		||||
 | 
			
		||||
**Why choose Gin?**
 | 
			
		||||
 | 
			
		||||
Gin combines the simplicity of Express.js-style routing with Go's performance characteristics, making it ideal for:
 | 
			
		||||
 | 
			
		||||
- Building high-throughput REST APIs
 | 
			
		||||
- Developing microservices that need to handle many concurrent requests
 | 
			
		||||
- Creating web applications that require fast response times
 | 
			
		||||
- Prototyping web services quickly with minimal boilerplate
 | 
			
		||||
 | 
			
		||||
**Gin's key features:**
 | 
			
		||||
 | 
			
		||||
- **Zero allocation router** - Extremely memory-efficient routing with no heap allocations
 | 
			
		||||
- **High performance** - Benchmarks show superior speed compared to other Go web frameworks
 | 
			
		||||
- **Middleware support** - Extensible middleware system for authentication, logging, CORS, etc.
 | 
			
		||||
- **Crash-free** - Built-in recovery middleware prevents panics from crashing your server
 | 
			
		||||
- **JSON validation** - Automatic request/response JSON binding and validation
 | 
			
		||||
- **Route grouping** - Organize related routes and apply common middleware
 | 
			
		||||
- **Error management** - Centralized error handling and logging
 | 
			
		||||
- **Built-in rendering** - Support for JSON, XML, HTML templates, and more
 | 
			
		||||
- **Extensible** - Large ecosystem of community middleware and plugins
 | 
			
		||||
 | 
			
		||||
## Getting Started
 | 
			
		||||
 | 
			
		||||
### Prerequisites
 | 
			
		||||
 | 
			
		||||
Gin requires [Go](https://go.dev/) version [1.23](https://go.dev/doc/devel/release#go1.23.0) or above.
 | 
			
		||||
- **Go version**: Gin requires [Go](https://go.dev/) version [1.24](https://go.dev/doc/devel/release#go1.24.0) or above
 | 
			
		||||
- **Basic Go knowledge**: Familiarity with Go syntax and package management is helpful
 | 
			
		||||
 | 
			
		||||
### Getting Gin
 | 
			
		||||
### Installation
 | 
			
		||||
 | 
			
		||||
With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), `go [build|run|test]` automatically fetches the necessary dependencies when you add the import in your code:
 | 
			
		||||
With [Go's module support](https://go.dev/wiki/Modules#how-to-use-modules), simply import Gin in your code and Go will automatically fetch it during build:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
```go
 | 
			
		||||
import "github.com/gin-gonic/gin"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Alternatively, use `go get`:
 | 
			
		||||
### Your First Gin Application
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
go get -u github.com/gin-gonic/gin
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Running Gin
 | 
			
		||||
 | 
			
		||||
A basic example:
 | 
			
		||||
Here's a complete example that demonstrates Gin's simplicity:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
package main
 | 
			
		||||
@ -60,59 +68,80 @@ 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
 | 
			
		||||
    c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
      "message": "pong",
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
 | 
			
		||||
  
 | 
			
		||||
  // Start server on port 8080 (default)
 | 
			
		||||
  // Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows)
 | 
			
		||||
  r.Run()
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To run the code, use the `go run` command, like:
 | 
			
		||||
**Running the application:**
 | 
			
		||||
 | 
			
		||||
1. Save the code above as `main.go`
 | 
			
		||||
2. Run the application:
 | 
			
		||||
 | 
			
		||||
   ```sh
 | 
			
		||||
go run example.go
 | 
			
		||||
   go run main.go
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
Then visit [`0.0.0.0:8080/ping`](http://0.0.0.0:8080/ping) in your browser to see the response!
 | 
			
		||||
3. Open your browser and visit [`http://localhost:8080/ping`](http://localhost:8080/ping)
 | 
			
		||||
4. You should see: `{"message":"pong"}`
 | 
			
		||||
 | 
			
		||||
### See more examples
 | 
			
		||||
**What this example demonstrates:**
 | 
			
		||||
 | 
			
		||||
#### Quick Start
 | 
			
		||||
- Creating a Gin router with default middleware
 | 
			
		||||
- Defining HTTP endpoints with simple handler functions
 | 
			
		||||
- Returning JSON responses
 | 
			
		||||
- Starting an HTTP server
 | 
			
		||||
 | 
			
		||||
Learn and practice with the [Gin Quick Start](docs/doc.md), which includes API examples and builds tag.
 | 
			
		||||
### Next Steps
 | 
			
		||||
 | 
			
		||||
#### Examples
 | 
			
		||||
After running your first Gin application, explore these resources to learn more:
 | 
			
		||||
 | 
			
		||||
A number of ready-to-run examples demonstrating various use cases of Gin are available in the [Gin examples](https://github.com/gin-gonic/examples) repository.
 | 
			
		||||
#### 📚 Learning Resources
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
- **[Gin Quick Start Guide](docs/doc.md)** - Comprehensive tutorial with API examples and build configurations
 | 
			
		||||
- **[Example Repository](https://github.com/gin-gonic/examples)** - Ready-to-run examples demonstrating various Gin use cases:
 | 
			
		||||
  - REST API development
 | 
			
		||||
  - Authentication & middleware
 | 
			
		||||
  - File uploads and downloads
 | 
			
		||||
  - WebSocket connections
 | 
			
		||||
  - Template rendering
 | 
			
		||||
 | 
			
		||||
See the [API documentation on go.dev](https://pkg.go.dev/github.com/gin-gonic/gin).
 | 
			
		||||
## 📖 Documentation
 | 
			
		||||
 | 
			
		||||
The documentation is also available on [gin-gonic.com](https://gin-gonic.com) in several languages:
 | 
			
		||||
### API Reference
 | 
			
		||||
 | 
			
		||||
- [English](https://gin-gonic.com/en/docs/)
 | 
			
		||||
- [简体中文](https://gin-gonic.com/zh-cn/docs/)
 | 
			
		||||
- [繁體中文](https://gin-gonic.com/zh-tw/docs/)
 | 
			
		||||
- [日本語](https://gin-gonic.com/ja/docs/)
 | 
			
		||||
- [Español](https://gin-gonic.com/es/docs/)
 | 
			
		||||
- [한국어](https://gin-gonic.com/ko-kr/docs/)
 | 
			
		||||
- [Turkish](https://gin-gonic.com/tr/docs/)
 | 
			
		||||
- [Persian](https://gin-gonic.com/fa/docs/)
 | 
			
		||||
- [Português](https://gin-gonic.com/pt/docs/)
 | 
			
		||||
- [Russian](https://gin-gonic.com/ru/docs/)
 | 
			
		||||
- [Indonesian](https://gin-gonic.com/id/docs/)
 | 
			
		||||
- **[Go.dev API Documentation](https://pkg.go.dev/github.com/gin-gonic/gin)** - Complete API reference with examples
 | 
			
		||||
 | 
			
		||||
### Articles
 | 
			
		||||
### User Guides
 | 
			
		||||
 | 
			
		||||
- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)
 | 
			
		||||
The comprehensive documentation is available on [gin-gonic.com](https://gin-gonic.com) in multiple languages:
 | 
			
		||||
 | 
			
		||||
## Benchmarks
 | 
			
		||||
- [English](https://gin-gonic.com/en/docs/) | [简体中文](https://gin-gonic.com/zh-cn/docs/) | [繁體中文](https://gin-gonic.com/zh-tw/docs/)
 | 
			
		||||
- [日本語](https://gin-gonic.com/ja/docs/) | [한국어](https://gin-gonic.com/ko-kr/docs/) | [Español](https://gin-gonic.com/es/docs/)
 | 
			
		||||
- [Turkish](https://gin-gonic.com/tr/docs/) | [Persian](https://gin-gonic.com/fa/docs/) | [Português](https://gin-gonic.com/pt/docs/)
 | 
			
		||||
- [Russian](https://gin-gonic.com/ru/docs/) | [Indonesian](https://gin-gonic.com/id/docs/)
 | 
			
		||||
 | 
			
		||||
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks](/BENCHMARKS.md).
 | 
			
		||||
### Official Tutorials
 | 
			
		||||
 | 
			
		||||
- [Go.dev Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)
 | 
			
		||||
 | 
			
		||||
## ⚡ Performance Benchmarks
 | 
			
		||||
 | 
			
		||||
Gin demonstrates exceptional performance compared to other Go web frameworks. It uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter) for maximum efficiency. [View detailed benchmarks →](/BENCHMARKS.md)
 | 
			
		||||
 | 
			
		||||
**Gin vs. Other Go Frameworks** (GitHub API routing benchmark):
 | 
			
		||||
 | 
			
		||||
| Benchmark name                 |       (1) |             (2) |          (3) |             (4) |
 | 
			
		||||
| ------------------------------ | --------: | --------------: | -----------: | --------------: |
 | 
			
		||||
@ -152,23 +181,44 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
 | 
			
		||||
- (3): Heap Memory (B/op), lower is better
 | 
			
		||||
- (4): Average Allocations per Repetition (allocs/op), lower is better
 | 
			
		||||
 | 
			
		||||
## Middleware
 | 
			
		||||
## 🔌 Middleware Ecosystem
 | 
			
		||||
 | 
			
		||||
You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib) and [gin-gonic/contrib](https://github.com/gin-gonic/contrib).
 | 
			
		||||
Gin has a rich ecosystem of middleware for common web development needs. Explore community-contributed middleware:
 | 
			
		||||
 | 
			
		||||
## Uses
 | 
			
		||||
- **[gin-contrib](https://github.com/gin-contrib)** - Official middleware collection including:
 | 
			
		||||
  - Authentication (JWT, Basic Auth, Sessions)
 | 
			
		||||
  - CORS, Rate limiting, Compression
 | 
			
		||||
  - Logging, Metrics, Tracing
 | 
			
		||||
  - Static file serving, Template engines
 | 
			
		||||
  
 | 
			
		||||
Here are some awesome projects that are using the [Gin](https://github.com/gin-gonic/gin) web framework.
 | 
			
		||||
- **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware
 | 
			
		||||
 | 
			
		||||
- [gorush](https://github.com/appleboy/gorush): A push notification server.
 | 
			
		||||
- [fnproject](https://github.com/fnproject/fn): A container native, cloud agnostic serverless platform.
 | 
			
		||||
- [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Google TensorFlow.
 | 
			
		||||
- [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middleware.
 | 
			
		||||
- [picfit](https://github.com/thoas/picfit): An image resizing server.
 | 
			
		||||
- [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
 | 
			
		||||
## 🏢 Production Usage
 | 
			
		||||
 | 
			
		||||
## Contributing
 | 
			
		||||
Gin powers many high-traffic applications and services in production:
 | 
			
		||||
 | 
			
		||||
Gin is the work of hundreds of contributors. We appreciate your help!
 | 
			
		||||
- **[gorush](https://github.com/appleboy/gorush)** - High-performance push notification server
 | 
			
		||||
- **[fnproject](https://github.com/fnproject/fn)** - Container-native, serverless platform
 | 
			
		||||
- **[photoprism](https://github.com/photoprism/photoprism)** - AI-powered personal photo management
 | 
			
		||||
- **[lura](https://github.com/luraproject/lura)** - Ultra-performant API Gateway framework
 | 
			
		||||
- **[picfit](https://github.com/thoas/picfit)** - Real-time image processing server
 | 
			
		||||
- **[dkron](https://github.com/distribworks/dkron)** - Distributed job scheduling system
 | 
			
		||||
 | 
			
		||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on submitting patches and the contribution workflow.
 | 
			
		||||
## 🤝 Contributing
 | 
			
		||||
 | 
			
		||||
Gin is the work of hundreds of contributors from around the world. We welcome and appreciate your contributions!
 | 
			
		||||
 | 
			
		||||
### How to Contribute
 | 
			
		||||
 | 
			
		||||
- 🐛 **Report bugs** - Help us identify and fix issues
 | 
			
		||||
- 💡 **Suggest features** - Share your ideas for improvements
 | 
			
		||||
- 📝 **Improve documentation** - Help make our docs clearer
 | 
			
		||||
- 🔧 **Submit code** - Fix bugs or implement new features
 | 
			
		||||
- 🧪 **Write tests** - Improve our test coverage
 | 
			
		||||
 | 
			
		||||
### Getting Started with Contributing
 | 
			
		||||
 | 
			
		||||
1. Check out our [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines
 | 
			
		||||
2. Join our community discussions and ask questions
 | 
			
		||||
 | 
			
		||||
**All contributions are valued and help make Gin better for everyone!**
 | 
			
		||||
 | 
			
		||||
@ -87,7 +87,7 @@ func BenchmarkOneRouteString(B *testing.B) {
 | 
			
		||||
	runRequest(B, router, http.MethodGet, "/text")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkManyRoutesFist(B *testing.B) {
 | 
			
		||||
func BenchmarkManyRoutesFirst(B *testing.B) {
 | 
			
		||||
	router := New()
 | 
			
		||||
	router.Any("/ping", func(c *Context) {})
 | 
			
		||||
	runRequest(B, router, http.MethodGet, "/ping")
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ package binding
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"maps"
 | 
			
		||||
	"mime/multipart"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
@ -230,9 +231,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
 | 
			
		||||
 | 
			
		||||
	switch value.Kind() {
 | 
			
		||||
	case reflect.Slice:
 | 
			
		||||
		if !ok {
 | 
			
		||||
			vs = []string{opt.defaultValue}
 | 
			
		||||
		if len(vs) == 0 {
 | 
			
		||||
			if !opt.isDefaultExists {
 | 
			
		||||
				return false, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vs = []string{opt.defaultValue}
 | 
			
		||||
			// pre-process the default value for multi if present
 | 
			
		||||
			cfTag := field.Tag.Get("collection_format")
 | 
			
		||||
			if cfTag == "" || cfTag == "multi" {
 | 
			
		||||
@ -250,9 +254,12 @@ func setByForm(value reflect.Value, field reflect.StructField, form map[string][
 | 
			
		||||
 | 
			
		||||
		return true, setSlice(vs, value, field)
 | 
			
		||||
	case reflect.Array:
 | 
			
		||||
		if !ok {
 | 
			
		||||
			vs = []string{opt.defaultValue}
 | 
			
		||||
		if len(vs) == 0 {
 | 
			
		||||
			if !opt.isDefaultExists {
 | 
			
		||||
				return false, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vs = []string{opt.defaultValue}
 | 
			
		||||
			// pre-process the default value for multi if present
 | 
			
		||||
			cfTag := field.Tag.Get("collection_format")
 | 
			
		||||
			if cfTag == "" || cfTag == "multi" {
 | 
			
		||||
@ -489,9 +496,7 @@ func setFormMap(ptr any, form map[string][]string) error {
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return ErrConvertMapStringSlice
 | 
			
		||||
		}
 | 
			
		||||
		for k, v := range form {
 | 
			
		||||
			ptrMap[k] = v
 | 
			
		||||
		}
 | 
			
		||||
		maps.Copy(ptrMap, form)
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -635,3 +635,83 @@ func TestMappingCustomArrayForm(t *testing.T) {
 | 
			
		||||
	expected, _ := convertTo(val)
 | 
			
		||||
	assert.Equal(t, expected, s.FileData)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMappingEmptyValues(t *testing.T) {
 | 
			
		||||
	t.Run("slice with default", func(t *testing.T) {
 | 
			
		||||
		var s struct {
 | 
			
		||||
			Slice []int `form:"slice,default=5"`
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// field not present
 | 
			
		||||
		err := mappingByPtr(&s, formSource{}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, []int{5}, s.Slice)
 | 
			
		||||
 | 
			
		||||
		// field present but empty
 | 
			
		||||
		err = mappingByPtr(&s, formSource{"slice": {}}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, []int{5}, s.Slice)
 | 
			
		||||
 | 
			
		||||
		// field present with values
 | 
			
		||||
		err = mappingByPtr(&s, formSource{"slice": {"1", "2", "3"}}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, []int{1, 2, 3}, s.Slice)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("array with default", func(t *testing.T) {
 | 
			
		||||
		var s struct {
 | 
			
		||||
			Array [1]int `form:"array,default=5"`
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// field not present
 | 
			
		||||
		err := mappingByPtr(&s, formSource{}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, [1]int{5}, s.Array)
 | 
			
		||||
 | 
			
		||||
		// field present but empty
 | 
			
		||||
		err = mappingByPtr(&s, formSource{"array": {}}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, [1]int{5}, s.Array)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("slice without default", func(t *testing.T) {
 | 
			
		||||
		var s struct {
 | 
			
		||||
			Slice []int `form:"slice"`
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// field present but empty
 | 
			
		||||
		err := mappingByPtr(&s, formSource{"slice": {}}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, []int(nil), s.Slice)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("array without default", func(t *testing.T) {
 | 
			
		||||
		var s struct {
 | 
			
		||||
			Array [1]int `form:"array"`
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// field present but empty
 | 
			
		||||
		err := mappingByPtr(&s, formSource{"array": {}}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, [1]int{0}, s.Array)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("slice with collection format", func(t *testing.T) {
 | 
			
		||||
		var s struct {
 | 
			
		||||
			SliceMulti []int `form:"slice_multi,default=1;2;3" collection_format:"multi"`
 | 
			
		||||
			SliceCsv   []int `form:"slice_csv,default=1;2;3" collection_format:"csv"`
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// field not present
 | 
			
		||||
		err := mappingByPtr(&s, formSource{}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, []int{1, 2, 3}, s.SliceMulti)
 | 
			
		||||
		assert.Equal(t, []int{1, 2, 3}, s.SliceCsv)
 | 
			
		||||
 | 
			
		||||
		// field present but empty
 | 
			
		||||
		err = mappingByPtr(&s, formSource{"slice_multi": {}, "slice_csv": {}}, "form")
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, []int{1, 2, 3}, s.SliceMulti)
 | 
			
		||||
		assert.Equal(t, []int{1, 2, 3}, s.SliceCsv)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										80
									
								
								context.go
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								context.go
									
									
									
									
									
								
							@ -10,6 +10,7 @@ import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"log"
 | 
			
		||||
	"maps"
 | 
			
		||||
	"math"
 | 
			
		||||
	"mime/multipart"
 | 
			
		||||
	"net"
 | 
			
		||||
@ -130,11 +131,8 @@ func (c *Context) Copy() *Context {
 | 
			
		||||
	cp.fullPath = c.fullPath
 | 
			
		||||
 | 
			
		||||
	cKeys := c.Keys
 | 
			
		||||
	cp.Keys = make(map[any]any, len(cKeys))
 | 
			
		||||
	c.mu.RLock()
 | 
			
		||||
	for k, v := range cKeys {
 | 
			
		||||
		cp.Keys[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	cp.Keys = maps.Clone(cKeys)
 | 
			
		||||
	c.mu.RUnlock()
 | 
			
		||||
 | 
			
		||||
	cParams := c.Params
 | 
			
		||||
@ -308,165 +306,175 @@ func getTyped[T any](c *Context, key any) (res T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetString returns the value associated with the key as a string.
 | 
			
		||||
func (c *Context) GetString(key any) (s string) {
 | 
			
		||||
func (c *Context) GetString(key any) string {
 | 
			
		||||
	return getTyped[string](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetBool returns the value associated with the key as a boolean.
 | 
			
		||||
func (c *Context) GetBool(key any) (b bool) {
 | 
			
		||||
func (c *Context) GetBool(key any) bool {
 | 
			
		||||
	return getTyped[bool](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt returns the value associated with the key as an integer.
 | 
			
		||||
func (c *Context) GetInt(key any) (i int) {
 | 
			
		||||
func (c *Context) GetInt(key any) int {
 | 
			
		||||
	return getTyped[int](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt8 returns the value associated with the key as an integer 8.
 | 
			
		||||
func (c *Context) GetInt8(key any) (i8 int8) {
 | 
			
		||||
func (c *Context) GetInt8(key any) int8 {
 | 
			
		||||
	return getTyped[int8](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt16 returns the value associated with the key as an integer 16.
 | 
			
		||||
func (c *Context) GetInt16(key any) (i16 int16) {
 | 
			
		||||
func (c *Context) GetInt16(key any) int16 {
 | 
			
		||||
	return getTyped[int16](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt32 returns the value associated with the key as an integer 32.
 | 
			
		||||
func (c *Context) GetInt32(key any) (i32 int32) {
 | 
			
		||||
func (c *Context) GetInt32(key any) int32 {
 | 
			
		||||
	return getTyped[int32](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt64 returns the value associated with the key as an integer 64.
 | 
			
		||||
func (c *Context) GetInt64(key any) (i64 int64) {
 | 
			
		||||
func (c *Context) GetInt64(key any) int64 {
 | 
			
		||||
	return getTyped[int64](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint returns the value associated with the key as an unsigned integer.
 | 
			
		||||
func (c *Context) GetUint(key any) (ui uint) {
 | 
			
		||||
func (c *Context) GetUint(key any) uint {
 | 
			
		||||
	return getTyped[uint](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint8 returns the value associated with the key as an unsigned integer 8.
 | 
			
		||||
func (c *Context) GetUint8(key any) (ui8 uint8) {
 | 
			
		||||
func (c *Context) GetUint8(key any) uint8 {
 | 
			
		||||
	return getTyped[uint8](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint16 returns the value associated with the key as an unsigned integer 16.
 | 
			
		||||
func (c *Context) GetUint16(key any) (ui16 uint16) {
 | 
			
		||||
func (c *Context) GetUint16(key any) uint16 {
 | 
			
		||||
	return getTyped[uint16](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint32 returns the value associated with the key as an unsigned integer 32.
 | 
			
		||||
func (c *Context) GetUint32(key any) (ui32 uint32) {
 | 
			
		||||
func (c *Context) GetUint32(key any) uint32 {
 | 
			
		||||
	return getTyped[uint32](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint64 returns the value associated with the key as an unsigned integer 64.
 | 
			
		||||
func (c *Context) GetUint64(key any) (ui64 uint64) {
 | 
			
		||||
func (c *Context) GetUint64(key any) uint64 {
 | 
			
		||||
	return getTyped[uint64](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetFloat32 returns the value associated with the key as a float32.
 | 
			
		||||
func (c *Context) GetFloat32(key any) (f32 float32) {
 | 
			
		||||
func (c *Context) GetFloat32(key any) float32 {
 | 
			
		||||
	return getTyped[float32](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetFloat64 returns the value associated with the key as a float64.
 | 
			
		||||
func (c *Context) GetFloat64(key any) (f64 float64) {
 | 
			
		||||
func (c *Context) GetFloat64(key any) float64 {
 | 
			
		||||
	return getTyped[float64](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetTime returns the value associated with the key as time.
 | 
			
		||||
func (c *Context) GetTime(key any) (t time.Time) {
 | 
			
		||||
func (c *Context) GetTime(key any) time.Time {
 | 
			
		||||
	return getTyped[time.Time](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDuration returns the value associated with the key as a duration.
 | 
			
		||||
func (c *Context) GetDuration(key any) (d time.Duration) {
 | 
			
		||||
func (c *Context) GetDuration(key any) time.Duration {
 | 
			
		||||
	return getTyped[time.Duration](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetIntSlice returns the value associated with the key as a slice of integers.
 | 
			
		||||
func (c *Context) GetIntSlice(key any) (is []int) {
 | 
			
		||||
func (c *Context) GetIntSlice(key any) []int {
 | 
			
		||||
	return getTyped[[]int](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt8Slice returns the value associated with the key as a slice of int8 integers.
 | 
			
		||||
func (c *Context) GetInt8Slice(key any) (i8s []int8) {
 | 
			
		||||
func (c *Context) GetInt8Slice(key any) []int8 {
 | 
			
		||||
	return getTyped[[]int8](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt16Slice returns the value associated with the key as a slice of int16 integers.
 | 
			
		||||
func (c *Context) GetInt16Slice(key any) (i16s []int16) {
 | 
			
		||||
func (c *Context) GetInt16Slice(key any) []int16 {
 | 
			
		||||
	return getTyped[[]int16](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt32Slice returns the value associated with the key as a slice of int32 integers.
 | 
			
		||||
func (c *Context) GetInt32Slice(key any) (i32s []int32) {
 | 
			
		||||
func (c *Context) GetInt32Slice(key any) []int32 {
 | 
			
		||||
	return getTyped[[]int32](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetInt64Slice returns the value associated with the key as a slice of int64 integers.
 | 
			
		||||
func (c *Context) GetInt64Slice(key any) (i64s []int64) {
 | 
			
		||||
func (c *Context) GetInt64Slice(key any) []int64 {
 | 
			
		||||
	return getTyped[[]int64](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUintSlice returns the value associated with the key as a slice of unsigned integers.
 | 
			
		||||
func (c *Context) GetUintSlice(key any) (uis []uint) {
 | 
			
		||||
func (c *Context) GetUintSlice(key any) []uint {
 | 
			
		||||
	return getTyped[[]uint](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint8Slice returns the value associated with the key as a slice of uint8 integers.
 | 
			
		||||
func (c *Context) GetUint8Slice(key any) (ui8s []uint8) {
 | 
			
		||||
func (c *Context) GetUint8Slice(key any) []uint8 {
 | 
			
		||||
	return getTyped[[]uint8](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint16Slice returns the value associated with the key as a slice of uint16 integers.
 | 
			
		||||
func (c *Context) GetUint16Slice(key any) (ui16s []uint16) {
 | 
			
		||||
func (c *Context) GetUint16Slice(key any) []uint16 {
 | 
			
		||||
	return getTyped[[]uint16](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint32Slice returns the value associated with the key as a slice of uint32 integers.
 | 
			
		||||
func (c *Context) GetUint32Slice(key any) (ui32s []uint32) {
 | 
			
		||||
func (c *Context) GetUint32Slice(key any) []uint32 {
 | 
			
		||||
	return getTyped[[]uint32](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetUint64Slice returns the value associated with the key as a slice of uint64 integers.
 | 
			
		||||
func (c *Context) GetUint64Slice(key any) (ui64s []uint64) {
 | 
			
		||||
func (c *Context) GetUint64Slice(key any) []uint64 {
 | 
			
		||||
	return getTyped[[]uint64](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers.
 | 
			
		||||
func (c *Context) GetFloat32Slice(key any) (f32s []float32) {
 | 
			
		||||
func (c *Context) GetFloat32Slice(key any) []float32 {
 | 
			
		||||
	return getTyped[[]float32](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers.
 | 
			
		||||
func (c *Context) GetFloat64Slice(key any) (f64s []float64) {
 | 
			
		||||
func (c *Context) GetFloat64Slice(key any) []float64 {
 | 
			
		||||
	return getTyped[[]float64](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStringSlice returns the value associated with the key as a slice of strings.
 | 
			
		||||
func (c *Context) GetStringSlice(key any) (ss []string) {
 | 
			
		||||
func (c *Context) GetStringSlice(key any) []string {
 | 
			
		||||
	return getTyped[[]string](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStringMap returns the value associated with the key as a map of interfaces.
 | 
			
		||||
func (c *Context) GetStringMap(key any) (sm map[string]any) {
 | 
			
		||||
func (c *Context) GetStringMap(key any) map[string]any {
 | 
			
		||||
	return getTyped[map[string]any](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStringMapString returns the value associated with the key as a map of strings.
 | 
			
		||||
func (c *Context) GetStringMapString(key any) (sms map[string]string) {
 | 
			
		||||
func (c *Context) GetStringMapString(key any) map[string]string {
 | 
			
		||||
	return getTyped[map[string]string](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
 | 
			
		||||
func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) {
 | 
			
		||||
func (c *Context) GetStringMapStringSlice(key any) map[string][]string {
 | 
			
		||||
	return getTyped[map[string][]string](c, key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Delete deletes the key from the Context's Key map, if it exists.
 | 
			
		||||
// This operation is safe to be used by concurrent go-routines
 | 
			
		||||
func (c *Context) Delete(key any) {
 | 
			
		||||
	c.mu.Lock()
 | 
			
		||||
	defer c.mu.Unlock()
 | 
			
		||||
	if c.Keys != nil {
 | 
			
		||||
		delete(c.Keys, key)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/************************************/
 | 
			
		||||
/************ INPUT DATA ************/
 | 
			
		||||
/************************************/
 | 
			
		||||
 | 
			
		||||
@ -404,6 +404,19 @@ func TestContextSetGetBool(t *testing.T) {
 | 
			
		||||
	assert.True(t, c.GetBool("bool"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetGetDelete(t *testing.T) {
 | 
			
		||||
	c, _ := CreateTestContext(httptest.NewRecorder())
 | 
			
		||||
	key := "example-key"
 | 
			
		||||
	value := "example-value"
 | 
			
		||||
	c.Set(key, value)
 | 
			
		||||
	val, exists := c.Get(key)
 | 
			
		||||
	assert.True(t, exists)
 | 
			
		||||
	assert.Equal(t, val, value)
 | 
			
		||||
	c.Delete(key)
 | 
			
		||||
	_, exists = c.Get(key)
 | 
			
		||||
	assert.False(t, exists)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestContextGetInt(t *testing.T) {
 | 
			
		||||
	c, _ := CreateTestContext(httptest.NewRecorder())
 | 
			
		||||
	c.Set("int", 1)
 | 
			
		||||
@ -1233,7 +1246,7 @@ func TestContextRenderNoContentHTML(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestContextXML tests that the response is serialized as XML
 | 
			
		||||
// TestContextRenderXML tests that the response is serialized as XML
 | 
			
		||||
// and Content-Type is set to application/xml
 | 
			
		||||
func TestContextRenderXML(t *testing.T) {
 | 
			
		||||
	w := httptest.NewRecorder()
 | 
			
		||||
@ -3320,7 +3333,7 @@ func TestContextSetCookieData(t *testing.T) {
 | 
			
		||||
	assert.Contains(t, setCookie, "Max-Age=1")
 | 
			
		||||
	assert.Contains(t, setCookie, "HttpOnly")
 | 
			
		||||
	assert.Contains(t, setCookie, "Secure")
 | 
			
		||||
	// SameSite=Lax might be omitted in Go 1.23+ as it's the default
 | 
			
		||||
	// SameSite=Lax might be omitted in Go 1.24+ as it's the default
 | 
			
		||||
	// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
			
		||||
 | 
			
		||||
	// Test that when Path is empty, "/" is automatically set
 | 
			
		||||
@ -3341,7 +3354,7 @@ func TestContextSetCookieData(t *testing.T) {
 | 
			
		||||
	assert.Contains(t, setCookie, "Max-Age=1")
 | 
			
		||||
	assert.Contains(t, setCookie, "HttpOnly")
 | 
			
		||||
	assert.Contains(t, setCookie, "Secure")
 | 
			
		||||
	// SameSite=Lax might be omitted in Go 1.23+ as it's the default
 | 
			
		||||
	// SameSite=Lax might be omitted in Go 1.24+ as it's the default
 | 
			
		||||
	// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
			
		||||
 | 
			
		||||
	// Test additional cookie attributes (Expires)
 | 
			
		||||
@ -3364,7 +3377,7 @@ func TestContextSetCookieData(t *testing.T) {
 | 
			
		||||
	assert.Contains(t, setCookie, "Domain=localhost")
 | 
			
		||||
	assert.Contains(t, setCookie, "HttpOnly")
 | 
			
		||||
	assert.Contains(t, setCookie, "Secure")
 | 
			
		||||
	// SameSite=Lax might be omitted in Go 1.23+ as it's the default
 | 
			
		||||
	// SameSite=Lax might be omitted in Go 1.24+ as it's the default
 | 
			
		||||
	// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
			
		||||
 | 
			
		||||
	// Test for Partitioned attribute (Go 1.18+)
 | 
			
		||||
@ -3384,7 +3397,7 @@ func TestContextSetCookieData(t *testing.T) {
 | 
			
		||||
	assert.Contains(t, setCookie, "Domain=localhost")
 | 
			
		||||
	assert.Contains(t, setCookie, "HttpOnly")
 | 
			
		||||
	assert.Contains(t, setCookie, "Secure")
 | 
			
		||||
	// SameSite=Lax might be omitted in Go 1.23+ as it's the default
 | 
			
		||||
	// SameSite=Lax might be omitted in Go 1.24+ as it's the default
 | 
			
		||||
	// assert.Contains(t, setCookie, "SameSite=Lax")
 | 
			
		||||
	// Not testing for Partitioned attribute as it may not be supported in all Go versions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								debug.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								debug.go
									
									
									
									
									
								
							@ -78,7 +78,7 @@ func getMinVer(v string) (uint64, error) {
 | 
			
		||||
 | 
			
		||||
func debugPrintWARNINGDefault() {
 | 
			
		||||
	if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
 | 
			
		||||
		debugPrint(`[WARNING] Now Gin requires Go 1.23+.
 | 
			
		||||
		debugPrint(`[WARNING] Now Gin requires Go 1.24+.
 | 
			
		||||
 | 
			
		||||
`)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -106,7 +106,7 @@ func TestDebugPrintWARNINGDefault(t *testing.T) {
 | 
			
		||||
	})
 | 
			
		||||
	m, e := getMinVer(runtime.Version())
 | 
			
		||||
	if e == nil && m < ginSupportMinGoVer {
 | 
			
		||||
		assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.23+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 | 
			
		||||
		assert.Equal(t, "[GIN-debug] [WARNING] Now Gin requires Go 1.24+.\n\n[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 | 
			
		||||
	} else {
 | 
			
		||||
		assert.Equal(t, "[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.\n\n", re)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								ginS/gins.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								ginS/gins.go
									
									
									
									
									
								
							@ -12,17 +12,9 @@ import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	once           sync.Once
 | 
			
		||||
	internalEngine *gin.Engine
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func engine() *gin.Engine {
 | 
			
		||||
	once.Do(func() {
 | 
			
		||||
		internalEngine = gin.Default()
 | 
			
		||||
var engine = sync.OnceValue(func() *gin.Engine {
 | 
			
		||||
	return gin.Default()
 | 
			
		||||
})
 | 
			
		||||
	return internalEngine
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob.
 | 
			
		||||
func LoadHTMLGlob(pattern string) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										32
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								go.mod
									
									
									
									
									
								
							@ -1,29 +1,30 @@
 | 
			
		||||
module github.com/gin-gonic/gin
 | 
			
		||||
 | 
			
		||||
go 1.23.0
 | 
			
		||||
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.27.0
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.28.0
 | 
			
		||||
	github.com/goccy/go-json v0.10.2
 | 
			
		||||
	github.com/goccy/go-yaml v1.18.0
 | 
			
		||||
	github.com/json-iterator/go v1.1.12
 | 
			
		||||
	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.54.0
 | 
			
		||||
	github.com/quic-go/quic-go v0.55.0
 | 
			
		||||
	github.com/stretchr/testify v1.11.1
 | 
			
		||||
	github.com/ugorji/go/codec v1.3.0
 | 
			
		||||
	golang.org/x/net v0.42.0
 | 
			
		||||
	google.golang.org/protobuf v1.36.9
 | 
			
		||||
	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.8 // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.10 // indirect
 | 
			
		||||
	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
 | 
			
		||||
@ -32,13 +33,12 @@ require (
 | 
			
		||||
	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
 | 
			
		||||
	go.uber.org/mock v0.5.0 // indirect
 | 
			
		||||
	golang.org/x/arch v0.20.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.40.0 // indirect
 | 
			
		||||
	golang.org/x/mod v0.25.0 // indirect
 | 
			
		||||
	golang.org/x/sync v0.16.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.35.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.27.0 // indirect
 | 
			
		||||
	golang.org/x/tools v0.34.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
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										66
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								go.sum
									
									
									
									
									
								
							@ -1,14 +1,16 @@
 | 
			
		||||
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=
 | 
			
		||||
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=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
 | 
			
		||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
 | 
			
		||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 | 
			
		||||
@ -17,8 +19,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 | 
			
		||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 | 
			
		||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
 | 
			
		||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 | 
			
		||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
			
		||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
 | 
			
		||||
@ -44,42 +46,44 @@ 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.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
 | 
			
		||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
 | 
			
		||||
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/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=
 | 
			
		||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
 | 
			
		||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
 | 
			
		||||
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=
 | 
			
		||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
 | 
			
		||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
 | 
			
		||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
 | 
			
		||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
 | 
			
		||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
 | 
			
		||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
 | 
			
		||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
 | 
			
		||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
 | 
			
		||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 | 
			
		||||
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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
 | 
			
		||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
			
		||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
 | 
			
		||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
 | 
			
		||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
 | 
			
		||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
 | 
			
		||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
 | 
			
		||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
 | 
			
		||||
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=
 | 
			
		||||
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
 | 
			
		||||
@ -81,25 +81,25 @@ func TestStringToBytes(t *testing.T) {
 | 
			
		||||
// go test -v -run=none -bench=^BenchmarkBytesConv -benchmem=true
 | 
			
		||||
 | 
			
		||||
func BenchmarkBytesConvBytesToStrRaw(b *testing.B) {
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
	for b.Loop() {
 | 
			
		||||
		rawBytesToStr(testBytes)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkBytesConvBytesToStr(b *testing.B) {
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
	for b.Loop() {
 | 
			
		||||
		BytesToString(testBytes)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkBytesConvStrToBytesRaw(b *testing.B) {
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
	for b.Loop() {
 | 
			
		||||
		rawStrToBytes(testString)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkBytesConvStrToBytes(b *testing.B) {
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
	for b.Loop() {
 | 
			
		||||
		StringToBytes(testString)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								logger.go
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								logger.go
									
									
									
									
									
								
							@ -103,6 +103,27 @@ func (p *LogFormatterParams) StatusCodeColor() string {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LatencyColor is the ANSI color for latency
 | 
			
		||||
func (p *LogFormatterParams) LatencyColor() string {
 | 
			
		||||
	latency := p.Latency
 | 
			
		||||
	switch {
 | 
			
		||||
	case latency < time.Millisecond*100:
 | 
			
		||||
		return white
 | 
			
		||||
	case latency < time.Millisecond*200:
 | 
			
		||||
		return green
 | 
			
		||||
	case latency < time.Millisecond*300:
 | 
			
		||||
		return cyan
 | 
			
		||||
	case latency < time.Millisecond*500:
 | 
			
		||||
		return blue
 | 
			
		||||
	case latency < time.Second:
 | 
			
		||||
		return yellow
 | 
			
		||||
	case latency < time.Second*2:
 | 
			
		||||
		return magenta
 | 
			
		||||
	default:
 | 
			
		||||
		return red
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MethodColor is the ANSI color for appropriately logging http method to a terminal.
 | 
			
		||||
func (p *LogFormatterParams) MethodColor() string {
 | 
			
		||||
	method := p.Method
 | 
			
		||||
@ -139,20 +160,27 @@ func (p *LogFormatterParams) IsOutputColor() bool {
 | 
			
		||||
 | 
			
		||||
// defaultLogFormatter is the default log format function Logger middleware uses.
 | 
			
		||||
var defaultLogFormatter = func(param LogFormatterParams) string {
 | 
			
		||||
	var statusColor, methodColor, resetColor string
 | 
			
		||||
	var statusColor, methodColor, resetColor, latencyColor string
 | 
			
		||||
	if param.IsOutputColor() {
 | 
			
		||||
		statusColor = param.StatusCodeColor()
 | 
			
		||||
		methodColor = param.MethodColor()
 | 
			
		||||
		resetColor = param.ResetColor()
 | 
			
		||||
		latencyColor = param.LatencyColor()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if param.Latency > time.Minute {
 | 
			
		||||
		param.Latency = param.Latency.Truncate(time.Second)
 | 
			
		||||
	switch {
 | 
			
		||||
	case param.Latency > time.Minute:
 | 
			
		||||
		param.Latency = param.Latency.Truncate(time.Second * 10)
 | 
			
		||||
	case param.Latency > time.Second:
 | 
			
		||||
		param.Latency = param.Latency.Truncate(time.Millisecond * 10)
 | 
			
		||||
	case param.Latency > time.Millisecond:
 | 
			
		||||
		param.Latency = param.Latency.Truncate(time.Microsecond * 10)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("[GIN] %v |%s %3d %s|%s %8v %s| %15s |%s %-7s %s %#v\n%s",
 | 
			
		||||
		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
 | 
			
		||||
		statusColor, param.StatusCode, resetColor,
 | 
			
		||||
		param.Latency,
 | 
			
		||||
		latencyColor, param.Latency, resetColor,
 | 
			
		||||
		param.ClientIP,
 | 
			
		||||
		methodColor, param.Method, resetColor,
 | 
			
		||||
		param.Path,
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,7 @@ func TestLogger(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	// I wrote these first (extending the above) but then realized they are more
 | 
			
		||||
	// like integration tests because they test the whole logging process rather
 | 
			
		||||
	// than individual functions.  Im not sure where these should go.
 | 
			
		||||
	// than individual functions. I'm not sure where these should go.
 | 
			
		||||
	buffer.Reset()
 | 
			
		||||
	PerformRequest(router, http.MethodPost, "/example")
 | 
			
		||||
	assert.Contains(t, buffer.String(), "200")
 | 
			
		||||
@ -103,7 +103,7 @@ func TestLoggerWithConfig(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	// I wrote these first (extending the above) but then realized they are more
 | 
			
		||||
	// like integration tests because they test the whole logging process rather
 | 
			
		||||
	// than individual functions.  Im not sure where these should go.
 | 
			
		||||
	// than individual functions. I'm not sure where these should go.
 | 
			
		||||
	buffer.Reset()
 | 
			
		||||
	PerformRequest(router, http.MethodPost, "/example")
 | 
			
		||||
	assert.Contains(t, buffer.String(), "200")
 | 
			
		||||
@ -278,10 +278,10 @@ func TestDefaultLogFormatter(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 |       5s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseParam))
 | 
			
		||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 |    2743h29m3s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseLongDurationParam))
 | 
			
		||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 | 200 | 2743h29m0s |     20.20.20.20 | GET      \"/\"\n", defaultLogFormatter(termFalseLongDurationParam))
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|            5s |     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
 | 
			
		||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|    2743h29m3s |     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
 | 
			
		||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|\x1b[97;41m       5s \x1b[0m|     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueParam))
 | 
			
		||||
	assert.Equal(t, "[GIN] 2018/12/07 - 09:11:42 |\x1b[97;42m 200 \x1b[0m|\x1b[97;41m 2743h29m0s \x1b[0m|     20.20.20.20 |\x1b[97;44m GET     \x1b[0m \"/\"\n", defaultLogFormatter(termTrueLongDurationParam))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestColorForMethod(t *testing.T) {
 | 
			
		||||
@ -317,6 +317,23 @@ func TestColorForStatus(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, red, colorForStatus(2), "other things should be red")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestColorForLatency(t *testing.T) {
 | 
			
		||||
	colorForLantency := func(latency time.Duration) string {
 | 
			
		||||
		p := LogFormatterParams{
 | 
			
		||||
			Latency: latency,
 | 
			
		||||
		}
 | 
			
		||||
		return p.LatencyColor()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, white, colorForLantency(time.Duration(0)), "0 should be white")
 | 
			
		||||
	assert.Equal(t, white, colorForLantency(time.Millisecond*20), "20ms should be white")
 | 
			
		||||
	assert.Equal(t, green, colorForLantency(time.Millisecond*150), "150ms should be green")
 | 
			
		||||
	assert.Equal(t, cyan, colorForLantency(time.Millisecond*250), "250ms should be cyan")
 | 
			
		||||
	assert.Equal(t, yellow, colorForLantency(time.Millisecond*600), "600ms should be yellow")
 | 
			
		||||
	assert.Equal(t, magenta, colorForLantency(time.Millisecond*1500), "1.5s should be magenta")
 | 
			
		||||
	assert.Equal(t, red, colorForLantency(time.Second*3), "other things should be red")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestResetColor(t *testing.T) {
 | 
			
		||||
	p := LogFormatterParams{}
 | 
			
		||||
	assert.Equal(t, string([]byte{27, 91, 48, 109}), p.ResetColor())
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ const (
 | 
			
		||||
	defaultStatus = http.StatusOK
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var errHijackAlreadyWritten = errors.New("gin: response already written")
 | 
			
		||||
var errHijackAlreadyWritten = errors.New("gin: response body already written")
 | 
			
		||||
 | 
			
		||||
// ResponseWriter ...
 | 
			
		||||
type ResponseWriter interface {
 | 
			
		||||
@ -109,7 +109,9 @@ func (w *responseWriter) Written() bool {
 | 
			
		||||
 | 
			
		||||
// Hijack implements the http.Hijacker interface.
 | 
			
		||||
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
 | 
			
		||||
	if w.Written() {
 | 
			
		||||
	// Allow hijacking before any data is written (size == -1) or after headers are written (size == 0),
 | 
			
		||||
	// but not after body data is written (size > 0). For compatibility with websocket libraries (e.g., github.com/coder/websocket)
 | 
			
		||||
	if w.size > 0 {
 | 
			
		||||
		return nil, nil, errHijackAlreadyWritten
 | 
			
		||||
	}
 | 
			
		||||
	if w.size < 0 {
 | 
			
		||||
 | 
			
		||||
@ -194,6 +194,64 @@ func TestResponseWriterHijackAfterWrite(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test: WebSocket compatibility - allow hijack after WriteHeaderNow(), but block after body data.
 | 
			
		||||
func TestResponseWriterHijackAfterWriteHeaderNow(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name                      string
 | 
			
		||||
		action                    func(w ResponseWriter) error
 | 
			
		||||
		expectWrittenBeforeHijack bool
 | 
			
		||||
		expectHijackSuccess       bool
 | 
			
		||||
		expectWrittenAfterHijack  bool
 | 
			
		||||
		expectError               error
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "hijack after WriteHeaderNow only should succeed (websocket pattern)",
 | 
			
		||||
			action: func(w ResponseWriter) error {
 | 
			
		||||
				w.WriteHeaderNow() // Simulate websocket.Accept() behavior
 | 
			
		||||
				return nil
 | 
			
		||||
			},
 | 
			
		||||
			expectWrittenBeforeHijack: true,
 | 
			
		||||
			expectHijackSuccess:       true, // NEW BEHAVIOR: allow hijack after just header write
 | 
			
		||||
			expectWrittenAfterHijack:  true,
 | 
			
		||||
			expectError:               nil,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "hijack after WriteHeaderNow + Write should fail",
 | 
			
		||||
			action: func(w ResponseWriter) error {
 | 
			
		||||
				w.WriteHeaderNow()
 | 
			
		||||
				_, err := w.Write([]byte("test"))
 | 
			
		||||
				return err
 | 
			
		||||
			},
 | 
			
		||||
			expectWrittenBeforeHijack: true,
 | 
			
		||||
			expectHijackSuccess:       false,
 | 
			
		||||
			expectWrittenAfterHijack:  true,
 | 
			
		||||
			expectError:               errHijackAlreadyWritten,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tc := range tests {
 | 
			
		||||
		t.Run(tc.name, func(t *testing.T) {
 | 
			
		||||
			hijacker := &mockHijacker{ResponseRecorder: httptest.NewRecorder()}
 | 
			
		||||
			writer := &responseWriter{}
 | 
			
		||||
			writer.reset(hijacker)
 | 
			
		||||
			w := ResponseWriter(writer)
 | 
			
		||||
 | 
			
		||||
			require.NoError(t, tc.action(w), "unexpected error during pre-hijack action")
 | 
			
		||||
 | 
			
		||||
			assert.Equal(t, tc.expectWrittenBeforeHijack, w.Written(), "unexpected w.Written() state before hijack")
 | 
			
		||||
 | 
			
		||||
			_, _, hijackErr := w.Hijack()
 | 
			
		||||
 | 
			
		||||
			if tc.expectError == nil {
 | 
			
		||||
				require.NoError(t, hijackErr, "expected hijack to succeed")
 | 
			
		||||
			} else {
 | 
			
		||||
				require.ErrorIs(t, hijackErr, tc.expectError, "unexpected error from Hijack()")
 | 
			
		||||
			}
 | 
			
		||||
			assert.Equal(t, tc.expectHijackSuccess, hijacker.hijacked, "unexpected hijacker.hijacked state")
 | 
			
		||||
			assert.Equal(t, tc.expectWrittenAfterHijack, w.Written(), "unexpected w.Written() state after hijack")
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestResponseWriterFlush(t *testing.T) {
 | 
			
		||||
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		writer := &responseWriter{}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								tree.go
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								tree.go
									
									
									
									
									
								
							@ -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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user