Giter Site home page Giter Site logo

grpc-service-rs's Introduction

Simple gRPC Service & Client

Information

This is just a quick shell service, to show the basics of building a gRPC service and client from the perspective of code infrastructure. If you want to learn how to do interesting things in gRPC & Rust, I'd recommend starting with the Tonic RouteGuide.

This isn't anything fancy, and loosely inspired by this article, I just wanted to focus more on form than function.

I made this repository, like this

cd ~/dev # everyone has their own place

mkdir grpc-service-rs
cd grpc-service-rs

Basic layout

cargo new --bin time-service-server
cargo new --bin time-service-client
cargo new --lib time-bindings
cargo new --lib time-common
mkdir -p proto/github/canardleteer/grpc_service_rs/v1alpha1
touch proto/github/canardleteer/grpc_service_rs/v1alpha1/time.proto
touch Cargo.toml
  • I made this a workspace, by editing Cargo.toml.
  • I also added rust-toolchain.toml.
  • I added time-* relative path dependencies to other `time-*`` packages as appropriate.
  • And for all crates, while writing code, I added Cargo.toml entries as appropriate.
  • I later added some Docker "stuff," mostly in the docker directory.

proto

I had to build out a protobuf declaration. This service just has 2 verbs, one that returns the time, and another that's not implemented and a stub.

I lint & format with buf.

I also created a default proto/buf.yaml to scope rules, if needed.

# This should pass.
buf lint proto

# This should also pass.
buf format proto

I don't use buf to build Rust bindings, I let prost do that.

Normally, I would let proto be a relative submodule, but not for this example.

bindings

This is just a crate that builds the proto and manages the client/service generated code.

I added time-bindings/build.rs

...to build the protobuf bindings. I keep these in a separate package than the rest, just because this is a workspace, and it's reasonable to. The build.rs could live in each one independently. build.rs can become more interesting as you start to add things, like derive macros to the messages, or multiple proto files, but this is boring for this example.

This is generally just code generation shenanigans. YMMV.

  • You can dive deeper into using buf for Rust code generation, but it's not really necessary for this example. If you're interested, you can convert the time-bindings/build.rs file into a buf.gen.yaml by using the protoc-gen-prost plugin.

  • If you want to do something like JSON transcoding (or anything supported by serde), you can take a look at adding attributes to generated types with tonic_build. In general, annotations on generated code, is fairly useful.

  • There's a lot of useful stuff in tonic_build::Builder that's worth learning about (generate_default_stubs, for example).

protoc

If you don't have protoc installed, now would be a good time to install it.

I recommend grabbing an upstream protoc from https://github.com/google/protobuf/, but you can probably find an older one on your system via the package manager.

On Ubuntu, for instance:

sudo apt install protobuf-compiler

The Service Code: time-service

This is the actual Service implementation of an extremely boring service. Also in here, is the Resource implementation for this particular gRPC binding.

I have not implemented the SomethingUnimplemented method, just to show that default implementations work and return NOT_IMPLEMENTED.

The Server & Client Application Code

Running it

  • You'll probably want grpcurl if you don't have it already.

In one terminal window:

cargo run --bin time-service-server

In another terminal window:

cargo run --bin time-service-client

The output, from your client run, should look something like:

  2023-12-13T18:59:41.800217Z  WARN client: Text to stdout Level set to: Some(LevelFilter::INFO)
    at src/main.rs:84

Response from service was: 1702493981

And that's it! Leave the service running, we're going to do some additional introspection.

Probe the service for Reflection

Describe

grpcurl -plaintext localhost:50051 describe

And you should see everything, including the comments in your proto file.

-> grpcurl -plaintext localhost:50051 describe
github.canardleteer.grpc_service_rs.v1alpha1.SimpleTimestampService is a service:
service SimpleTimestampService {
  // This exists to show an unimplemented method.
  rpc SomethingUnimplemented ( .github.canardleteer.grpc_service_rs.v1alpha1.WhatTimeIsItRequest ) returns ( .github.canardleteer.grpc_service_rs.v1alpha1.WhatTimeIsItResponse );
  // Returns the services current timestamp with no additional information.
  rpc WhatTimeIsIt ( .github.canardleteer.grpc_service_rs.v1alpha1.WhatTimeIsItRequest ) returns ( .github.canardleteer.grpc_service_rs.v1alpha1.WhatTimeIsItResponse );
}
grpc.health.v1.Health is a service:
service Health {
  // If the requested service is unknown, the call will fail with status
  // NOT_FOUND.
  rpc Check ( .grpc.health.v1.HealthCheckRequest ) returns ( .grpc.health.v1.HealthCheckResponse );
  // Performs a watch for the serving status of the requested service.
  // The server will immediately send back a message indicating the current
  // serving status.  It will then subsequently send a new message whenever
  // the service's serving status changes.
  //
  // If the requested service is unknown when the call is received, the
  // server will send a message setting the serving status to
  // SERVICE_UNKNOWN but will *not* terminate the call.  If at some
  // future point, the serving status of the service becomes known, the
  // server will send a new message with the service's serving status.
  //
  // If the call terminates with status UNIMPLEMENTED, then clients
  // should assume this method is not supported and should not retry the
  // call.  If the call terminates with any other status (including OK),
  // clients should retry the call with appropriate exponential backoff.
  rpc Watch ( .grpc.health.v1.HealthCheckRequest ) returns ( stream .grpc.health.v1.HealthCheckResponse );
}
grpc.reflection.v1alpha.ServerReflection is a service:
service ServerReflection {
  // The reflection service is structured as a bidirectional stream, ensuring
  // all related requests go to a single server.
  rpc ServerReflectionInfo ( stream .grpc.reflection.v1alpha.ServerReflectionRequest ) returns ( stream .grpc.reflection.v1alpha.ServerReflectionResponse );
}

List

grpcurl -plaintext localhost:50051 list

And you'll see what we offer:

-> grpcurl -plaintext localhost:50051 list
github.canardleteer.grpc_service_rs.v1alpha1.SimpleTimestampService
grpc.health.v1.Health
grpc.reflection.v1alpha.ServerReflection

Check the service Health Check

If you want to download grpc-health-probe, you can and use that, or you can use grpcurl.

grpc_health_probe

-> grpc_health_probe -addr 127.0.0.1:50051
status: SERVING

grpcurl

-> grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check
{
  "status": "SERVING"
}

Docker Container

You can build the container:

docker build -t time:latest -f docker/Dockerfile .

You can test the container:

docker network create time-network
docker run --rm -it -d \
    --name time_server \
    --net time-network \
    -p 50051:50051 \
    time:latest

docker run --rm -it \
    --net time-network \
    -e USE_CLIENT_BINARY=true \
    time:latest -a time_server

# cleanup
docker rm -f time_server
docker network remove time-network

Actions

  • We run the following steps in our GitHub Actions for all branches & PRs:
    • buf lint
    • cargo check
    • cargo fmt
    • cargo clippy
    • cargo test
  • For Pull Requests, we perform a:
    • buf breaking
  • For pushes to "special" branches, we perform a:
    • buf push
    • docker build
    • docker push

I've omitted versioning for this example, but auto versioning pipelines are easy. I the past I've had luck with cargo-smart-release, but there are likley more and better tools now.

Output

docker compose

Basic docker compose with Envoy:

docker compose up --build

grpcurl -plaintext localhost:10200 describe
grpcurl -plaintext localhost:10200 github.canardleteer.grpc_service_rs.v1alpha1.SimpleTimestampService/WhatTimeIsIt

Advanced Envoy with docker compose

NOTE: I haven't quite gotten gRPC transcoding working yet.

  • There is a mostly commented out configuration in a second Envoy listener in envoy/envoy.yaml
  • There is a commented out file mapping for time_service.binpb in docker-compose.yaml

To generate envoy/time_service.binpb, you'll need to do the following:

buf build --as-file-descriptor-set -o envoy/time_service.binpb

grpc-service-rs's People

Contributors

canardleteer avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.