go-http-peering/client/client.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}
}