genrpc

package module
v0.0.0-...-64a1e9d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 18, 2023 License: BSD-3-Clause Imports: 14 Imported by: 0

README

go-libp2p-genrpc

Go Go Reference Coverage Status

Generics based RPC framework using libp2p

go-libp2p-genrpc is a small library which provides a type-safe API to write go functions as RPCs over libp2p transport.

The goal was to see if we can provide similar functionality as go-libp2p-gorpc but using generics in a type-safe manner. The result is a slightly different API which is more verbose. Additional functionality like writing middlewares is also possible.

Install

This module can be installed with go get:

> go get github.com/plexsysio/go-libp2p-genrpc

Usage

Check docs.

There are also some examples.

Documentation

Overview

Package genrpc is heavily inspired by the go-libp2p-gorpc package. It tries to provide a similar API to the go-libp2p-gorpc package, but with the use of generics. With generics we can provide a more type-safe API.

Aim was to see if we can use generics to remove all the reflection code and still provide a similar API. The result is a bit more verbose, but it is type-safe. The API is also a bit different, but it is still easy to use and inspired by the net/http package.

Differences with go-libp2p-gorpc:

  • Each service and rpc has its own protocol ID. This allows us to support multiple versions of the same service. The protocol ID is of the form: /<service-name>/<version>/<rpc>. The version is a semantic version string. The rpc is the string path which is used to register the handler. This could be a method name or a path describing the method. As each rpc has its own protocol ID, it also allows us to use the underlying libp2p observability (for eg. resource manager dashboards etc).
  • Middlewares can be registered per rpc. This allows us to have different middlewares for different rpcs.
  • There are four types of RPCs: 1. Request-Response: The client sends a request and waits for a response. 2. Request-Stream: The client sends a request and gets a stream of responses. 3. Stream-Request: The client sends a stream of requests and gets a response. 4. Stream-Stream: The client sends a stream of requests and gets a stream of responses.

Users have to write the methods and register them with the server. Each method can be registered with a different path. The path is used to register the handler. The path can be a method name or a path describing the method. Each type of RPC has a specific signature. The method and object types dont need to be exported. Only the exported fields in the object are sent in the message. It uses msgpack for serialization, so go structs with exported fields can be used. The method can return an error. The error is sent back to the client as a response. Streams are mapped to channels. Closing the channel will close the stream. Corresponding to each RPC, there is a client method. The client method takes the same arguments as the server method. On the client side as well, streams are mapped to channels. Closing the channel will close the stream. Only thing required to start the server or client is a libp2p host. The server and client will use the host to start listening and dialing. Typical workflow:

  1. Create a host.
  2. Create a mux.
  3. Register the methods with the mux.
  4. Register the mux with the host.
  5. Start the host.

On the client side:

  1. Create a host.
  2. Create a request.
  3. Execute the request with peer ID.

The peer address information should be added to the Peerstore of the host prior to executing the request.

Check examples for more details.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BidirStream

func BidirStream[Req any, Resp any](
	handlerFn func(context.Context, <-chan *Req) (<-chan *Resp, error),
	mws ...StreamMiddleware,
) network.StreamHandler

BidirStream provides a network.StreamHandler wrapped with message handling required for bidirectional streaming APIs. Each API can be wrapped with middlewares to provide additional functionality. The middlewares are applied in the order they are passed. The Req and Resp types are the request and response types respectively. Currently the request and response types are expected to be go structs that can be marshaled and unmarshaled using msgpack. This means only exported fields will be sent. The handler function is expected to read from the request channel untill it is closed. The handler can only return error on start. Any error during the operation should be communicated via the response channel. The handler is expected to close the response channel when the operation is complete. Bidirectional streaming messages can be tricky for the underlying transport. Ideally the client and the handler should send and receive messages in a synchronized manner. If the client sends a message and the handler does not read it, the client will block. If the handler sends a message and the client does not read it, the handler will block.

func DownStream

func DownStream[Req any, Resp any](
	handler func(context.Context, *Req) (<-chan *Resp, error),
	mws ...UnaryMiddleware,
) network.StreamHandler

DownStream provides a network.StreamHandler wrapped with the message handling required for streaming response type APIs. Each API can be wrapped with middlewares to provide additional functionality. The middlewares are applied in the order they are passed. The Req and Resp types are the request and response types respectively. Currently the request and response types are expected to be go structs that can be marshaled and unmarshaled using msgpack. This means only exported fields will be sent. The handler function is expected to close the response channel when the operation is complete. The handler can only return error on start. Any error during the operation should be communicated via the response channel.

