echo
echo copied to clipboard
Add SSE function in Context.Response
Add SSE function in Context.Response
New feature discussion
I am building a project that needs to provide an SSE (server-sent event) function to the client. Currently I have to build the struct that meets SSE standard manually. Is there any plan to add SSE function to Context.Response so that we do not need to build the response struct that SSE required manually.
Do you mean something like these Gin examples are:
- https://github.com/gin-gonic/examples/blob/master/server-sent-event/main.go
- https://dev.to/mirzaakhena/server-sent-events-sse-server-implementation-with-go-4ck2
- https://github.com/lmas/gin-sse/blob/master/sse_handler.go
"SSE server" would probably fit better as separate library.
p.s. to be honest this does not seems much different (conceptually) fro the server you would need when dealing with Websockets. I am saying this because I do not have experience with SSE but I have done application that streams real-time updates for graphs over Websockets.
hi @aldas ,
Thank you for your response. You are right, Gin has this function already.
The reason why I choose SSE other than web socket is that I am trying to build a chatgpt-like function, which could send the response back to the frontend word by word as a one-way connection. Both Chatgpt and Llama are using SSE to send the response. And my frontend code is currently working with SSE. So if the backend could work in the same, that would be perfect.
So I manually built the response to meet SSE format requirement.
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
func buildSeverSentEvent(event, context string) string {
var result string
if len(event) != 0 {
result = result + "event: " + event + "\n"
}
if len(context) != 0 {
result = result + "data: " + context + "\n"
}
result = result + "\n"
return result
}
As LLM is becoming more popular and more and more similar web services will adopt the same pattern to send the response, it would be better to add SSE as a formal function to Echo, which could enlarge Echo's scope and help developers to reduce manual work.
Is SSE supported now?
+1 for SSE support
+1 for SSE support
+1 for SSE support
+1 for SSE support
+1 for SSE support
+1 for SSE support
Hi,
Could people here specify in which situation you would like to use SSE?
For example if we are talking about broadcasting SSE messages to all connected clients - For that there exists https://github.com/r3labs/sse library
See this example:
main.go
package main
import (
"errors"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/r3labs/sse/v2"
"log"
"net/http"
"time"
)
func main() {
e := echo.New()
server := sse.New() // create SSE broadcaster server
server.AutoReplay = false // do not replay messages for each new subscriber that connects
_ = server.CreateStream("ping") // EventSource in "index.html" connecting to stream named "ping"
go func(s *sse.Server) {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.Publish("ping", &sse.Event{
Data: []byte("ping: " + time.Now().Format(time.RFC3339Nano)),
})
}
}
}(server)
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.File("/", "./index.html")
//e.GET("/sse", echo.WrapHandler(server))
e.GET("/sse", func(c echo.Context) error { // longer variant
log.Printf("The client is connected: %v\n", c.RealIP())
go func() {
<-c.Request().Context().Done() // Received Browser Disconnection
log.Printf("The client is disconnected: %v\n", c.RealIP())
return
}()
server.ServeHTTP(c.Response(), c.Request())
return nil
})
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}
index.html (in same folder)
<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>
<script>
// Example taken from: https://www.w3schools.com/html/html5_serversentevents.asp
if (typeof (EventSource) !== "undefined") {
const source = new EventSource("/sse?stream=ping");
source.onmessage = function (event) {
document.getElementById("result").innerHTML += event.data + "<br>";
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>
</body>
</html>
If you do not need broadcasting you can just create Event structure and WriteTo method for it
// Event structure is defined here: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format
type Event struct {
ID []byte
Data []byte
Event []byte
Retry []byte
Comment []byte
}
func (ev *Event) WriteTo(w http.ResponseWriter) error {
// Marshalling part is taken from: https://github.com/r3labs/sse/blob/c6d5381ee3ca63828b321c16baa008fd6c0b4564/http.go#L16
if len(ev.Data) == 0 && len(ev.Comment) == 0 {
return nil
}
if len(ev.Data) > 0 {
if _, err := fmt.Fprintf(w, "id: %s\n", ev.ID); err != nil {
return err
}
sd := bytes.Split(ev.Data, []byte("\n"))
for i := range sd {
if _, err := fmt.Fprintf(w, "data: %s\n", sd[i]); err != nil {
return err
}
}
if len(ev.Event) > 0 {
if _, err := fmt.Fprintf(w, "event: %s\n", ev.Event); err != nil {
return err
}
}
if len(ev.Retry) > 0 {
if _, err := fmt.Fprintf(w, "retry: %s\n", ev.Retry); err != nil {
return err
}
}
}
if len(ev.Comment) > 0 {
if _, err := fmt.Fprintf(w, ": %s\n", ev.Comment); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, "\n"); err != nil {
return err
}
return nil
}
and this is Echo part for SSE handler
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.File("/", "./index.html")
e.GET("/sse", func(c echo.Context) error {
log.Printf("SSE client connected, ip: %v", c.RealIP())
w := c.Response()
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-c.Request().Context().Done():
log.Printf("SSE client disconnected, ip: %v", c.RealIP())
return nil
case <-ticker.C:
event := Event{
Data: []byte("ping: " + time.Now().Format(time.RFC3339Nano)),
}
if err := event.WriteTo(w); err != nil {
return err
}
w.Flush()
}
}
})
if err := e.Start(":8080"); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}
and index.html for testing the example:
<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>
<script>
// Example taken from: https://www.w3schools.com/html/html5_serversentevents.asp
if (typeof (EventSource) !== "undefined") {
const source = new EventSource("/sse");
source.onmessage = function (event) {
document.getElementById("result").innerHTML += event.data + "<br>";
};
} else {
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
</script>
</body>
</html>
Thank you so much for your demo code.