108 lines
2.7 KiB
Go
108 lines
2.7 KiB
Go
package httpnode
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
|
|
"git.sunturtle.xyz/zephyr/chord/chord"
|
|
)
|
|
|
|
type Client struct {
|
|
// HTTP is the client used to make requests.
|
|
HTTP http.Client
|
|
// APIBase is the path under which the Chord API is served.
|
|
APIBase string
|
|
}
|
|
|
|
// Find asks s to find a value and the peer that owns it.
|
|
func (cl *Client) Find(ctx context.Context, s chord.Peer, id chord.ID) (chord.Peer, string, error) {
|
|
_, addr := s.Values()
|
|
if !addr.IsValid() {
|
|
return chord.Peer{}, "", errors.New("Find with invalid peer")
|
|
}
|
|
url := url.URL{
|
|
Scheme: "http",
|
|
Host: addr.String(),
|
|
Path: path.Join("/", cl.APIBase, "key", id.String()),
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url.String(), nil)
|
|
if err != nil {
|
|
return chord.Peer{}, "", err
|
|
}
|
|
resp, err := cl.HTTP.Do(req)
|
|
if err != nil {
|
|
return chord.Peer{}, "", err
|
|
}
|
|
p, err := readResponse[peervalue](resp)
|
|
if err != nil {
|
|
return chord.Peer{}, "", fmt.Errorf("%w (%s)", err, resp.Status)
|
|
}
|
|
return chord.Address(p.Peer), p.Value, nil
|
|
}
|
|
|
|
// Notify tells s we believe n to be its predecessor.
|
|
func (cl *Client) Notify(ctx context.Context, n *chord.Node, s chord.Peer) error {
|
|
_, addr := s.Values()
|
|
if !addr.IsValid() {
|
|
return errors.New("Notify with invalid peer")
|
|
}
|
|
_, self := n.Self().Values()
|
|
url := url.URL{
|
|
Scheme: "http",
|
|
Host: addr.String(),
|
|
Path: path.Join("/", cl.APIBase, "pred"),
|
|
RawQuery: url.Values{"s": {self.String()}}.Encode(),
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, "POST", url.String(), nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := cl.HTTP.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// We expect the server to only be capable of responding with No Content.
|
|
if resp.StatusCode != http.StatusNoContent {
|
|
return fmt.Errorf("strange response: %s", resp.Status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Neighbors requests a peer's beliefs about its own neighbors.
|
|
func (cl *Client) Neighbors(ctx context.Context, p chord.Peer) (pred chord.Peer, succ []chord.Peer, err error) {
|
|
_, addr := p.Values()
|
|
if !addr.IsValid() {
|
|
return chord.Peer{}, nil, errors.New("Neighbors with invalid peer")
|
|
}
|
|
url := url.URL{
|
|
Scheme: "http",
|
|
Host: addr.String(),
|
|
Path: path.Join("/", cl.APIBase, "neighbors"),
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url.String(), nil)
|
|
if err != nil {
|
|
return chord.Peer{}, nil, err
|
|
}
|
|
resp, err := cl.HTTP.Do(req)
|
|
if err != nil {
|
|
return chord.Peer{}, nil, err
|
|
}
|
|
n, err := readResponse[neighbors](resp)
|
|
if err != nil {
|
|
return chord.Peer{}, nil, fmt.Errorf("%w (%s)", err, resp.Status)
|
|
}
|
|
pred = chord.Address(n.Pred)
|
|
succ = make([]chord.Peer, 0, len(n.Succ))
|
|
for _, addr := range n.Succ {
|
|
p := chord.Address(addr)
|
|
if p.IsValid() {
|
|
succ = append(succ, p)
|
|
}
|
|
}
|
|
return pred, succ, nil
|
|
}
|