FastEdge workers aren't limited to just responding to incoming requests — they can also make outbound network requests to external services. This opens up a world of possibilities: aggregating data from multiple APIs, proxying requests to upstream services, fetching remote content for caching, and more.
This post covers Making Outbound Requests from the Edge — a fundamental pattern for any edge application that needs to communicate with external services.
How It Works
Fetch data from external APIs from your FastEdge worker. Patterns for aggregation, caching, and error handling.
The FastEdge Rust SDK provides two approaches for making outbound requests:
- Synchronous (fastedge::http_client) — simple, blocking API that works within the standard Request/Response handler model. Best for straightforward fetch-and-return scenarios.
- Asynchronous (proxy-wasm dispatch) — uses the proxy-wasm ABI to pause request processing, make an HTTP call, and resume. Best for intercepting and augmenting client requests with external data.
Approach 1: Synchronous HTTP Client
The simplest approach — build a Request, send it, and get a Response back:
use fastedge::{
body::Body,
http::{Request, Response, StatusCode, Error},
http_client,
};
#[fastedge::http]
fn main(req: Request<Body>) -> Result<Response<Body>, Error> {
// Build an outbound request to an external API
let upstream = Request::builder()
.method("GET")
.uri("https://httpbin.org/ip")
.header("User-Agent", "fastedge-worker")
.header("Accept", "application/json")
.body(Body::empty())
.map_err(|_| Error::InvalidBody)?;
// Send it from the edge
let resp = http_client::send_request(upstream)?;
// Forward the upstream response headers plus our own
let status = resp.status();
let body = resp.into_body();
Response::builder()
.status(status)
.header("content-type", "application/json")
.header("x-edge-cached", "false")
.body(body)
}
Approach 2: Async Proxy-Wasm Dispatch
For more advanced scenarios where you need to intercept a client request and augment it with external data:
use fastedge::proxywasm::*;
use std::time::Duration;
struct MyHttp { state: u32 }
impl Context for MyHttp {}
impl HttpContext for MyHttp {
fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action {
// Already got the data, continue the request
if self.state == 1 {
return Action::Continue;
}
// Pause the client request and fetch external data
match self.dispatch_http_call(
"api.example.com", // authority
vec![ // headers
(":scheme", "https"),
(":authority", "api.example.com"),
(":path", "/auth/check"),
("Authorization", "Bearer token123"),
],
None, // no body
vec![], // no trailers
Duration::from_secs(5), // timeout
) {
Ok(token) => Action::Pause,
Err(status) => {
self.send_http_response(502, vec![], b"Upstream unavailable");
Action::Pause
}
}
}
fn on_http_call_response(
&mut self,
token_id: u32,
num_headers: usize,
body_size: usize,
_: usize,
) {
if num_headers > 0 {
let body = self.get_http_call_response_body(0, body_size);
// Store the response data and resume the client request
self.state = 1;
self.resume_http_request();
} else {
// HTTP call failed, reset
self.reset_http_request();
}
}
}
Common Use Cases
- API aggregation — fetch data from multiple upstream APIs and combine into a single response
- Authentication — validate tokens against an external auth service before allowing access
- Content caching — fetch remote content on first request, cache at the edge for subsequent requests
- Proxying — act as a reverse proxy with additional processing (header injection, body transformation)
- Webhook forwarding — receive webhooks at the edge and fan them out to multiple downstream services
Error Handling and Timeouts
Outbound HTTP calls are subject to network conditions. Always handle failures gracefully:
match http_client::send_request(upstream) {
Ok(resp) => handle_success(resp),
Err(e) => {
logging::error!("Upstream call failed: {}", e);
// Return a cached or fallback response instead of failing
Response::builder()
.status(StatusCode::OK)
.header("content-type", "text/plain")
.header("x-fallback", "true")
.body(Body::from("Service temporarily unavailable"))
}
}