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 WebSocket Proxying at the Edge (Part 3) — a fundamental pattern for any edge application that needs to communicate with external services.
How It Works
Proxy and manage WebSocket connections at the edge — terminate WebSocket upgrades, authenticate before upgrading, and route to the right backend.
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"))
}
}