strest 0.1.10

Blazing-fast async HTTP load tester in Rust - lock-free design, real-time stats, distributed runs, and optional chart exports for high-load API testing.
Documentation
use aws_credential_types::Credentials;
use aws_sigv4::http_request::{SignableBody, SignableRequest, SigningSettings, sign};
use aws_sigv4::sign::v4;
use aws_smithy_runtime_api::client::identity::Identity;
use base64::Engine as _;
use reqwest::{RequestBuilder, Url};

use crate::{
    args::HttpMethod,
    error::{AppError, AppResult, HttpError},
};

use super::data::AuthConfig;

const fn http_method_str(method: HttpMethod) -> &'static str {
    match method {
        HttpMethod::Get => "GET",
        HttpMethod::Post => "POST",
        HttpMethod::Patch => "PATCH",
        HttpMethod::Put => "PUT",
        HttpMethod::Delete => "DELETE",
    }
}

pub(super) fn apply_auth_headers(
    mut builder: RequestBuilder,
    method: HttpMethod,
    url: &Url,
    headers: &[(String, String)],
    body: &str,
    auth: &AuthConfig,
) -> AppResult<RequestBuilder> {
    match auth {
        AuthConfig::Basic { username, password } => {
            let token = format!("{}:{}", username, password);
            let encoded = base64::engine::general_purpose::STANDARD.encode(token.as_bytes());
            builder = builder.header("Authorization", format!("Basic {}", encoded));
            Ok(builder)
        }
        AuthConfig::SigV4 {
            access_key,
            secret_key,
            session_token,
            region,
            service,
        } => {
            let identity: Identity = Credentials::new(
                access_key,
                secret_key,
                session_token.clone(),
                None,
                "strest",
            )
            .into();
            let signing_settings = SigningSettings::default();
            let signing_params = v4::SigningParams::builder()
                .identity(&identity)
                .region(region)
                .name(service)
                .time(std::time::SystemTime::now())
                .settings(signing_settings)
                .build()
                .map_err(|err| {
                    AppError::http(HttpError::SigV4Params {
                        source: Box::new(err),
                    })
                })?
                .into();

            let method_str = http_method_str(method);
            let signable = SignableRequest::new(
                method_str,
                url.as_str(),
                headers.iter().map(|(k, v)| (k.as_str(), v.as_str())),
                SignableBody::Bytes(body.as_bytes()),
            )
            .map_err(|err| {
                AppError::http(HttpError::SigV4Request {
                    source: Box::new(err),
                })
            })?;

            let (instructions, _signature) = sign(signable, &signing_params)
                .map_err(|err| {
                    AppError::http(HttpError::SigV4Sign {
                        source: Box::new(err),
                    })
                })?
                .into_parts();

            let mut http_req = http::Request::builder()
                .method(method_str)
                .uri(url.as_str());
            for (key, value) in headers {
                http_req = http_req.header(key, value);
            }
            let mut http_req = http_req.body(()).map_err(|err| {
                AppError::http(HttpError::SigV4BuildSign {
                    source: Box::new(err),
                })
            })?;
            instructions.apply_to_request_http1x(&mut http_req);

            for (name, value) in http_req.headers() {
                builder = builder.header(name, value);
            }
            Ok(builder)
        }
    }
}