use crate::Result;
use http::{Request, Response};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub trait RequestMiddleware: Send + Sync {
fn process<'a>(&'a self, request: Request<Vec<u8>>) -> BoxFuture<'a, Result<Request<Vec<u8>>>>;
fn clone_box(&self) -> Box<dyn RequestMiddleware>;
}
pub trait ResponseMiddleware: Send + Sync {
fn process<'a>(
&'a self,
response: Response<Vec<u8>>,
) -> BoxFuture<'a, Result<Response<Vec<u8>>>>;
fn clone_box(&self) -> Box<dyn ResponseMiddleware>;
}
#[derive(Clone)]
pub struct HeaderMiddleware {
headers: Vec<(String, String)>,
}
impl HeaderMiddleware {
pub fn new() -> Self {
Self {
headers: Vec::new(),
}
}
pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers.push((name.into(), value.into()));
self
}
pub fn with_tor_browser_headers(self) -> Self {
self.with_header(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0",
)
.with_header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
)
.with_header("Accept-Language", "en-US,en;q=0.5")
.with_header("Accept-Encoding", "gzip, deflate")
.with_header("DNT", "1")
.with_header("Upgrade-Insecure-Requests", "1")
}
}
impl Default for HeaderMiddleware {
fn default() -> Self {
Self::new()
}
}
impl RequestMiddleware for HeaderMiddleware {
fn process<'a>(
&'a self,
mut request: Request<Vec<u8>>,
) -> BoxFuture<'a, Result<Request<Vec<u8>>>> {
Box::pin(async move {
let headers = request.headers_mut();
for (name, value) in &self.headers {
if let (Ok(name), Ok(value)) = (
http::header::HeaderName::try_from(name.as_str()),
http::header::HeaderValue::try_from(value.as_str()),
) {
headers.insert(name, value);
}
}
Ok(request)
})
}
fn clone_box(&self) -> Box<dyn RequestMiddleware> {
Box::new(self.clone())
}
}
#[derive(Clone, Default)]
pub struct LoggingMiddleware {
log_body: bool,
}
impl LoggingMiddleware {
pub fn new() -> Self {
Self::default()
}
pub fn with_body_logging(mut self) -> Self {
self.log_body = true;
self
}
}
impl RequestMiddleware for LoggingMiddleware {
fn process<'a>(&'a self, request: Request<Vec<u8>>) -> BoxFuture<'a, Result<Request<Vec<u8>>>> {
let log_body = self.log_body;
Box::pin(async move {
tracing::debug!(
method = %request.method(),
uri = %request.uri(),
"Sending request"
);
if log_body && !request.body().is_empty() {
if let Ok(body) = std::str::from_utf8(request.body()) {
tracing::trace!(body = %body, "Request body");
}
}
Ok(request)
})
}
fn clone_box(&self) -> Box<dyn RequestMiddleware> {
Box::new(self.clone())
}
}
impl ResponseMiddleware for LoggingMiddleware {
fn process<'a>(
&'a self,
response: Response<Vec<u8>>,
) -> BoxFuture<'a, Result<Response<Vec<u8>>>> {
let log_body = self.log_body;
Box::pin(async move {
tracing::debug!(
status = %response.status(),
"Received response"
);
if log_body && !response.body().is_empty() {
if let Ok(body) = std::str::from_utf8(response.body()) {
tracing::trace!(body = %body, "Response body");
}
}
Ok(response)
})
}
fn clone_box(&self) -> Box<dyn ResponseMiddleware> {
Box::new(self.clone())
}
}
#[derive(Clone)]
pub struct RateLimitMiddleware {
requests_per_second: f64,
last_request: Arc<parking_lot::Mutex<std::time::Instant>>,
}
impl RateLimitMiddleware {
pub fn new(requests_per_second: f64) -> Self {
Self {
requests_per_second,
last_request: Arc::new(parking_lot::Mutex::new(std::time::Instant::now())),
}
}
}
impl RequestMiddleware for RateLimitMiddleware {
fn process<'a>(&'a self, request: Request<Vec<u8>>) -> BoxFuture<'a, Result<Request<Vec<u8>>>> {
let min_interval = std::time::Duration::from_secs_f64(1.0 / self.requests_per_second);
let last_request = self.last_request.clone();
Box::pin(async move {
let elapsed = {
let last = last_request.lock();
last.elapsed()
};
if elapsed < min_interval {
let sleep_duration = min_interval - elapsed;
tokio::time::sleep(sleep_duration).await;
}
{
let mut last = last_request.lock();
*last = std::time::Instant::now();
}
Ok(request)
})
}
fn clone_box(&self) -> Box<dyn RequestMiddleware> {
Box::new(self.clone())
}
}
pub struct MiddlewareStack {
request_middlewares: Vec<Box<dyn RequestMiddleware>>,
response_middlewares: Vec<Box<dyn ResponseMiddleware>>,
}
impl MiddlewareStack {
pub fn new() -> Self {
Self {
request_middlewares: Vec::new(),
response_middlewares: Vec::new(),
}
}
pub fn with_request<M: RequestMiddleware + 'static>(mut self, middleware: M) -> Self {
self.request_middlewares.push(Box::new(middleware));
self
}
pub fn with_response<M: ResponseMiddleware + 'static>(mut self, middleware: M) -> Self {
self.response_middlewares.push(Box::new(middleware));
self
}
pub async fn process_request(&self, mut request: Request<Vec<u8>>) -> Result<Request<Vec<u8>>> {
for middleware in &self.request_middlewares {
request = middleware.process(request).await?;
}
Ok(request)
}
pub async fn process_response(
&self,
mut response: Response<Vec<u8>>,
) -> Result<Response<Vec<u8>>> {
for middleware in &self.response_middlewares {
response = middleware.process(response).await?;
}
Ok(response)
}
pub fn is_empty(&self) -> bool {
self.request_middlewares.is_empty() && self.response_middlewares.is_empty()
}
}
impl Default for MiddlewareStack {
fn default() -> Self {
Self::new()
}
}
impl Clone for MiddlewareStack {
fn clone(&self) -> Self {
Self {
request_middlewares: self
.request_middlewares
.iter()
.map(|m| m.clone_box())
.collect(),
response_middlewares: self
.response_middlewares
.iter()
.map(|m| m.clone_box())
.collect(),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use http::Method;
#[tokio::test]
async fn test_header_middleware() {
let middleware = HeaderMiddleware::new()
.with_header("X-Custom", "value")
.with_header("X-Another", "test");
let request = Request::builder()
.method(Method::GET)
.uri("https://example.com")
.body(Vec::new())
.unwrap();
let processed = middleware.process(request).await.unwrap();
assert_eq!(processed.headers().get("X-Custom").unwrap(), "value");
assert_eq!(processed.headers().get("X-Another").unwrap(), "test");
}
#[tokio::test]
async fn test_middleware_stack() {
let stack = MiddlewareStack::new()
.with_request(HeaderMiddleware::new().with_header("X-Test", "1"))
.with_request(LoggingMiddleware::new());
let request = Request::builder()
.method(Method::GET)
.uri("https://example.com")
.body(Vec::new())
.unwrap();
let processed = stack.process_request(request).await.unwrap();
assert_eq!(processed.headers().get("X-Test").unwrap(), "1");
}
}