110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
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)
|
|
}
|