func GetPeerID

func GetPeerID(ctx context.Context) (peer.ID, error)

GetPeerID returns the peer id of the remote peer from the context.

func Register

func Register(h Libp2pHost, mux *Mux)

Register is used to register the Mux on a libp2p host.

func SetPeerID

func SetPeerID(ctx context.Context, peerID peer.ID) context.Context

SetPeerID sets the peer id of the remote peer in the context.

func Unary

func Unary[Req any, Resp any](
	handlerFn func(context.Context, *Req) (*Resp, error),
	mws ...UnaryMiddleware,
) network.StreamHandler

Unary function provides a network.StreamHandler wrapped with message handling for unary request type APIs. Each API can be wrapped with middlewares to provide additional functionality. The middlewares are applied in the order they are passed. The Req and Resp types are the request and response types respectively. Currently the request and response types are expected to be go structs that can be marshaled and unmarshaled using msgpack. This means only exported fields will be sent. The error returned by the handler is sent back to the client as a new message.

func UpStream

func UpStream[Req any, Resp any](
	handlerFn func(context.Context, <-chan *Req) (*Resp, error),
	mws ...StreamMiddleware,
) network.StreamHandler

UpStream function provides a network.StreamHandler wrapped with message handling for streaming request type APIs. Each API can be wrapped with middlewares to provide additional functionality. The middlewares are applied in the order they are passed. The Req and Resp types are the request and response types respectively. Currently the request and response types are expected to be go structs that can be marshaled and unmarshaled using msgpack. This means only exported fields will be sent. The error returned by the handler is sent back to the client as a new message. The handler function is expected to read from the request channel till it is closed or the context is canceled. The handler function is expected to send the response back at the end of the operation. An error can be sent earlier if the operation fails. The client will receive the error and the stream will be closed. Client should send the headers only in the first message. If they are sent in subsequent messages, they will be ignored.

Types

type BidirStreamRequest

type BidirStreamRequest[Req any, Resp any] interface {
	// Header interface provides functionality to add headers to the requests
	Header
	// Execute performs the bidirectional RPC. The request is tied to a service and a
	// a path. The client can execute this request on any peer that supports
	// the service and the path. The request can be reused. The client can close the
	// request channel to signal to the server that it is done sending requests. The
	// server can send error in the start. If the server sends error, the response
	// channel will be nil. The response channel is closed by the server
	// when it is done sending responses. The client can send headers along with the
	// request. Execute blocks untill the first response is received, so client is
	// expected to start pumping the requests before calling Execute.
	Execute(context.Context, peer.ID, <-chan *Req) (<-chan *Resp, error)
}

BidirStreamRequest is used to call a bidirectional RPC registered on the server. The client can send a stream of requests and receive a stream of responses. The client can send headers along with the request. The request and response types are expected to be same as the ones registered on the server.

func NewBidirStreamReq

func NewBidirStreamReq[Req any, Resp any](
	h Streamer,
	service string,
	args ...string,
) BidirStreamRequest[Req, Resp]

NewBidirStreamReq initializes a new BidirStreamRequest

type DownStreamRequest

type DownStreamRequest[Req any, Resp any] interface {
	// Header interface provides functionality to add headers to the requests
	Header
	// Execute performs the down-stream RPC. The request is tied to a service and a
	// a path. The client can execute this request on any peer that supports
	// the service and the path. The request can be reused. The response channel
	// is closed by the server when it is done sending responses.
	Execute(context.Context, peer.ID, *Req) (<-chan *Resp, error)
}

DownStreamRequest is used to call a down-stream RPC registered on the server. The client is expected to send a single request and wait for a stream of responses. If the error is non-nil, the client can expect no more responses. The client can send headers along with the request. The request and response types are expected to be same as the ones registered on the server.

func NewDownStreamReq

func NewDownStreamReq[Req any, Resp any](
	h Streamer,
	service string,
	args ...string,
) DownStreamRequest[Req, Resp]

NewDownStreamReq initializes a new DownStreamRequest. The service and path are required to be passed as arguments. The version is optional and defaults to 0.0.0. Path is the path of the RPC. The path is used to identify the RPC on the server. The path is the first argument in the args. The version is the second argument in the args.

