examples
examples copied to clipboard
Content negotiate custom mediatype
As with #41, I'm trying to have Gin negotiate a custom mediatype. From the client, I'm sending the following request header:
Accept: application/problem+json, application/json
And on the server, I'm trying to negotiate all errors (with middleware) as such:
problem := Problem{
Detail: errorText,
Status: status,
Title: http.StatusText(status),
}
c.Negotiate(status, gin.Negotiate{
Offered: []string{"application/problem+json", gin.MIMEJSON, gin.MIMEHTML},
HTMLName: "error",
HTMLData: &problem,
JSONData: &problem,
})
However, somewhere before I'm able to handle the error, Gin intercepts and for some reason decides that the requested Accept
header can't be satisfied, writes the following to the log, and responds with 406 Not Acceptable
:
Error #01: the accepted formats are not offered by the server
I would love to see a full example of how content negotiation a custom mediatype in Gin works. Would you be able to contribute your working code @jarrodhroberson?
It's weird, because if I do c.NegotiateFormat("application/problem+json", gin.MIMEJSON, gin.MIMEHTML)
, I get application/problem+json
in return as expected. However, c.Negotiate()
somehow comes to another conclusion and responds with 406 Not Acceptable
.
Ok, after digging into the Gin source code, I think I understand what's going on. In context.go:1110-1135
, Negotiate()
only supports MIMEJSON
, MIMEHTML
, MIMEXML
, MIMEYAML
and MIMETOML
:
func (c *Context) Negotiate(code int, config Negotiate) {
switch c.NegotiateFormat(config.Offered...) {
case binding.MIMEJSON:
data := chooseData(config.JSONData, config.Data)
c.JSON(code, data)
case binding.MIMEHTML:
data := chooseData(config.HTMLData, config.Data)
c.HTML(code, config.HTMLName, data)
case binding.MIMEXML:
data := chooseData(config.XMLData, config.Data)
c.XML(code, data)
case binding.MIMEYAML:
data := chooseData(config.YAMLData, config.Data)
c.YAML(code, data)
case binding.MIMETOML:
data := chooseData(config.TOMLData, config.Data)
c.TOML(code, data)
default:
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck
}
}
Which of course makes some sense. It would be nice if +json
and +xml
was translated into MIMEJSON
and MIMEXL
respectively, but knowing this, I should be able to circumvent it somehow.
Hm, no. I seem unable to properly set the Content-Type
of the response to … anything, really. For some weird reason Gin responds with:
Content-Type: text/plain; charset=utf-8
Even though I've explicitly set c.Header("Content-Type", "application/problem+json")
. This is my handler code now:
problem := Problem{
Detail: errorText,
Status: status,
Title: http.StatusText(status),
}
allMimeTypes := []string{"application/problem+json", gin.MIMEJSON, gin.MIMEHTML)
negotiatedMimeType := c.NegotiateFormat(allMimeTypes...)
switch negotiatedMimeType {
case gin.MIMEHTML:
c.HTML(status, "error", &problem)
default:
c.JSON(status, &problem)
}
c.Header("Content-Type", negotiatedMimeType)
c.Abort()
Why doesn't c.Header("Content-Type", negotiatedMimeType)
work here?
As you closed #41, were you able to set the Content-Type
of the response @jarrodhroberson? If so, could you please post a full example of how you got it to work?
my solution is in #41 and the example I posted works when combined with the solution I provided when I closed that issue.
On Fri, May 5, 2023 at 5:14 AM Asbjørn Ulsberg @.***> wrote:
As you closed #41 https://github.com/gin-gonic/examples/issues/41, were you able to set the Content-Type of the response @jarrodhroberson https://github.com/jarrodhroberson? If so, could you please post a full example of how you got it to work?
— Reply to this email directly, view it on GitHub https://github.com/gin-gonic/examples/issues/106#issuecomment-1535966532, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABF773MRIAP5IBNC6KZQELXETAF5ANCNFSM6AAAAAAXSDPBRU . You are receiving this because you were mentioned.Message ID: @.***>
-- Jarrod Roberson 678.551.2852
@jarrodhroberson, so with the following, you're able to have Gin respond with Content-Type: application/vnd.health.json;version=1.0.0
?
func Health(c *gin.Context) {
startupTime := c.MustGet("startupTime").(time.Time)
status := models.NewHealth(startupTime)
c.Negotiate(http.StatusOK, gin.Negotiate{
Offered: []string{"application/vnd.health.json;version=1.0.0", gin.MIMEJSON, gin.MIMEYAML, gin.MIMEXML, gin.MIMEHTML},
HTMLName: "",
HTMLData: status,
JSONData: status,
XMLData: status,
YAMLData: status,
Data: status,
})
}
If you read https://github.com/gin-gonic/examples/issues/106#issuecomment-1530323693, I can't see how that actually works, because c.Negotiate()
only supports MIMEJSON
, MIMEHTML
, MIMEXML
, MIMEYAML
and MIMETOML
. Any other MIME type and it will do c.AbortWithError()
.