Skip to content
Snippets Groups Projects
Commit 20f588be authored by Jake Taylor's avatar Jake Taylor :lips:
Browse files

Merge branch 'XX-3916/RestlikeConn' into 'restructure'

Xx 3916/restlike conn

See merge request !220
parents 124438ec 36792e39
No related branches found
No related tags found
3 merge requests!510Release,!220Xx 3916/restlike conn,!207WIP: Client Restructure
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package connect
import (
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/connect"
"gitlab.com/elixxir/client/e2e"
"gitlab.com/elixxir/client/e2e/receive"
"gitlab.com/elixxir/client/restlike"
"google.golang.org/protobuf/proto"
)
// receiver is the reception handler for a RestServer
type receiver struct {
conn connect.Connection
endpoints *restlike.Endpoints
}
// Hear handles connect.Connection message reception for a RestServer
// Automatically responds to invalid endpoint requests
func (c receiver) Hear(item receive.Message) {
// Unmarshal the request payload
newMessage := &restlike.Message{}
err := proto.Unmarshal(item.Payload, newMessage)
if err != nil {
jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err)
return
}
var respondErr error
if cb, err := c.endpoints.Get(restlike.URI(newMessage.GetUri()), restlike.Method(newMessage.GetMethod())); err == nil {
// Send the payload to the proper Callback if it exists and singleRespond with the result
respondErr = respond(cb(newMessage), c.conn)
} else {
// If no callback, automatically send an error response
respondErr = respond(&restlike.Message{Error: err.Error()}, c.conn)
}
if respondErr != nil {
jww.ERROR.Printf("Unable to singleRespond to request: %+v", err)
}
}
// respond to connect.Connection with the given Message
func respond(response *restlike.Message, conn connect.Connection) error {
payload, err := proto.Marshal(response)
if err != nil {
return errors.Errorf("unable to marshal restlike response message: %+v", err)
}
// TODO: Parameterize params
_, _, _, err = conn.SendE2E(catalog.XxMessage, payload, e2e.GetDefaultParams())
if err != nil {
return errors.Errorf("unable to send restlike response message: %+v", err)
}
return nil
}
// Name is used for debugging
func (c receiver) Name() string {
return "Restlike"
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package connect
import (
"gitlab.com/elixxir/client/e2e/receive"
"gitlab.com/elixxir/client/restlike"
"testing"
)
// Test failure of proto unmarshal
func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) {
ep := restlike.NewEndpoints()
r := receiver{endpoints: ep}
r.Hear(receive.Message{Payload: []byte("test")})
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package connect
import (
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/connect"
"gitlab.com/elixxir/client/e2e"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/xx_network/crypto/csprng"
"google.golang.org/protobuf/proto"
)
// Request allows for making REST-like requests to a RestServer using connect.Connection
// Can be used as stateful or declared inline without state
type Request struct {
Net connect.Connection
Rng csprng.Source
E2eGrp *cyclic.Group
}
// Request provides several Method of sending Data to the given URI
// and blocks until the Message is returned
func (s *Request) Request(method restlike.Method, path restlike.URI,
content restlike.Data, headers *restlike.Headers, e2eParams e2e.Params) (*restlike.Message, error) {
// Build the Message
newMessage := &restlike.Message{
Content: content,
Headers: headers,
Method: uint32(method),
Uri: string(path),
}
msg, err := proto.Marshal(newMessage)
if err != nil {
return nil, err
}
// Build callback for the response
signalChannel := make(chan *restlike.Message, 1)
cb := func(msg *restlike.Message) {
signalChannel <- msg
}
s.Net.RegisterListener(catalog.XxMessage, response{responseCallback: cb})
// Transmit the Message
_, _, _, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams)
if err != nil {
return nil, err
}
// Block waiting for single-use response
jww.DEBUG.Printf("Restlike waiting for connect response from %s...",
s.Net.GetPartner().PartnerId().String())
newResponse := <-signalChannel
jww.DEBUG.Printf("Restlike connect response received from %s",
s.Net.GetPartner().PartnerId().String())
return newResponse, nil
}
// AsyncRequest provides several Method of sending Data to the given URI
// and will return the Message to the given Callback when received
func (s *Request) AsyncRequest(method restlike.Method, path restlike.URI,
content restlike.Data, headers *restlike.Headers, cb restlike.RequestCallback, e2eParams e2e.Params) error {
// Build the Message
newMessage := &restlike.Message{
Content: content,
Headers: headers,
Method: uint32(method),
Uri: string(path),
}
msg, err := proto.Marshal(newMessage)
if err != nil {
return err
}
// Build callback for the response
s.Net.RegisterListener(catalog.XxMessage, response{responseCallback: cb})
// Transmit the Message
_, _, _, err = s.Net.SendE2E(catalog.XxMessage, msg, e2eParams)
return err
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package connect
import (
"gitlab.com/elixxir/client/e2e/receive"
"gitlab.com/elixxir/client/restlike"
"google.golang.org/protobuf/proto"
)
// response is the response handler for a Request
type response struct {
responseCallback restlike.RequestCallback
}
// Hear handles for connect.Connection message responses for a Request
func (r response) Hear(item receive.Message) {
newMessage := &restlike.Message{}
// Unmarshal the payload
err := proto.Unmarshal(item.Payload, newMessage)
if err != nil {
newMessage.Error = err.Error()
}
// Send the response payload to the responseCallback
r.responseCallback(newMessage)
}
// Name is used for debugging
func (r response) Name() string {
return "Restlike"
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package connect
import (
"bytes"
"gitlab.com/elixxir/client/e2e/receive"
"gitlab.com/elixxir/client/restlike"
"google.golang.org/protobuf/proto"
"testing"
"time"
)
// Test happy path
func TestSingleResponse_Callback(t *testing.T) {
resultChan := make(chan *restlike.Message, 1)
cb := func(input *restlike.Message) {
resultChan <- input
}
testPath := "test/path"
testMethod := restlike.Get
testMessage := &restlike.Message{
Content: []byte("test"),
Headers: nil,
Method: uint32(testMethod),
Uri: testPath,
Error: "",
}
r := response{cb}
testPayload, err := proto.Marshal(testMessage)
if err != nil {
t.Errorf(err.Error())
}
r.Hear(receive.Message{Payload: testPayload})
select {
case result := <-resultChan:
if result.Uri != testPath {
t.Errorf("Mismatched uri")
}
if result.Method != uint32(testMethod) {
t.Errorf("Mismatched method")
}
if !bytes.Equal(testMessage.Content, result.Content) {
t.Errorf("Mismatched content")
}
case <-time.After(3 * time.Second):
t.Errorf("Test SingleResponse timed out!")
}
}
// Test proto error path
func TestSingleResponse_Callback_ProtoErr(t *testing.T) {
resultChan := make(chan *restlike.Message, 1)
cb := func(input *restlike.Message) {
resultChan <- input
}
r := response{cb}
r.Hear(receive.Message{Payload: []byte("test")})
select {
case result := <-resultChan:
if len(result.Error) == 0 {
t.Errorf("Expected cb proto error!")
}
case <-time.After(3 * time.Second):
t.Errorf("Test SingleResponse proto error timed out!")
}
}
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 Privategrity Corporation /
// /
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package connect
import (
"gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/cmix"
"gitlab.com/elixxir/client/connect"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/elixxir/crypto/fastRNG"
"gitlab.com/xx_network/primitives/id"
)
// Server implements the RestServer interface using connect.Connection
type Server struct {
receptionId *id.ID
endpoints *restlike.Endpoints
}
// NewServer builds a RestServer with connect.Connection and
// the provided arguments, then registers necessary external services
func NewServer(receptionId *id.ID, privKey *cyclic.Int,
rng *fastRNG.StreamGenerator, grp *cyclic.Group, net cmix.Client, p connect.Params) (*Server, error) {
newServer := &Server{
receptionId: receptionId,
endpoints: restlike.NewEndpoints(),
}
// Callback for connection requests
cb := func(conn connect.Connection) {
handler := receiver{endpoints: newServer.endpoints, conn: conn}
conn.RegisterListener(catalog.XxMessage, handler)
}
// Build the connection listener
err := connect.StartServer(cb, receptionId, privKey, rng, grp, net, p)
if err != nil {
return nil, err
}
return newServer, nil
}
// GetEndpoints returns the association of a Callback with
// a specific URI and a variety of different REST Method
func (c *Server) GetEndpoints() *restlike.Endpoints {
return c.endpoints
}
// Close the internal RestServer endpoints and external services
func (c *Server) Close() {
// Clear all internal endpoints
c.endpoints = nil
// TODO: Destroy external services
}
......@@ -4,7 +4,7 @@
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package restlike
package single
import (
"github.com/pkg/errors"
......@@ -12,21 +12,22 @@ import (
"gitlab.com/elixxir/client/cmix"
"gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/cmix/rounds"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/client/single"
"google.golang.org/protobuf/proto"
"time"
)
// processor is the reception handler for a RestServer
type singleReceiver struct {
endpoints *Endpoints
// receiver is the reception handler for a RestServer
type receiver struct {
endpoints *restlike.Endpoints
}
// Callback is the handler for single-use message reception for a RestServer
// Automatically responds to invalid endpoint requests
func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.EphemeralIdentity, rounds []rounds.Round) {
func (s *receiver) Callback(req *single.Request, receptionId receptionID.EphemeralIdentity, rounds []rounds.Round) {
// Unmarshal the request payload
newMessage := &Message{}
newMessage := &restlike.Message{}
err := proto.Unmarshal(req.GetPayload(), newMessage)
if err != nil {
jww.ERROR.Printf("Unable to unmarshal restlike message: %+v", err)
......@@ -34,20 +35,20 @@ func (s *singleReceiver) Callback(req *single.Request, receptionId receptionID.E
}
var respondErr error
if cb, err := s.endpoints.Get(URI(newMessage.GetUri()), Method(newMessage.GetMethod())); err == nil {
// Send the payload to the proper Callback if it exists and respond with the result
respondErr = respond(cb(newMessage), req)
if cb, err := s.endpoints.Get(restlike.URI(newMessage.GetUri()), restlike.Method(newMessage.GetMethod())); err == nil {
// Send the payload to the proper Callback if it exists and singleRespond with the result
respondErr = singleRespond(cb(newMessage), req)
} else {
// If no callback, automatically send an error response
respondErr = respond(&Message{Error: err.Error()}, req)
respondErr = singleRespond(&restlike.Message{Error: err.Error()}, req)
}
if respondErr != nil {
jww.ERROR.Printf("Unable to respond to request: %+v", err)
jww.ERROR.Printf("Unable to singleRespond to request: %+v", err)
}
}
// respond to a single.Request with the given Message
func respond(response *Message, req *single.Request) error {
// singleRespond to a single.Request with the given Message
func singleRespond(response *restlike.Message, req *single.Request) error {
payload, err := proto.Marshal(response)
if err != nil {
return errors.Errorf("unable to marshal restlike response message: %+v", err)
......
......@@ -4,21 +4,22 @@
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package restlike
package single
import (
"gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/client/single"
"testing"
)
// Test failure of proto unmarshal
func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) {
ep := &Endpoints{endpoints: make(map[URI]map[Method]Callback)}
receiver := singleReceiver{endpoints: ep}
ep := restlike.NewEndpoints()
r := receiver{endpoints: ep}
testReq := single.BuildTestRequest(make([]byte, 0), t)
receiver.Callback(testReq, receptionID.EphemeralIdentity{}, nil)
r.Callback(testReq, receptionID.EphemeralIdentity{}, nil)
}
// Test happy path
......@@ -43,7 +44,7 @@ func TestSingleReceiver_Callback_FailUnmarshal(t *testing.T) {
// if err != nil {
// t.Errorf(err.Error())
// }
// receiver := singleReceiver{endpoints: ep}
// receiver := receiver{endpoints: ep}
//
// testPayload, err := proto.Marshal(testMessage)
// if err != nil {
......
......@@ -4,11 +4,12 @@
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package restlike
package single
import (
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/client/single"
"gitlab.com/elixxir/crypto/contact"
"gitlab.com/elixxir/crypto/cyclic"
......@@ -16,9 +17,9 @@ import (
"google.golang.org/protobuf/proto"
)
// SingleRequest allows for making REST-like requests to a RestServer using single-use messages
// Request allows for making REST-like requests to a RestServer using single-use messages
// Can be used as stateful or declared inline without state
type SingleRequest struct {
type Request struct {
Net single.Cmix
Rng csprng.Source
E2eGrp *cyclic.Group
......@@ -26,10 +27,10 @@ type SingleRequest struct {
// Request provides several Method of sending Data to the given URI
// and blocks until the Message is returned
func (s *SingleRequest) Request(recipient contact.Contact, method Method, path URI,
content Data, headers *Headers, singleParams single.RequestParams) (*Message, error) {
func (s *Request) Request(recipient contact.Contact, method restlike.Method, path restlike.URI,
content restlike.Data, headers *restlike.Headers, singleParams single.RequestParams) (*restlike.Message, error) {
// Build the Message
newMessage := &Message{
newMessage := &restlike.Message{
Content: content,
Headers: headers,
Method: uint32(method),
......@@ -41,14 +42,14 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, path U
}
// Build callback for the single-use response
signalChannel := make(chan *Message, 1)
cb := func(msg *Message) {
signalChannel := make(chan *restlike.Message, 1)
cb := func(msg *restlike.Message) {
signalChannel <- msg
}
// Transmit the Message
_, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg,
&singleResponse{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp)
&response{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp)
if err != nil {
return nil, err
}
......@@ -63,10 +64,10 @@ func (s *SingleRequest) Request(recipient contact.Contact, method Method, path U
// AsyncRequest provides several Method of sending Data to the given URI
// and will return the Message to the given Callback when received
func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, path URI,
content Data, headers *Headers, cb RequestCallback, singleParams single.RequestParams) error {
func (s *Request) AsyncRequest(recipient contact.Contact, method restlike.Method, path restlike.URI,
content restlike.Data, headers *restlike.Headers, cb restlike.RequestCallback, singleParams single.RequestParams) error {
// Build the Message
newMessage := &Message{
newMessage := &restlike.Message{
Content: content,
Headers: headers,
Method: uint32(method),
......@@ -79,6 +80,6 @@ func (s *SingleRequest) AsyncRequest(recipient contact.Contact, method Method, p
// Transmit the Message
_, _, err = single.TransmitRequest(recipient, catalog.RestLike, msg,
&singleResponse{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp)
&response{responseCallback: cb}, singleParams, s.Net, s.Rng, s.E2eGrp)
return err
}
......@@ -4,22 +4,23 @@
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package restlike
package single
import (
"gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/cmix/rounds"
"gitlab.com/elixxir/client/restlike"
"google.golang.org/protobuf/proto"
)
// processor is the response handler for a Request
type singleResponse struct {
responseCallback RequestCallback
// response is the response handler for a Request
type response struct {
responseCallback restlike.RequestCallback
}
// Callback is the handler for single-use message responses for a Request
func (s *singleResponse) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, rounds []rounds.Round, err error) {
newMessage := &Message{}
func (s *response) Callback(payload []byte, receptionID receptionID.EphemeralIdentity, rounds []rounds.Round, err error) {
newMessage := &restlike.Message{}
// Handle response errors
if err != nil {
......
......@@ -4,12 +4,13 @@
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package restlike
package single
import (
"bytes"
"github.com/pkg/errors"
"gitlab.com/elixxir/client/cmix/identity/receptionID"
"gitlab.com/elixxir/client/restlike"
"google.golang.org/protobuf/proto"
"testing"
"time"
......@@ -17,13 +18,13 @@ import (
// Test happy path
func TestSingleResponse_Callback(t *testing.T) {
resultChan := make(chan *Message, 1)
cb := func(input *Message) {
resultChan := make(chan *restlike.Message, 1)
cb := func(input *restlike.Message) {
resultChan <- input
}
testPath := "test/path"
testMethod := Get
testMessage := &Message{
testMethod := restlike.Get
testMessage := &restlike.Message{
Content: []byte("test"),
Headers: nil,
Method: uint32(testMethod),
......@@ -31,13 +32,13 @@ func TestSingleResponse_Callback(t *testing.T) {
Error: "",
}
response := singleResponse{cb}
r := response{cb}
testPayload, err := proto.Marshal(testMessage)
if err != nil {
t.Errorf(err.Error())
}
response.Callback(testPayload, receptionID.EphemeralIdentity{}, nil, nil)
r.Callback(testPayload, receptionID.EphemeralIdentity{}, nil, nil)
select {
case result := <-resultChan:
......@@ -57,13 +58,13 @@ func TestSingleResponse_Callback(t *testing.T) {
// Test error input path
func TestSingleResponse_Callback_Err(t *testing.T) {
resultChan := make(chan *Message, 1)
cb := func(input *Message) {
resultChan := make(chan *restlike.Message, 1)
cb := func(input *restlike.Message) {
resultChan <- input
}
response := singleResponse{cb}
r := response{cb}
response.Callback(nil, receptionID.EphemeralIdentity{}, nil, errors.New("test"))
r.Callback(nil, receptionID.EphemeralIdentity{}, nil, errors.New("test"))
select {
case result := <-resultChan:
......@@ -77,13 +78,13 @@ func TestSingleResponse_Callback_Err(t *testing.T) {
// Test proto error path
func TestSingleResponse_Callback_ProtoErr(t *testing.T) {
resultChan := make(chan *Message, 1)
cb := func(input *Message) {
resultChan := make(chan *restlike.Message, 1)
cb := func(input *restlike.Message) {
resultChan <- input
}
response := singleResponse{cb}
r := response{cb}
response.Callback([]byte("test"), receptionID.EphemeralIdentity{}, nil, nil)
r.Callback([]byte("test"), receptionID.EphemeralIdentity{}, nil, nil)
select {
case result := <-resultChan:
......
......@@ -4,62 +4,43 @@
// All rights reserved. /
////////////////////////////////////////////////////////////////////////////////
package restlike
package single
import (
"gitlab.com/elixxir/client/catalog"
"gitlab.com/elixxir/client/restlike"
"gitlab.com/elixxir/client/single"
"gitlab.com/elixxir/crypto/cyclic"
"gitlab.com/xx_network/primitives/id"
)
// RestServer allows for clients to make REST-like requests this client
type RestServer interface {
// RegisterEndpoint allows the association of a Callback with
// a specific URI and a variety of different REST Method
RegisterEndpoint(path URI, method Method, cb Callback) error
// UnregisterEndpoint removes the Callback associated with
// a specific URI and REST Method
UnregisterEndpoint(path URI, method Method) error
// Close the internal RestServer endpoints and external services
Close()
}
// singleServer implements the RestServer interface using single-use
type singleServer struct {
// Server implements the RestServer interface using single-use
type Server struct {
receptionId *id.ID
listener single.Listener
endpoints *Endpoints
endpoints *restlike.Endpoints
}
// NewSingleServer builds a RestServer with single-use and
// NewServer builds a RestServer with single-use and
// the provided arguments, then registers necessary external services
func NewSingleServer(receptionId *id.ID, privKey *cyclic.Int, net single.ListenCmix, e2eGrp *cyclic.Group) RestServer {
newServer := &singleServer{
func NewServer(receptionId *id.ID, privKey *cyclic.Int, net single.ListenCmix, e2eGrp *cyclic.Group) *Server {
newServer := &Server{
receptionId: receptionId,
endpoints: &Endpoints{endpoints: make(map[URI]map[Method]Callback)},
endpoints: restlike.NewEndpoints(),
}
newServer.listener = single.Listen(catalog.RestLike, receptionId, privKey,
net, e2eGrp, &singleReceiver{newServer.endpoints})
net, e2eGrp, &receiver{newServer.endpoints})
return newServer
}
// RegisterEndpoint allows the association of a Callback with
// GetEndpoints returns the association of a Callback with
// a specific URI and a variety of different REST Method
func (r *singleServer) RegisterEndpoint(path URI, method Method, cb Callback) error {
return r.endpoints.Add(path, method, cb)
}
// UnregisterEndpoint removes the Callback associated with
// a specific URI and REST Method
func (r *singleServer) UnregisterEndpoint(path URI, method Method) error {
return r.endpoints.Remove(path, method)
func (r *Server) GetEndpoints() *restlike.Endpoints {
return r.endpoints
}
// Close the internal RestServer endpoints and external services
func (r *singleServer) Close() {
func (r *Server) Close() {
// Clear all internal endpoints
r.endpoints = nil
// Destroy external services
......
......@@ -68,6 +68,11 @@ type Endpoints struct {
sync.RWMutex
}
// NewEndpoints returns a new Endpoints object
func NewEndpoints() *Endpoints {
return &Endpoints{endpoints: make(map[URI]map[Method]Callback)}
}
// Add a new Endpoint
// Returns an error if Endpoint already exists
func (e *Endpoints) Add(path URI, method Method, cb Callback) error {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment