'Change Content-Type header in Gin middleware

I have a custom gin middleware set up to handle errors but however, it does change to the Content-Type header.

package middleware

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

func validationErrorToText(e validator.FieldError) string {
    switch e.Tag() {
    case "required":
        return fmt.Sprintf("%s is required", e.Field())
    case "max":
        return fmt.Sprintf("%s cannot be longer than %s", e.Field(), e.Param())
    case "min":
        return fmt.Sprintf("%s must be longer than %s", e.Field(), e.Param())
    case "email":
        return fmt.Sprintf("Invalid email format")
    case "len":
        return fmt.Sprintf("%s must be %s characters long", e.Field(), e.Param())
    }
    return fmt.Sprintf("%s is not valid", e.Field())
}

func Errors() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        // Only run if there are some errors to handle
        if len(c.Errors) > 0 {
            for _, e := range c.Errors {
                // Find out what type of error it is
                switch e.Type {
                case gin.ErrorTypePublic:
                    // Only output public errors if nothing has been written yet
                    if !c.Writer.Written() {
                        c.JSON(c.Writer.Status(), gin.H{"error": e.Error()})
                    }
                case gin.ErrorTypeBind:
                    errs := e.Err.(validator.ValidationErrors)
                    list := make(map[int]string)

                    for field, err := range errs {
                        list[field] = validationErrorToText(err)
                    }
                    // Make sure we maintain the preset response status
                    status := http.StatusBadRequest
                    if c.Writer.Status() != http.StatusOK {
                        status = c.Writer.Status()
                    }
                    c.Header("Content-Type", "application/json")
                    c.JSON(status, gin.H{
                        "status": "error",
                        "errors": list,
                    })

                default:
                    c.JSON(http.StatusBadRequest, gin.H{"errors": c.Errors.JSON()})
                }
            }
        }
    }
}

I get a text/plain; charset=utf-8 in the response Content-Type header instead.



Solution 1:[1]

Put this code c.Header("Content-Type", "application/json") as the 1st line in the function body func(c *gin.Context) {...}

The problem is this package gin changes the header somewhere internally even before you call the c.JSON function. That's the issue.

Solution 2:[2]

The problem is that calling methods like c.AbortWithStatus (or c.BindJSON) leads to actually writing headers, since under the hood they invoke c.Writer.WriteHeaderNow(). You cannot overwrite the header afterwards.

The solution is pretty simple:

  • do not use c.BindJSON, call c.ShouldBindJSON and then handle binding errors manually
  • do not use c.AbortWithError, call c.Status(http.StatusBadRequest), c.Error(err), c.Abort() (in any order)

You can create a wrapper just for that:

func BindJSON(c *gin.Context, obj interface{}) error {
    if err := c.ShouldBindJSON(obj); err != nil {
        c.Status(http.StatusBadRequest)
        c.Error(err)
        c.Abort()
        return err
    }
    return nil
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dharman
Solution 2 Iskander Sitdikov