Compare commits
No commits in common. "462a9783e9360a992b69ba223fa556a60e4fcfe0" and "ce8d1090395d25d0c25e4dddb8873a4265dea616" have entirely different histories.
462a9783e9
...
ce8d109039
@ -22,15 +22,6 @@ type Client interface {
|
|||||||
// considering we can sort by increasing distance from the origin and then do
|
// considering we can sort by increasing distance from the origin and then do
|
||||||
// the query in linear time.
|
// the query in linear time.
|
||||||
|
|
||||||
// Find finds the first node in the network preceding id.
|
|
||||||
func Find(ctx context.Context, cl Client, n *Node, id ID) (Peer, error) {
|
|
||||||
if n.IsLocal(id) {
|
|
||||||
return n.self, nil
|
|
||||||
}
|
|
||||||
p, err := cl.FindSuccessor(ctx, n.Closest(id), id)
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join creates a new node joining an existing Chord network by communicating
|
// Join creates a new node joining an existing Chord network by communicating
|
||||||
// with any peer already in the network.
|
// with any peer already in the network.
|
||||||
func Join(ctx context.Context, cl Client, addr netip.AddrPort, np Peer) (*Node, error) {
|
func Join(ctx context.Context, cl Client, addr netip.AddrPort, np Peer) (*Node, error) {
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
"github.com/go-json-experiment/json"
|
||||||
|
|
||||||
"git.sunturtle.xyz/zephyr/chord/chord"
|
"git.sunturtle.xyz/zephyr/chord/chord"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,11 +41,14 @@ func (cl *Client) FindSuccessor(ctx context.Context, s chord.Peer, id chord.ID)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return chord.Peer{}, err
|
return chord.Peer{}, err
|
||||||
}
|
}
|
||||||
p, err := readResponse[netip.AddrPort](resp)
|
var r response[netip.AddrPort]
|
||||||
if err != nil {
|
if err := json.UnmarshalRead(resp.Body, &r); err != nil {
|
||||||
return chord.Peer{}, fmt.Errorf("%w (%s)", err, resp.Status)
|
return chord.Peer{}, err
|
||||||
}
|
}
|
||||||
return chord.Address(p), nil
|
if r.Error != "" {
|
||||||
|
return chord.Peer{}, fmt.Errorf("%s (%s)", r.Error, resp.Status)
|
||||||
|
}
|
||||||
|
return chord.Address(*r.Data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify tells s we believe n to be its predecessor.
|
// Notify tells s we believe n to be its predecessor.
|
||||||
|
@ -2,43 +2,11 @@
|
|||||||
// using JSON over HTTP.
|
// using JSON over HTTP.
|
||||||
package httpnode
|
package httpnode
|
||||||
|
|
||||||
import (
|
import "net/netip"
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/go-json-experiment/json"
|
type response[T any] struct {
|
||||||
)
|
Data *T `json:"data,omitzero"`
|
||||||
|
Error string `json:"error,omitzero"`
|
||||||
func writeOk[T any](w http.ResponseWriter, x T) {
|
|
||||||
w.Header().Set("content-type", "application/json")
|
|
||||||
r := struct {
|
|
||||||
Data T `json:"data"`
|
|
||||||
}{x}
|
|
||||||
json.MarshalWrite(w, &r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeError(w http.ResponseWriter, status int, msg string) {
|
|
||||||
w.Header().Set("content-type", "application/json")
|
|
||||||
w.WriteHeader(status)
|
|
||||||
r := struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
}{msg}
|
|
||||||
json.MarshalWrite(w, &r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readResponse[T any](r *http.Response) (x T, err error) {
|
|
||||||
var b struct {
|
|
||||||
Data T `json:"data"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
if err := json.UnmarshalRead(r.Body, &b); err != nil {
|
|
||||||
return x, err
|
|
||||||
}
|
|
||||||
if b.Error != "" {
|
|
||||||
return x, errors.New(b.Error)
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type neighbors struct {
|
type neighbors struct {
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
package httpnode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"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.
|
|
||||||
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 /succ", n.successor)
|
|
||||||
m.HandleFunc("POST /pred", n.notify)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) successor(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, err := chord.Find(r.Context(), n.client, n.self, id)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, addr := p.Values()
|
|
||||||
writeOk(w, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Node) notify(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Another node is telling us they think they're our predecessor.
|
|
||||||
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)
|
|
||||||
}
|
|
10
chord/id.go
10
chord/id.go
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,12 +49,3 @@ func (id ID) String() string {
|
|||||||
b = hex.AppendEncode(b, id[:])
|
b = hex.AppendEncode(b, id[:])
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseID(s string) (ID, error) {
|
|
||||||
if len(s) != len(ID{})*2 {
|
|
||||||
return ID{}, errors.New("invalid ID")
|
|
||||||
}
|
|
||||||
var id ID
|
|
||||||
_, err := hex.AppendDecode(id[:], []byte(s))
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
@ -26,19 +26,16 @@ func (n *Node) Successor() Peer {
|
|||||||
return n.succ[0]
|
return n.succ[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neighbors returns the node's predecessor and appends its successor list to s.
|
|
||||||
func (n *Node) Neighbors(s []Peer) (Peer, []Peer) {
|
|
||||||
return n.pred, append(s, n.succ...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsLocal reports whether this node owns the given key.
|
// IsLocal reports whether this node owns the given key.
|
||||||
func (n *Node) IsLocal(id ID) bool {
|
func (n *Node) IsLocal(key string) bool {
|
||||||
|
id := keyID(key)
|
||||||
return contains(n.self.id, n.Successor().id, id)
|
return contains(n.self.id, n.Successor().id, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closest finds the locally known peer which is the closest predecessor of key.
|
// Closest finds the locally known peer which is the closest predecessor of key.
|
||||||
func (n *Node) Closest(id ID) Peer {
|
func (n *Node) Closest(key string) Peer {
|
||||||
self := n.self.id
|
self := n.self.id
|
||||||
|
id := keyID(key)
|
||||||
l := n.fingers
|
l := n.fingers
|
||||||
for i := len(l) - 1; i >= 0; i-- {
|
for i := len(l) - 1; i >= 0; i-- {
|
||||||
f := l[i]
|
f := l[i]
|
||||||
|
Loading…
Reference in New Issue
Block a user