Skip to content

HTTP Client

rust_web_server::http_client is a small outbound HTTP/1.1 client with no third-party HTTP dependency. It follows redirects (up to 10 by default; 301/302/303 become GET, 307/308 keep the method) and decodes chunked bodies.

Build What works
any build Client over plain http://
http-client feature, or http2/http3 (the default) Client over https:// (rustls + WebPKI roots)
http2/http3 AsyncClient (async send())
[dependencies]
# Only needed for HTTPS in an http1-only build; the default features already include it.
rust-web-server = { version = "18", default-features = false, features = ["http1", "http-client"] }
use rust_web_server::http_client::{Client, HttpClientError};
fn fetch_user(id: u64) -> Result<String, HttpClientError> {
let client = Client::new();
let resp = client
.get(&format!("https://api.example.com/users/{id}"))
.header("Authorization", "Bearer tok_…")
.timeout_ms(5_000)
.send()?;
if resp.is_success() {
resp.text()
} else {
Err(HttpClientError(format!("upstream returned {}", resp.status())))
}
}

Client::new() uses a 30-second timeout and follows up to 10 redirects; change them with Client::new().timeout_ms(ms) and .max_redirects(n), or per request with RequestBuilder::timeout_ms. send() blocks the calling thread — in the async servers a synchronous handler already runs on the blocking pool, so that is fine; inside an async handler use AsyncClient instead.

use rust_web_server::http_client::Client;
// JSON — sets Content-Type: application/json
let resp = Client::new()
.post("https://api.example.com/charges")
.body_json(r#"{"amount":1000,"currency":"usd"}"#)
.header("Authorization", "Bearer sk_…")
.send()?;
assert!(resp.is_success());
// Form — percent-encodes each pair, sets Content-Type: application/x-www-form-urlencoded
let resp = Client::new()
.post("https://oauth2.googleapis.com/token")
.form(&[
("grant_type", "authorization_code"),
("code", "abc123"),
("client_id", "my-client-id"),
("client_secret", "my-client-secret"),
])
.send()?;
let json = resp.text()?;

.body_text(s) sends text/plain; .body(bytes) sends raw bytes with whatever Content-Type you set via .header. .form() is the body shape OAuth 2.0 token endpoints require — SSO uses it for the code exchange.

Method Returns
.status() u16
.is_success() true for 200–299
.is_redirect() true for 301/302/303/307/308 (returned once max_redirects is reached — max_redirects(0) disables following)
.header(name) Option<&str>, case-insensitive
.headers() &[(String, String)]
.bytes() &[u8]
.text() Result<String, HttpClientError> (UTF-8)
.json::<T>() Result<T, HttpClientError> — requires the serde feature

HttpClientError(pub String) implements std::error::Error and Display.

With the http2 feature (included in the default build), AsyncClient has the same builder API — get/post/put/patch/delete/request, .header, .body*, .form, .timeout_ms — with an async fn send():

use rust_web_server::http_client::{AsyncClient, HttpClientError};
async fn fetch_async(url: &str) -> Result<String, HttpClientError> {
AsyncClient::new().get(url).send().await?.text()
}

See Async Handlers for a complete handler that forwards an upstream response.