Categorygithub.com/smartcontractkit/wsrpc
modulepackage
0.8.1
Repository: https://github.com/smartcontractkit/wsrpc.git
Documentation: pkg.go.dev

# 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.

  1. Run the server in examples/simple/server with go run main.go
  2. Run a client (Alice) in examples/simple/server with go run main.go 0
  3. Run a client (Bob) in examples/simple/server with go run main.go 1
  4. Run a invalid client (Charlie) in examples/simple/server with go 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

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.

# Packages

No description provided by the author
Package connectivity defines connectivity semantics.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author

# Functions

No description provided by the author
DialUniWithContext will blocks until connection is established or context expires.
Dial creates a client connection to the given target.
MarshalProtoMessage returns the protobuf message wire format of v.
NewServer initializes a new wsrpc server.
WriteBufferSize specifies the I/O read buffer size in bytes.
Unmarshal parses the protobuf wire format into v.
WithBlock returns a DialOption which makes caller of Dial blocks until the underlying connection is up.
WithCreds returns a ServerOption that sets credentials for server connections.
WithHealthcheck specifies whether to run a healthcheck endpoint.
returns a ServerOption that sets the healthcheck HTTP read timeout and the server HTTP read timeout.
No description provided by the author
No description provided by the author
WithTransportCredentials returns a DialOption which configures a connection level security credentials (e.g., TLS/SSL).
WithWriteTimeout returns a DialOption which sets the write timeout for a message to be sent on the wire.
No description provided by the author
WriteBufferSize specifies the I/O write buffer size in bytes.

# Variables

No description provided by the author

# Structs

ClientConn represents a virtual connection to a websocket endpoint, to perform and serve RPCs.
MethodDesc represents an RPC service's method specification.
Server is a wsrpc server to both perform and serve RPC requests.
ServiceDesc represents an RPC service's specification.
No description provided by the author

# Interfaces

ClientInterface defines the functions clients need to perform an RPC.
go:generate mockery --name Conn --output ./mocks/ --case=underscore.
DialOption configures how we set up the connection.
go:generate mockery --name Logger --output ./mocks/ --case=underscore.
A ServerOption sets options such as credentials, codec and keepalive parameters, etc.
ServiceRegistrar wraps a single method that supports service registration.

# Type aliases

MethodCallHandler defines a handler which is called when the websocket message contains a response to an RPC call.