188 lines
3.8 KiB
Go
188 lines
3.8 KiB
Go
|
package client
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/sha256"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net/http"
|
||
|
"time"
|
||
|
|
||
|
"github.com/dgrijalva/jwt-go"
|
||
|
|
||
|
peering "forge.cadoles.com/wpetit/go-http-peering"
|
||
|
"forge.cadoles.com/wpetit/go-http-peering/crypto"
|
||
|
"forge.cadoles.com/wpetit/go-http-peering/server"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
DefaultBody = "{}"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ErrInvalidPrivateKey = errors.New("invalid private key")
|
||
|
ErrUnexpectedResponse = errors.New("unexpected response")
|
||
|
ErrUnauthorized = errors.New("unauthorized")
|
||
|
ErrRejected = errors.New("rejected")
|
||
|
)
|
||
|
|
||
|
type Client struct {
|
||
|
options *Options
|
||
|
}
|
||
|
|
||
|
func (c *Client) Advertise(attrs peering.PeerAttributes) error {
|
||
|
url := c.options.BaseURL + peering.AdvertisePath
|
||
|
|
||
|
publicKey, err := crypto.EncodePublicKeyToPEM(c.options.PrivateKey.Public())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
data := &peering.AdvertisingRequest{
|
||
|
ID: c.options.PeerID,
|
||
|
Attributes: attrs,
|
||
|
PublicKey: publicKey,
|
||
|
}
|
||
|
|
||
|
req, _, err := c.createPostRequest(url, data)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
res, err := c.options.HTTPClient.Do(req)
|
||
|
|
||
|
switch res.StatusCode {
|
||
|
case http.StatusCreated:
|
||
|
return nil
|
||
|
case http.StatusConflict:
|
||
|
return peering.ErrPeerExists
|
||
|
default:
|
||
|
return ErrUnexpectedResponse
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Client) UpdateAttributes(attrs peering.PeerAttributes) error {
|
||
|
url := c.options.BaseURL + peering.UpdatePath
|
||
|
|
||
|
data := &peering.UpdateRequest{
|
||
|
Attributes: attrs,
|
||
|
}
|
||
|
|
||
|
res, err := c.Post(url, data)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
switch res.StatusCode {
|
||
|
case http.StatusNoContent:
|
||
|
return nil
|
||
|
case http.StatusUnauthorized:
|
||
|
return ErrUnauthorized
|
||
|
case http.StatusForbidden:
|
||
|
return ErrRejected
|
||
|
default:
|
||
|
return ErrUnexpectedResponse
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Client) Ping() error {
|
||
|
url := c.options.BaseURL + peering.PingPath
|
||
|
|
||
|
res, err := c.Post(url, nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
switch res.StatusCode {
|
||
|
case http.StatusNoContent:
|
||
|
return nil
|
||
|
case http.StatusUnauthorized:
|
||
|
return ErrUnauthorized
|
||
|
case http.StatusForbidden:
|
||
|
return ErrRejected
|
||
|
default:
|
||
|
return ErrUnexpectedResponse
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Client) Get(url string) (*http.Response, error) {
|
||
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if err := c.signRequest(req, nil); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return c.options.HTTPClient.Do(req)
|
||
|
}
|
||
|
|
||
|
func (c *Client) Post(url string, data interface{}) (*http.Response, error) {
|
||
|
req, body, err := c.createPostRequest(url, data)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if err := c.signRequest(req, body); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return c.options.HTTPClient.Do(req)
|
||
|
}
|
||
|
|
||
|
func (c *Client) createPostRequest(url string, data interface{}) (*http.Request, []byte, error) {
|
||
|
body, err := json.Marshal(data)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
||
|
return req, body, nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) signRequest(r *http.Request, body []byte) error {
|
||
|
bodySum, err := c.createBodySum(body)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, peering.PeerClaims{
|
||
|
StandardClaims: jwt.StandardClaims{
|
||
|
NotBefore: time.Now().Unix(),
|
||
|
Issuer: string(c.options.PeerID),
|
||
|
ExpiresAt: time.Now().Add(time.Minute * 10).Unix(),
|
||
|
},
|
||
|
BodySum: bodySum,
|
||
|
})
|
||
|
|
||
|
tokenStr, err := token.SignedString(c.options.PrivateKey)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
r.Header.Set("Authorization", fmt.Sprintf("%s %s", server.AuthorizationType, tokenStr))
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *Client) createBodySum(body []byte) ([]byte, error) {
|
||
|
if body == nil || len(body) == 0 {
|
||
|
body = []byte(DefaultBody)
|
||
|
}
|
||
|
sha := sha256.New()
|
||
|
_, err := sha.Write(body)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return sha.Sum(nil), nil
|
||
|
}
|
||
|
|
||
|
func New(funcs ...OptionFunc) *Client {
|
||
|
options := createOptions(funcs...)
|
||
|
return &Client{options}
|
||
|
}
|