use super::utils::{parse_version, reqwest_to_ferox_error};
use super::{AsyncRequests, HttpClient};
use crate::error::FeroxFuzzError;
use crate::requests::Request;
use crate::responses::AsyncResponse;
use crate::std_ext::convert::{AsInner, IntoInner};
use std::time::Instant;
use async_trait::async_trait;
use reqwest::{self, Method, Version};
use tracing::{error, instrument};
#[derive(Clone, Default, Debug)]
pub struct AsyncClient {
client: reqwest::Client,
}
impl HttpClient for AsyncClient {
type ClientType = reqwest::Client;
fn with_client(client: Self::ClientType) -> Self {
Self { client }
}
}
#[async_trait]
impl AsyncRequests for AsyncClient {
#[instrument(skip_all, level = "trace")]
async fn send(&self, request: Request) -> Result<AsyncResponse, FeroxFuzzError> {
let parsed_version = parse_version(request.version())?;
let reqwest_request = self.build_request(parsed_version, request.clone())?;
let now = Instant::now();
let reqwest_response = self
.client
.execute(reqwest_request)
.await
.map_err(reqwest_to_ferox_error)?;
let response =
AsyncResponse::try_from_reqwest_response(request, reqwest_response, now.elapsed())
.await?;
Ok(response)
}
}
impl AsInner for AsyncClient {
type Type = reqwest::Client;
fn inner(&self) -> &Self::Type {
&self.client
}
}
impl AsyncClient {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[instrument(skip_all, level = "trace")]
fn build_request(
&self,
version: Version,
request: Request,
) -> Result<reqwest::Request, FeroxFuzzError> {
let method = Method::from_bytes(request.method().inner()).map_err(|source| {
error!(
method = format!("{}", request.method()),
%source,
"could not parse the given http method; must be a valid http method when using a reqwest client"
);
FeroxFuzzError::MethodParseError {
method: format!("{}", request.method()),
}
})?;
let mut builder = if request.url_is_fuzzable() {
self.inner().request(method, request.url_to_string()?)
} else {
self.inner().request(method, request.original_url())
};
builder = builder.timeout(request.timeout).version(version);
if let Some(data_body) = request.body {
builder = builder.body(data_body.into_inner());
}
if let Some(headers) = request.headers {
for (key, value) in headers {
builder = builder.header(key.into_inner(), value.into_inner());
}
}
if let Some(user_agent) = request.user_agent {
builder = builder.header("User-Agent", user_agent.into_inner());
}
let reqwest_request = builder.build()?;
Ok(reqwest_request)
}
}