type Header interface {
	SetHeader(key string, value []byte)
}

Header interface provides functionality to add headers to the requests

type Libp2pHost

type Libp2pHost interface {
	SetStreamHandlerMatch(protocol.ID, func(protocol.ID) bool, network.StreamHandler)
}

Libp2pHost is the interface required from libp2p.Host. This is done for mocking in the tests.

type Mux

type Mux struct {
	// contains filtered or unexported fields
}

Mux provides a multiplexer per service and version. It can be used to register different stream handler endpoints.

func New

func New(svcName string) *Mux

New returns a Mux which uses the default semantic version. The default version is 0.0.0.

func NewWithVersion

func NewWithVersion(svcName, version string) (*Mux, error)

NewWithVersion returns a Mux which uses the provided semantic version. The version must be a valid semantic version string.

func (*Mux) Handle

func (m *Mux) Handle(path string, handler network.StreamHandler)

Handle is used to register stream handler for a particular path. If a path is already registered, it will overwrite the handler for that path.

type StreamHandlerFunc

type StreamHandlerFunc func(context.Context, map[string][]byte, <-chan []byte) error

StreamHandlerFunc is the signature of the handler function for streaming RPCs. It provides access to the headers and the raw request stream. This type allows us to write middlewares for streaming RPCs.

type StreamMiddleware

type StreamMiddleware func(StreamHandlerFunc) StreamHandlerFunc

StreamMiddleware is the signature of the middleware function for streaming RPCs.

type Streamer

type Streamer interface {
	NewStream(context.Context, peer.ID, ...protocol.ID) (network.Stream, error)
}

Streamer interface provides functionality to open a new stream. This is main functionality that is required by the RPCs to perform the RPCs.

type UnaryHandlerFunc

type UnaryHandlerFunc func(context.Context, map[string][]byte, []byte) error

UnaryHandlerFunc is the signature of the handler function for unary RPCs. It provides access to the headers and the raw request. This type allows us to write middlewares for unary RPCs.

type UnaryMiddleware

type UnaryMiddleware func(UnaryHandlerFunc) UnaryHandlerFunc

UnaryMiddleware is the signature of the middleware function for unary RPCs.

type UnaryRequest

type UnaryRequest[Req any, Resp any] interface {
	// Header interface provides functionality to add headers to the requests
	Header
	// Execute performs the unary RPC. The request is tied to a service and a
	// a path. The client can execute this request on any peer that supports
	// the service and the path. The request can be reused.
	Execute(context.Context, peer.ID, *Req) (*Resp, error)
}

UnaryRequest is used to call a unary RPC registered on the server. The client is expected to send a single request and wait for a single response. The server can send a single response or an error. The client can send headers along with the request. The request and response types are expected to be same as the ones registered on the server.

func NewUnaryReq

func NewUnaryReq[Req any, Resp any](
	h Streamer,
	service string,
	args ...string,
) UnaryRequest[Req, Resp]

NewUnaryReq initializes a new UnaryRequest. The service and path are required to be passed as arguments. The version is optional and defaults to 0.0.0. Path is the path of the RPC. The path is used to identify the RPC on the server. The path is the first argument in the args. The version is the second argument in the args.

type UpStreamRequest

type UpStreamRequest[Req any, Resp any] interface {
	// Header interface provides functionality to add headers to the requests
	Header
	// Execute performs the up-stream RPC. The request is tied to a service and a
	// a path. The client can execute this request on any peer that supports
	// the service and the path. The request can be reused. Execute blocks until
	// the server sends a response or an error, so the client is expected to asynchronously
	// pump the requests.
	Execute(context.Context, peer.ID, <-chan *Req) (*Resp, error)
}

UpStreamRequest is used to call a up-stream RPC registered on the server. The client is expected to send a stream of requests and wait for a single response. The server can send a single response or an error. The client can send headers along with the request. The request and response types are expected to be same as the ones registered on the server.

func NewUpStreamReq

func NewUpStreamReq[Req any, Resp any](
	h Streamer,
	service string,
	args ...string,
) UpStreamRequest[Req, Resp]

NewUpStreamReq initializes a new UpStreamRequest. The service and path are required to be passed as arguments. The version is optional and defaults to 0.0.0. Path is the path of the RPC. The path is used to identify the RPC on the server. The path is the first argument in the args. The version is the second argument in the args.

Directories

Path Synopsis
examples
gen
v1

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL