e9571_http_pro_lib 0.1.0

A Rust HTTP client library for making GET, POST, and other requests with header and proxy support
Documentation
use reqwest::{Client, ClientBuilder, Proxy, header::{HeaderMap, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue}};
use rand::seq::SliceRandom;
use std::str::FromStr;

// Custom error type to handle multiple error sources
#[derive(Debug)]
pub enum Error {
    Reqwest(reqwest::Error),
    InvalidHeaderName(InvalidHeaderName),
    InvalidHeaderValue(InvalidHeaderValue),
    SerdeJson(serde_json::Error), // Added to handle serde_json::Error
}

// Implement From traits for error conversion
impl From<reqwest::Error> for Error {
    fn from(err: reqwest::Error) -> Self {
        Error::Reqwest(err)
    }
}

impl From<InvalidHeaderName> for Error {
    fn from(err: InvalidHeaderName) -> Self {
        Error::InvalidHeaderName(err)
    }
}

impl From<InvalidHeaderValue> for Error {
    fn from(err: InvalidHeaderValue) -> Self {
        Error::InvalidHeaderValue(err)
    }
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::SerdeJson(err)
    }
}

pub mod e9571_http_pro_lib {
    use super::{Error, HeaderMap, HeaderName, HeaderValue};
    use reqwest::{Client, ClientBuilder, Proxy, RequestBuilder};
    use serde_json::{json, Value};
    use std::collections::HashMap;
    use std::time::Duration;
    use std::str::FromStr;
    use rand::prelude::IndexedRandom;

    /// Simulated Random User-Agent for HTTP requests
    fn random_ua() -> &'static str {
        let user_agents = [
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
            "Mozilla/5.0 (X11; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0",
        ];
        user_agents.choose(&mut rand::rng()).unwrap_or(&user_agents[0])
    }

    /// Generic HTTP worker with optional proxy support
    ///
    /// # Arguments
    /// * `url` - The target URL for the HTTP request
    /// * `config` - Optional headers as a HashMap
    /// * `json_value` - Optional JSON data for the request body
    /// * `request_type` - HTTP method (e.g., "get", "post", "postjson", "delete", "put", "putjson")
    /// * `proxy` - Optional proxy URL (e.g., "http://127.0.0.1:8080"). None or empty string means no proxy
    ///
    /// # Returns
    /// A Result containing the response body as a String and headers as a serde_json::Value
    pub async fn marmot_worker(
        url: &str,
        config: Option<HashMap<String, String>>,
        json_value: Option<&Value>,
        request_type: &str,
        proxy: Option<&str>,
    ) -> Result<(String, serde_json::Value), Error> {
        let mut client_builder = ClientBuilder::new()
            .user_agent(random_ua())
            .timeout(Duration::from_secs(30));

        // Set proxy if provided
        if let Some(proxy_url) = proxy {
            if !proxy_url.is_empty() {
                let proxy = Proxy::all(proxy_url)?;
                client_builder = client_builder.proxy(proxy);
            }
        }

        let client = client_builder.build()?;

        let mut headers = HeaderMap::new();
        if let Some(config_map) = config {
            for (key, value) in config_map {
                let header_name = HeaderName::from_str(&key)?;
                let header_value = HeaderValue::from_str(&value)?;
                headers.insert(header_name, header_value);
            }
        }

        let mut request_builder = match request_type.to_lowercase().as_str() {
            "get" => client.get(url),
            "post" => {
                headers.insert("Content-Type", HeaderValue::from_static("application/x-www-form-urlencoded"));
                let default_json = json!({});
                let form_data = json_value.unwrap_or(&default_json);
                client.post(url).form(form_data)
            },
            "postjson" => {
                headers.insert("Content-Type", HeaderValue::from_static("application/json"));
                let default_json = json!({});
                let json_data = json_value.unwrap_or(&default_json);
                client.post(url).json(json_data)
            },
            "delete" => client.delete(url),
            "put" => {
                headers.insert("Content-Type", HeaderValue::from_static("application/json"));
                let default_json = json!({});
                let json_data = json_value.unwrap_or(&default_json);
                client.put(url).json(json_data)
            },
            "putjson" => {
                headers.insert("Content-Type", HeaderValue::from_static("application/json"));
                let default_json = json!({});
                let json_data = json_value.unwrap_or(&default_json);
                client.put(url).json(json_data)
            },
            _ => client.get(url),
        };

        request_builder = request_builder.headers(headers.clone());
        let response = request_builder.send().await?;

        let response_headers = response.headers().clone();
        let body = response.text().await?;
        let headers_json = json!(response_headers
            .iter()
            .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
            .collect::<HashMap<String, String>>());

        Ok((body, headers_json))
    }

    /// GET request with custom headers
    ///
    /// # Arguments
    /// * `url` - The target URL for the GET request
    /// * `config` - Headers as a HashMap
    ///
    /// # Returns
    /// A Result containing the response body as a String and headers as a serde_json::Value
    pub async fn create_url_data_get_header(
        url: &str,
        config: HashMap<String, String>,
    ) -> Result<(String, serde_json::Value), Error> {
        marmot_worker(url, Some(config), None, "get", None).await
    }

    /// GET request with custom headers and proxy
    ///
    /// # Arguments
    /// * `url` - The target URL for the GET request
    /// * `config` - Headers as a HashMap
    /// * `proxy` - Proxy URL (e.g., "http://127.0.0.1:8080")
    ///
    /// # Returns
    /// A Result containing the response body as a String and headers as a serde_json::Value
    pub async fn create_url_data_get_header_proxy(
        url: &str,
        config: HashMap<String, String>,
        proxy: &str,
    ) -> Result<(String, serde_json::Value), Error> {
        marmot_worker(url, Some(config), None, "get", Some(proxy)).await
    }

    /// POST request with form data
    ///
    /// # Arguments
    /// * `url` - The target URL for the POST request
    /// * `config` - Optional headers as a HashMap
    /// * `form_data` - Form data as a HashMap
    ///
    /// # Returns
    /// A Result containing the response body as a String and headers as a serde_json::Value
    pub async fn marmot_worker_post_form(
        url: &str,
        config: Option<HashMap<String, String>>,
        form_data: HashMap<String, String>,
    ) -> Result<(String, serde_json::Value), Error> {
        let form_json = json!(form_data);
        marmot_worker(url, config, Some(&form_json), "post", None).await
    }

    /// POST request with JSON data
    ///
    /// # Arguments
    /// * `url` - The target URL for the POST request
    /// * `config` - Optional headers as a HashMap
    /// * `json_data` - JSON-serializable data
    ///
    /// # Returns
    /// A Result containing the response body as a String and headers as a serde_json::Value
    pub async fn marmot_worker_post_json<T: serde::Serialize>(
        url: &str,
        config: Option<HashMap<String, String>>,
        json_data: &T,
    ) -> Result<(String, serde_json::Value), Error> {
        let json_value = serde_json::to_value(json_data)?;
        marmot_worker(url, config, Some(&json_value), "postjson", None).await
    }

    /// POST request with JSON data (simplified, standalone version)
    ///
    /// # Arguments
    /// * `url` - The target URL for the POST request
    /// * `data` - JSON-serializable data
    ///
    /// # Returns
    /// A Result containing the response body as a String
    pub async fn post_json<T: serde::Serialize>(url: &str, data: &T) -> Result<String, Error> {
        let client = Client::builder()
            .timeout(Duration::from_secs(30))
            .build()?;

        let response = client
            .post(url)
            .header("Content-Type", "application/json")
            .json(data)
            .send()
            .await?;

        let body = response.text().await?;
        Ok(body)
    }
}