# scanclient — HTTP client tuned for security scanning
[](https://opensource.org/licenses/MIT) [](https://img.shields.io/badge/tests-13%20passing-brightgreen.svg) [](https://crates.io/crates/scanclient)
## Why
Security scanners perform huge numbers of HTTP requests under strict resource constraints and retry semantics. `scanclient` gives one production-oriented client with timeout, retry, proxy, rate limiting, and request observability baked into the crate.
It wraps `reqwest` but keeps a small, scan-specific API focused on scan workloads: easy defaults, bounded retries, response helpers, and typed errors.
## Quick Start
```rust
use scanclient::{HttpConfig, ScanClient};
#[tokio::main]
async fn main() -> scanclient::Result<()> {
let mut config = HttpConfig::default();
config.timeout_secs = 5;
config.max_retries = 2;
let client = ScanClient::from_config(config)?;
let response = client.get("https://example.com").await?;
println!("status={}", response.status());
if let Ok(body) = response.body_text() {
println!("len={}", body.len());
}
Ok(())
}
```
## Features
- Thin `ScanClient` API (`get`, `head`, `post`, `request`, `execute`).
- Configurable retries with exponential backoff and idempotent status retry policy.
- Optional rate limiting and proxy support.
- Request/response helpers (`body_text`, `contains`, header lookup).
- TOML load/parse for client configuration.
## TOML Configuration
Use `HttpConfig::from_toml` or `HttpConfig::load`.
```toml
timeout_secs = 10
max_retries = 3
retry_delay_ms = 250
max_redirects = 5
user_agent = "SanthScanner/1.0"
tls_verify = false
rate_limit_per_sec = 8
[custom_headers]
X-Scanner = "santh"
```
```rust
use scanclient::{HttpConfig, ScanClient};
let cfg = HttpConfig::from_toml(r#"timeout_secs=12\nrate_limit_per_sec=5\n"#).unwrap();
let _client = ScanClient::from_config(cfg);
```
## API Overview
- `HttpConfig`: configuration model.
- `ScanClient::from_config`: create client.
- `ScanClient::get`, `head`, `post`, `request`, `execute`.
- `ScanResponse`: typed response wrapper (`status`, `headers`, `body_*`, `contains`).
- `Error`, `Result`: crate-level error handling.
## Examples
### 1) Submit a POST and inspect response
```rust
use scanclient::{HttpConfig, ScanClient};
#[tokio::main]
async fn main() -> scanclient::Result<()> {
let client = ScanClient::from_config(HttpConfig::default())?;
let response = client.post("https://httpbin.org/post", "a=b&c=d").await?;
println!("status={} contains=a={}", response.status(), response.contains("args"));
Ok(())
}
```
### 2) Reuse a custom request with method + execute
```rust
use reqwest::Method;
use scanclient::{HttpConfig, ScanClient};
#[tokio::main]
async fn main() -> scanclient::Result<()> {
let client = ScanClient::from_config(HttpConfig::default())?;
let builder = client.request(Method::HEAD, "https://example.com");
let response = client.execute(builder).await?;
println!("ok={}", response.status().is_success());
Ok(())
}
```
### 3) Load settings from TOML in scanner bootstrap
```rust
use scanclient::HttpConfig;
let cfg = HttpConfig::from_toml(r#"
user_agent = "MyScanner/2.0"
timeout_secs = 8
max_redirects = 2
"#).unwrap();
println!("ua={}", cfg.user_agent);
```
## Traits
`scanclient` does not define custom traits in the public API.
## Related Crates
- [secreport](https://docs.rs/secreport)
- [multimatch](https://docs.rs/multimatch)
- [scanstate](https://docs.rs/scanstate)
## License
MIT, Corum Collective LLC
Docs: https://docs.rs/scanclient
Santh ecosystem: https://santh.io