Description
Using the function SSEvent
sends back the status code as -1
. Code: https://github.com/gin-gonic/gin/blob/master/context.go#L1060
https://github.com/gin-gonic/gin/blob/caf2802593277033683c4e8cb5f22c81cc35eae8/context.go#L1057-L1065
Is there any reason why this is done? This causes the tracking applications like newrelic to get the statuscode for the request as -1
and also similar case in case of the client app.
How to reproduce
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
g := gin.Default()
g.GET("/hello/:name", func(c *gin.Context) {
c.SSEvent("message", "payload") // this will send status code as -1
})
g.Run(":9000")
}
Expectations
StatusCode is -1
Actual result
StatusCode should be 200, or configurable.
Environment
- go version: 1.16.6
- gin version (or commit ref): 1.6.3
- operating system: Mac OS Big Sur
Comment From: zihengCat
@appleboy @thinkerou
Hi, take a look. Why SSEvent
always send status code -1
?
https://github.com/gin-gonic/gin/blob/c7a28f85320125709e47c592a92421a4d6f192a7/context.go#L1058-L1064
Comment From: ankur-lt
Can we get to know why SSEvent always sends status code -1? @appleboy
Comment From: ankur-lt
Any update here? @appleboy @thinkerou Till then I am using π to have status code as 200.
c.Render(http.StatusOK, sse.Event{
Event: parsedMessage.Title,
Data: parsedMessage.Payload,
})
Comment From: GwynethLlewelyn
A pity this hasn't been addressed yet. I have some issues with the error code -1 β since it's not an Internet standard, as far as I know, some upstream software gets confused, and interprets -1 as some sort of error, not as 'status ok'. Also consider that -1 is a signed integer β some implementations use unsigned integers (thus giving as a result a huge error code), since that's all that is required to conform to the HTTP specifications:
https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 https://datatracker.ietf.org/doc/html/rfc7231#section-6
Comment From: GwynethLlewelyn
Also see the 'official' status codes used by Go: https://go.dev/src/net/http/status.go Each entry also refers to the documentation used to define it.
Note that -1 is conspicuously absent from that list.
Comment From: GwynethLlewelyn
Hm. Taking a look again at the Gin code, the non-official -1
status is also used elsewhere, namely, to do Redirects.
What seems to be happening here is that the -1
is a kludgy way of forcing the c.Render()
code to render the body of the message. This works, because on the body of the Render()
function, http.bodyAllowedForStatus()
is called to check, for a particular HTTP status code, if a body is allowed or not. -1
is obviously not one of the possibilities here, so it will return true
(meaning: yes, this status code allows the body to be rendered). The complication comes from the fact that some status codes arbitrarily imply that a body may be present or not, while others strictly forbid that a body is returned. Why Tim Berners-Lee thought this was a good idea back in 1989, I don't know; but we're stuck with his HTTP semantics, and that's it.
The problem is that, in this case, Render()
will also expose the HTTP headers with a (non-existing) status code of -1
β this is done by the call to c.Status()
method, which is done in any case, no matter what the status code actually is. The HTTP protocol obviously requires that the headers are returned, so that's not an issue; however, it also expects that only valid status codes are provided. The current Gin code does not pre-check if the passed status code is valid or not; it expects users to only use the built-in Go status codes from package http
.
Playing around with non-standard status codes is therefore undefined. RFC 7231 (Section 6) states clearly:
The status-code element is a three-digit integer code giving the result of the attempt to understand and satisfy the request.
HTTP status codes are extensible. HTTP clients are not required to understand the meaning of all registered status codes, though such understanding is obviously desirable. However, a client MUST understand the class of any status code, as indicated by the first digit, and treat an unrecognized status code as being equivalent to the x00 status code of that class [...]
(bold emphasis mine).
In other words, -1
is not a valid code under the HTTP protocol definition, and cannot be 'turned' into a valid code, since HTTP status codes are always three-digit (unsigned) integers.
And this, in turn, is what will make middleware such as New Relic get utterly confused (I use it too, although I came upon this issue on my own middleware).
I understand why the Gin developers opted for that kludge: it allows them to make sure that a request body is always returned, no matter what the error code, since I guess that's what is expected to happen on the client side: SSE errors are supposed to be inside the JSON wrapper, not on the headers.
However, the same would be accomplished by using a non-standard 2xx
code, which would be acceptable under the HTTP protocol, it would produce a body to be sent back as desired, and it would not be flagged as a violation by middleware such as New Relic or others.
I'd suspect that there are good reasons for not having chosen http.StatusOK
or a new code in the 2xx
class (say, 299
) for SSE requests, but I don't understand why not. I'm not quite sure how exactly to 'fix' this.
Comment From: idc77
You need to wrap it in context.Stream()
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
g := gin.Default()
ch := make(chan string)
g.GET("/hello/:name", func(c *gin.Context) {
c.Stream(func(w io.Writer) bool) {
if msg, ok := <-ch; ok {
c.SSEvent("message", msg)
return true // keep connection open
}
return false // close connection
}
})
g.Run(":9000")
}
That said, the whole SSE thing in gin is broken, or I don't know better.
Comment From: TobiasGrether
Currently facing this issue, are there any updates on this? SSE doesn't seem to be working well for me either.
Comment From: ankur-lt
Any update here? @appleboy @thinkerou Till then I am using π to have status code as 200.
c.Render(http.StatusOK, sse.Event{ Event: parsedMessage.Title, Data: parsedMessage.Payload, })
@TobiasGrether , you can use the above mentioned code to get the statusCode as 200.