Skip to content

Commit

Permalink
Add endpoint for sse server-sent events
Browse files Browse the repository at this point in the history
  • Loading branch information
tsaarni committed Feb 19, 2025
1 parent f6017d1 commit 7f2a6d0
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 1 deletion.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,36 @@ Set-Cookie: hello=world

</details>


<details>
<summary><code>/sse</code> Server-Sent Events (SSE) endpoint that sends a message every second.</summary>
</details>

#### Responses

| Status | Description |
| ------ | ---------------------- |
| 200 OK | Server is operational. |


#### Example

Server will respond with `Content-Type: text/event-stream` with the following content:

```http
HTTP/1.1 200 OK
Cache-Control: no-cache
Connection: keep-alive
Content-Type: text/event-stream
Date: Wed, 19 Feb 2025 10:10:15 GMT
Transfer-Encoding: chunked
data: { "counter": "1", "timestamp": "2025-02-19T12:10:15+02:00" }
data: { "counter": "2", "timestamp": "2025-02-19T12:10:16+02:00" }
...
```

<details>
<summary><code>/apps/</code> Returns a list of available applications.</summary>
</details>
Expand Down Expand Up @@ -245,7 +275,7 @@ Example commands in the descriptions are given using the
To build and run the echoserver locally, use the following commands:

```sh
make build
make
./echoserver
```

Expand Down
37 changes: 37 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.statusHandler(w, r)
case strings.HasPrefix(r.URL.Path, "/apps/"):
h.templateHandler(w, r)
case r.URL.Path == "/sse":
h.serverSentEventHandler(w, r)
default:
h.echoHandler(w, r)
}
Expand Down Expand Up @@ -303,3 +305,38 @@ func (h *Handler) templateHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Error executing template", http.StatusInternalServerError)
}
}

// serverSentEventHandler implements a long polling server that periodically sends "ping" messages.
func (h *Handler) serverSentEventHandler(w http.ResponseWriter, r *http.Request) {
slog.Info("Handling stream request", "url", r.URL.String())
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()

ctx := r.Context()
counter := 0

for {
select {
case <-ticker.C:
timestamp := time.Now().Format(time.RFC3339)
counter++
message := fmt.Sprintf("data: { \"timestamp\": \"%s\", \"counter\": \"%d\" }\n\n", timestamp, counter)
_, err := w.Write([]byte(message))
if err != nil {
slog.Error("Error writing to stream", "error", err)
return
}

if f, ok := w.(http.Flusher); ok {
f.Flush()
}
case <-ctx.Done():
slog.Info("Client disconnected")
return
}
}
}

0 comments on commit 7f2a6d0

Please sign in to comment.