Introducing wasi-grpc for Spin

Brian Hardock profile

Sep 08, 2025

Brian Hardock

Brian Hardock profile

Written by

Brian Hardock

Brian is a Senior Software Engineer at Akamai. He is the lead on the Functions developer experiences team and a core maintainer of the CNCF Spinframework/Spin project.

Share

Modern microservice ecosystems often rely on gRPC for efficient, strongly typed communication. gRPC builds on top of HTTP/2, taking advantage of multiplexed streams and binary serialization (Protobuf) to deliver high throughput and low latency.

Until Spin 3.4, Spin components could only make outbound HTTP/1.1 requests. This meant gRPC clients could not run inside Spin directly; developers instead had to rely on sidecars or proxy services to bridge the gap. This added extra complexity and slowed down applications.

Spin 3.4 introduces outbound HTTP/2 support, enabling components to act as first-class gRPC clients. Spin applications can now call into existing gRPC-based systems, cloud APIs, and service meshes directly, without workarounds.

In this blog post, we will walk through a tutorial showing how to build a Spin component in Rust that connects to a simple gRPC service using the new wasi-grpc crate.

Tutorial

For this tutorial, we will be adapting the tonic helloworld example client to execute in Spin. Additionally, for testing, we will run the helloworld server to execute our component against.

Step 1: Create a new Spin app

spin new -t http-rust helloworld-client --accept-defaults

This creates a new helloworld-client directory with a Spin HTTP Rust application. Change into this directory to continue with the tutorial:

cd helloworld-client

Step 2: Setup

First, in Cargo.toml, add the dependencies for working with gRPC, Protobuf, and the wasi-grpc crate:

[dependencies]
anyhow = "1"
prost = "0.13.5"
wasi-grpc = "0.1.0"
spin-sdk = "4.0.0"
tonic = { version = "0.13.1", features = ["codegen", "prost", "router"], default-features = false}

Next, copy the helloworld proto into your current working directory.

With that in place, we can add our build dependencies for generating code from our proto file. In your Cargo.toml add the following:

[build-dependencies]
tonic-build = { version = "0.13.1", features = ["prost"] }

And finally we can add the build.rs which will generate our gRPC client from the helloworld proto:

// In build.rs
fn main() {
    tonic_build::configure()
        .build_transport(false)
        .compile_protos(&["helloworld.proto"], &[""])
        .unwrap();
}

Let's implement

In src/lib.rs, let's start by pulling in the generated code from executing our build.rs:

use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;
 
pub mod hello_world {
    tonic::include_proto!("helloworld");
}

Next let's pull in the wasi_grpc::WasiGrpcEndpoint type which implements tower_service::Service. This type creates a bridge between tonic and wasi-hyperium which is what wasi-grpc uses under the hood for sending outbound HTTP requests.

use wasi_grpc::WasiGrpcEndpoint;
NOTE: It was decided to use wasi-hyperium as the underlying mechanism for sending outbound requests because the spin-sdk currently has certain limitations with respect to outbound streaming.

And finally we can implement our request handler:

#[http_component]
async fn handler(_req: http::Request) -> anyhow::Result<impl IntoResponse> {
    let endpoint_uri = "http://[::1]:50051".parse().context("Failed to parse endpoint URI")?;
    let endpoint = WasiGrpcEndpoint::new(endpoint_uri);
    let mut client = GreeterClient::new(endpoint);
 
    let request = Request::new(HelloRequest {
        name: "World".to_string(),
    });
 
    let message = client
        .say_hello(request)
        .await?
        .into_inner()
        .message;
 
    let response = http::Response::new(200, message);
    Ok(response)
}
NOTE: For simplicity, we are using a hardcoded endpoint to communicate with our local gRPC server. For production applications it is recommended to use application variables to inject in the value of the address at run/deploy time.

Putting this all together, the final code in src/lib.rs looks like this:

use anyhow::Context;
use spin_sdk::http::{self, IntoResponse};
use spin_sdk::http_component;
use tonic::Request;
use wasi_grpc::WasiGrpcEndpoint;
 
use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;
 
pub mod hello_world {
    tonic::include_proto!("helloworld");
}
 
#[http_component]
async fn handler(_req: http::Request) -> anyhow::Result<impl IntoResponse> {
    let endpoint_uri = "http://[::1]:50051".parse().context("Failed to parse endpoint URI")?;
    let endpoint = WasiGrpcEndpoint::new(endpoint_uri);
    let mut client = GreeterClient::new(endpoint);
 
    let request = Request::new(HelloRequest {
        name: "World".to_string(),
    });
 
    let message = client
        .say_hello(request)
        .await?
        .into_inner()
        .message;
 
    let response = http::Response::new(200, message);
    Ok(response)
}

Let's take it for a Spin!

As mentioned earlier, we will be using the helloworld server to execute our app. The easiest way to run this would be to clone the tonic repository and run the helloworld server:

$ gh repo clone hyperium/tonic
$ cargo run --bin helloworld-server
...
GreeterServer listening on [::1]:50051

Next, we will want to configure our Spin app so that our component can communicate with the server running at [::1]:50051. In your spin.toml, you'll want to add the following:

[component.helloworld-client]
...
allowed_outbound_hosts = ["http://[::1]:50051"]

Now that we have our server running and app properly configured, let's spin up our application and send it a request:

$ SPIN_OUTBOUND_H2C_PRIOR_KNOWLEDGE=[::1]:50051 spin up --build
NOTE: The SPIN_OUTBOUND_H2C_PRIOR_KNOWLEDGE environment variable is to tell the Spin runtime that we are intending to use HTTP/2 without TLS.

And finally, let's send a request to our app:

$ curl localhost:3000/
Hello World!

Conclusion

Spin 3.4's outbound HTTP/2 support removes one of the biggest barriers to using Spin in modern service-oriented systems: direct gRPC client support. With the help of the wasi-grpc crate, components can now:

  • Call gRPC microservices directly without sidecars
  • Integrate with APIs built on gRPC
  • Leverage high-performance streaming patterns as first-class citizens in Spin

The example above uses the simple helloworld service, but the same approach applies to more advanced services, including streaming APIs. For inspiration, check out the routeguide-client in the wasi-grpc repository.

If you have any questions, we'd love to help in the Spin CNCF Slack channel. Hope to see you there.

Brian Hardock profile

Sep 08, 2025

Brian Hardock

Brian Hardock profile

Written by

Brian Hardock

Brian is a Senior Software Engineer at Akamai. He is the lead on the Functions developer experiences team and a core maintainer of the CNCF Spinframework/Spin project.

Tags

Share

Related Blog Posts

Developers
Introducing Akamai Cloud Pulse: Observability for Your Cloud Infrastructure – Now in Open Beta
July 15, 2025
Akamai Cloud Pulse is now entering Open Beta for all Akamai Managed Database customers. Following successful closed beta testing, we are ready to provide you with real-time insights into your database performance and resource utilization.
Cloud
No Lag, All Frag: Level Up Your Gaming with Xonotic, K3s, and Edge Computing
March 20, 2025
Let’s set the scene for a gamer: you’re having the game of your life (and you wish you were streaming today of all days). You’re lining up the perfect Level up your gaming with Xonotic, K3s, and edge computing! Discover how to host a high-performance, low-latency game server using Akamai Cloud’s distributed compute regions. Say goodbye to lag and hello to seamless gameplay.
Cloud
An Inside Look at our Next Gen Object Storage Launch
August 28, 2025