gin-gonic / gin

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
https://gin-gonic.com/
MIT License
78.03k stars 7.97k forks source link

README.md error errA := c.ShouldBindBodyWith(&objA, binding.Form) #3296

Open ItisDL opened 2 years ago

ItisDL commented 2 years ago

Description

How to reproduce

package main

import (
    "context"
    "errors"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
)

func Logger() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()

        c.Set("example", "12345")

        // before request
        c.Next()
        // after request
        latency := time.Since(t)
        log.Println(latency)
    }
}

func main() {
    r := gin.Default()

    // 中间件测试
    r.Use(Logger())

    r.StaticFS("/assets", http.Dir("assets"))
    r.GET("/local/file", func(c *gin.Context) {
        c.File("main.go")
    })
    r.POST("/test", func(c *gin.Context) {
        example := c.MustGet("example").(string)
        log.Println(example)

        type formA struct {
            Foo string `json:"foo" xml:"foo" binding:"required"`
        }

        type formB struct {
            Bar string `json:"bar" xml:"bar" binding:"required"`
        }
        objA := formA{}
        objB := formB{}
        // This reads c.Request.Body and stores the result into the context.
        if errA := c.ShouldBindBodyWith(&objA, binding.Form); errA == nil {
            c.String(http.StatusOK, `the body should be formA`)
          // At this time, it reuses body stored in the context.
          } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
            c.String(http.StatusOK, `the body should be formB JSON`)
          // And it can accepts other formats
          } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
            c.String(http.StatusOK, `the body should be formB XML`)
          }

        // c.Redirect(http.StatusFound, "/someDataFromReader")
    })
    r.GET("/long_async", func(c *gin.Context) {
        cCp := c.Copy()
        go func() {
            time.Sleep(5 * time.Second)
            log.Println("Done! in path " + cCp.Request.URL.Path)
        }()
    })
    r.GET("/someDataFromReader", func(c *gin.Context) {
        response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
        if err != nil || response.StatusCode != http.StatusOK {
            c.Status(http.StatusServiceUnavailable)
            return
        }

        reader := response.Body
        defer reader.Close()
        contentLength := response.ContentLength
        contentType := response.Header.Get("Content-Type")

        extraHeaders := map[string]string{
            "Content-Disposition": `attachment; filename="gopher.png"`,
        }

        c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
    })
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    // gin.H is a shortcut for map[string]interface{}
    r.GET("/someJSON", func(c *gin.Context) {
        names := []string{"lena", "austin", "foo"}

        // Will output  :   while(1);["lena","austin","foo"]
        c.SecureJSON(http.StatusOK, names)
    })

    r.GET("/moreJSON", func(c *gin.Context) {
        // You also can use a struct
        var msg struct {
            Name    string `json:"user"`
            Message string
            Number  int
        }
        msg.Name = "Lena"
        msg.Message = "hey"
        msg.Number = 123
        // Note that msg.Name becomes "user" in the JSON
        // Will output  :   {"user": "Lena", "Message": "hey", "Number": 123}
        c.JSON(http.StatusOK, msg)
    })

    r.GET("/someXML", func(c *gin.Context) {
        c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })

    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
    })

    // 优雅启动或者关闭
    srv := &http.Server{
        Addr:           ":8080",
        Handler:        r,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20, // 2 的 20次方
    }

    // 协程启动服务
    go func() {
        if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
            log.Printf("listen:%s\n", err)
        }
    }()

    quit := make(chan os.Signal)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("Shutting down server...")

    // 延迟关闭
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown:", err)
    }
    log.Println("Server exiting")
}

Expectations

cannot use binding.Form (variable of type binding.formBinding) as binding.BindingBody value in argument to c.ShouldBindBodyWith: binding.formBinding does not implement binding.BindingBody (missing method BindBody)

Actual result

``` ## Environment - go version:1.19 - gin version (or commit ref):1.8.1 - operating system:windows
Gasoid commented 2 years ago

looks like it is a kind of typo in README. obviously binding.Form doesn't have BindBody method and it doesn't implement BindingBody interface.

c.ShouldBindBodyWith(&objA, binding.Form)
Gasoid commented 2 years ago

My PR: https://github.com/gin-gonic/gin/pull/3312 however I think it is possible to update README and delete this feature for binding.Form