bt_http_utils 0.8.1

A simple HTTP wrapper to simplify POST and GET calls. Default headers with set and get headers. Support cookies. Request generic function for GET, POST, PUT, PATCH, and DELETE.
use std::collections::HashMap;

use bt_logger::{log_error, log_warning};
use reqwest::Response;

use crate::{convert_headers, HttpResponse};

const MAX_NUMBER_ERROR: i8 = 5;

///The HttpStreamResponse struct and its associated methods are designed to handle HTTP responses, including logging errors, 
/// checking response statuses, and reading the response stream asynchronously.
/// 
///Fields:
/// - ini_status_str: Initial status string of the HTTP response.
/// - ini_header: Initial headers of the HTTP response.
/// - url: URL from which the response was received.
/// - remote_address: Remote address of the server that sent the response.
/// - error_count: Counter for errors encountered during stream reading.
/// - resp: The actual reqwest::Response object.
/// 
/// Methods:
/// - new(http_resp: Response) -> Self: Initializes a new instance of HttpStreamResponse from a reqwest::Response.
/// - is_error() -> bool: Checks if the HTTP status code indicates an error.
/// - get_status() -> u16: Returns the HTTP status code as an unsigned 16-bit integer.
/// - get_ini_header() -> HashMap: Returns a copy of the initial headers.
/// - read_stream(&mut self) -> Option: Asynchronously reads and processes the stream from the HTTP response. 
///     * It handles errors by logging them and optionally stopping execution if too many errors occur.
///     * The read_stream method uses asynchronous I/O to read chunks from the response stream.
///     * It processes each chunk individually, converting it to a string if possible.
#[derive(Debug)]
pub struct HttpStreamResponse {
    //ini_status_code: u16,
    ini_status_str: String,
    ini_header: HashMap<String, String>,
    url: String,
    remote_address: String,
    error_count: i8,
    resp: Response,
}

impl HttpStreamResponse {
    pub fn new(http_resp: Response) -> Self{
        let ra = match http_resp.remote_addr() {
            Some(ip) => ip.ip().to_string(),
            None => {
                log_warning!("", "Remote Address not found. Using default 0.0.0.0");
                "0.0.0.0".to_owned()
            },
        };


        Self { 
            ini_status_str: http_resp.status().canonical_reason().unwrap_or("UNKNOWN ERROR!").to_owned(),
            ini_header: convert_headers(http_resp.headers()), 
            url: http_resp.url().to_string(), 
            remote_address: ra, 
            error_count: 0,
            resp: http_resp,
        }
    }

    pub fn is_error(&self) -> bool{
        self.resp.status().is_client_error() || self.resp.status().is_server_error()
    }

    pub fn get_status(&self) -> u16{
        self.resp.status().as_u16()
    }

    pub fn get_ini_header(&self) -> HashMap<String, String>{
        self.ini_header.clone()
    }

    pub async fn read_stream(&mut self) -> Option<HttpResponse> {
        if self.is_error() { //if response.status().is_client_error() || response.status().is_server_error() {
            log_error!( "", "ERROR: Failed to read stream response from {}. Status Code: {} ({})", self.url,self.get_status(),self.ini_status_str );
            Some(HttpResponse {
                status_code: self.get_status(),//response.status().as_u16(),
                header: self.get_ini_header(), //convert_headers(response.headers()),
                body: format!( "ERROR: Failed to read stream response from {}. Status: {}.", self.url, self.ini_status_str ),
                remote_address: self.remote_address.clone(),
            })
        } else {
                let chunk = self.resp.chunk().await;
                match chunk { 
                    Ok(r) => {
                        match r{
                            Some(chunk) => {
                                Some(HttpResponse {
                                    status_code: self.get_status(),
                                    header: convert_headers(self.resp.headers()),
                                    body: (String::from_utf8_lossy(&chunk)).to_string(),
                                    remote_address: self.remote_address.clone(),
                                })
                            },
                            None => None, //Stop
                        }
                    },
                    Err(e) => {
                        self.error_count += 1;
                        if self.error_count > MAX_NUMBER_ERROR{
                           log_error!("","Error reading streaming (>{} errors) from {}. Stop Executing returning None. Error {}",MAX_NUMBER_ERROR, self.url, e);
                           return None //Stop
                        }

                        log_error!("","Error reading streaming from {}. Return Empty body but continue. Error {}", &self.url, e);
                        Some(HttpResponse {
                            status_code: self.get_status(),
                            header: convert_headers(self.resp.headers()),
                            body: "".to_owned(),
                            remote_address: self.remote_address.clone(),
                        })
                    },
                }
            }
        }        
}