aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/api/api0/client.go21
-rw-r--r--pkg/api/api0/serverlist.go55
2 files changed, 75 insertions, 1 deletions
diff --git a/pkg/api/api0/client.go b/pkg/api/api0/client.go
index d3097b1..5a8aaf6 100644
--- a/pkg/api/api0/client.go
+++ b/pkg/api/api0/client.go
@@ -7,6 +7,7 @@ import (
"net/http"
"net/netip"
"strconv"
+ "strings"
"time"
"github.com/pg9182/atlas/pkg/origin"
@@ -455,7 +456,25 @@ func (h *Handler) handleClientServers(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
- respMaybeCompress(w, r, http.StatusOK, h.ServerList.csGetJSON())
+
+ buf := h.ServerList.csGetJSON()
+ for _, e := range strings.Split(r.Header.Get("Accept-Encoding"), ",") {
+ if t, _, _ := strings.Cut(e, ";"); strings.TrimSpace(t) == "gzip" {
+ if zbuf, ok := h.ServerList.csGetJSONGzip(); ok {
+ buf = zbuf
+ w.Header().Set("Content-Encoding", "gzip")
+ } else {
+ hlog.FromRequest(r).Error().Msg("failed to gzip server list")
+ }
+ break
+ }
+ }
+
+ w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
+ w.WriteHeader(http.StatusOK)
+ if r.Method != http.MethodHead {
+ w.Write(buf)
+ }
}
/*
diff --git a/pkg/api/api0/serverlist.go b/pkg/api/api0/serverlist.go
index de48ae0..9c9d04e 100644
--- a/pkg/api/api0/serverlist.go
+++ b/pkg/api/api0/serverlist.go
@@ -2,6 +2,7 @@ package api0
import (
"bytes"
+ "compress/gzip"
"errors"
"fmt"
"net/netip"
@@ -35,6 +36,12 @@ type ServerList struct {
csUpdateWg sync.WaitGroup // allows other goroutines to wait for that update to complete
csBytes atomic.Pointer[[]byte] // contents of buffer must not be modified; only swapped
+ // /client/servers gzipped json
+ csgzUpdate atomic.Pointer[*byte] // pointer to the first byte of the last known json (works because it must be swapped, not modified)
+ csgzUpdateMu sync.Mutex // ensures only one update runs at a time
+ csgzUpdateWg sync.WaitGroup // allows other goroutines to wait for that update to complete
+ csgzBytes atomic.Pointer[[]byte] // gzipped
+
// for unit tests
__clock func() time.Time
}
@@ -239,6 +246,54 @@ func (s *ServerList) csGetJSON() []byte {
return b.Bytes()
}
+// csGetJSONGzip is like csGetJSON, but returns it gzipped with true, or false
+// if an error occurs.
+func (s *ServerList) csGetJSONGzip() ([]byte, bool) {
+ buf := s.csGetJSON()
+ if len(buf) == 0 {
+ return nil, false
+ }
+ cur := &buf[0]
+
+ last := s.csgzUpdate.Load()
+ if last != nil && *last == cur {
+ if zbuf := s.csgzBytes.Load(); zbuf != nil && *zbuf != nil {
+ return *zbuf, true
+ }
+ }
+
+ if !s.csgzUpdateMu.TryLock() {
+ s.csgzUpdateWg.Wait()
+ if zbuf := s.csgzBytes.Load(); zbuf != nil && *zbuf != nil {
+ return *zbuf, true
+ }
+ return nil, false
+ } else {
+ defer s.csgzUpdateMu.Unlock()
+ s.csgzUpdateWg.Add(1)
+ defer s.csgzUpdateWg.Done()
+ }
+
+ var b bytes.Buffer
+ zw := gzip.NewWriter(&b)
+ if _, err := zw.Write(buf); err != nil {
+ s.csgzBytes.Store(nil)
+ s.csgzUpdate.Store(&cur)
+ return nil, false
+ }
+ if err := zw.Close(); err != nil {
+ s.csgzBytes.Store(nil)
+ s.csgzUpdate.Store(&cur)
+ return nil, false
+ }
+
+ zbuf := b.Bytes()
+ s.csgzBytes.Store(&zbuf)
+ s.csgzUpdate.Store(&cur)
+
+ return zbuf, true
+}
+
// csUpdateNextUpdateTime updates the next update time for the cached
// /client/servers response. It must be called after any time updates while
// holding a write lock on s.mu.