# README
Websockets RPC
Establishes a persistent bi-directional communication channel using mTLS and websockets.
Set up
In order to generate a service definition you will need the wsrpc protoc plugin.
Build the protoc plugin
cd cmd/protoc-gen-go-wsrpc
go build
Place the resulting binary in your GOPATH.
In the directory containing your protobuf service definition, run:
protoc --go_out=. --go_opt=paths=source_relative \
--go-wsrpc_out=. \
--go-wsrpc_opt=paths=source_relative yourproto.proto
This will generate the service definitions in *_wsrpc.pb.go
Usage
Client to Server RPC
Implement handlers for the server
type pingServer struct {}
func (s *pingServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PingResponse, error) {
// Extracts the connection client's public key.
// You can use this to identify the client
p, ok := peer.FromContext(ctx)
if !ok {
return nil, errors.New("could not extract public key")
}
pubKey := p.PublicKey
fmt.Println(pubKey)
return &pb.PingResponse{
Body: "Pong",
}, nil
}
Initialize a server with the server's private key and a slice of allowable public keys.
lis, err := net.Listen("tcp", "127.0.0.1:1337")
if err != nil {
log.Fatalf("[MAIN] failed to listen: %v", err)
}
s := wsrpc.NewServer(wsrpc.Creds(privKey, pubKeys))
// Register the ping server implementation with the wsrpc server
pb.RegisterPingServer(s, &pingServer{})
s.Serve(lis)
Initialize a client with the client's private key and the server's public key
conn, err := wsrpc.Dial("127.0.0.1:1338", wsrpc.WithTransportCreds(privKey, serverPubKey))
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
// Initialize a new wsrpc client caller
// This is used to called RPC methods on the server
c := pb.NewPingClient(conn)
c.Ping(context.Background(), &pb.Ping{Body: "Ping"})
Server to Client RPC
Implement handlers for the client
type pingClient struct{}
func (c *pingClient) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PingResponse, error) {
return &pb.PingResponse{
Body: "Pong",
}, nil
}
Initialize a server with the server's private key and a slice of allowable public keys.
lis, err := net.Listen("tcp", "127.0.0.1:1337")
if err != nil {
log.Fatalf("[MAIN] failed to listen: %v", err)
}
s := wsrpc.NewServer(wsrpc.Creds(privKey, pubKeys))
c := pb.NewPingClient(s)
s.Serve(lis)
// Call the RPC method with the pub key so we know which connection to send it to
// otherwise it will error.
ctx := peer.NewCallContext(context.Background(), pubKey)
c.Ping(ctx, &pb.PingRequest{Body: "Ping"})
Initialize a client with the client's private key and the server's public key
conn, err := wsrpc.Dial("127.0.0.1:1337", wsrpc.WithTransportCreds(privKey, serverPubKey))
if err != nil {
log.Fatalln(err)
}
defer conn.Close()
// Initialize RPC call handlers on the client connection
pb.RegisterPingServer(conn, &pingClient{})
Example
You can run a simple example where both the client and server implement a Ping service, and perform RPC calls to each other every 5 seconds.
- Run the server in
examples/simple/server
withgo run main.go
- Run a client (Alice) in
examples/simple/server
withgo run main.go 0
- Run a client (Bob) in
examples/simple/server
withgo run main.go 1
- Run a invalid client (Charlie) in
examples/simple/server
withgo run main.go 2
. The server will reject this connection.
While the client's are connected, kill the server and see the client's enter a backoff retry loop. Start the server again and they will reconnect.
TODO
- Improve Tests
- Return a response status
- Add a Blocking DialOption
Release Process
The release process for this package is based off smartcontractkit/releng-go-lib. This release process leverages changesets.
Setup
- Install
pnpm
(pnpm.io/installation) - Run
pnpm install
General usage
During a regular change, include a changeset
file in the PR by running pnpm changeset
. It will prompt you for a description of the change, and whether the change is a major/minor/patch version bump.
This will create a file in the .changeset
directory. When a release is created this file will be "consumed", applying the version bump, and including the change's description to the release's notes.
Creating a release
When changeset
files are present on the main branch, there will be a persistent pull request open. This pull request "consumes" the changeset files, and bumps the version in package.json
.
When this PR is merged, the automated workflow running against main
will create a tag and release.