From b60fac92d998f7d1ff42251c74ce947ae08f53c6 Mon Sep 17 00:00:00 2001 From: flybread <1015126672@qq.com> Date: Wed, 4 Jun 2025 18:20:15 +0800 Subject: [PATCH] example --- .github/ISSUE_TEMPLATE.md | 49 ---------- .github/PULL_REQUEST_TEMPLATE.md | 7 -- .github/dependabot.yml | 10 -- .github/workflows/codeql.yml | 49 ---------- .github/workflows/gin.yml | 83 ----------------- .github/workflows/goreleaser.yml | 31 ------- binding/json.go | 1 + context.go | 1 + examples/main.go | 17 ++++ examples/url/ini/utl_gin.go | 152 +++++++++++++++++++++++++++++++ examples/url/ping-pong_test.go | 65 +++++++++++++ 11 files changed, 236 insertions(+), 229 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/gin.yml delete mode 100644 .github/workflows/goreleaser.yml create mode 100644 examples/main.go create mode 100644 examples/url/ini/utl_gin.go create mode 100644 examples/url/ping-pong_test.go diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 864787ca..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -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 - - - -## How to reproduce - - -``` -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 - - -``` -$ curl http://localhost:9000/hello/world -Hello world -``` - -## Actual result - - -``` -$ curl -i http://localhost:9000/hello/world - -``` - -## Environment - -- go version: -- gin version (or commit ref): -- operating system: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 96e70bba..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -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. - diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 632e8eb2..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - - package-ecosystem: gomod - directory: / - schedule: - interval: weekly diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 9a4c40d7..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -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 diff --git a/.github/workflows/gin.yml b/.github/workflows/gin.yml deleted file mode 100644 index 6f0f7c11..00000000 --- a/.github/workflows/gin.yml +++ /dev/null @@ -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 }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml deleted file mode 100644 index 22edf453..00000000 --- a/.github/workflows/goreleaser.yml +++ /dev/null @@ -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 }} diff --git a/binding/json.go b/binding/json.go index e21c2ee3..4fa90282 100644 --- a/binding/json.go +++ b/binding/json.go @@ -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) } diff --git a/context.go b/context.go index bf12830c..d949ccda 100644 --- a/context.go +++ b/context.go @@ -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 diff --git a/examples/main.go b/examples/main.go new file mode 100644 index 00000000..20f04d01 --- /dev/null +++ b/examples/main.go @@ -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 +} diff --git a/examples/url/ini/utl_gin.go b/examples/url/ini/utl_gin.go new file mode 100644 index 00000000..db92b0c2 --- /dev/null +++ b/examples/url/ini/utl_gin.go @@ -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": "
", + } + // 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": "
", + } + // 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()}) + } +} diff --git a/examples/url/ping-pong_test.go b/examples/url/ping-pong_test.go new file mode 100644 index 00000000..d370af2d --- /dev/null +++ b/examples/url/ping-pong_test.go @@ -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 +}