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
 | 
						|
}
 |