a-b-street/abstreet · error
HTTP error
Error message
HTTP error {}: {} What it means
abstio::http_post performs an HTTP POST and returns the body as text. If the response status is a client error (4xx) or server error (5xx), it bails with "HTTP error {status}: {body}" so the caller can see both the status code and the server's error message.
Solutions
- Inspect the status code and body in the error to identify whether the problem is auth, payload, or server-side.
- Verify the URL, headers (Content-Type, auth), and request body format match what the endpoint expects.
- Add retry logic with backoff for transient 5xx (e.g. 502/503), but not for 4xx errors.
Example fix
// before
let text = abstio::http_post(url, body).await?;
// after
match abstio::http_post(url, body).await {
Ok(text) => Ok(text),
Err(err) => { log::error!("POST to {} failed: {}", url, err); Err(err) }
} Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight checks before POST:
assert!(!auth_token.is_empty(), "missing auth token");
assert!(url.starts_with("https://"), "unexpected URL"); Try / catch
match abstio::http_post(url, body).await {
Ok(text) => Ok(text),
Err(e) if e.to_string().contains("HTTP error 5") => retry_with_backoff(url, body).await,
Err(e) => Err(e),
} Prevention
- Log the status and body from the error message before deciding to retry.
- Retry only 5xx responses; treat 4xx as a caller bug.
- Validate the payload serializes to what the endpoint expects.
When it happens
Trigger: Any POST whose server responds with 4xx/5xx: bad endpoint URL, missing/invalid authentication, malformed request payload, server-side failure, rate limiting.
Common situations: Hitting an API whose auth token expired; posting to a URL that only accepts GET; network proxies returning 502/503; server validation rejecting the posted JSON.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
AI-assisted analysis of a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/f1db5d875065a592.
Report an issue: GitHub.
Appendix: source
Thrown at abstio/src/http.rs:18
use anyhow::{Context, Result};
/// Performs an HTTP POST request and returns the response.
pub async fn http_post<U: AsRef<str>, B: Into<reqwest::Body>>(url: U, body: B) -> Result<String> {
let url = url.as_ref();
info!("HTTP POST to {}", url);
let resp = reqwest::Client::new()
.post(url)
.body(body)
.send()
.await
.with_context(|| url.to_string())?;
let status = resp.status();
let text = resp.text().await.with_context(|| url.to_string())?;
// With error_for_status{_ref}, it's unclear how to propagate errors and also get the error
// message from the body, so do this
if status.is_client_error() || status.is_server_error() {
bail!("HTTP error {}: {}", status, text);
}
Ok(text)
}
/// Performs an HTTP GET request and returns the raw response. Unlike the variations in
/// download.rs, no progress -- but it works on native and web.
pub async fn http_get<I: AsRef<str>>(url: I) -> Result<Vec<u8>> {
let url = url.as_ref();
info!("HTTP GET {}", url);
let resp = reqwest::get(url).await?.error_for_status()?.bytes().await?;
Ok(resp.to_vec())
}
View on GitHub (pinned to 0964f29315)