Build a Resilient Chain of Clue-Driven HTTP Requests
RampSoftware Engineer · OnsiteLast updated
Implement a resilient HTTP traversal client that follows a sequence of clue-bearing API responses to reach a final payload while enforcing safety policies like loop detection, retry budgets, deadline management, response size caps, and origin-scoped auth tokens.
Problem Statement
Modern financial integrations frequently require chaining multiple asynchronous or cursor-based API calls across internal and external partner endpoints. Implement a resilient resolver, ClueChainResolver, that navigates a linked sequence of HTTP endpoints to retrieve a terminal result.
Each successful step returns a JSON payload containing either a pointer to the next clue or the terminal result:
- Step response:
{"status": "continue", "next_url": "", "auth_token": ""} - Terminal response:
{"status": "complete", "result": ""}
Your client must safely traverse these steps while handling real-world distributed systems failures.
Requirements
- Loop Detection & Hop Limits: Terminate with a
TraversalErrorif a URL has already been visited in the current chain, or if total hops exceedmax_hops. - Security & Origin Boundaries: The initial request uses a base
auth_header. If a subsequentnext_urlbelongs to a different origin (scheme + host + port), do not forward the initialauth_headerunless a newauth_tokenwas explicitly provided for that hop. Never leak credentials across origins. - Deadlines & Retry Budgets: Enforce an overall execution deadline (
deadline_seconds) across the entire traversal. For transient HTTP errors (429,502,503,504), retry using exponential backoff with a cap on attempts per step (max_retries_per_step). Deduct elapsed backoff time from the global deadline. - Defensive Validation: Validate that responses are valid JSON and contain expected keys. Enforce
max_response_bytesto prevent unbounded memory consumption from malicious or corrupted endpoints.
Interface
from typing import Protocol, Optional, Dict, Any
class HttpResponse:
status_code: int
headers: Dict[str, str]
content: bytes
class Transport(Protocol):
def get(self, url: str, headers: Dict[str, str], timeout_seconds: float) -> HttpResponse:
"""Executes a GET request. Raises NetworkError on socket-level failures."""
...
class ClueChainResolver:
def __init__(
self,
transport: Transport,
max_hops: int = 15,
max_retries_per_step: int = 3,
deadline_seconds: float = 30.0,
max_response_bytes: int = 1_000_000,
):
...
def resolve(self, initial_url: str, initial_auth: Optional[str] = None) -> str:
"""Executes the chain traversal and returns the terminal result string."""
...
Examples
Example 1: Clean 2-Hop Traversal
# Step 1: GET https://api.ramp.com/clue/1 with header {"Authorization": "Bearer init_token"}
# Response 200: {"status": "continue", "next_url": "https://api.ramp.com/clue/2"}
# Step 2: GET https://api.ramp.com/clue/2 with header {"Authorization": "Bearer init_token"}
# Response 200: {"status": "complete", "result": "TX_SUCCESS_9481"}
resolver.resolve("https://api.ramp.com/clue/1", initial_auth="Bearer init_token")
# Output: "TX_SUCCESS_9481"
Example 2: Cross-Origin Boundary & Loop Protection
# Step 1: GET https://api.ramp.com/clue/1 -> next_url: "https://partner.bank.com/probe"
# Step 2: GET https://partner.bank.com/probe -> must NOT send "Bearer init_token"
# If https://partner.bank.com/probe returns next_url: "https://api.ramp.com/clue/1"
# Traversal raises TraversalError("Cycle detected")
Edge Cases
- Server returns
429with aRetry-Afterheader. - Non-transient errors (e.g.,
401 Unauthorized,404 Not Found) must fail immediately without retry. - Relative vs. absolute URLs in
next_url(normalize them relative to current URL).
Hints
Hint 1
Track visited URLs using canonicalized URLs (e.g., lowercase schemes and hosts, stripped fragments) to reliably catch loops.
Hint 2
Use Python's urllib.parse.urlsplit to verify whether next_url matches the origin of initial_url before forwarding authorization headers.
Hint 3
Calculate remaining time at the start of each network call and backoff sleep: timeout = deadline - current_time; if timeout <= 0, raise a deadline exceeded error.
Follow-up questions
- How would you make this chain resolver resumable across process crashes using persistent state storage?
- If some steps are read-only idempotency checks while others trigger state mutations, how would you adjust your retry and backoff semantics?
- How would you integrate streaming chunk reads to abort downloads immediately when exceeding max_response_bytes before buffering into memory?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.