Description

How can i write xml declaration when i return the http response with xml?

How to reproduce

package main

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

func main() {
    g := gin.Default()
    g.GET("/", func(c *gin.Context) {
        data := gin.H{
            "message": "Hello Snowdream Server!",
        }

        c.Negotiate(200, gin.Negotiate{Offered: []string{gin.MIMEXML, gin.MIMEJSON}, Data: data})
    })

    g.Run(":9000")
}

Expectations

<? xml version = "1.0" encoding = "UTF-8">
<map>
<message>Hello Snowdream Server!</message>
</map>

Actual result

<map>
<message>Hello Snowdream Server!</message>
</map>

Environment

  • go version: go version go1.12.5 darwin/amd64
  • gin version (or commit ref): v1.6.3
  • operating system: macOS

Comment From: athe0i

Hi! So basically it might be a bug of a sort, but thing is - in gin render xml.Encoer.Encode is being used directly(https://github.com/gin-gonic/gin/blob/master/render/xml.go#L22), which respectively writes only encoded structure, without header. So there are two things - either create PR to fix render/xml.go (i am just not sure if the current behavior is intended or not) or use middleware to handle this case:

func setupRouter() *gin.Engine {
    r := gin.Default()

    r.GET("/ping/", xmlHeaderMiddleware, pong)

    return r
}

func xmlHeaderMiddleware(c *gin.Context) {
// same function is used inside the c.Negotiate function, so both should return same format
    if c.NegotiateFormat(gin.MIMEXML, gin.MIMEJSON) == gin.MIMEXML {
        c.Writer.Write([]byte(xml.Header))
    }
}

func pong(c *gin.Context) {
    c.Negotiate(200, gin.Negotiate{Offered: []string{gin.MIMEXML, gin.MIMEJSON}, Data: gin.H{"message": "pong"}})
    c.Next()
}

Comment From: snowdream

@athe0i Thank you .

package middlewares

import (
    "encoding/xml"

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

// XMLHeadergo XMLHeadergo
func XMLHeadergo() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.NegotiateFormat(gin.MIMEXML) == gin.MIMEXML {
            c.Writer.Write([]byte(xml.Header))
        }

        // 请求前

        c.Next()

        // 请求后
    }
}

Comment From: realtebo

An example for future googlers, updated to 2023

declare to want to use a middleware

router := gin.Default()

router.Use(XMLHeadergo())

and this is the middleware code

``` func XMLHeadergo() gin.HandlerFunc { return func(c *gin.Context) { if c.NegotiateFormat(gin.MIMEXML) == gin.MIMEXML { c.Writer.Write([]byte(xml.Header)) } c.Next() } }




**Comment From: AlvaroLarumbe**

The previous approaches didn't worked for me because don't allow to return other HTTP status than 200 in the handler, this occurs because `c.Writer.Write()` calls to `WriteHeader(http.StatusOK)` before writing.

So I did this:

```go
// AddXMLDeclarationMiddleware is a Gin middleware that adds the XML declaration to XML responses.
func AddXMLDeclarationMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.NegotiateFormat(gin.MIMEXML) == gin.MIMEXML {
            c.Writer = &xmlWriter{c.Writer}
        }

        c.Next()
    }
}

type xmlWriter struct {
    gin.ResponseWriter
}

// Write writes the XML declaration before writing the response body.
func (w xmlWriter) Write(b []byte) (int, error) {
    return w.ResponseWriter.Write(append([]byte(xml.Header), b...))
}

And now I can return whatever status I want in the handler, for example:

c.XML(http.StatusBadRequest, "whatever error")