Initial commit
This commit is contained in:
187
client/client.go
Normal file
187
client/client.go
Normal file
@ -0,0 +1,187 @@
|
||||
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}
|
||||
}
|
56
client/option.go
Normal file
56
client/option.go
Normal file
@ -0,0 +1,56 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"net/http"
|
||||
|
||||
peering "forge.cadoles.com/wpetit/go-http-peering"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
PeerID peering.PeerID
|
||||
HTTPClient *http.Client
|
||||
BaseURL string
|
||||
PrivateKey *rsa.PrivateKey
|
||||
}
|
||||
|
||||
type OptionFunc func(*Options)
|
||||
|
||||
func WithPeerID(id peering.PeerID) OptionFunc {
|
||||
return func(opts *Options) {
|
||||
opts.PeerID = id
|
||||
}
|
||||
}
|
||||
|
||||
func WithPrivateKey(pk *rsa.PrivateKey) OptionFunc {
|
||||
return func(opts *Options) {
|
||||
opts.PrivateKey = pk
|
||||
}
|
||||
}
|
||||
|
||||
func WithHTTPClient(client *http.Client) OptionFunc {
|
||||
return func(opts *Options) {
|
||||
opts.HTTPClient = client
|
||||
}
|
||||
}
|
||||
|
||||
func WithBaseURL(url string) OptionFunc {
|
||||
return func(opts *Options) {
|
||||
opts.BaseURL = url
|
||||
}
|
||||
}
|
||||
|
||||
func defaultOptions() *Options {
|
||||
return &Options{
|
||||
HTTPClient: http.DefaultClient,
|
||||
PeerID: peering.NewPeerID(),
|
||||
}
|
||||
}
|
||||
|
||||
func createOptions(funcs ...OptionFunc) *Options {
|
||||
options := defaultOptions()
|
||||
for _, fn := range funcs {
|
||||
fn(options)
|
||||
}
|
||||
return options
|
||||
}
|
Reference in New Issue
Block a user