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.97k stars 8.02k forks source link

Middleware ignores custom set response status code #3613

Open SuperNove02 opened 1 year ago

SuperNove02 commented 1 year ago

Description

I have a Middleware which adds the xml.Header string to every response body. As you can see in the Code Example provided this works flawlessly when the StatusCode is set to be 200. If the status Code set to something else the middleware still returns 200 in the response status. The expected behaviour should be the Status code of the one set by c.XML.

I nailed down the Problem to the function:

func (w *responseWriter) WriteHeader(code int) {
    if code > 0 && w.status != code {
        if w.Written() {
            debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
            return
        }
        w.status = code
    }
}

I want to override the response header. I accedently set the ResponseHeader when writing to the response body befor calling c.Next(). Currently I don't see another solution to prepend the xml.Header-string to the body than my solution. But the behaviour of the response status code is an unexpected side behaviour I am trying to solve.

How to reproduce

Playground Link

package main

import (
    "encoding/xml"
    "github.com/gin-gonic/gin"
    "log"
    "net/http"
)

func Pong(c *gin.Context) {
    c.XML(http.StatusOK, gin.H{"message": "ping"})
}

func PingBadRequest(c *gin.Context) {
    c.XML(http.StatusBadRequest, gin.H{"message": "some error"})
}

func CustomMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // add xml.Header in front of response body
        if c.NegotiateFormat(gin.MIMEXML) == gin.MIMEXML {
            if _, err := c.Writer.Write([]byte(xml.Header)); err != nil {
                log.Fatalln("Failed: " + err.Error())
            }
        }
        c.Next()
    }
}

func main() {

    r := gin.Default()
    r.Use(CustomMiddleware())
    // Route which always returns 200
    r.GET("/pong", Pong)
    // Route which always returns 400
    r.GET("/ping", PingBadRequest)
    r.Run("localhost:8081")

}

Expectations

$ curl -i localhost:8081/ping
HTTP/1.1 400 OK
Date: Wed, 24 May 2023 09:08:30 GMT
Content-Length: 79
Content-Type: text/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<map><message>some error</message></map>

Actual result

$ curl -i localhost:8081/ping
HTTP/1.1 200 OK
Date: Wed, 24 May 2023 09:08:30 GMT
Content-Length: 79
Content-Type: text/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<map><message>some error</message></map>

Environment

bestgopher commented 1 year ago

Beacuse the Write will call w.WriteHeaderNow(), and will write the default status code 200. Then w.Written will return true, and not overwrite the http code.