https://github.com/smartcontractkit/wsrpc
A bi-directional Websockets RPC library
https://github.com/smartcontractkit/wsrpc
Last synced: about 2 months ago
JSON representation
A bi-directional Websockets RPC library
- Host: GitHub
- URL: https://github.com/smartcontractkit/wsrpc
- Owner: smartcontractkit
- License: mit
- Created: 2021-05-12T10:33:02.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2025-03-20T16:23:33.000Z (3 months ago)
- Last Synced: 2025-04-19T08:10:37.340Z (about 2 months ago)
- Language: Go
- Homepage:
- Size: 3.38 MB
- Stars: 11
- Watchers: 107
- Forks: 5
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
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
```go
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.PublicKeyfmt.Println(pubKey)
return &pb.PingResponse{
Body: "Pong",
}, nil
}
```Initialize a server with the server's private key and a slice of allowable public keys.
```go
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
```go
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
```go
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.
```go
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
```go
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
- [x] Add a Blocking DialOption## Release Process
The release process for this package is based off [smartcontractkit/releng-go-lib](https://github.com/smartcontractkit/releng-go-lib). This release process leverages [changesets](https://github.com/changesets/changesets).
### Setup
- Install `pnpm` ([pnpm.io/installation](https://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.