Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 133 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[workspace]
members = ["derive", ".", "examples/*"]
# examples/fetch is a component-model/WASI-HTTP example built with cargo-component, not plain `cargo build`
exclude = [ "examples/fetch" ]

[workspace.package]
version = "0.3.4"
Expand Down
15 changes: 15 additions & 0 deletions examples/fetch/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "fetch"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
crate-type = ["cdylib"]

[dependencies]
wstd = "0.6"
anyhow = "1"

[package.metadata.component]
package = "component:fetch"
56 changes: 56 additions & 0 deletions examples/fetch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
⏮️ Back to Rust [README.md](../../README.md)

# Fetch

A minimal example demonstrating outbound HTTP requests using the [WASI-HTTP](https://github.com/WebAssembly/wasi-http) interface via the [`wstd`](https://crates.io/crates/wstd) crate.

Unlike the other HTTP examples that use the synchronous FastEdge SDK, this example uses the WASI component model with an **async** handler and a proper HTTP client (`wstd::http::Client`).

## How it works

The app receives an incoming request, reads the target URL from the `x-fetch-url` header, makes an outbound GET request to that URL, and streams the response back to the caller.

If the `x-fetch-url` header is absent, it defaults to `https://httpbin.org/get`.

## Request headers

| Header | Required | Description |
|--------|----------|-------------|
| `x-fetch-url` | No | URL to fetch. Defaults to `https://httpbin.org/get` |

## Example

```bash
curl -H "x-fetch-url: https://httpbin.org/uuid" https://<your-app-domain>/
```

## Build

### Prerequisites

- Rust toolchain
- [`cargo-component`](https://github.com/bytecodealliance/cargo-component)

```bash
cargo install cargo-component
```

### Compile

```bash
cargo component build --release
```

The compiled component will be at:
```
target/wasm32-wasip1/release/fetch.wasm
```

## Key differences from FastEdge SDK examples

| | FastEdge SDK | This example (WASI-HTTP) |
|---|---|---|
| Handler | `fn main(req)` — sync | `async fn main(req)` — async |
| Macro | `#[fastedge::http]` | `#[wstd::http_server]` |
| Outbound HTTP | `fastedge::send_request(req)` | `Client::new().send(req).await` |
| Build tool | `cargo build` | `cargo component build` |
43 changes: 43 additions & 0 deletions examples/fetch/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2025 G-Core Innovations SARL
*/
/*
Example app demonstrating the WASI-HTTP interface via the wstd crate.

The app receives an incoming HTTP request and makes an outbound HTTP request
to the URL specified in the `x-fetch-url` header (defaults to https://httpbin.org/get).

Build with cargo-component:
cargo component build --release
*/

use anyhow::anyhow;
use wstd::http::body::Body;
use wstd::http::{Client, Request, Response};

#[wstd::http_server]
async fn main(request: Request<Body>) -> anyhow::Result<Response<Body>> {
let target_url = request
.headers()
.get("x-fetch-url")
.and_then(|v| v.to_str().ok())
.unwrap_or("https://httpbin.org/get")
.to_string();

println!("Fetching: {target_url}");

let upstream_req = Request::get(&target_url)
.header("accept", "application/json")
.body(Body::empty())
.map_err(|e| anyhow!("failed to build request: {e}"))?;

let client = Client::new();
let response = client
.send(upstream_req)
.await
.map_err(|e| anyhow!("request failed: {e}"))?;

println!("Response status: {}", response.status());

Ok(response)
}
38 changes: 38 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@
//! * **ProxyWasm API**: Compatibility layer for [ProxyWasm] environments (Envoy, etc.).
//! Available via the [`fastedge::proxywasm`](`proxywasm`) module when the `proxywasm` feature is enabled.
//!
//! * **WASI-HTTP interface**: An alternative to the FastEdge SDK using the standard [WASI-HTTP]
//! interface via the [`wstd`] crate.
//!
//! [WIT]: https://component-model.bytecodealliance.org/design/wit.html
//! [WebAssembly components]: https://component-model.bytecodealliance.org
//! [ProxyWasm]: https://github.com/proxy-wasm/spec
//! [WASI-HTTP]: https://github.com/WebAssembly/wasi-http
//! [`wstd`]: https://crates.io/crates/wstd
//!
//! ## Features
//!
Expand Down Expand Up @@ -64,6 +69,39 @@
//! cargo build --target wasm32-wasip1 --release
//! ```
//!
//! ### Using the WASI-HTTP Interface (async, `wstd`)
//!
//! As an alternative to the synchronous FastEdge SDK, you can use the standard [WASI-HTTP]
//! interface via the [`wstd`] crate. This enables an async handler and a proper HTTP client:
//!
//! ```no_run
//! use wstd::http::body::Body;
//! use wstd::http::{Client, Request, Response};
//!
//! #[wstd::http_server]
//! async fn main(_request: Request<Body>) -> anyhow::Result<Response<Body>> {
//! let upstream_req = Request::get("https://api.example.com/data")
//! .header("accept", "application/json")
//! .body(Body::empty())?;
//!
//! let response = Client::new().send(upstream_req).await?;
//! Ok(response)
//! }
Comment on lines +77 to +89
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new rustdoc example is marked no_run, so it still must compile during doctests, but the fastedge crate does not declare wstd (or anyhow) as a (dev-)dependency. As written, cargo test will fail when running doc tests. Consider switching the block to ignore (or adding the needed crates under [dev-dependencies], possibly behind a feature) so docs don’t introduce uncompilable examples.

Copilot uses AI. Check for mistakes.
//! ```
//!
//! Build with [`cargo-component`] instead of `cargo build`:
//!
//! ```bash
//! cargo install cargo-component
//! cargo component build --release
//! ```
//!
//! See the [fetch example] for a complete working app and a side-by-side comparison
//! with the FastEdge SDK approach.
//!
//! [`cargo-component`]: https://github.com/bytecodealliance/cargo-component
//! [fetch example]: https://github.com/G-Core/FastEdge-sdk-rust/tree/main/examples/fetch
//!
//! ## Feature Flags
//!
//! - `proxywasm` (default): Enable ProxyWasm compatibility layer
Expand Down
Loading