Establishes a persistent bi-directional communication channel using mTLS and websockets.
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
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"})
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{})
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.
- Improve Tests
- Return a response status
- Add a Blocking DialOption
The release process for this package is based off smartcontractkit/releng-go-lib. This release process leverages changesets.
- Install
pnpm
(pnpm.io/installation) - Run
pnpm install
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.
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.