This commit is contained in:
flybread 2025-06-04 18:20:15 +08:00
parent e30123ad73
commit b60fac92d9
11 changed files with 236 additions and 229 deletions

View File

@ -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:

View File

@ -1,7 +0,0 @@
- 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.

View File

@ -1,10 +0,0 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
- package-ecosystem: gomod
directory: /
schedule:
interval: weekly

View File

@ -1,49 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [master]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: "0 17 * * 5"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
# required for all workflows
security-events: write
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
# TODO: Enable for javascript later
language: ["go"]
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View File

@ -1,83 +0,0 @@
name: Run Tests
on:
push:
branches:
- master
pull_request:
branches:
- master
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "^1"
- name: Setup golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.1.6
args: --verbose
test:
needs: lint
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
go: ["1.23", "1.24"]
test-tags:
[
"",
"-tags nomsgpack",
'--ldflags="-checklinkname=0" -tags sonic',
"-tags go_json",
"-race",
]
include:
- os: ubuntu-latest
go-build: ~/.cache/go-build
- os: macos-latest
go-build: ~/Library/Caches/go-build
name: ${{ matrix.os }} @ Go ${{ matrix.go }} ${{ matrix.test-tags }}
runs-on: ${{ matrix.os }}
env:
GO111MODULE: on
TESTTAGS: ${{ matrix.test-tags }}
GOPROXY: https://proxy.golang.org
steps:
- name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
cache: false
- name: Checkout Code
uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
- uses: actions/cache@v4
with:
path: |
${{ matrix.go-build }}
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run Tests
run: make test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
flags: ${{ matrix.os }},go-${{ matrix.go }},${{ matrix.test-tags }}

View File

@ -1,31 +0,0 @@
name: Goreleaser
on:
push:
tags:
- "*"
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "^1"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -34,6 +34,7 @@ func (jsonBinding) Bind(req *http.Request, obj any) error {
if req == nil || req.Body == nil {
return errors.New("invalid request")
}
//这里的body是io.Reader如果关闭了则不能读取第二次
return decodeJSON(req.Body, obj)
}

View File

