gRPC Healthchecking in Rust
Golang
Go provides an implementation gprc_health_v1 of the gRPC Health-checking Protocol proto.
This is easily implemented:
package main
import (
pb "github.com/DazWilkin/.../protos"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
)
func main() {
...
serverOpts := []grpc.ServerOption{}
grpcServer := grpc.NewServer(serverOpts...)
// Register the pb service
pb.RegisterSomeServer(grpcServer, NewServer())
// Register the healthpb service
healthpb.RegisterHealthServer(grpcServer, health.NewServer())
listen, err := net.Listen("tcp", *grpcEndpoint)
if err != nil {
log.Fatal(err)
}
log.Printf("[main] Starting gRPC Listener [%s]\n", *grpcEndpoint)
log.Fatal(grpcServer.Serve(listen))
}
Because it’s gRPC, you need an implementation of the proto for the client, one is provided too grpc-health-probe:
grpc_health_probe -addr :50051
Should yield:
status: SERVING
Rust
Assuming the proto is stored in ./proto/grpc_health_v1.proto.
Tonic supports compiling protos using build.rs:
fn main() -> Result<(), Box<dyn std::error::Error>> {
// compile refers to the path
tonic_build::compile_protos("proto/grpc_health_v1.proto")?;
Ok(())
}
And then useing these:
// Compiled by build.rs
pub mod grpc_health_v1 {
// include refers to the package
tonic::include_proto!("grpc.health.v1");
}
use grpc_health_v1::{
health_check_response::ServingStatus,
health_server::{Health, HealthServer},
HealthCheckRequest, HealthCheckResponse,
};
// Implement
#[derive(Default)]
pub struct Health {}
#[tonic::async_trait]
impl HealthService for Health {
async fn check(
&self,
_rqst: Request<HealthCheckRequest>,
) -> Result<Response<HealthCheckResponse>, Status> {
Ok(Response::new(HealthCheckResponse {
status: ServingStatus::Serving as i32,
}))
}
type WatchStream = mpsc::Receiver<Result<HealthCheckResponse, Status>>;
async fn watch(
&self,
_rqst: Request<HealthCheckRequest>,
) -> Result<Response<Self::WatchStream>, Status> {
println!("[Health::watch]");
let (mut tx, rx) = mpsc::channel(4);
tokio::spawn(async move {
tx.send(Ok(HealthCheckResponse {
status: ServingStatus::Serving as i32,
}))
.await
.unwrap();
});
Ok(Response::new(rx))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Our primary service
let x = X {};
let x_service = XServer::new(x);
// Our Healthcheck service
let health = Health {};
let health_service = HealthServiceServer::new(health);
Server::builder()
.add_service(device_service)
.add_service(health_service)
.serve(addr)
.await
.expect("unable to start gRPC server");
Ok(())
}