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)