···77NOTES:
88- this should run as close to your infrastructure as possible. you then
99 would connect to `ws://localhost:6969/subscribe`
1010+- no cursor support. since there will be multiple jetstream upstreams that
1111+ run at separate cursor timelines, it would be pretty hard to rewrite cursors
1212+ in such a way that everything works. if your application relies on cursors,
1313+ then it's probably best for your application to deal with multi-upstream support.
10141115```
1216go build
+23-2
main.go
···77 "os"
88 "strings"
99 "sync"
1010+ "sync/atomic"
1011 "time"
11121213 "github.com/gorilla/websocket"
···2627type Broadcaster struct {
2728 listeners []chan []byte
2829 mu sync.Mutex
3030+ connected atomic.Bool
2931}
30323133// Subscribe returns a new channel that will receive Jetstream events
···8486 }
85878688 start := time.Now()
8989+ // jetstream instances return the "Welcome to jetstream!" banner on / which
9090+ // should be useful enough for latency
8791 resp, err := client.Get(url)
8892 if err != nil {
8993 return 0, err
···99103 },
100104}
101105106106+// handleHealth returns 200 if connected to upstream
107107+func handleHealth(broadcaster *Broadcaster) http.HandlerFunc {
108108+ return func(w http.ResponseWriter, r *http.Request) {
109109+ if broadcaster.connected.Load() {
110110+ w.WriteHeader(http.StatusOK)
111111+ w.Write([]byte("Welcome to jetstream!"))
112112+ } else {
113113+ w.WriteHeader(http.StatusServiceUnavailable)
114114+ w.Write([]byte("Not connected to upstream"))
115115+ }
116116+ }
117117+}
118118+102119// handleSubscribe upgrades HTTP connection to websocket and streams events
103120func handleSubscribe(broadcaster *Broadcaster) http.HandlerFunc {
104121 return func(w http.ResponseWriter, r *http.Request) {
···130147131148// connectToUpstream maintains a connection to the upstream websocket and broadcasts messages
132149func connectToUpstream(pool []string, broadcaster *Broadcaster) {
133133- backoff := time.Second
134134- maxBackoff := time.Minute
150150+ backoff := 50 * time.Millisecond
151151+ maxBackoff := 20 * time.Second
135152 var currentUpstream string
136153137154 for {
···157174 conn, _, err := websocket.DefaultDialer.Dial(currentUpstream+"/subscribe", nil)
158175 if err != nil {
159176 slog.Error("Failed to connect to upstream", slog.String("url", currentUpstream), slog.Any("error", err))
177177+ broadcaster.connected.Store(false)
160178 time.Sleep(backoff)
161179 backoff *= 2
162180 if backoff > maxBackoff {
···166184 }
167185168186 slog.Info("Connected to upstream", slog.String("url", currentUpstream))
187187+ broadcaster.connected.Store(true)
169188 backoff = time.Second // Reset backoff on successful connection
170189171190 // Read messages from upstream and broadcast them
···173192 messageType, message, err := conn.ReadMessage()
174193 if err != nil {
175194 slog.Error("Error reading from upstream", slog.Any("error", err))
195195+ broadcaster.connected.Store(false)
176196 conn.Close()
177197 break
178198 }
···271291 go connectToUpstream(pool, broadcaster)
272292273293 // Setup HTTP server
294294+ http.HandleFunc("/", handleHealth(broadcaster))
274295 http.HandleFunc("/subscribe", handleSubscribe(broadcaster))
275296276297 slog.Info("Starting proxy server", slog.String("bind", bindAddr))