Skip to content

Commit 35728dd

Browse files
committed
WIP: grpc example
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
1 parent 1fc1b43 commit 35728dd

12 files changed

Lines changed: 477 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 175 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
resolver = "2"
33
members = [
4+
"examples/grpc",
45
"examples/hello-world",
56
"examples/http-axum-router",
67
"examples/http-concurrent-outbound-calls",

crates/spin-sdk/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ name = "spin_sdk"
2424
default = ["http", "key-value", "json", "llm", "mqtt", "mysql", "pg", "postgres4-types", "redis", "sqlite", "variables", "export-sdk-language"]
2525
export-sdk-language = []
2626
http = ["dep:wasip3", "dep:bytes", "dep:http-body", "dep:http-body-util", "dep:hyperium"]
27+
grpc = ["http", "dep:tower-service"]
2728
key-value = []
2829
json = ["dep:serde", "dep:serde_json"]
2930
llm = []
@@ -48,6 +49,9 @@ http-body-util = { version = "0.1.3", optional = true }
4849
hyperium = { package = "http", version = "1.3.1", optional = true }
4950
wasip3 = { version = "0.5.0", git = "https://github.com/bytecodealliance/wasi-rs", features = ["http-compat"], optional = true }
5051

52+
# grpc
53+
tower-service = { version = "0.3", optional = true }
54+
5155
# pg
5256
chrono = { version = "0.4.42", optional = true }
5357
postgres_range = { version = "0.11.1", optional = true }

crates/spin-sdk/src/grpc.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//! gRPC helpers for serving tonic services from Spin components.
2+
//!
3+
//! This module provides a thin integration layer between the Spin HTTP
4+
//! subsystem and [tonic](https://docs.rs/tonic)-generated gRPC servers.
5+
//! Tonic's generated server types (e.g. `GreeterServer<T>`) implement
6+
//! [`tower_service::Service`] over HTTP requests, so they can accept
7+
//! Spin's incoming [`Request`](crate::http::Request) directly.
8+
//!
9+
//! # Example
10+
//!
11+
//! ```ignore
12+
//! use spin_sdk::http::{IntoResponse, Request};
13+
//! use spin_sdk::http_service;
14+
//!
15+
//! #[http_service]
16+
//! async fn handler(req: Request) -> impl IntoResponse {
17+
//! spin_sdk::grpc::serve(GreeterServer::new(MyGreeter), req).await
18+
//! }
19+
//! ```
20+
21+
use hyperium as http;
22+
use std::convert::Infallible;
23+
24+
/// Serve a gRPC request by forwarding it to a tower service.
25+
///
26+
/// This function is designed to work with tonic-generated server types,
27+
/// which implement `tower::Service<http::Request<B>>` for any body `B`
28+
/// satisfying `http_body::Body + Send + 'static`.
29+
///
30+
/// The response is returned as an [`http::Response<B>`] which implements
31+
/// [`IntoResponse`](crate::http::IntoResponse), so it integrates directly
32+
/// with the `#[http_service]` handler return type.
33+
///
34+
/// # Extracting a gRPC service from a [`Router`]
35+
///
36+
/// If you have multiple services, you can compose them with
37+
/// [`tonic::transport::server::Router`] at the type level, or simply
38+
/// match on the request path and delegate to different `serve` calls.
39+
///
40+
/// # Example
41+
///
42+
/// ```ignore
43+
/// use spin_sdk::http::{IntoResponse, Request};
44+
/// use spin_sdk::{grpc, http_service};
45+
///
46+
/// #[http_service]
47+
/// async fn handler(req: Request) -> impl IntoResponse {
48+
/// grpc::serve(GreeterServer::new(MyGreeter), req).await
49+
/// }
50+
/// ```
51+
pub async fn serve<S, B>(mut svc: S, req: crate::http::Request) -> http::Response<B>
52+
where
53+
S: tower_service::Service<
54+
crate::http::Request,
55+
Response = http::Response<B>,
56+
Error = Infallible,
57+
>,
58+
{
59+
// Infallible error — unwrap is safe.
60+
svc.call(req).await.unwrap_or_else(|e| match e {})
61+
}

crates/spin-sdk/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! | Module | Feature | Purpose |
1111
//! |--------|---------|---------|
1212
//! | [`http`] | `http` | Incoming and outgoing HTTP requests |
13+
//! | [`grpc`] | `grpc` | gRPC helpers for tonic integration |
1314
//! | [`key_value`] | `key-value` | Persistent key-value storage |
1415
//! | [`llm`] | `llm` | Large-language-model inference |
1516
//! | [`mqtt`] | `mqtt` | MQTT message publishing |
@@ -34,6 +35,11 @@ pub use spin_sdk_macro::{http_service, redis_subscriber};
3435
#[cfg_attr(docsrs, doc(cfg(feature = "http")))]
3536
pub mod http;
3637

38+
/// gRPC helpers for serving tonic services.
39+
#[cfg(feature = "grpc")]
40+
#[cfg_attr(docsrs, doc(cfg(feature = "grpc")))]
41+
pub mod grpc;
42+
3743
/// Persistent key-value storage.
3844
#[cfg(feature = "key-value")]
3945
#[cfg_attr(docsrs, doc(cfg(feature = "key-value")))]

examples/grpc/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "grpc"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
futures = "0.3"
11+
prost = "0.13"
12+
spin-sdk = { path = "../../crates/spin-sdk", features = ["grpc"] }
13+
tokio-stream = "0.1"
14+
tonic = { version = "0.12", default-features = false, features = ["codegen", "prost"] }
15+
16+
[build-dependencies]
17+
tonic-build = { version = "0.12", default-features = false, features = ["prost"] }

0 commit comments

Comments
 (0)