Documentation Index
Fetch the complete documentation index at: https://docs.esperr.com/llms.txt
Use this file to discover all available pages before exploring further.
Start With Integrations For Faster SetupIf you want a recipe-backed path for a specific platform, start with the
Integrations guides. The SDK is best when you want
direct control inside your own service or gateway layer.
Installation
Add to yourCargo.toml:
[dependencies]
esper-rs = "0.1.0"
# For async support
tokio = { version = "1", features = ["full"] }
Basic Usage
Synchronous Client
use esper_rs::{BeaconEvent, EsperClient, EsperConfig, MitigationRequest};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize client
let config = EsperConfig::new("your-api-key");
let client = EsperClient::new(config)?;
// Send the first request's traffic to Esper beacon for analysis.
let event = BeaconEvent::new("http_request")
.with_ip("192.168.1.1")
.with_user_agent("Mozilla/5.0...")
.with_path("/login")
.with_method("POST");
client.send_beacon_event(&event)?;
// Check a later request against active mitigation state.
let request = MitigationRequest::new("192.168.1.1")
.with_user_agent("Mozilla/5.0...")
.with_path("/checkout")
.with_method("POST")
.with_metadata("request_id", serde_json::json!("req-2"))
.with_metadata("session_id", serde_json::json!("session-123"));
let response = client.check_mitigation(&request)?;
match response.action {
esper_rs::MitigationAction::Allow => {
println!("Request allowed");
}
esper_rs::MitigationAction::Block => {
println!("Request blocked: {:?}", response.reason);
}
esper_rs::MitigationAction::Challenge => {
println!("Challenge required");
}
}
Ok(())
}
Asynchronous Client
use esper_rs::{AsyncEsperClient, EsperConfig, MitigationRequest};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = EsperConfig::new("your-api-key");
let client = AsyncEsperClient::new(config)?;
let request = MitigationRequest::new("192.168.1.1")
.with_user_agent("Mozilla/5.0...")
.with_path("/api/endpoint");
let response = client.check_mitigation(&request).await?;
println!("Action: {:?}", response.action);
Ok(())
}
Configuration
use esper_rs::EsperConfig;
let config = EsperConfig::new("your-api-key")
.with_api_url("https://api.esperr.com") // Optional
.with_timeout(30) // Optional (seconds)
.with_max_retries(3); // Optional
API Methods
check_mitigation(&request)
Check if a request should be mitigated.use esper_rs::{MitigationRequest, MitigationAction};
let request = MitigationRequest::new("192.168.1.1")
.with_user_agent("Mozilla/5.0...")
.with_path("/api/endpoint")
.with_method("POST")
.with_header("X-Custom", "value")
.with_metadata("user_id", serde_json::json!("123"));
let response = client.check_mitigation(&request)?;
match response.action {
MitigationAction::Allow => {
println!("Allowed with score: {:?}", response.score);
}
MitigationAction::Block => {
println!("Blocked: {:?}", response.reason);
}
MitigationAction::Challenge => {
if let Some(challenge) = response.challenge {
println!("Challenge type: {}", challenge.challenge_type);
println!("Challenge token: {}", challenge.token);
}
}
}
verify_challenge(token, response)
Verify a user’s challenge response.let is_valid = client.verify_challenge(
"challenge-token",
"user-response"
)?;
if is_valid {
println!("Challenge passed");
} else {
println!("Challenge failed");
}
send_beacon_event(&event)
Send request traffic to the beacon server so Esper can analyze it and update state for later mitigation checks.use esper_rs::BeaconEvent;
let event = BeaconEvent::new("page_view")
.with_ip("192.168.1.1")
.with_user_agent("Mozilla/5.0...")
.with_session_id("session-123")
.with_data("page", serde_json::json!("/home"));
client.send_beacon_event(&event)?;
get_usage()
Get API usage statistics.let usage = client.get_usage()?;
println!("Total requests: {}", usage.requests);
println!("Blocked: {}", usage.blocked);
println!("Challenged: {}", usage.challenged);
println!("Allowed: {}", usage.allowed);
Framework Integration
Axum Middleware
use axum::{
extract::{ConnectInfo, Request, State},
http::StatusCode,
middleware::{self, Next},
response::{IntoResponse, Json, Response},
routing::get,
Router,
};
use esper_rs::{AsyncEsperClient, EsperConfig, MitigationRequest};
use std::{net::SocketAddr, sync::Arc};
#[derive(Clone)]
struct AppState {
esper_client: Arc<AsyncEsperClient>,
}
async fn esper_middleware(
State(state): State<AppState>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
request: Request,
next: Next,
) -> Response {
let mitigation_request = MitigationRequest::new(addr.ip().to_string())
.with_path(request.uri().path().to_string())
.with_method(request.method().to_string());
match state.esper_client.check_mitigation(&mitigation_request).await {
Ok(response) => match response.action {
esper_rs::MitigationAction::Block => {
(StatusCode::FORBIDDEN, "Access denied").into_response()
}
esper_rs::MitigationAction::Challenge => {
(StatusCode::TOO_MANY_REQUESTS, "Challenge required")
.into_response()
}
esper_rs::MitigationAction::Allow => next.run(request).await,
},
Err(e) => {
// Log error and allow on failure
eprintln!("Esper error: {}", e);
next.run(request).await
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = EsperConfig::new("your-api-key");
let esper_client = Arc::new(AsyncEsperClient::new(config)?);
let state = AppState { esper_client };
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn_with_state(
state.clone(),
esper_middleware
))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;
Ok(())
}
Actix-web Middleware
use actix_web::{
dev::{Service, ServiceRequest, ServiceResponse, Transform},
error::ErrorForbidden,
http::StatusCode,
Error, HttpMessage, HttpResponse,
web, App, HttpServer,
};
use esper_rs::{AsyncEsperClient, EsperConfig, MitigationRequest};
use futures_util::future::LocalBoxFuture;
use std::{
future::{ready, Ready},
rc::Rc,
};
pub struct EsperMiddleware {
client: Rc<AsyncEsperClient>,
}
impl EsperMiddleware {
pub fn new(api_key: &str) -> Self {
let config = EsperConfig::new(api_key);
let client = AsyncEsperClient::new(config)
.expect("Failed to create Esper client");
Self {
client: Rc::new(client),
}
}
}
// Transform implementation
impl<S, B> Transform<S, ServiceRequest> for EsperMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = EsperMiddlewareService<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(EsperMiddlewareService {
service,
client: self.client.clone(),
}))
}
}
pub struct EsperMiddlewareService<S> {
service: S,
client: Rc<AsyncEsperClient>,
}
// Service implementation
impl<S, B> Service<ServiceRequest> for EsperMiddlewareService<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(
&self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, req: ServiceRequest) -> Self::Future {
let client = self.client.clone();
let fut = self.service.call(req);
Box::pin(async move {
// Check mitigation
let conn_info = req.connection_info();
let mitigation_request = MitigationRequest::new(
conn_info.realip_remote_addr()
.unwrap_or("127.0.0.1")
.to_string()
)
.with_path(req.path().to_string())
.with_method(req.method().to_string());
match client.check_mitigation(&mitigation_request).await {
Ok(response) => match response.action {
esper_rs::MitigationAction::Block => {
Err(ErrorForbidden("Access denied"))
}
esper_rs::MitigationAction::Challenge => {
Err(actix_web::error::ErrorTooManyRequests(
"Challenge required"
))
}
esper_rs::MitigationAction::Allow => fut.await,
},
Err(_) => fut.await, // Allow on error
}
})
}
}
// Usage
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(EsperMiddleware::new("your-api-key"))
.route("/", web::get().to(handler))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Rocket Fairing
use rocket::{
fairing::{Fairing, Info, Kind},
http::Status,
request::{self, FromRequest, Request},
Data, Response, State,
};
use esper_rs::{AsyncEsperClient, EsperConfig, MitigationRequest};
use std::sync::Arc;
pub struct EsperFairing {
client: Arc<AsyncEsperClient>,
}
impl EsperFairing {
pub fn new(api_key: &str) -> Self {
let config = EsperConfig::new(api_key);
let client = AsyncEsperClient::new(config)
.expect("Failed to create Esper client");
Self {
client: Arc::new(client),
}
}
}
#[rocket::async_trait]
impl Fairing for EsperFairing {
fn info(&self) -> Info {
Info {
name: "Esper Traffic Protection",
kind: Kind::Request | Kind::Response,
}
}
async fn on_request(&self, req: &mut Request<'_>, _: &mut Data<'_>) {
let ip = req.client_ip()
.map(|ip| ip.to_string())
.unwrap_or_else(|| "127.0.0.1".to_string());
let mitigation_request = MitigationRequest::new(ip)
.with_path(req.uri().path().to_string())
.with_method(req.method().to_string());
match self.client.check_mitigation(&mitigation_request).await {
Ok(response) => match response.action {
esper_rs::MitigationAction::Block => {
req.local_cache(|| EsperResult::Blocked);
}
esper_rs::MitigationAction::Challenge => {
req.local_cache(|| EsperResult::Challenged);
}
esper_rs::MitigationAction::Allow => {
req.local_cache(|| EsperResult::Allowed);
}
},
Err(_) => {
req.local_cache(|| EsperResult::Error);
}
}
}
}
#[derive(Clone, Copy)]
enum EsperResult {
Allowed,
Blocked,
Challenged,
Error,
}
// Usage
#[rocket::launch]
fn rocket() -> _ {
rocket::build()
.attach(EsperFairing::new("your-api-key"))
.mount("/", routes![index])
}
Error Handling
use esper_rs::Error;
match client.check_mitigation(&request) {
Ok(response) => {
// Handle response
}
Err(Error::ApiError { message, status_code, error_code }) => {
eprintln!("API error: {} (status: {:?})", message, status_code);
}
Err(Error::TimeoutError) => {
eprintln!("Request timed out");
}
Err(Error::ConnectionError(msg)) => {
eprintln!("Connection error: {}", msg);
}
Err(e) => {
eprintln!("Other error: {}", e);
}
}
Features
The SDK supports different TLS implementations:# Use rustls (default)
[dependencies]
esper-rs = "0.1.0"
# Use native TLS
[dependencies]
esper-rs = { version = "0.1.0", default-features = false, features = ["native-tls"] }
Testing
#[cfg(test)]
mod tests {
use esper_rs::{EsperClient, EsperConfig, MitigationRequest};
use mockito::{mock, Matcher};
#[test]
fn test_check_mitigation() {
let mut server = mockito::Server::new();
let url = server.url();
let _m = server
.mock("POST", "/api/v1/runtime/mitigation")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"action": "allow", "score": 10.0}"#)
.create();
let config = EsperConfig::new("test-key")
.with_api_url(url);
let client = EsperClient::new(config).unwrap();
let request = MitigationRequest::new("192.168.1.1");
let response = client.check_mitigation(&request).unwrap();
assert_eq!(response.action, esper_rs::MitigationAction::Allow);
}
#[tokio::test]
async fn test_async_check_mitigation() {
use esper_rs::AsyncEsperClient;
let mut server = mockito::Server::new_async().await;
let url = server.url();
let _m = server
.mock("POST", "/api/v1/runtime/mitigation")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"action": "block", "reason": "Bot detected"}"#)
.create_async()
.await;
let config = EsperConfig::new("test-key")
.with_api_url(url);
let client = AsyncEsperClient::new(config).unwrap();
let request = MitigationRequest::new("10.0.0.1");
let response = client.check_mitigation(&request).await.unwrap();
assert_eq!(response.action, esper_rs::MitigationAction::Block);
}
}
Performance
The Rust SDK is optimized for high performance:- Zero-copy deserialization with
serde - Connection pooling with
reqwest - Automatic retry with exponential backoff
- Async/await support for concurrent operations
Examples
Complete examples are available in the GitHub repository:- Basic usage
- Axum middleware
- Actix-web middleware
- Rocket fairing
- Batch processing
- Custom retry logic