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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-rust
ref: main
commit: 6347a7c2fda0d03e66f1214db5eec041c16801b7
updated: 2026-08-17
updated: 2026-08-20
-->

# FastEdge Rust SDK — CDN Apps (Proxy-Wasm)
Expand Down Expand Up @@ -197,10 +197,10 @@ impl HttpContext for HelloWorld {

| Callback | Phase | Description |
| ----------------------------------------------------------------- | ---------------- | --------------------------------------------------- |
| `on_http_request_headers(num_headers, end_of_stream) -> Action` | Request headers | Inspect or modify request headers before forwarding |
| `on_http_request_body(body_size, end_of_stream) -> Action` | Request body | Inspect or modify request body before forwarding |
| `on_http_response_headers(num_headers, end_of_stream) -> Action` | Response headers | Inspect or modify response headers from origin |
| `on_http_response_body(body_size, end_of_stream) -> Action` | Response body | Inspect or modify response body from origin |
| `on_http_request_headers(num_headers: usize, end_of_stream: bool) -> Action` | Request headers | Inspect or modify request headers before forwarding |
| `on_http_request_body(body_size: usize, end_of_stream: bool) -> Action` | Request body | Inspect or modify request body before forwarding |
| `on_http_response_headers(num_headers: usize, end_of_stream: bool) -> Action` | Response headers | Inspect or modify response headers from origin |
| `on_http_response_body(body_size: usize, end_of_stream: bool) -> Action` | Response body | Inspect or modify response body from origin |

All callbacks have default no-op implementations. Override only the phases your app needs to process.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-rust
ref: main
commit: 6347a7c2fda0d03e66f1214db5eec041c16801b7
updated: 2026-08-17
updated: 2026-08-20
-->

---
Expand Down Expand Up @@ -248,191 +248,3 @@ No additional dependencies. Uses `std::env` and `std::time::UNIX_EPOCH` from the
- FastEdge environment variable configuration (setting `EXPERIMENT_NAME`, `VARIANT_A_PATH`, `VARIANT_B_PATH` at deploy time)
- examples-geoblock-rust reference (similar CDN proxy-wasm filter structure)
- examples-auth-jwt-rust reference (CDN Rust example with cross-hook state pattern)

## Source Material

### FILE: examples/cdn/ab_testing/src/lib.rs

```rust
/*
* Copyright 2025 G-Core Innovations SARL
*/
/*
Example CDN app demonstrating A/B traffic splitting at the CDN layer.

Uses a cookie to assign users to variant A or B, then rewrites the
request path to route to different parts of the origin server.

Required configuration:
- Environment variable: EXPERIMENT_NAME
- Environment variable: VARIANT_A_PATH (path prefix for variant A)
- Environment variable: VARIANT_B_PATH (path prefix for variant B)
*/

use proxy_wasm::traits::*;
use proxy_wasm::types::*;
use std::env;
use std::time::UNIX_EPOCH;

proxy_wasm::main! {{
proxy_wasm::set_log_level(LogLevel::Info);
proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> { Box::new(AbTestingRoot) });
}}

struct AbTestingRoot;

impl Context for AbTestingRoot {}

impl RootContext for AbTestingRoot {
fn get_type(&self) -> Option<ContextType> {
Some(ContextType::HttpContext)
}

fn create_http_context(&self, _: u32) -> Option<Box<dyn HttpContext>> {
Some(Box::new(AbTestingContext))
}
}

struct AbTestingContext;

impl Context for AbTestingContext {}

impl HttpContext for AbTestingContext {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
let Ok(experiment_name) = env::var("EXPERIMENT_NAME") else {
self.send_http_response(
500,
vec![],
Some(b"App misconfigured - EXPERIMENT_NAME must be set"),
);
return Action::Pause;
};

let Ok(variant_a_path) = env::var("VARIANT_A_PATH") else {
self.send_http_response(
500,
vec![],
Some(b"App misconfigured - VARIANT_A_PATH must be set"),
);
return Action::Pause;
};

let Ok(variant_b_path) = env::var("VARIANT_B_PATH") else {
self.send_http_response(
500,
vec![],
Some(b"App misconfigured - VARIANT_B_PATH must be set"),
);
return Action::Pause;
};

let cookie_name = format!("fe_exp_{}", experiment_name);

// Check for existing experiment cookie
let cookie_header = self
.get_http_request_header("Cookie")
.unwrap_or_default();
let mut assigned = get_cookie_value(&cookie_header, &cookie_name);

// Assign variant if not already set
if assigned != "A" && assigned != "B" {
let now = self
.get_current_time()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis();
assigned = if now % 2 == 0 { "A" } else { "B" }.to_string();
}

// Rewrite request path
let path = self
.get_property(vec!["request.path"])
.and_then(|bytes| String::from_utf8(bytes).ok())
.unwrap_or_else(|| "/".to_string());

let variant_path = if assigned == "A" {
&variant_a_path
} else {
&variant_b_path
};
let new_path = format!("{}{}", variant_path, path);

// Update the request path directly to avoid ambiguous URL rewriting.
self.set_property(vec!["request.path"], Some(new_path.as_bytes()));

// Add variant headers for upstream visibility
self.add_http_request_header("X-Experiment", &experiment_name);
self.add_http_request_header("X-Variant", &assigned);

println!(
"A/B test \"{}\": variant {}, path {}",
experiment_name, assigned, new_path
);

Action::Continue
}

fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action {
// Recover the assigned variant and experiment name from the request headers set in
// on_http_request_headers. Instance state does not survive the nginx -> core-proxy hop.
let Some(variant) = self.get_http_request_header("X-Variant") else {
return Action::Continue;
};
let Some(experiment_name) = self.get_http_request_header("X-Experiment") else {
return Action::Continue;
};

let cookie = format!(
"fe_exp_{}={}; Path=/; Max-Age=86400; SameSite=Lax",
experiment_name, variant
);
self.add_http_response_header("Set-Cookie", &cookie);
self.add_http_response_header("X-Variant", &variant);

Action::Continue
}
}

fn get_cookie_value(cookie_header: &str, name: &str) -> String {
if cookie_header.is_empty() {
return String::new();
}
let prefix = format!("{}=", name);
for pair in cookie_header.split(';') {
let pair = pair.trim();
if let Some(value) = pair.strip_prefix(&prefix) {
return value.to_string();
}
}
String::new()
}
```


### FILE: examples/cdn/ab_testing/Cargo.toml

```toml
[workspace]

[package]
name = "ab_testing"
version = "0.1.0"
edition = "2024"

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

[dependencies]
proxy-wasm = "0.2"
```


### FILE: examples/cdn/ab_testing/README.md

```
[← Back to examples](../../README.md)

# A/B Testing (CDN)

Cookie-based A/B traffic splitting at the CDN layer, routing requests to different origin paths based on variant assignment.
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-rust
ref: main
commit: 6347a7c2fda0d03e66f1214db5eec041c16801b7
updated: 2026-08-17
updated: 2026-08-20
-->

# API Key Validation — CDN (Rust)
Expand Down Expand Up @@ -159,3 +159,120 @@ return Action::Pause;
- Host services reference — secrets API (`fastedge::proxywasm::secret`) and other CDN host services
- CDN apps reference — proxy-wasm app structure, `RootContext`/`HttpContext` setup, `Action` enum, and request property encodings
- SDK API reference — Rust CDN SDK traits and types

## Source Material

### FILE: examples/cdn/api_key/src/lib.rs

```rust
/*
* Copyright 2025 G-Core Innovations SARL
*/
/*
Example CDN app demonstrating API key validation.

Validates requests using an X-API-Key header checked against a stored
secret. Simpler alternative to JWT when token expiry and claims are
not needed.

Required configuration:
- Secret: API_KEY
*/

use fastedge::proxywasm::secret;
use proxy_wasm::traits::*;
use proxy_wasm::types::*;

proxy_wasm::main! {{
proxy_wasm::set_log_level(LogLevel::Info);
proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> { Box::new(ApiKeyRoot) });
}}

