Skip to content
Snippets Groups Projects
Unverified Commit a78b8b97 authored by jonastheis's avatar jonastheis
Browse files

Introduce small API wrapper for GoShimmer HTTP API

parent 907e8ca2
No related branches found
No related tags found
No related merge requests found
package api
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
)
var (
ErrBadRequest = errors.New("bad request")
ErrInternalServerError = errors.New("internal server error")
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrUnknownError = errors.New("unknown error")
ErrNotImplemented = errors.New("operation not implemented/supported/available")
)
const (
routeBroadcastData = "broadcastData"
routeGetNeighbors = "getNeighbors"
contentTypeJSON = "application/json"
)
func New(baseUrl string, httpClient http.Client) *Api {
return &Api{BaseUrl: baseUrl, httpClient: httpClient}
}
// Api is an API wrapper over the web API of GoShimmer.
type Api struct {
httpClient http.Client
BaseUrl string
jwt string
}
type errorResponse struct {
Error string `json:"error"`
}
func interpretBody(res *http.Response, decodeTo interface{}) error {
resBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("unable to read response body: %w", err)
}
defer res.Body.Close()
if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusCreated {
return json.Unmarshal(resBody, decodeTo)
}
errRes := &errorResponse{}
if err := json.Unmarshal(resBody, errRes); err != nil {
return fmt.Errorf("unable to read error from response body: %w", err)
}
switch res.StatusCode {
case http.StatusInternalServerError:
return fmt.Errorf("%w: %s", ErrInternalServerError, errRes.Error)
case http.StatusNotFound:
return fmt.Errorf("%w: %s", ErrNotFound, res.Request.URL.String())
case http.StatusBadRequest:
return fmt.Errorf("%w: %s", ErrBadRequest, errRes.Error)
case http.StatusUnauthorized:
return fmt.Errorf("%w: %s", ErrUnauthorized, errRes.Error)
case http.StatusNotImplemented:
return fmt.Errorf("%w: %s", ErrNotImplemented, errRes.Error)
}
return fmt.Errorf("%w: %s", ErrUnknownError, errRes.Error)
}
func (api *Api) do(method string, route string, reqObj interface{}, resObj interface{}) error {
// marshal request object
var data []byte
if reqObj != nil {
var err error
data, err = json.Marshal(reqObj)
if err != nil {
return err
}
}
// construct request
req, err := http.NewRequest(method, fmt.Sprintf("%s/%s", api.BaseUrl, route), func() io.Reader {
if data == nil {
return nil
}
return bytes.NewReader(data)
}())
if err != nil {
return err
}
if data != nil {
req.Header.Set("Content-Type", contentTypeJSON)
}
// add authorization header with JWT
if len(api.jwt) > 0 {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", api.jwt))
}
// make the request
res, err := api.httpClient.Do(req)
if err != nil {
return err
}
if resObj == nil {
return nil
}
// write response into response object
if err := interpretBody(res, resObj); err != nil {
return err
}
return nil
}
func (api *Api) BroadcastData(data string) (string, error) {
res := &BroadcastDataResponse{}
err := api.do(
http.MethodPost,
routeBroadcastData,
&BroadcastDataRequest{Data: data},
res,
)
if err != nil {
return "", err
}
return res.Hash, nil
}
// GetNeighbors gets the chosen/accepted neighbors.
// If knownPeers is set, also all known peers to the node are returned additionally.
func (api *Api) GetNeighbors(knownPeers bool) (*GetNeighborResponse, error) {
res := &GetNeighborResponse{}
err := api.do(
http.MethodGet,
func() string {
if !knownPeers {
return routeGetNeighbors
}
return fmt.Sprintf("%s?known=1", routeGetNeighbors)
}(),
nil,
res,
)
if err != nil {
return nil, err
}
return res, nil
}
package api
type BroadcastDataResponse struct {
Hash string `json:"hash,omitempty"`
Error string `json:"error,omitempty"`
}
type BroadcastDataRequest struct {
Data string `json:"data"`
}
type GetMessageByHashResponse struct {
Messages []Message `json:"messages,omitempty"`
Error string `json:"error,omitempty"`
}
type GetMessageByHashRequest struct {
Hashes []string `json:"hashes"`
}
type Message struct {
MessageId string `json:"messageId,omitempty"`
TrunkTransactionId string `json:"trunkTransactionId,omitempty"`
BranchTransactionId string `json:"branchTransactionId,omitempty"`
IssuerPublicKey string `json:"issuerPublicKey,omitempty"`
IssuingTime string `json:"issuingTime,omitempty"`
SequenceNumber uint64 `json:"sequenceNumber,omitempty"`
Payload string `json:"payload,omitempty"`
Signature string `json:"signature,omitempty"`
}
type GetNeighborResponse struct {
KnownPeers []Neighbor `json:"known,omitempty"`
Chosen []Neighbor `json:"chosen"`
Accepted []Neighbor `json:"accepted"`
Error string `json:"error,omitempty"`
}
type Neighbor struct {
ID string `json:"id"` // comparable node identifier
PublicKey string `json:"publicKey"` // public key used to verify signatures
Services []PeerService `json:"services,omitempty"`
}
type PeerService struct {
ID string `json:"id"` // ID of the service
Address string `json:"address"` // network address of the service
}
......@@ -3,13 +3,11 @@ package framework
import (
"fmt"
"math/rand"
"net/http"
"time"
)
type Framework struct {
peers []*Peer
httpClient *http.Client
}
func New() *Framework {
......@@ -17,7 +15,6 @@ func New() *Framework {
f := &Framework{
peers: getAvailablePeers(),
httpClient: &http.Client{Timeout: 30 * time.Second},
}
if len(f.peers) == 0 {
......@@ -37,8 +34,7 @@ func (f *Framework) waitForAutopeering() {
for maxTries > 0 {
for _, p := range f.peers {
resp := new(GetNeighborResponse)
if err := f.HttpGet(p, apiGetNeighbors, resp); err != nil {
if resp, err := p.GetNeighbors(false); err != nil {
fmt.Printf("request error: %v\n", err)
} else {
p.SetNeighbors(resp.Chosen, resp.Accepted)
......@@ -67,16 +63,6 @@ func (f *Framework) waitForAutopeering() {
panic("Peering not successful.")
}
func (f *Framework) HttpGet(peer *Peer, endpoint string, target interface{}) error {
url := fmt.Sprintf("%s%s", peer.api, endpoint)
return getJson(f.httpClient, url, target)
}
func (f *Framework) HttpPost(peer *Peer, endpoint string, requestBody interface{}, responseBody interface{}) error {
url := fmt.Sprintf("%s%s", peer.api, endpoint)
return postJson(f.httpClient, url, requestBody, responseBody)
}
func (f *Framework) Peers() []*Peer {
return f.peers
}
......
......@@ -8,5 +8,4 @@ const (
autopeeringMinimumNeighbors = 2
apiPort = "8080"
apiGetNeighbors = "getNeighbors"
)
......@@ -3,37 +3,41 @@ package framework
import (
"fmt"
"net"
"net/http"
"time"
"github.com/iotaledger/goshimmer/tools/integration-tests/tester/api"
)
type Peer struct {
name string
ip net.IP
api string
chosen []Neighbor
accepted []Neighbor
*api.Api
chosen []api.Neighbor
accepted []api.Neighbor
}
func NewPeer(name string, ip net.IP) *Peer {
return &Peer{
name: name,
ip: ip,
api: getWebApi(ip),
Api: api.New(getWebApiBaseUrl(ip), http.Client{Timeout: 30 * time.Second}),
}
}
func (p *Peer) String() string {
return fmt.Sprintf("Peer:{%s, %s, %s, %d}", p.name, p.ip.String(), p.api, p.TotalNeighbors())
return fmt.Sprintf("Peer:{%s, %s, %s, %d}", p.name, p.ip.String(), p.BaseUrl, p.TotalNeighbors())
}
func (p *Peer) TotalNeighbors() int {
return len(p.chosen) + len(p.accepted)
}
func (p *Peer) SetNeighbors(chosen, accepted []Neighbor) {
p.chosen = make([]Neighbor, len(chosen))
func (p *Peer) SetNeighbors(chosen, accepted []api.Neighbor) {
p.chosen = make([]api.Neighbor, len(chosen))
copy(p.chosen, chosen)
p.accepted = make([]Neighbor, len(accepted))
p.accepted = make([]api.Neighbor, len(accepted))
copy(p.accepted, accepted)
}
......@@ -60,24 +64,6 @@ func getAvailablePeers() (peers []*Peer) {
return
}
func getWebApi(ip net.IP) string {
return fmt.Sprintf("http://%s:%s/", ip.String(), apiPort)
}
type GetNeighborResponse struct {
KnownPeers []Neighbor `json:"known,omitempty"`
Chosen []Neighbor `json:"chosen"`
Accepted []Neighbor `json:"accepted"`
Error string `json:"error,omitempty"`
}
type Neighbor struct {
ID string `json:"id"` // comparable node identifier
PublicKey string `json:"publicKey"` // public key used to verify signatures
Services []PeerService `json:"services,omitempty"`
}
type PeerService struct {
ID string `json:"id"` // ID of the service
Address string `json:"address"` // network address of the service
func getWebApiBaseUrl(ip net.IP) string {
return fmt.Sprintf("http://%s:%s", ip.String(), apiPort)
}
package framework
import (
"bytes"
"encoding/json"
"net/http"
)
func getJson(client *http.Client, url string, responseBody interface{}) error {
r, err := client.Get(url)
if err != nil {
return err
}
defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(responseBody)
}
func postJson(client *http.Client, url string, requestBody interface{}, responseBody interface{}) error {
buf := new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(requestBody)
if err != nil {
return err
}
response, err := client.Post(url, "application/json", buf)
if err != nil {
return err
}
defer response.Body.Close()
return json.NewDecoder(response.Body).Decode(responseBody)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment