mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-14 04:08:15 +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 {
|
||||
return errors.New("invalid request")
|
||||
}
|
||||
//这里的body是io.Reader,如果关闭了,则不能读取第二次
|
||||
return decodeJSON(req.Body, obj)
|
||||
}
|
||||
|
||||
|
@ -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
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