struct ApiKeyRoot;

impl Context for ApiKeyRoot {}

impl RootContext for ApiKeyRoot {
fn get_type(&self) -> Option<ContextType> {
Some(ContextType::HttpContext)
}

fn create_http_context(&self, _: u32) -> Option<Box<dyn HttpContext>> {
Some(Box::new(ApiKeyContext))
}
}

struct ApiKeyContext;

impl Context for ApiKeyContext {}

impl HttpContext for ApiKeyContext {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
let expected_key = match secret::get("API_KEY") {
Ok(Some(bytes)) => match String::from_utf8(bytes) {
Ok(s) if !s.is_empty() => s,
_ => {
self.send_http_response(500, vec![], Some(b"App misconfigured"));
return Action::Pause;
}
},
_ => {
self.send_http_response(500, vec![], Some(b"App misconfigured"));
return Action::Pause;
}
};

let provided_key = match self.get_http_request_header("X-API-Key") {
Some(k) if !k.is_empty() => k,
_ => {
self.send_http_response(
401,
vec![("WWW-Authenticate", "API-Key")],
Some(b"Missing X-API-Key header"),
);
return Action::Pause;
}
};

if provided_key != expected_key {
println!("API key validation failed");
self.send_http_response(403, vec![], Some(b"Invalid API key"));
return Action::Pause;
}

// Strip the API key header before forwarding to upstream
self.set_http_request_header("X-API-Key", None);

println!("API key validated successfully");
Action::Continue
}
}
```

### FILE: examples/cdn/api_key/Cargo.toml

```toml
[workspace]

[package]
name = "api_key"
version = "0.1.0"
edition = "2024"

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

[dependencies]
proxy-wasm = "0.2"
fastedge = { version = "0.4", features = ["proxywasm"] }
```

### FILE: examples/cdn/api_key/README.md

```
[← Back to examples](../../README.md)

# API Key (CDN)

Validates requests using an `X-API-Key` header checked against a stored secret. Returns 401 if missing, 403 if invalid, and strips the header before forwarding to upstream.
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-rust
ref: main
commit: 6347a7c2fda0d03e66f1214db5eec041c16801b7
updated: 2026-08-17
updated: 2026-08-20
-->

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-rust
ref: main
commit: 6347a7c2fda0d03e66f1214db5eec041c16801b7
updated: 2026-08-17
updated: 2026-08-20
-->

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- id: fastedge-sdk-rust
ref: main
commit: 6347a7c2fda0d03e66f1214db5eec041c16801b7
updated: 2026-08-17
updated: 2026-08-20
-->

---
Expand Down
Loading