mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-14 12:12:12 +08:00
example
This commit is contained in:
parent
e30123ad73
commit
b60fac92d9
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:
|
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -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.
|
|
||||||
|
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@ -1,10 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
- package-ecosystem: gomod
|
|
||||||
directory: /
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
49
.github/workflows/codeql.yml
vendored
49
.github/workflows/codeql.yml
vendored
@ -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
|
|
83
.github/workflows/gin.yml
vendored
83
.github/workflows/gin.yml
vendored
@ -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 }}
|
|
31
.github/workflows/goreleaser.yml
vendored
31
.github/workflows/goreleaser.yml
vendored
@ -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 }}
|
|
@ -34,6 +34,7 @@ func (jsonBinding) Bind(req *http.Request, obj any) error {
|
|||||||
if req == nil || req.Body == nil {
|
if req == nil || req.Body == nil {
|
||||||
return errors.New("invalid request")
|
return errors.New("invalid request")
|
||||||
}
|
}
|
||||||
|
//这里的body是io.Reader,如果关闭了,则不能读取第二次
|
||||||
return decodeJSON(req.Body, obj)
|
return decodeJSON(req.Body, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -865,6 +865,7 @@ func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if body == nil {
|
if body == nil {
|
||||||
|
//这里可以对比一下 io.ReadAll 和 readValue
|
||||||
body, err = io.ReadAll(c.Request.Body)
|
body, err = io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
17
examples/main.go
Normal file
17
examples/main.go
Normal 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
152
examples/url/ini/utl_gin.go
Normal 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()})
|
||||||
|
}
|
||||||
|
}
|
65
examples/url/ping-pong_test.go
Normal file
65
examples/url/ping-pong_test.go
Normal 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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user