Making Outbound Requests from the Edge

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:

  1. Synchronous (fastedge::http_client) — simple, blocking API that works within the standard Request/Response handler model. Best for straightforward fetch-and-return scenarios.
  2. 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

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"))
    }
}
Caveat: Outbound requests from edge workers are subject to provider-configured timeouts (typically 5-30 seconds depending on your plan). Plan for failures with retries, circuit breakers, and fallback responses. Never assume an outbound call will succeed.