@ -865,6 +865,7 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error
}
}
if body == nil {
//这里可以对比一下 io.ReadAll 和 readValue
body, err = io.ReadAll(c.Request.Body)
if err != nil {
return err

17
examples/main.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/examples/url/ini"
)
func main() {
fmt.Println("Hello Gin")
router := gin.Default()
ini.UrlInit(router)
err := router.Run(":8080")
if err != nil {
return
} // listen and serve on 0.0.0.0:8080
}

152
examples/url/ini/utl_gin.go Normal file
View File

@ -0,0 +1,152 @@
package ini
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"net/http"
)
type Body struct {
// json tag to de-serialize json body
Name string `json:"name"`
}
func UrlInit(router *gin.Engine) {
//普通url测试
router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "You Can Try Another",
})
})
router.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// 测试AsciiJSON数据返回
router.GET("/someJSON", func(c *gin.Context) {
data := map[string]interface{}{
"lang": "GO语言",
"tag": "<br>",
}
// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
c.AsciiJSON(http.StatusOK, data)
})
// 正常的json数据返回
router.GET("/someJSON2", func(c *gin.Context) {
data := map[string]interface{}{
"lang": "GO语言",
"tag": "<br>",
}
// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
c.JSON(http.StatusOK, data)
})
//Gin bindings are used to serialize JSON, XML, path parameters, form data, etc.
//to structs and maps.
//It also has a baked-in validation framework with complex validations.
router.POST("/bingJson", func(c *gin.Context) {
// one: de-serialize json body
body := Body{}
// using BindJson method to serialize body with struct
// BindJSON reads the body buffer to de-serialize it to a struct.
// BindJSON cannot be called on the same context twice because it flushes the body buffer.
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
fmt.Println(body)
c.JSON(http.StatusAccepted, &body)
})
router.POST("/bingJson1", func(c *gin.Context) {
// one: de-serialize json body
body := Body{}
// using BindJson method to serialize body with struct
// BindJSON reads the body buffer to de-serialize it to a struct.
// BindJSON cannot be called on the same context twice because it flushes the body buffer.
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
body2 := Body{}
if err := c.BindJSON(&body2); err != nil {
//在Gin框架中c.BindJSON()第二次调用会报错的原因是因为:
//BindJSON()方法会读取并消耗HTTP请求的Body数据流。HTTP请求的Body是一个只能读取一次的io.ReadCloser接口实现。
//当第一次调用c.BindJSON(&body)时它会完整读取请求Body中的数据并解析到第一个结构体中同时会将Body流关闭。
//当第二次尝试调用c.BindJSON(&body2)时Body流已经被关闭且数据已被消耗所以会返回错误。
//解决方案:
//如果需要多次绑定同一个请求体应该使用ShouldBindBodyWith()方法(如代码中/bingJson2路由所示这个方法会将请求体内容缓存起来允许后续多次绑定。
//或者,可以在第一次绑定后将数据手动复制一份,供后续使用。
c.AbortWithError(http.StatusBadRequest, err)
return
}
fmt.Println(body)
c.JSON(http.StatusAccepted, &body)
c.JSON(http.StatusAccepted, &body2)
})
router.POST("/bingJson2", func(c *gin.Context) {
// one: de-serialize json body
body := Body{}
// using BindJson method to serialize body with struct
// BindJSON reads the body buffer to de-serialize it to a struct.
// BindJSON cannot be called on the same context twice because it flushes the body buffer.
if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
body2 := Body{}
if err := c.ShouldBindBodyWith(&body2, binding.JSON); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
fmt.Println(body)
c.JSON(http.StatusAccepted, &body)
c.JSON(http.StatusAccepted, &body2)
})
}
type formB struct {
Bar string `json:"bar" xml:"bar" binding:"required"`
}
type formA struct {
Foo string `json:"foo" xml:"foo" binding:"required"`
}
func BindHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.
if errA := c.ShouldBind(&objA); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// Always an error is occurred by this because c.Request.Body is EOF now.
} else if errB := c.ShouldBind(&objB); errB == nil {
c.String(http.StatusOK, `the body should be formB`)
} else {
c.JSON(http.StatusOK, gin.H{"error": errA.Error()})
}
}
func MulBindHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// 读取 c.Request.Body 并将结果存入上下文。
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// 这时, 复用存储在上下文中的 body。
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
c.String(http.StatusOK, `the body should be formB JSON`)
// 可以接受其他格式
} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
c.String(http.StatusOK, `the body should be formB XML`)
} else {
c.JSON(http.StatusOK, gin.H{"error": errA.Error()})
}
}

View File

@ -0,0 +1,65 @@
package test_url
import (
"context"
"fmt"
"io"
"net/http"
"testing"
)
const Host = "http://127.0.0.1:8080/"
func TestUrl(t *testing.T) {
json, err := PostJsonFormServer(context.TODO(), "bingJson")
if err != nil {
fmt.Println(err)
}
fmt.Println("对应的响应是:", json)
url := []string{"ping", "someJSON", "someJSON2"}
for _, v := range url {
json, err := GetJsonFormServer(context.TODO(), v)
if err != nil {
fmt.Println(err)
}
fmt.Println(v, "对应的响应是:", json)
}
}
func PostJsonFormServer(ctx context.Context, v string) (string, error) {
httpUrl := Host + v
// 发送POST请求
var reqBody io.Reader
resp, err := http.Post(httpUrl, "application/json", reqBody)
if err != nil {
return "", err
}
defer resp.Body.Close() // 确保关闭响应体
// 读取响应内容
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
// GetJsonFormServer
func GetJsonFormServer(todo context.Context, url string) (string, error) {
httpUrl := Host + url
// 发送GET请求
resp, err := http.Get(httpUrl)
if err != nil {
return "", err
}
defer resp.Body.Close() // 确保关闭响应体
// 读取响应内容
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}