Skip to content
Snippets Groups Projects
Commit f9c187ea authored by Jake Taylor's avatar Jake Taylor
Browse files

add restlike connection impl

parent 9adbcd08
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"
)
// processor is the reception handler for a RestServer
type connectReceiver struct {
conn connect.Connection
endpoints *restlike.Endpoints
}
// Hear handles connect.Connection message reception for a RestServer
// Automatically responds to invalid endpoint requests
func (c connectReceiver) 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 = connRespond(cb(newMessage), c.conn)
} else {
// If no callback, automatically send an error response
respondErr = connRespond(&restlike.Message{Error: err.Error()}, c.conn)
}
if respondErr != nil {
jww.ERROR.Printf("Unable to singleRespond to request: %+v", err)
}
}
// connRespond to connect.Connection with the given Message
func connRespond(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 and timeout
_, _, _, 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 connectReceiver) Name() string {
return "Restlike"
}
////////////////////////////////////////////////////////////////////////////////
// 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 (
"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 := connectReceiver{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,6 +12,7 @@ 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"
......@@ -19,14 +20,14 @@ import (
// processor is the reception handler for a RestServer
type singleReceiver struct {
endpoints *Endpoints
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) {
// 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,17 +4,18 @@
// 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)}
ep := restlike.NewEndpoints()
receiver := singleReceiver{endpoints: ep}
testReq := single.BuildTestRequest(make([]byte, 0), t)
......
......@@ -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,7 +32,7 @@ func TestSingleResponse_Callback(t *testing.T) {
Error: "",
}
response := singleResponse{cb}
response := response{cb}
testPayload, err := proto.Marshal(testMessage)
if err != nil {
......@@ -57,11 +58,11 @@ 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}
response := response{cb}
response.Callback(nil, receptionID.EphemeralIdentity{}, nil, errors.New("test"))
......@@ -77,11 +78,11 @@ 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}
response := response{cb}
response.Callback([]byte("test"), receptionID.EphemeralIdentity{}, nil, nil)
......
......@@ -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})
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