maybe related #633
Description
invalid content type
How to reproduce
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type obj struct {
Message string
}
func main() {
r := gin.New()
r.POST("/", func(c *gin.Context) {
body := obj{}
err := c.BindJSON(&body)
if err != nil {
c.JSON(http.StatusBadRequest, obj{"bad message"})
return
}
c.JSON(http.StatusOK, obj{"ok"})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
Expectations
I received expected Content-Type
header (application/json) with valid json body {}
curl -vvv -d '{}' -H "Content-Type: application/json" -X POST http://localhost:8080/
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1:8080...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 2
>
* upload completely sent off: 2 out of 2 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Date: Thu, 12 Mar 2020 18:50:55 GMT
< Content-Length: 17
<
{"Message":"ok"}
* Connection #0 to host localhost left intact
Actual result
I received invalid Content-Type
header with invalid post body {
$ curl -vvv -d '{' -H "Content-Type: application/json" -X POST http://localhost:8080/
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1:8080...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 1
>
* upload completely sent off: 1 out of 1 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Date: Thu, 12 Mar 2020 18:46:09 GMT
< Content-Length: 26
< Content-Type: text/plain; charset=utf-8
<
{"Message":"bad message"}
* Connection #0 to host localhost left intact
Environment
- go version:
go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/me/.cache/go-build"
GOENV="/home/me/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/me/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/tmp/issue/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build332976201=/tmp/go-build -gno-record-gcc-switches"
- gin version (or commit ref):
1.5.0
- operating system: linux
Comment From: chyroc
please use ShouldBindWith
.
BindJSON
well call ShouldBindWith
and AbortWithError
(when err occur) and set status-code and header
code:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
type obj struct {
Message string
}
func main() {
r := gin.New()
r.POST("/", func(c *gin.Context) {
body := obj{}
err := c.ShouldBindJSON(&body)
if err != nil {
c.JSON(http.StatusOK, obj{"bad message"})
return
}
c.JSON(http.StatusOK, obj{"ok"})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
Comment From: celogeek
Why do we write the header here?
func (c *Context) AbortWithStatus(code int) {
c.Status(code)
c.Writer.WriteHeaderNow()
c.Abort()
}
The issue here is the c.Writer.WriteHeaderNow()
For example if we have a middleware that handler errors, then we want to do a AbortWithStatusError, and the handler format the error in json format.
But because of the
c.Writer.WriteHeaderNow()
We need to write the "application/json" header before we call AbortWithStatusError.
So calling c.JSON in the middleware won't set the header properly. it will keep the "plain/text" as it seems to be the default.