-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.go
More file actions
138 lines (121 loc) · 4.35 KB
/
server.go
File metadata and controls
138 lines (121 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// Server handles HTTP requests for metadata and data files.
type Server struct {
cfg Config
cache *NetworkCache
client *http.Client
}
// newServer creates a Server with the given configuration, cache, and HTTP client.
func newServer(cfg Config, cache *NetworkCache, client *http.Client) *Server {
return &Server{cfg: cfg, cache: cache, client: client}
}
// routes registers HTTP handlers on a new ServeMux and returns it.
// The data route uses Go 1.22+ wildcard patterns. The metadata route
// is handled by the root handler because Go's ServeMux wildcards must be
// entire path segments, and /network-state-{name}.json embeds the variable
// within a segment. The root handler checks the path prefix and dispatches.
func (s *Server) routes() *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("GET /ready", s.handleReady)
mux.HandleFunc("GET /data/{network}/{rest...}", s.handleDataFile)
// The metadata pattern /network-state-{name}.json cannot use ServeMux
// wildcards. Register at the root and filter by path prefix in the handler.
// ServeMux longest-match guarantees /data/ routes are tried first.
mux.HandleFunc("/", s.handleRoot)
return mux
}
// handleReady returns 200 when all networks have completed initial download,
// 503 otherwise. Used as a Kubernetes readiness probe.
func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
if !s.cache.Ready() {
http.Error(w, "not ready", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
io.WriteString(w, "ok\n")
}
// handleRoot dispatches metadata requests matching /network-state-*.json
// and returns 404 for everything else.
func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/network-state-") && strings.HasSuffix(r.URL.Path, ".json") {
s.handleMetadata(w, r)
return
}
http.NotFound(w, r)
}
// handleMetadata serves network metadata JSON with fb_url_v1 rewritten to point
// at this cache service. If the cache has no entry for the network (data file
// not yet downloaded), the request is proxied to the upstream service unchanged.
func (s *Server) handleMetadata(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.Header().Set("Allow", "GET")
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Extract network name from /network-state-{name}.json
path := r.URL.Path
name := strings.TrimPrefix(path, "/network-state-")
if !strings.HasSuffix(name, ".json") {
http.NotFound(w, r)
return
}
network := strings.TrimSuffix(name, ".json")
if network == "" || strings.Contains(network, "/") {
http.NotFound(w, r)
return
}
entry := s.cache.Get(network)
if entry == nil {
// SERV-03: data file not ready -- proxy upstream unchanged
s.proxyUpstream(w, r, network)
return
}
// SERV-01: rewrite fb_url_v1 to point at our cache
rewritten := *entry.State
rewritten.Assignment.FBURL = fmt.Sprintf("http://%s/data/%s/%s.fb.1.gz",
r.Host, network, entry.State.Assignment.ID)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(&rewritten)
}
// handleDataFile streams the cached data file from disk using http.ServeFile.
// Returns 404 if no cache entry exists for the network.
func (s *Server) handleDataFile(w http.ResponseWriter, r *http.Request) {
network := r.PathValue("network")
entry := s.cache.Get(network)
if entry == nil || entry.FilePath == "" {
http.NotFound(w, r)
return
}
http.ServeFile(w, r, entry.FilePath)
}
// proxyUpstream forwards the metadata request to the upstream service unchanged.
// Used when the local cache does not yet have a data file for the network.
func (s *Server) proxyUpstream(w http.ResponseWriter, r *http.Request, network string) {
url := fmt.Sprintf("%s/network-state-%s.json", s.cfg.BaseURL, network)
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, url, nil)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
resp, err := s.client.Do(req)
if err != nil {
http.Error(w, "upstream unavailable", http.StatusBadGateway)
return
}
defer resp.Body.Close()
// Copy response headers from upstream to client
for k, vv := range resp.Header {
for _, v := range vv {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
}