mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-20 00:47:57 +00:00
feat: M6 cluster binary registry + peer exchange (stage A)
This commit is contained in:
@ -8,9 +8,11 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"webui4frpc/internal/cluster"
|
||||
"webui4frpc/internal/process"
|
||||
"webui4frpc/internal/store"
|
||||
)
|
||||
@ -34,6 +36,8 @@ type Handler struct {
|
||||
BinaryPath func() string
|
||||
// RunInstall is a hook to trigger canary tasks after canvas save.
|
||||
SyncWorkers func()
|
||||
// Cluster is the peer-to-peer binary registry (M6). Nil disables M6 routes.
|
||||
Cluster *cluster.Registry
|
||||
}
|
||||
|
||||
const (
|
||||
@ -79,6 +83,13 @@ func NewServeMux(h *Handler) (http.Handler, error) {
|
||||
mux.HandleFunc(apiPrefix+"/remotes", auth(h.handleRemoteUpsert))
|
||||
mux.HandleFunc(apiPrefix+"/remotes/", auth(h.handleRemoteDelete))
|
||||
|
||||
// M6: cluster nodes + per-node cached versions (UI + discovery).
|
||||
mux.HandleFunc(apiPrefix+"/cluster/nodes", auth(h.handleClusterNodes))
|
||||
|
||||
// M6: peer-to-peer binary exchange endpoint (Basic Auth, same creds).
|
||||
// Not under /api so peers hit it directly; auth still applied.
|
||||
mux.HandleFunc("/frpc/", auth(h.handleFrpcBinary))
|
||||
|
||||
// Static assets (also basic auth) under /.
|
||||
mux.HandleFunc("/", h.handleStatic)
|
||||
|
||||
@ -244,6 +255,68 @@ func (h *Handler) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// handleClusterNodes reports registered cluster nodes and their cached frpc
|
||||
// versions (M6). Used by discovery and the frontend cluster page.
|
||||
func (h *Handler) handleClusterNodes(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
methodNotAllowed(w)
|
||||
return
|
||||
}
|
||||
if h.Cluster == nil {
|
||||
http.Error(w, "cluster registry not enabled", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
nodes := h.Cluster.NodeList()
|
||||
type nodeResp struct {
|
||||
Addr string `json:"addr"`
|
||||
Cache []string `json:"cache,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
out := make([]nodeResp, 0, len(nodes))
|
||||
for _, n := range nodes {
|
||||
out = append(out, nodeResp{Addr: n.Addr, Version: n.Version, Cache: n.Cache})
|
||||
}
|
||||
out = append(out, nodeResp{
|
||||
Addr: "self",
|
||||
Cache: h.Cluster.CachedVersions(),
|
||||
Version: currentVersion(),
|
||||
})
|
||||
writeJSON(w, http.StatusOK, map[string]any{"nodes": out})
|
||||
}
|
||||
|
||||
// handleFrpcBinary serves a cached frpc binary to peer nodes over
|
||||
// GET /frpc/{version}. Basic Auth is required (same creds as the manager).
|
||||
func (h *Handler) handleFrpcBinary(w http.ResponseWriter, r *http.Request) {
|
||||
if h.Cluster == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
name := strings.TrimPrefix(r.URL.Path, "/frpc/")
|
||||
if name == "" || strings.Contains(name, "/") || strings.Contains(name, "..") {
|
||||
http.Error(w, "bad version", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
path, checksum, err := h.Cluster.Provide(name)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("X-Frpc-SHA256", checksum)
|
||||
_, _ = io.Copy(w, f)
|
||||
}
|
||||
|
||||
// currentVersion returns the app version for cluster node reporting.
|
||||
func currentVersion() string {
|
||||
return "0.1.0"
|
||||
}
|
||||
|
||||
// connStateOf derives the frps connection state from the local worker process
|
||||
// state plus a probe of its log. We never control the remote frps: the worker is
|
||||
// our local frpc; "connected" means frpc has successfully logged in to frps.
|
||||
|
||||
Reference in New Issue
Block a user