// Join helper: a newcomer POSTs its JoinInfo to a target node's join endpoint // and adopts the returned ring state (leader preserved, newcomer inserted as // the target's successor). package cluster import ( "bytes" "context" "crypto/rand" "encoding/json" "fmt" "net/http" "time" ) // GenerateNodeKey returns a random 16-byte hex string for use as a cluster // admission key. Called on first startup when no key is persisted yet. func GenerateNodeKey() string { b := make([]byte, 16) if _, err := rand.Read(b); err != nil { return fmt.Sprintf("%x", time.Now().UnixNano()) } return fmt.Sprintf("%x", b) } // JoinRing asks target to add us to its ring and returns the adopted state. func (e *Engine) JoinRing(ctx context.Context, targetAddr string, ji JoinInfo) error { url := fmt.Sprintf("http://%s/api/manager/cluster/join", targetAddr) body, err := json.Marshal(ji) if err != nil { return err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") if e.User != "" || e.Pass != "" { req.SetBasicAuth(e.User, e.Pass) } cli := &http.Client{Timeout: 8 * time.Second} resp, err := cli.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("join %s -> HTTP %d", targetAddr, resp.StatusCode) } var out struct { State State `json:"state"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { return err } e.AdoptState(out.State) return nil } // JoinRingAddr wraps JoinRing for HTTP handlers: it builds the newcomer's // JoinInfo from this node's own id/addr/version/cache (so the caller doesn't // touch the engine's unexported fields) and asks targetAddr to sponsor us // into its ring. joinKey is the sponsor's nodeKey — the sponsor verifies it // before admitting. This is the runtime "加入集群" path (vs. the startup // bootstrap call in cmd/webui4frpc/main.go). func (e *Engine) JoinRingAddr(ctx context.Context, targetAddr, joinKey string) error { ji := JoinInfo{ID: e.ID, Addr: e.myAddr, Version: e.Version, Cache: e.Cache, JoinKey: joinKey} return e.JoinRing(ctx, targetAddr, ji) }