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:
- Create a host.
- Create a mux.
- Register the methods with the mux.
- Register the mux with the host.
- Start the host.
On the client side:
- Create a host.
- Create a request.
- 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 ¶
- func BidirStream[Req any, Resp any](handlerFn func(context.Context, <-chan *Req) (<-chan *Resp, error), ...) network.StreamHandler
- func DownStream[Req any, Resp any](handler func(context.Context, *Req) (<-chan *Resp, error), ...) network.StreamHandler
- func GetPeerID(ctx context.Context) (peer.ID, error)
- func Register(h Libp2pHost, mux *Mux)
- func SetPeerID(ctx context.Context, peerID peer.ID) context.Context
- func Unary[Req any, Resp any](handlerFn func(context.Context, *Req) (*Resp, error), mws ...UnaryMiddleware) network.StreamHandler
- func UpStream[Req any, Resp any](handlerFn func(context.Context, <-chan *Req) (*Resp, error), ...) network.StreamHandler
- type BidirStreamRequest
- type DownStreamRequest
- type Header
- type Libp2pHost
- type Mux
- type StreamHandlerFunc
- type StreamMiddleware
- type Streamer
- type UnaryHandlerFunc
- type UnaryMiddleware
- type UnaryRequest
- type UpStreamRequest
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 Register ¶
func Register(h Libp2pHost, mux *Mux)
Register is used to register the Mux on a libp2p host.
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 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 NewWithVersion ¶
NewWithVersion returns a Mux which uses the provided semantic version. The version must be a valid semantic version string.
type StreamHandlerFunc ¶
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 ¶
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.