Announcing Spin v3.5
The CNCF Spin project recently released Spin v3.5.0 which includes support for a release candidate of the upcoming WASIp3 release, along with several developer experience enhancements! In this blog post, we’ll dive into a few exciting features of this release and highlight an example of what is enabled with WASIp3 in Spin.
If you want to skip ahead to the official Release Notes, you can find them here.
Experimental WASIp3 Support
WASIp3 and Why It Is Special
Read all about WASIp3 in Joel Dice’s blog post here. To recap, it’s the next release of WASI, a set of standard APIs for portable application development with WebAssembly, and currently has one release candidate. This release of WASI comes with first class support for concurrency and async and significantly simplified APIs or WebAssembly Interface Types (WIT) definitions. These additions bring full and fine-grained composability of components written in different languages and with different concurrency models, enabling scenarios such as a Python and a Rust component seamlessly doing concurrent HTTP requests, each using their respective idiomatic concurrency models.
WASIp3 and the Spin Experience
WASIp3 isn’t fully stable yet; it’s going through a Release Candidate (RC) stage, and there are likely to be some minor changes before the final release. To ensure a consistent experience for developers, Spin requires you to opt in to using this unstable RC in the HTTP trigger section of your spin.toml file:
[[trigger.http]]
executor = { type = "wasip3-unstable" }
A Shiny New Rust SDK
The latest release of the Spin Rust SDK introduces:
- An
#[http_service]macro - defines the entry point to your component. Functions annotated with this macro must be markedasync. - An
http-wasip3module – offers the core SDK experience for working with WASIp3 in Spin.
To use these, make sure to enable the wasip3-unstable feature for your spin-sdk dependency in your application’s Cargo.toml.
[package]
name = "concurrent-outbound-http-calls"
rust-version = "1.90" # required for components using WASIp3 APIs
[...]
[dependencies]
spin-sdk = { version = "5.1.1", features = ["wasip3-unstable"] }
One thing you’ll notice the moment you try out the new SDK is that you’re using familiar Rust types from the http and hyper ecosystem, rather than Spin- or WASI-specific types. This was not an accident as one of the main goals in designing the new Rust SDK was to enhance interoperability with the Rust HTTP ecosystem. During implementation, we developed conversions between native Rust HTTP types and those generated by WASIp3, allowing developers to write HTTP code that feels more idiomatic and natural in Rust. We contributed these conversions upstream to the Bytecode Alliance’s wasi-rs project, with the hope that they’ll benefit the broader WASI ecosystem as well.
But that’s not where the interoperability story ends. Because of the design of the SDK in conjunction with the async support provided by WASIp3, it is now more seamless to bring your own web frameworks to use in Spin components. See for instance the Axum example here. Historically, this was possible to do but required management of more lower level types. See the Axum example with WASIp2 here for comparison. We’d love to hear more feedback on this experience through GitHub issues, on Slack, or during project meetings.
More on Language Support
WASIp3 support recently landed in componentize-py, which the Spin Python SDK is based on. A new release of the SDK will soon follow. This work is based on the new wit-dylib library, which is designed to make support of WASIp3 in dynamically typed languages easy.
Building on the same foundations, we can also look forward to WASIp3 support in JavaScript in the coming months.
In the meantime, these languages continue to be supported using WASIp2 just as before.
Getting Started
Get started with WASIp3 using a Spin template.
Install the new template:
$ spin templates install --upgrade --git https://github.com/spinframework/spin
Use the new template to scaffold a WASIp3 Spin application:
$ spin new hello-p3 -t http-rust-wasip3-unstable
You should now see a hello-p3 directory containing a basic p3 application using the new Rust SDK and the wasip3-unstable executor type.
Deep Dive Into Example
The Rust SDK repo provides examples of things you can now do with p3 support. Let’s dive into one of them. With WASIp3 support in Spin, you can make concurrent outbound HTTP requests from within a component. See full example here.
The first step of using the SDK is opting into the wasip3-unstable feature as is done on the last line of the Cargo.toml file above. Also note Rust 1.90 and the wasm32-wasip2 target are required to build WASIp3 components. (WASIp2 and p3 have the same binary representation, with p3 adding additional features. The wasm32-wasip2 target causes the Rust compiler to emit the right binary format, with the SDK adding the additional p3 features.)
The following src/lib.rs file contains the crux of the component.
[...]
#[http_service]
async fn handle_concurrent_outbound_http_calls(_req: spin_sdk::http_wasip3::Request) -> anyhow::Result<impl IntoResponse> {
let spin = pin!(get_content_length("https://spinframework.dev"));
let book = pin!(get_content_length("https://component-model.bytecodealliance.org/"));
let (first, len) = match futures::future::select(spin, book).await {
Either::Left(len) => ("Spin docs", len),
Either::Right(len) => ("Component model book", len),
};
let response = format!("{first} site was first response with content-length {:?}\n", len.0?);
Ok(response)
}
Here we are creating two async tasks (Futures) that will each fetch the content-length from different URLs. futures::future::select(spin, book).await runs both tasks concurrently and waits until one of them completes. It then matches and returns the site that returned first and the resulting content length.
async fn get_content_length(url: &str) -> anyhow::Result<Option<u64>> {
let request = Request::get(url).body(EmptyBody::new())?;
let response = send(request).await?;
let cl_header = response.headers().get("content-length");
let cl = cl_header.and_then(|hval| hval.to_str().ok()).and_then(|hval| hval.parse().ok());
Ok(cl)
}
get_content_length is an async function that sends an HTTP GET request to the given URL and extracts the Content-Length header (if any) from the response.
What’s Next with WASIp3
The WASI community is gearing up to finalize WASIp3. This process includes completing two implementations of the release candidate in two independent runtimes and a vote. Voting is expected to happen within the next few months. Once a final release is available, we can expect a Spin release shortly after.
That doesn’t mean that you’ll have to wait quite as long with using WASIp3, though. In Spin 3.5, p3 support is still experimental, but as we did with WASIp2, we’ll ship ungated support for p3 meant for production use in an upcoming release, before the release is fully finalized. With that release, you’ll be able to use WASIp3 for real workloads and be confident that it will continue working. At the same time, any feedback you can provide based on this use will help us and the wider community make the final p3 release even better.
More Developer Experience Enhancements
Beyond experimental support for WASIp3, the Spin 3.5 release contains the following additional features.
Static HTTP Responses
You can now easily define static HTTP responses right from within the HTTP trigger section of the spin.toml file rather than a component. The response may contain status code, headers, and body. This is especially great for handling fallbacks and redirects. See example here.
# Example use case: fallback 404 handling
[[trigger.http]]
route = "/..."
static_response = { status_code = 404, body = "not found" }
# Example use case: redirect
[[trigger.http]]
route = "/bob"
static_response = { status_code = 302, headers = { location = "/users/bob" }
Reference Dependencies by Component ID
Previously, we could specify component dependencies from a registry, a local component on the file system, or from a URL. You can now specify a component from within you application a dependency using the Component ID. See documentation here.
OpenAI Support
Spin provides a Large Language Model interface for interacting with LLMs for inferencing and embedding. This release adds support for integrating with the OpenAI API as one of the LLM backend options through runtime configuration. See more here.
Thank You
A huge thank you to contributors new and old for helping bring this release together. Thank you to our growing community and to the CNCF for their support and to the Bytecode Alliance for making such great strides on WASI.
Stay In Touch
We look forward to seeing you all at KubeCon Atlanta. Stop by the Project Pavilion for demos and to chat with contributors. We’d love to meet you. Please join us for weekly project meetings, chat in the Spin CNCF Slack channel and follow on X (formerly Twitter) @spinframework!
To get started with Spin and explore the latest features, follow the Spin quickstart which provides step-by-step instructions for installing Spin, creating your first application, and running it locally. Also head over to the Spin Hub for inspiration on what you can build!