unify admin api

Kim committed Oct 21, 2025 at 14:07 UTC 8c015ba93e165b200ac72d114ad484fa920f2ce7
7 files changed +193 -278
Dockerfile
+1 -1
@@ -17,6 +17,6 @@ FROM gcr.io/distroless/static-debian12:nonroot
17
18 COPY --from=builder /out/relayserver /usr/bin/relayserver
19
20 -EXPOSE 8080 8082 4001/tcp 4001/udp
20 +EXPOSE 8080 4001/tcp 4001/udp
21
22 ENTRYPOINT ["/usr/bin/relayserver"]
README.md
+2 -4
@@ -46,8 +46,7 @@ docker compose logs -f relayserver
46 ```
47
48 Published ports:
49 -- Admin HTTP: `8080`
50 -- HTTP ingress (tcp-level): `8082`
49 +- Unified Admin UI + HTTP proxy: `8080`
50 - libp2p TCP/QUIC: `4001/tcp`, `4001/udp`
51
52 To add bootstraps, edit `docker-compose.yml` and append repeated `--bootstrap` flags under `relayserver.command`.
@@ -110,8 +109,7 @@ func main() {
109 ## Configuration Reference
110
111 Server flags (see `docker-compose.yml`):
113 -- `--admin-http` Admin API listen address (default `:8080`)
114 -- `--ingress-http` HTTP ingress listen address (default `:8082`)
112 +- `--http` Unified admin UI + HTTP proxy listen address (default `:8080`)
113 - `--bootstrap` Repeatable multiaddr with `/p2p/`
114
115 Example client flags (see `make client-run`):
cmd/example_client/main.go
+27 -8
@@ -2,7 +2,9 @@ package main
2
3 import (
4 "context"
5 + "crypto/rand"
6 "fmt"
7 + "net"
8 "net/http"
9 "os"
10 "os/signal"
@@ -22,16 +24,18 @@ var rootCmd = &cobra.Command{
24 }
25
26 var (
25 - flagServerURL string
26 - flagBootstraps []string
27 - flagBackendHTTP string
27 + flagServerURL string
28 + flagBootstraps []string
29 + flagAddr string
30 + flagClientName string
31 )
32
33 func init() {
34 flags := rootCmd.PersistentFlags()
35 flags.StringVar(&flagServerURL, "server-url", "http://localhost:8080", "relayserver admin base URL to auto-fetch multiaddrs from /health")
36 flags.StringSliceVar(&flagBootstraps, "bootstrap", nil, "multiaddrs with /p2p/ (supports /dnsaddr/ that resolves to /p2p/)")
34 - flags.StringVar(&flagBackendHTTP, "backend-http", ":8081", "local backend HTTP listen address")
37 + flags.StringVar(&flagAddr, "addr", ":8081", "local backend HTTP listen address")
38 + flags.StringVar(&flagClientName, "name", "", "backend display name shown on server UI")
39 }
40
41 func main() {
@@ -44,7 +48,21 @@ func runClient(cmd *cobra.Command, args []string) error {
48 ctx, cancel := context.WithCancel(context.Background())
49 defer cancel()
50
51 + if flagClientName == "" {
52 + hn, err := os.Hostname()
53 + if err != nil {
54 + hn = "unknown-" + rand.Text()
55 + }
56 + flagClientName = hn
57 + }
58 +
59 // 1) HTTP backend
60 + ln, err := net.Listen("tcp", flagAddr)
61 + if err != nil {
62 + log.Fatal().Err(err).Msg("failed to listen")
63 + }
64 + log.Info().Msgf("[client] local backend http listening on %s", ln.Addr().String())
65 +
66 go func() {
67 mux := http.NewServeMux()
68 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
@@ -55,7 +73,7 @@ func runClient(cmd *cobra.Command, args []string) error {
73 }{
74 Now: time.Now().Format(time.RFC1123),
75 Host: r.Host,
58 - Addr: flagBackendHTTP,
76 + Addr: flagAddr,
77 }
78 _ = pageTmpl.Execute(w, data)
79 })
@@ -64,8 +82,8 @@ func runClient(cmd *cobra.Command, args []string) error {
82 _, _ = w.Write([]byte("ok"))
83 })
84
67 - log.Info().Msgf("[client] local backend http %s", flagBackendHTTP)
68 - if err := http.ListenAndServe(flagBackendHTTP, mux); err != nil {
85 + log.Info().Msgf("[client] local backend http %s", flagAddr)
86 + if err := http.Serve(ln, mux); err != nil {
87 log.Error().Err(err).Msg("[client] http backend error")
88 cancel()
89 }
@@ -76,7 +94,8 @@ func runClient(cmd *cobra.Command, args []string) error {
94 Protocol: "/relaydns/http/1.0",
95 Topic: "relaydns.backends",
96 AdvertiseEvery: 3 * time.Second,
79 - TargetTCP: addrToTarget(flagBackendHTTP),
97 + Name: flagClientName,
98 + TargetTCP: addrToTarget(flagAddr),
99
100 ServerURL: flagServerURL,
101 Bootstraps: flagBootstraps,
cmd/server/main.go
+147 -20
@@ -2,8 +2,13 @@ package main
2
3 import (
4 "context"
5 + "encoding/json"
6 + "fmt"
7 + "html/template"
8 + "net/http"
9 "os"
10 "os/signal"
11 + "strings"
12 "syscall"
13 "time"
14
@@ -21,16 +26,14 @@ var rootCmd = &cobra.Command{
26 var (
27 flagBootstraps []string
28
24 - ingressHTTP string // e.g. :8082 (HTTP ingress, Browser)
25 - adminHTTP string // e.g. :8080 (admin API)
29 + httpAddr string // unified admin + HTTP proxy (e.g. :8080)
30 )
31
32 func init() {
33 flags := rootCmd.PersistentFlags()
34 flags.StringSliceVar(&flagBootstraps, "bootstrap", nil, "multiaddrs with /p2p/ (supports /dnsaddr/ that resolves to /p2p/)")
35
32 - flags.StringVar(&ingressHTTP, "ingress-http", ":8082", "HTTP ingress (browser-friendly TCP port for HTTP backends)")
33 - flags.StringVar(&adminHTTP, "admin-http", ":8080", "Admin HTTP API (status/control)")
36 + flags.StringVar(&httpAddr, "http", ":8080", "Unified admin UI and HTTP proxy listen address")
37 }
38
39 func main() {
@@ -55,26 +58,86 @@ func runServer(cmd *cobra.Command, args []string) error {
58 return err
59 }
60
58 - // 1) admin API
61 + // Admin UI + per-peer HTTP proxy served here
62 go func() {
60 - if adminHTTP == "" {
63 + if httpAddr == "" {
64 return
65 }
63 - log.Info().Msgf("[server] admin http: %s", adminHTTP)
64 - if err := d.ServeHTTP(adminHTTP); err != nil {
65 - log.Error().Err(err).Msg("[server] admin http error")
66 - cancel()
67 - }
68 - }()
66 + mux := http.NewServeMux()
67 + // Index page
68 + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
69 + if r.URL.Path != "/" {
70 + http.NotFound(w, r)
71 + return
72 + }
73 + type row struct {
74 + Peer string
75 + Name string
76 + DNS string
77 + LastSeen string
78 + Link string
79 + }
80 + type page struct {
81 + NodeID string
82 + Addrs []string
83 + Rows []row
84 + }
85 + rows := make([]row, 0)
86 + for _, v := range d.Hosts() {
87 + rows = append(rows, row{
88 + Peer: v.Info.Peer,
89 + Name: v.Info.Name,
90 + DNS: v.Info.DNS,
91 + LastSeen: time.Since(v.LastSeen).Round(time.Second).String() + " ago",
92 + Link: "/peer/" + v.Info.Peer + "/",
93 + })
94 + }
95 + w.Header().Set("Content-Type", "text/html; charset=utf-8")
96 + log.Debug().Int("clients", len(rows)).Msg("render admin index")
97 + addrs := make([]string, 0)
98 + for _, a := range h.Addrs() {
99 + addrs = append(addrs, fmt.Sprintf("%s/p2p/%s", a.String(), h.ID().String()))
100 + }
101 + _ = adminIndexTmpl.Execute(w, page{NodeID: h.ID().String(), Addrs: addrs, Rows: rows})
102 + })
103 + // Per-peer proxy
104 + mux.HandleFunc("/peer/", func(w http.ResponseWriter, r *http.Request) {
105 + p := strings.TrimPrefix(r.URL.Path, "/peer/")
106 + parts := strings.SplitN(p, "/", 2)
107 + if len(parts) == 0 || parts[0] == "" {
108 + http.Error(w, "missing peer id", http.StatusBadRequest)
109 + return
110 + }
111 + peerID := parts[0]
112 + pathSuffix := "/"
113 + if len(parts) == 2 {
114 + pathSuffix = "/" + parts[1]
115 + }
116 + d.ProxyHTTP(w, r, peerID, pathSuffix)
117 + })
118 + // JSON hosts
119 + mux.HandleFunc("/hosts", func(w http.ResponseWriter, r *http.Request) {
120 + _ = json.NewEncoder(w).Encode(d.Hosts())
121 + })
122 + // No override endpoint; selection is explicit per-peer via /peer/{id}
123 + // Health
124 + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
125 + type info struct {
126 + Status string `json:"status"`
127 + Addrs []string `json:"multiaddrs"`
128 + }
129 + list := make([]string, 0)
130 + for _, a := range h.Addrs() {
131 + list = append(list, fmt.Sprintf("%s/p2p/%s", a.String(), h.ID().String()))
132 + }
133 + resp := info{Status: "ok", Addrs: list}
134 + w.Header().Set("Content-Type", "application/json")
135 + _ = json.NewEncoder(w).Encode(resp)
136 + })
137
70 - // 2) HTTP ingress (browser/HTTP traffic)
71 - go func() {
72 - if ingressHTTP == "" {
73 - return
74 - }
75 - log.Info().Msgf("[server] http ingress (tcp-level): %s", ingressHTTP)
76 - if err := d.ServeTCP(ingressHTTP); err != nil {
77 - log.Error().Err(err).Msg("[server] http ingress error")
138 + log.Info().Msgf("[server] http (admin+proxy): %s", httpAddr)
139 + if err := http.ListenAndServe(httpAddr, mux); err != nil {
140 + log.Error().Err(err).Msg("[server] http error")
141 cancel()
142 }
143 }()
@@ -87,3 +150,67 @@ func runServer(cmd *cobra.Command, args []string) error {
150 time.Sleep(300 * time.Millisecond)
151 return nil
152 }
153 +
154 +var adminIndexTmpl = template.Must(template.New("admin-index").Parse(`<!doctype html>
155 +<html lang="ko">
156 +<head>
157 + <meta charset="utf-8"/>
158 + <meta name="viewport" content="width=device-width, initial-scale=1" />
159 + <title>RelayDNS — Admin</title>
160 + <style>
161 + * { box-sizing: border-box }
162 + body {
163 + margin: 0;
164 + background: #f6f7fb;
165 + color: #111827;
166 + font-family: system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
167 + font-size: 16px;
168 + line-height: 1.5;
169 + }
170 + .wrap { max-width: 960px; margin: 0 auto; padding: 28px 18px }
171 + header { display:flex; align-items:center; justify-content:space-between; padding: 14px 18px; background:#ffffff; border:1px solid #e5e7eb; border-radius: 10px }
172 + .brand { font-weight: 700; font-size: 20px }
173 + .status { color:#059669; font-weight:600 }
174 + main { margin-top: 18px; display:block }
175 + .box { background:#ffffff; border:1px solid #e5e7eb; border-radius:10px; padding:16px; margin-bottom:12px }
176 + .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 14px; color:#374151; word-break: break-all }
177 + .title { font-weight:700; margin: 0 0 8px 0; font-size: 18px; color:#374151 }
178 + .muted { color:#6b7280; font-size: 14px }
179 + .btn { display:inline-block; background:#2563eb; color:#fff; text-decoration:none; border-radius:8px; padding:10px 14px; font-weight:700; margin-top: 6px }
180 + </style>
181 + </head>
182 +<body>
183 + <div class="wrap">
184 + <header>
185 + <div class="brand">RelayDNS Admin</div>
186 + <div class="status">Active</div>
187 + </header>
188 + <main>
189 + <section class="box">
190 + <div class="title">Server</div>
191 + <div class="mono">Peer ID: {{.NodeID}}</div>
192 + {{if .Addrs}}
193 + <div class="muted" style="margin-top:6px">Multiaddrs</div>
194 + <div class="mono">{{range .Addrs}}{{.}}<br/>{{end}}</div>
195 + {{end}}
196 + <div class="muted" style="margin-top:6px">Known clients: {{len .Rows}}</div>
197 + </section>
198 + {{range .Rows}}
199 + <section class="box">
200 + <div class="title">{{if .Name}}{{.Name}}{{else}}(unnamed){{end}}</div>
201 + {{if .DNS}}<div class="muted">DNS: <span class="mono">{{.DNS}}</span></div>{{end}}
202 + <div class="muted">Peer</div>
203 + <div class="mono">{{.Peer}}</div>
204 + <div class="muted" style="margin-top:6px">Last seen: {{.LastSeen}}</div>
205 + <a class="btn" href="{{.Link}}">Open</a>
206 + </section>
207 + {{else}}
208 + <section class="box">
209 + <div class="title">No clients discovered</div>
210 + <div class="muted">Start a client and ensure bootstraps are configured.</div>
211 + </section>
212 + {{end}}
213 + </main>
214 + </div>
215 +</body>
216 +</html>`))
docker-compose.yml
+2 -5
@@ -7,16 +7,13 @@ services:
7 dockerfile: Dockerfile
8 image: relaydns/server:local
9 command:
10 - - "--admin-http"
10 + - "--http"
11 - ":8080"
12 - - "--ingress-http"
13 - - ":8082"
12 # To add bootstraps, add more lines like below (repeatable):
13 # - "--bootstrap"
14 # - "/dnsaddr/bootstrap.example/p2p/12D3Koo..."
15 ports:
18 - - "8080:8080" # Admin HTTP
19 - - "8082:8082" # HTTP ingress (tcp-level)
16 + - "8080:8080" # Admin UI + HTTP proxy
17 - "4001:4001/tcp" # libp2p tcp
18 - "4001:4001/udp" # libp2p quic/udp
19 restart: unless-stopped
relaydns/director.go
+10 -194
@@ -4,13 +4,9 @@ import (
4 "bufio"
5 "context"
6 "encoding/json"
7 - "fmt"
8 - "html/template"
7 "io"
10 - "net"
8 "net/http"
9 "net/url"
13 - "strings"
10 "sync"
11 "time"
12
@@ -31,7 +27,6 @@ type Director struct {
27 storeMu sync.Mutex
28 store map[string]HostEntry
29 ttl time.Duration
34 - pick *Picker
30 }
31
32 func NewDirector(ctx context.Context, h host.Host, protocol, topic string) (*Director, error) {
@@ -55,7 +50,6 @@ func NewDirector(ctx context.Context, h host.Host, protocol, topic string) (*Dir
50 sub: sub,
51 store: map[string]HostEntry{},
52 ttl: 45 * time.Second,
58 - pick: &Picker{},
53 }
54 go d.collect()
55 go d.gc()
@@ -107,7 +101,7 @@ func (d *Director) collect() {
101 } else {
102 log.Info().Str("peer", ad.Peer).Str("name", ad.Name).Msg("director: added client")
103 }
110 - d.pick.update(snap)
104 + _ = snap // snapshot kept local; selection handled explicitly via /peer
105 }
106 }
107
@@ -136,148 +130,24 @@ func (d *Director) gc() {
130 for _, r := range removed {
131 log.Info().Str("peer", r.Info.Peer).Str("name", r.Info.Name).Dur("idle", now.Sub(r.LastSeen)).Msg("director: removed stale client")
132 }
139 - d.pick.update(snap)
133 + _ = snap
134 }
135 }
136 }
137
144 -func (d *Director) ServeTCP(addr string) error {
145 - ln, err := net.Listen("tcp", addr)
146 - if err != nil {
147 - return err
148 - }
149 -
150 - log.Info().Msgf("director TCP listening on %s (protocol %s)", addr, d.protocol)
151 - for {
152 - c, err := ln.Accept()
153 - if err != nil {
154 - continue
155 - }
156 - go d.handleConn(c)
157 - }
158 -}
159 -
160 -func (d *Director) handleConn(c net.Conn) {
161 - defer c.Close()
162 - entry, ok := d.pick.choose()
163 - if !ok {
164 - log.Warn().Msg("no backend peers available")
165 - return
166 - }
167 - // ensure we're connected (AddrInfo contains addrs)
168 - if err := d.h.Connect(d.ctx, *entry.AddrInfo); err != nil {
169 - log.Error().Err(err).Msgf("connect %s failed", entry.AddrInfo.ID)
170 - return
171 - }
172 - s, err := d.h.NewStream(d.ctx, entry.AddrInfo.ID, protocolID(d.protocol))
173 - if err != nil {
174 - log.Error().Err(err).Msg("new stream")
175 - return
176 - }
177 - defer s.Close()
178 - // raw byte tunnel
179 - go io.Copy(s, c)
180 - io.Copy(c, s)
181 -}
182 -
183 -func (d *Director) ServeHTTP(addr string) error {
184 - mux := http.NewServeMux()
185 - mux.HandleFunc("/", d.handleIndex)
186 - mux.HandleFunc("/peer/", d.handlePeerProxy)
187 - mux.HandleFunc("/hosts", d.handleHosts)
188 - mux.HandleFunc("/override", d.handleOverride)
189 - mux.HandleFunc("/health", d.handleHealth)
190 - log.Info().Msgf("director HTTP API on %s", addr)
191 - return http.ListenAndServe(addr, mux)
192 -}
193 -
194 -var adminIndexTmpl = template.Must(template.New("admin-index").Parse(`<!doctype html>
195 -<html>
196 -<head>
197 - <meta charset="utf-8"/>
198 - <title>RelayDNS Admin</title>
199 - <style>
200 - body { font-family: system-ui, sans-serif; margin: 24px; }
201 - h1 { margin: 0 0 16px 0; }
202 - .card { border: 1px solid #ddd; border-radius: 10px; padding: 16px; margin: 12px 0; }
203 - .row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
204 - .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
205 - input[type=text] { padding: 6px 8px; border: 1px solid #ccc; border-radius: 6px; min-width: 260px; }
206 - a.btn { text-decoration: none; background:#2d6cdf; color:white; padding:6px 10px; border-radius:6px; }
207 - small { color:#666 }
208 - </style>
209 - <script>
210 - function goToPeer(peerId){
211 - const inp = document.getElementById('path-'+peerId);
212 - const path = inp && inp.value ? ('/' + inp.value.replace(/^\/+/, '')) : '/';
213 - window.location.href = '/peer/' + peerId + path;
214 - return false;
215 - }
216 - </script>
217 - </head>
218 -<body>
219 - <h1>RelayDNS Admin</h1>
220 - <p>Known clients: {{len .Rows}}</p>
221 - {{range .Rows}}
222 - <div class="card">
223 - <div class="row">
224 - <b>{{if .Name}}{{.Name}}{{else}}(unnamed){{end}}</b>
225 - <span class="mono">{{.Peer}}</span>
226 - {{if .DNS}}<span>DNS: <span class="mono">{{.DNS}}</span></span>{{end}}
227 - <small>last seen: {{.LastSeen}}</small>
228 - </div>
229 - <div class="row" style="margin-top:8px;">
230 - <a class="btn" href="{{.Link}}">Open</a>
231 - <form onsubmit="return goToPeer('{{.Peer}}')">
232 - <input id="path-{{.Peer}}" type="text" placeholder="optional path, e.g. api/health" />
233 - <button class="btn" type="submit">Open Path</button>
234 - </form>
235 - </div>
236 - </div>
237 - {{else}}
238 - <p>No clients discovered yet. Ensure backends are advertising and bootstraps are configured.</p>
239 - {{end}}
240 -</body>
241 -</html>`))
242 -
243 -func (d *Director) handleIndex(w http.ResponseWriter, r *http.Request) {
244 - type row struct {
245 - Peer string
246 - Name string
247 - DNS string
248 - LastSeen string
249 - Link string
250 - }
251 - type page struct{ Rows []row }
138 +// Hosts returns a snapshot of current known hosts.
139 +func (d *Director) Hosts() []HostEntry {
140 d.storeMu.Lock()
253 - rows := make([]row, 0, len(d.store))
141 + defer d.storeMu.Unlock()
142 + list := make([]HostEntry, 0, len(d.store))
143 for _, v := range d.store {
255 - rows = append(rows, row{
256 - Peer: v.Info.Peer,
257 - Name: v.Info.Name,
258 - DNS: v.Info.DNS,
259 - LastSeen: time.Since(v.LastSeen).Round(time.Second).String() + " ago",
260 - Link: "/peer/" + v.Info.Peer + "/",
261 - })
144 + list = append(list, v)
145 }
263 - d.storeMu.Unlock()
264 - w.Header().Set("Content-Type", "text/html; charset=utf-8")
265 - _ = adminIndexTmpl.Execute(w, page{Rows: rows})
146 + return list
147 }
148
268 -func (d *Director) handlePeerProxy(w http.ResponseWriter, r *http.Request) {
269 - p := strings.TrimPrefix(r.URL.Path, "/peer/")
270 - parts := strings.SplitN(p, "/", 2)
271 - if len(parts) == 0 || parts[0] == "" {
272 - http.Error(w, "missing peer id", http.StatusBadRequest)
273 - return
274 - }
275 - peerID := parts[0]
276 - pathSuffix := "/"
277 - if len(parts) == 2 {
278 - pathSuffix = "/" + parts[1]
279 - }
280 -
149 +// ProxyHTTP proxies the given HTTP request to the specified peer and writes the response to w.
150 +func (d *Director) ProxyHTTP(w http.ResponseWriter, r *http.Request, peerID, pathSuffix string) {
151 d.storeMu.Lock()
152 entry, ok := d.store[peerID]
153 d.storeMu.Unlock()
@@ -285,13 +155,11 @@ func (d *Director) handlePeerProxy(w http.ResponseWriter, r *http.Request) {
155 http.Error(w, "peer not found", http.StatusNotFound)
156 return
157 }
288 -
158 if err := d.h.Connect(d.ctx, *entry.AddrInfo); err != nil {
159 log.Error().Err(err).Msgf("connect %s failed", entry.AddrInfo.ID)
160 http.Error(w, "upstream connect failed", http.StatusBadGateway)
161 return
162 }
294 -
163 s, err := d.h.NewStream(d.ctx, entry.AddrInfo.ID, protocolID(d.protocol))
164 if err != nil {
165 log.Error().Err(err).Msg("new stream")
@@ -299,7 +167,6 @@ func (d *Director) handlePeerProxy(w http.ResponseWriter, r *http.Request) {
167 return
168 }
169 defer s.Close()
302 -
170 outReq := r.Clone(d.ctx)
171 outReq.URL = &url.URL{Path: pathSuffix, RawQuery: r.URL.RawQuery}
172 outReq.RequestURI = ""
@@ -308,7 +175,6 @@ func (d *Director) handlePeerProxy(w http.ResponseWriter, r *http.Request) {
175 http.Error(w, "write upstream failed", http.StatusBadGateway)
176 return
177 }
311 -
178 br := bufio.NewReader(s)
179 resp, err := http.ReadResponse(br, outReq)
180 if err != nil {
@@ -317,7 +183,6 @@ func (d *Director) handlePeerProxy(w http.ResponseWriter, r *http.Request) {
183 return
184 }
185 defer resp.Body.Close()
320 -
186 for k, vv := range resp.Header {
187 for _, v := range vv {
188 w.Header().Add(k, v)
@@ -326,52 +191,3 @@ func (d *Director) handlePeerProxy(w http.ResponseWriter, r *http.Request) {
191 w.WriteHeader(resp.StatusCode)
192 _, _ = io.Copy(w, resp.Body)
193 }
329 -
330 -func (d *Director) handleHosts(w http.ResponseWriter, r *http.Request) {
331 - d.storeMu.Lock()
332 - defer d.storeMu.Unlock()
333 - list := make([]HostEntry, 0, len(d.store))
334 - for _, v := range d.store {
335 - list = append(list, v)
336 - }
337 - _ = json.NewEncoder(w).Encode(list)
338 -}
339 -
340 -func (d *Director) handleOverride(w http.ResponseWriter, r *http.Request) {
341 - switch r.Method {
342 - case "POST":
343 - peerID := r.URL.Query().Get("peer")
344 - dur := 30 * time.Second
345 - if s := r.URL.Query().Get("ttl"); s != "" {
346 - if v, err := time.ParseDuration(s); err == nil {
347 - dur = v
348 - }
349 - }
350 - d.pick.pin(peerID, dur)
351 - log.Info().Str("peer", peerID).Dur("ttl", dur).Msg("director: pin override")
352 - w.WriteHeader(204)
353 - case "DELETE":
354 - d.pick.unpin()
355 - log.Info().Msg("director: unpin override")
356 - w.WriteHeader(204)
357 - default:
358 - w.WriteHeader(405)
359 - }
360 -}
361 -
362 -func (d *Director) handleHealth(w http.ResponseWriter, r *http.Request) {
363 - type info struct {
364 - Status string `json:"status"`
365 - Addrs []string `json:"multiaddrs"`
366 - }
367 - var list []string = make([]string, 0)
368 - for _, a := range d.h.Addrs() {
369 - list = append(list, fmt.Sprintf("%s/p2p/%s", a.String(), d.h.ID().String()))
370 - }
371 - resp := info{
372 - Status: "ok",
373 - Addrs: list,
374 - }
375 - w.Header().Set("Content-Type", "application/json")
376 - _ = json.NewEncoder(w).Encode(resp)
377 -}
relaydns/types.go
+4 -46
@@ -1,12 +1,10 @@
1 package relaydns
2
3 import (
4 - "sync"
5 - "sync/atomic"
6 - "time"
4 + "time"
5
8 - "github.com/libp2p/go-libp2p/core/peer"
9 - "github.com/libp2p/go-libp2p/core/protocol"
6 + "github.com/libp2p/go-libp2p/core/peer"
7 + "github.com/libp2p/go-libp2p/core/protocol"
8 )
9
10 type Advertise struct {
@@ -25,47 +23,7 @@ type HostEntry struct {
23 LastSeen time.Time
24 }
25
28 -type Picker struct {
29 - mu sync.RWMutex
30 - rr uint64
31 - list []HostEntry
32 - pinTo string
33 - pinTil time.Time
34 -}
35 -
36 -func (p *Picker) update(list []HostEntry) {
37 - p.mu.Lock()
38 - p.list = list
39 - p.mu.Unlock()
40 -}
41 -func (p *Picker) choose() (HostEntry, bool) {
42 - p.mu.RLock()
43 - defer p.mu.RUnlock()
44 - if len(p.list) == 0 {
45 - return HostEntry{}, false
46 - }
47 - if p.pinTo != "" && time.Now().Before(p.pinTil) {
48 - for _, e := range p.list {
49 - if e.Info.Peer == p.pinTo {
50 - return e, true
51 - }
52 - }
53 - }
54 - i := atomic.AddUint64(&p.rr, 1)
55 - return p.list[i%uint64(len(p.list))], true
56 -}
57 -func (p *Picker) pin(peerID string, dur time.Duration) {
58 - p.mu.Lock()
59 - p.pinTo = peerID
60 - p.pinTil = time.Now().Add(dur)
61 - p.mu.Unlock()
62 -}
63 -func (p *Picker) unpin() {
64 - p.mu.Lock()
65 - p.pinTo = ""
66 - p.pinTil = time.Time{}
67 - p.mu.Unlock()
68 -}
26 +// Removed Picker: selection is explicit via /peer/{peerID}/...
27
28 func protocolID(s string) protocol.ID {
29 return protocol.ID(s)