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")