|
| 1 | +# gRPC Integration |
| 2 | + |
| 3 | +RustAPI allows you to seamlessly integrate gRPC services alongside your HTTP API, running both on the same Tokio runtime or even the same port (with proper multiplexing, though separate ports are simpler). We use the `rustapi-grpc` crate, which provides helpers for [Tonic](https://github.com/hyperium/tonic). |
| 4 | + |
| 5 | +## Dependencies |
| 6 | + |
| 7 | +Add the following to your `Cargo.toml`: |
| 8 | + |
| 9 | +```toml |
| 10 | +[dependencies] |
| 11 | +rustapi-rs = { version = "0.1.335", features = ["grpc"] } |
| 12 | +tonic = "0.10" |
| 13 | +prost = "0.12" |
| 14 | +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } |
| 15 | + |
| 16 | +[build-dependencies] |
| 17 | +tonic-build = "0.10" |
| 18 | +``` |
| 19 | + |
| 20 | +## Defining the Service (Proto) |
| 21 | + |
| 22 | +Create a `proto/helloworld.proto` file: |
| 23 | + |
| 24 | +```protobuf |
| 25 | +syntax = "proto3"; |
| 26 | +
|
| 27 | +package helloworld; |
| 28 | +
|
| 29 | +service Greeter { |
| 30 | + rpc SayHello (HelloRequest) returns (HelloReply); |
| 31 | +} |
| 32 | +
|
| 33 | +message HelloRequest { |
| 34 | + string name = 1; |
| 35 | +} |
| 36 | +
|
| 37 | +message HelloReply { |
| 38 | + string message = 1; |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +## The Build Script |
| 43 | + |
| 44 | +In `build.rs`: |
| 45 | + |
| 46 | +```rust,no_run |
| 47 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 48 | + tonic_build::compile_protos("proto/helloworld.proto")?; |
| 49 | + Ok(()) |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +## Implementation |
| 54 | + |
| 55 | +Here is how to run both servers concurrently with shared shutdown. |
| 56 | + |
| 57 | +```rust,no_run |
| 58 | +use rustapi_rs::prelude::*; |
| 59 | +use rustapi_rs::grpc::{run_rustapi_and_grpc_with_shutdown, tonic}; |
| 60 | +use tonic::{Request, Response, Status}; |
| 61 | +
|
| 62 | +// Import generated proto code (simplified for example) |
| 63 | +pub mod hello_world { |
| 64 | + tonic::include_proto!("helloworld"); |
| 65 | +} |
| 66 | +use hello_world::greeter_server::{Greeter, GreeterServer}; |
| 67 | +use hello_world::{HelloReply, HelloRequest}; |
| 68 | +
|
| 69 | +// --- gRPC Implementation --- |
| 70 | +#[derive(Default)] |
| 71 | +pub struct MyGreeter {} |
| 72 | +
|
| 73 | +#[tonic::async_trait] |
| 74 | +impl Greeter for MyGreeter { |
| 75 | + async fn say_hello( |
| 76 | + &self, |
| 77 | + request: Request<HelloRequest>, |
| 78 | + ) -> Result<Response<HelloReply>, Status> { |
| 79 | + let name = request.into_inner().name; |
| 80 | + let reply = hello_world::HelloReply { |
| 81 | + message: format!("Hello {} from gRPC!", name), |
| 82 | + }; |
| 83 | + Ok(Response::new(reply)) |
| 84 | + } |
| 85 | +} |
| 86 | +
|
| 87 | +// --- HTTP Implementation --- |
| 88 | +#[rustapi_rs::get("/health")] |
| 89 | +async fn health() -> Json<&'static str> { |
| 90 | + Json("OK") |
| 91 | +} |
| 92 | +
|
| 93 | +#[tokio::main] |
| 94 | +async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
| 95 | + // 1. Define HTTP App |
| 96 | + let http_app = RustApi::new().route("/health", get(health)); |
| 97 | + let http_addr = "0.0.0.0:3000"; |
| 98 | +
|
| 99 | + // 2. Define gRPC Service |
| 100 | + let grpc_addr = "0.0.0.0:50051".parse()?; |
| 101 | + let greeter = MyGreeter::default(); |
| 102 | +
|
| 103 | + println!("HTTP listening on http://{}", http_addr); |
| 104 | + println!("gRPC listening on grpc://{}", grpc_addr); |
| 105 | +
|
| 106 | + // 3. Run both with shared shutdown (Ctrl+C) |
| 107 | + run_rustapi_and_grpc_with_shutdown( |
| 108 | + http_app, |
| 109 | + http_addr, |
| 110 | + tokio::signal::ctrl_c(), |
| 111 | + move |shutdown| { |
| 112 | + tonic::transport::Server::builder() |
| 113 | + .add_service(GreeterServer::new(greeter)) |
| 114 | + .serve_with_shutdown(grpc_addr, shutdown) |
| 115 | + }, |
| 116 | + ).await?; |
| 117 | +
|
| 118 | + Ok(()) |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +## How It Works |
| 123 | + |
| 124 | +1. **Shared Runtime**: Both servers run on the same Tokio runtime, sharing thread pool resources efficiently. |
| 125 | +2. **Graceful Shutdown**: When `Ctrl+C` is pressed, `run_rustapi_and_grpc_with_shutdown` signals both the HTTP server and the gRPC server to stop accepting new connections and finish pending requests. |
| 126 | +3. **Simplicity**: You don't need to manually spawn tasks or manage channels for shutdown signals. |
| 127 | + |
| 128 | +## Advanced: Multiplexing |
| 129 | + |
| 130 | +To run both HTTP and gRPC on the **same port**, you would typically use a library like `tower` to inspect the `Content-Type` header (`application/grpc` vs others) and route accordingly. However, running on separate ports (e.g., 8080 for HTTP, 50051 for gRPC) is standard practice in Kubernetes and most deployment environments. |
0 commit comments