228 lines
5.7 KiB
Go
228 lines
5.7 KiB
Go
|
package server
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"net/http"
|
||
|
"time"
|
||
|
|
||
|
peering "forge.cadoles.com/wpetit/go-http-peering"
|
||
|
"forge.cadoles.com/wpetit/go-http-peering/crypto"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ErrInvalidAdvertisingRequest = errors.New("invalid advertising request")
|
||
|
ErrInvalidUpdateRequest = errors.New("invalid update request")
|
||
|
ErrPeerRejected = errors.New("peer rejected")
|
||
|
ErrPeerIDAlreadyInUse = errors.New("peer id already in use")
|
||
|
ErrUnauthorized = errors.New("unauthorized")
|
||
|
)
|
||
|
|
||
|
func AdvertiseHandler(store peering.Store, funcs ...OptionFunc) http.HandlerFunc {
|
||
|
|
||
|
options := createOptions(funcs...)
|
||
|
logger := options.Logger
|
||
|
|
||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||
|
advertising := &peering.AdvertisingRequest{}
|
||
|
|
||
|
decoder := json.NewDecoder(r.Body)
|
||
|
if err := decoder.Decode(advertising); err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if !options.PeerIDValidator(advertising.ID) {
|
||
|
logger.Printf("[ERROR] %s", ErrInvalidAdvertisingRequest)
|
||
|
options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if _, err := crypto.DecodePEMToPublicKey(advertising.PublicKey); err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
peer, err := store.Get(advertising.ID)
|
||
|
|
||
|
if err == nil {
|
||
|
logger.Printf("[ERROR] %s", ErrPeerIDAlreadyInUse)
|
||
|
options.ErrorHandler(w, r, ErrPeerIDAlreadyInUse)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err != peering.ErrPeerNotFound {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
attrs := filterAttributes(options.PeerAttributes, advertising.Attributes)
|
||
|
|
||
|
peer, err = store.Create(advertising.ID, attrs)
|
||
|
if err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := store.UpdatePublicKey(peer.ID, advertising.PublicKey); err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.WriteHeader(http.StatusCreated)
|
||
|
|
||
|
}
|
||
|
|
||
|
return handler
|
||
|
}
|
||
|
|
||
|
func UpdateHandler(store peering.Store, funcs ...OptionFunc) http.HandlerFunc {
|
||
|
options := createOptions(funcs...)
|
||
|
logger := options.Logger
|
||
|
|
||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
||
|
update := &peering.UpdateRequest{}
|
||
|
decoder := json.NewDecoder(r.Body)
|
||
|
if err := decoder.Decode(update); err != nil {
|
||
|
options.ErrorHandler(w, r, ErrInvalidUpdateRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
peerID, err := GetPeerID(r)
|
||
|
if err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
peer, err := store.Get(peerID)
|
||
|
if err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if peer == nil {
|
||
|
logger.Printf("[ERROR] %s", ErrUnauthorized)
|
||
|
options.ErrorHandler(w, r, ErrUnauthorized)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if peer.Status == peering.StatusRejected {
|
||
|
logger.Printf("[ERROR] %s", ErrPeerRejected)
|
||
|
options.ErrorHandler(w, r, ErrPeerRejected)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
attrs := filterAttributes(options.PeerAttributes, update.Attributes)
|
||
|
if err := store.UpdateAttributes(peer.ID, attrs); err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.WriteHeader(http.StatusNoContent)
|
||
|
|
||
|
}
|
||
|
|
||
|
return handler
|
||
|
}
|
||
|
|
||
|
func PingHandler(store peering.Store, funcs ...OptionFunc) http.HandlerFunc {
|
||
|
options := createOptions(funcs...)
|
||
|
logger := options.Logger
|
||
|
|
||
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
||
|
update := &peering.UpdateRequest{}
|
||
|
decoder := json.NewDecoder(r.Body)
|
||
|
if err := decoder.Decode(update); err != nil {
|
||
|
options.ErrorHandler(w, r, ErrInvalidUpdateRequest)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
peerID, err := GetPeerID(r)
|
||
|
if err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
peer, err := store.Get(peerID)
|
||
|
if err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if peer == nil {
|
||
|
logger.Printf("[ERROR] %s", ErrUnauthorized)
|
||
|
options.ErrorHandler(w, r, ErrUnauthorized)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if peer.Status == peering.StatusRejected {
|
||
|
logger.Printf("[ERROR] %s", ErrPeerRejected)
|
||
|
options.ErrorHandler(w, r, ErrPeerRejected)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil {
|
||
|
logger.Printf("[ERROR] %s", err)
|
||
|
options.ErrorHandler(w, r, err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
w.WriteHeader(http.StatusNoContent)
|
||
|
}
|
||
|
|
||
|
return handler
|
||
|
}
|
||
|
|
||
|
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
|
||
|
switch err {
|
||
|
case ErrInvalidAdvertisingRequest:
|
||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||
|
case ErrPeerIDAlreadyInUse:
|
||
|
http.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict)
|
||
|
case ErrUnauthorized:
|
||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||
|
case ErrPeerRejected:
|
||
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||
|
default:
|
||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func DefaultPeerIDValidator(id peering.PeerID) bool {
|
||
|
return string(id) != ""
|
||
|
}
|
||
|
|
||
|
func filterAttributes(filters []string, attrs peering.PeerAttributes) peering.PeerAttributes {
|
||
|
filtered := peering.PeerAttributes{}
|
||
|
for _, key := range filters {
|
||
|
if _, exists := attrs[key]; exists {
|
||
|
filtered[key] = attrs[key]
|
||
|
}
|
||
|
}
|
||
|
return filtered
|
||
|
}
|