package httpnode import ( "context" "errors" "fmt" "net" "net/http" "net/netip" "git.sunturtle.xyz/zephyr/chord/chord" ) type Node struct { // self is the node topology this server represents. self *chord.Node // client is the Chord client for forwarding queries to other nodes. // TODO(branden): really we should have a client per peer so that we can // interchange protocols client chord.Client } // New creates an instance of a Chord network that responds on HTTP. // The listener must be the same one used for the HTTP server that routes to // [Node.ServeHTTP]. It must be bound to a single interface and port. func New(l net.Listener, cl chord.Client) (*Node, error) { addr, err := netip.ParseAddrPort(l.Addr().String()) if err != nil { return nil, err } if addr.Addr().IsUnspecified() || addr.Port() == 0 { return nil, errors.New("listener must be bound to a single interface and port") } n, err := chord.Create(addr) if err != nil { return nil, err } r := &Node{ self: n, client: cl, } return r, nil } // Router creates a handler for the Chord HTTP endpoints. func (n *Node) Router() http.Handler { m := http.NewServeMux() m.HandleFunc("GET /key", n.key) m.HandleFunc("POST /pred", n.notify) m.HandleFunc("GET /neighbors", n.neighbors) return m } // Check performs checks that implement the Chord protocol. // It must be called periodically while the node is alive. func (n *Node) Check(ctx context.Context) error { if err := chord.Stabilize(ctx, n.client, n.self); err != nil { return fmt.Errorf("failed to stabilize: %w", err) } // TODO(zeph): enable once implemented // if err := chord.FixFingers(ctx, n.client, n.self); err != nil { // return fmt.Errorf("failed to fix fingers: %w", err) // } chord.CheckPredecessor(ctx, n.client, n.self) return nil } func (n *Node) key(w http.ResponseWriter, r *http.Request) { s := r.FormValue("s") id, err := chord.ParseID(s) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } p, v, err := chord.Find(r.Context(), n.client, n.self, id) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } _, addr := p.Values() pv := peervalue{addr, v} writeOk(w, pv) } func (n *Node) notify(w http.ResponseWriter, r *http.Request) { s := r.FormValue("p") addr, err := netip.ParseAddrPort(s) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } np := chord.Address(addr) chord.Notify(n.self, np) w.WriteHeader(http.StatusNoContent) } func (n *Node) neighbors(w http.ResponseWriter, r *http.Request) { pred, succ := n.self.Neighbors(nil) _, paddr := pred.Values() u := neighbors{ Succ: make([]netip.AddrPort, 0, len(succ)), Pred: paddr, } for _, s := range succ { _, addr := s.Values() u.Succ = append(u.Succ, addr) } writeOk(w, &u) }