Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content-Type is forced to text/plain when context.Bind() fails #633

Closed
danslimmon opened this issue Jun 3, 2016 · 6 comments
Closed

Content-Type is forced to text/plain when context.Bind() fails #633

danslimmon opened this issue Jun 3, 2016 · 6 comments
Assignees

Comments

@danslimmon
Copy link

If you call context.Bind() and it fails, context.AbortWithError() gets called, which means headers get sent. Result: you can't send a non-text/plain response later.

Example

package main

import (
    "net/http"

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

func main() {
    router := gin.Default()
    router.POST("/", func(c *gin.Context) {
        reqBody := make(map[string]string)
        err := c.BindJSON(&reqBody)
        if err != nil {
            c.JSON(http.StatusBadRequest, map[string]string{
                "error": err.Error(),
            })
        } else {
            c.JSON(http.StatusOK, map[string]string{
                "error": "No error. Everything is fine.",
            })
        }
    })
    router.Run(":8913")
}

If you send invalid JSON in the request, you should still expect the response to have the header Content-Type: application/json; charset=utf-8, but instead it has Content-Type: text/plain; charset=utf-8.

IMO context.BindJSON() should not call context.AbortWithError(). Instead, the caller should have to handle sending their own error response. If you agree, I'll certainly be happy to write the PR.

@stxml
Copy link

stxml commented Jun 6, 2016

Since gin actually has a special error type for this, ErrorTypeBind, it would be better if you still add the error to the context.

c.Error(err).SetType(ErrorTypeBind)

@stxml
Copy link

stxml commented Jun 7, 2016

Two Issues:

  1. it breaks the test for gin.Bind() Middleware, because that relied on the Abort() that was removed. The Abort() needs to be added to the Middleware.
func Bind(val interface{}) HandlerFunc {
    value := reflect.ValueOf(val)
    if value.Kind() == reflect.Ptr {
        panic(`Bind struct can not be a pointer. Example:
    Use: gin.Bind(Struct{}) instead of gin.Bind(&Struct{})
`)
    }
    typ := value.Type()

    return func(c *Context) {
        obj := reflect.New(typ).Interface()
        if c.Bind(obj) == nil {
            c.Set(BindKey, obj)
        } else {
            c.Abort() // <-- this was added
        }
    }
}
  1. The test TestContextBadAutoBind needs to be changed. Probably just remove the last two tests and test if the len(gin.Errors) has increased

@danslimmon
Copy link
Author

Thank you, will fix

@kenny-house
Copy link

Is this still an issue the project maintainers would like to resolve? At my company we just moved a project away from Gin because of this issue (which was very surprising from a developer perspective).

I don't have time within the next few days, but I could probably get a fix for this together. I presume this isn't being treated as a bug and we'll want to keep the current behavior unless headers or a response are explicitly set?

@tom-drake
Copy link

I came across this problem also and google lead me here. Not sure if this is a hack or a work around but its possible just to do the binding yourself and dealing with the error manually.

if err := binding.JSON.Bind(c.Request, &json); err == nil {
	c.JSON(http.StatusOK, json)
} else {
	c.JSON(http.StatusBadRequest, gin.H {
		"Message": "Could not parse body",
	})
}

It just skips a few helper methods mainly c.BindJSON(obj) which is really just c.BindWith(obj, binding.JSON). It is this BindWith function which calls c.AbortWithError(400, err).SetType(ErrorTypeBind).

Unfortunately there is no other way to enforce the "Content-type" to be anything other than text as c.AbortWithError calls c.Writer.WriteHeaderNow()

@easonlin404
Copy link
Contributor

easonlin404 commented Aug 1, 2017

@tom-drake @kenny-house you can use new c.ShouldBindWithfunction instead of c.Bind. It binds the passed struct pointer using the specified binding and without forced to text/plain when input is not valid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants