Skip to content

Commit

Permalink
feat:Redirects trapped in dead loop detection
Browse files Browse the repository at this point in the history
  • Loading branch information
ltpp-universe committed Dec 1, 2024
1 parent 2766a00 commit 7eec740
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 204 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "http-request"
version = "3.1.0"
version = "3.2.0"
edition = "2021"
authors = ["ltpp-universe <[email protected]>"]
license = "MIT"
Expand Down
2 changes: 2 additions & 0 deletions src/constant/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// The name of the application.
pub static APP_NAME: &str = "http-request";
20 changes: 16 additions & 4 deletions src/constant/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,27 @@ pub static CONTENT_LENGTH: &str = "Content-Length";
/// The HTTP header field name `Content-Type`, used to specify the media type of the resource or the data being sent in an HTTP request or response.
pub static CONTENT_TYPE: &str = "Content-Type";

/// The HTTP header field "Accept".
pub static ACCEPT: &str = "Accept";

/// The default value for the `Accept` header.
pub static ACCEPT_VALUE: &str = "*/*";

/// The HTTP header field "User-Agent".
pub static USER_AGENT: &str = "User-Agent";

/// The HTTP header field name `Host`, used to specify the host and port number of the server.
pub static HOST: &str = "Host";

/// The default HTTP version `HTTP/1.1` used in requests and responses.
pub static DEFAULT_HTTP_VERSION: &str = "HTTP/1.1";
pub static HTTP_VERSION_1_1: &str = "HTTP/1.1";

/// The default HTTP version `HTTP/2` used in requests and responses.
pub static HTTP_VERSION_2: &str = "HTTP/2";

/// The default HTTP path (`/`), typically used in requests when no specific path is provided.
pub static DEFAULT_HTTP_PATH: &str = "/";

/// The HTTP header field name `Connection`, used to specify control options for the current connection.
pub static CONNECTION: &str = "Connection";

/// The MIME type for JSON content, typically used for requests and responses
/// containing JSON data.
pub static APPLICATION_JSON: &str = "application/json";
Expand All @@ -44,3 +53,6 @@ pub static TEXT_HTML: &str = "text/html";
/// The MIME type for form-encoded data, commonly used for sending data in the
/// body of HTTP requests, especially for form submissions.
pub static FORM_URLENCODED: &str = "application/x-www-form-urlencoded";

/// Query symbols
pub static QUERY_SYMBOL: &str = "?";
1 change: 1 addition & 0 deletions src/constant/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod common;
pub mod http;
pub mod request;
22 changes: 12 additions & 10 deletions src/content_type/impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::constant::http::{
use serde::Serialize;
use serde_json;
use serde_xml_rs;
use std::fmt::Debug;
use std::fmt::{Debug, Display};
use std::str::FromStr;

impl ContentType {
Expand All @@ -21,7 +21,7 @@ impl ContentType {
/// # Returns
/// A string containing the serialized JSON representation of the provided data.
/// If serialization fails, it returns an empty JSON object (`{}`).
fn get_application_json<T: Serialize>(data: &T) -> String {
fn get_application_json<T: Serialize + Display>(data: &T) -> String {
serde_json::to_string(data).unwrap_or_else(|_| String::from("{}"))
}

Expand All @@ -37,7 +37,7 @@ impl ContentType {
/// # Returns
/// A string containing the serialized XML representation of the provided data.
/// If serialization fails, it returns an empty XML root element (`<root></root>`).
fn get_application_xml<T: Serialize>(data: &T) -> String {
fn get_application_xml<T: Serialize + Display>(data: &T) -> String {
serde_xml_rs::to_string(data).unwrap_or_else(|_| String::from("<root></root>"))
}

Expand All @@ -52,8 +52,8 @@ impl ContentType {
///
/// # Returns
/// A plain text string representing the provided data, formatted with the `Debug` trait.
fn get_text_plain<T: Serialize + Debug + Clone + Default>(data: &T) -> String {
format!("{:?}", data)
fn get_text_plain<T: Serialize + Debug + Clone + Default + Display>(data: &T) -> String {
data.to_string()
}

/// Handles the `text/html` Content-Type by formatting the provided data
Expand Down Expand Up @@ -86,7 +86,7 @@ impl ContentType {
/// # Returns
/// A string containing the URL-encoded representation of the provided data.
/// If serialization fails, it returns an empty string.
fn get_form_url_encoded<T: Serialize>(data: &T) -> String {
fn get_form_url_encoded<T: Serialize + Display>(data: &T) -> String {
serde_urlencoded::to_string(data).unwrap_or_else(|_| String::from(""))
}

Expand All @@ -101,9 +101,8 @@ impl ContentType {
///
/// # Returns
/// A string containing the hexadecimal encoding of the provided data.
fn get_binary<T: Serialize + Debug + Clone + Default>(data: &T) -> String {
let raw_data = format!("{:?}", data);
hex::encode(raw_data)
fn get_binary<T: Serialize + Debug + Clone + Default + Display>(data: &T) -> String {
hex::encode(data.to_string())
}

/// Public interface for getting a formatted body string based on the `ContentType`.
Expand All @@ -119,7 +118,10 @@ impl ContentType {
///
/// # Returns
/// A string containing the formatted body based on the content type, such as JSON, XML, plain text, HTML, etc.
pub fn get_body_string<T: Serialize + Debug + Clone + Default>(&self, data: &T) -> String {
pub fn get_body_string<T: Serialize + Debug + Clone + Default + Display>(
&self,
data: &T,
) -> String {
match self {
ContentType::ApplicationJson => ContentType::get_application_json(data),
ContentType::ApplicationXml => ContentType::get_application_xml(data),
Expand Down
30 changes: 14 additions & 16 deletions src/request/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,22 @@ fn output(title: &str, msg: &str, color: Color) {
#[test]
fn test_http_post_request() {
let mut header: HashMap<&str, &str> = HashMap::new();
header.insert("header-key", "header-value");
header.insert(":authority", "code.ltpp.vip");
header.insert(":method", "POST");
header.insert(":path", "/");
header.insert(":scheme", "http");
header.insert("Accept", "*/*");
header.insert("Content-Type", "application/json");
header.insert("Connection", "keep-alive");
header.insert("Accept-Encoding", "gzip, deflate");
let mut body: HashMap<&str, &str> = HashMap::new();
body.insert("body-key", "body-value");
body.insert("code", "hello");
body.insert("language", "rust");
body.insert("testin", "");
let mut _request_builder = HttpRequestBuilder::new()
.post("http://localhost:80")
.get("http://localhost:80/rust?hello=rust")
.json(body)
.headers(header)
.timeout(6000)
.redirect()
.max_redirect_times(8)
.http1_1_only()
.builder();
_request_builder
.send()
Expand All @@ -60,6 +60,7 @@ fn test_http_get_request() {
.timeout(6000)
.redirect()
.max_redirect_times(8)
.http1_1_only()
.builder();
_request_builder
.send()
Expand All @@ -73,14 +74,12 @@ fn test_http_get_request() {
#[test]
fn test_https_post_request() {
let mut header: HashMap<&str, &str> = HashMap::new();
header.insert(":authority", "code.ltpp.vip");
header.insert(":method", "POST");
header.insert(":path", "/");
header.insert(":scheme", "https");
header.insert("Accept", "*/*");
header.insert("Content-Type", "application/json");
header.insert("Connection", "keep-alive");
header.insert("Accept-Encoding", "gzip, deflate");
let mut body: HashMap<&str, &str> = HashMap::new();
body.insert("code", "fn main() {\r\n println!(\"hello world\");\r\n}");
body.insert("code", "hello");
body.insert("language", "rust");
body.insert("testin", "");
let mut _request_builder = HttpRequestBuilder::new()
Expand All @@ -90,6 +89,7 @@ fn test_https_post_request() {
.timeout(6000)
.redirect()
.max_redirect_times(8)
.http1_1_only()
.builder();
_request_builder
.send()
Expand All @@ -112,6 +112,7 @@ fn test_https_get_request() {
.timeout(6000)
.redirect()
.max_redirect_times(8)
.http1_1_only()
.builder();
_request_builder
.send()
Expand All @@ -125,10 +126,6 @@ fn test_https_get_request() {
#[test]
fn test_http_post_text_request() {
let mut header: HashMap<&str, &str> = HashMap::new();
header.insert(":authority", "code.ltpp.vip");
header.insert(":method", "POST");
header.insert(":path", "/");
header.insert(":scheme", "http");
header.insert("Accept", "*/*");
header.insert("Content-Type", "application/json");
let mut _request_builder = HttpRequestBuilder::new()
Expand All @@ -138,6 +135,7 @@ fn test_http_post_text_request() {
.timeout(6000)
.redirect()
.max_redirect_times(8)
.http1_1_only()
.builder();
_request_builder
.send()
Expand Down
18 changes: 17 additions & 1 deletion src/request/config/impl.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
use super::r#type::Config;
use crate::{constant::request::DEFAULT_TIMEOUT, request::request_url::r#type::RequestUrl};
use crate::{
constant::request::DEFAULT_TIMEOUT,
request::{http_version::r#type::HttpVersion, request_url::r#type::RequestUrl},
};

impl Default for Config {
/// Provides the default configuration for `Config`.
///
/// This method initializes a `Config` instance with default values:
/// - `timeout`: Set to the constant `DEFAULT_TIMEOUT`.
/// - `url_obj`: Initialized with the default value of `RequestUrl`.
/// - `redirect`: Set to `false` to disable redirects by default.
/// - `max_redirect_times`: Set to `8` to limit the number of allowed redirects.
/// - `redirect_times`: Set to `0` indicating no redirects have been made.
/// - `http_version`: Set to the default value of `HttpVersion`.
///
/// # Returns
/// Returns a `Config` instance with the default settings.
fn default() -> Self {
Config {
timeout: DEFAULT_TIMEOUT,
url_obj: RequestUrl::default(),
redirect: false,
max_redirect_times: 8,
redirect_times: 0,
http_version: HttpVersion::default(),
}
}
}
5 changes: 4 additions & 1 deletion src/request/config/type.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::request::request_url::r#type::RequestUrl;
use crate::request::{http_version::r#type::HttpVersion, request_url::r#type::RequestUrl};

/// Configuration for HTTP requests.
///
Expand Down Expand Up @@ -40,4 +40,7 @@ pub struct Config {

/// The current count of redirections followed during this request.
pub redirect_times: usize,

/// The type of this field is `HttpVersion`.
pub http_version: HttpVersion,
}
55 changes: 26 additions & 29 deletions src/request/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,22 @@ use std::{
fmt::{self},
};

/// Custom error type for handling various HTTP-related errors.
/// Represents different types of errors that can occur in the application.
///
/// This `Error` enum is designed to capture and represent specific errors that can occur
/// during HTTP operations, including issues with URL parsing, TCP stream connections,
/// request processing, unsupported HTTP methods, and connection read failures.
/// It provides clear and structured error reporting for HTTP-related functionalities.
/// The `Error` enum defines various error types related to HTTP requests, network connections, and TLS operations.
/// Each variant corresponds to a specific error that can occur during the execution of the application.
///
/// # Variants
///
/// - `InvalidUrl`:
/// Indicates that the provided URL is invalid, such as malformed or missing critical components.
/// - `TcpStreamConnectError`:
/// Represents a failure while attempting to establish a TCP stream connection.
/// - `RequestError`:
/// A general error that occurs during the processing of an HTTP request.
/// - `MethodsNotSupport`:
/// Signifies that the specified HTTP method is unsupported by the library or server.
/// - `ReadConnectionError`:
/// Occurs when reading data from a connection fails, such as during a response retrieval.
/// - `TlsConnectorBuildError`:
/// Indicates an error while constructing a TLS connector, potentially due to configuration issues.
/// - `SetReadTimeoutError`:
/// Represents an error when setting the read timeout on a connection fails.
/// - `TlsStreamConnectError`:
/// Occurs when a TLS-secured connection cannot be established.
///
/// # Traits Implemented
///
/// - `StdError`:
/// Enables integration with Rust's standard error handling mechanisms, such as `Result`.
/// - `fmt::Display`:
/// Provides human-readable error messages for debugging or logging.
/// - `InvalidUrl`: Indicates that the provided URL is invalid.
/// - `TcpStreamConnectError`: Represents an error that occurred while attempting to connect a TCP stream.
/// - `RequestError`: A general error related to making a request.
/// - `MethodsNotSupport`: Indicates that the requested HTTP method is not supported.
/// - `ReadConnectionError`: An error that occurred while reading from the connection.
/// - `TlsConnectorBuildError`: Indicates an error during the construction of the TLS connector.
/// - `SetReadTimeoutError`: Occurs when setting the read timeout fails.
/// - `TlsStreamConnectError`: Represents an error that occurred while establishing a TLS stream connection.
/// - `MaxRedirectTimes`: Occurs when the maximum number of redirects is exceeded.
/// - `RedirectUrlDeadLoop`: Indicates that a redirect URL has resulted in a dead loop.
#[derive(Debug)]
pub enum Error {
InvalidUrl,
Expand All @@ -46,11 +30,23 @@ pub enum Error {
SetReadTimeoutError,
TlsStreamConnectError,
MaxRedirectTimes,
RedirectUrlDeadLoop,
}

impl StdError for Error {}

impl fmt::Display for Error {
/// Formats the `Error` enum into a human-readable string.
///
/// This method implements the `fmt::Display` trait for the `Error` enum, allowing it to be
/// formatted into a string representation. Each variant is matched and a corresponding
/// error message is returned for display.
///
/// # Parameters
/// - `f`: A mutable reference to the `fmt::Formatter` that handles the formatting of the error.
///
/// # Returns
/// A `fmt::Result` which indicates whether the formatting was successful.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidUrl => write!(f, "Invalid URL"),
Expand All @@ -62,6 +58,7 @@ impl fmt::Display for Error {
Error::SetReadTimeoutError => write!(f, "Failed to Set Read Timeout"),
Error::TlsStreamConnectError => write!(f, "TLS Stream Connection Error"),
Error::MaxRedirectTimes => write!(f, "Max Redirect Times"),
Error::RedirectUrlDeadLoop => write!(f, "Redirect URL Dead Loop"),
}
}
}
Loading

0 comments on commit 7eec740

Please sign in to comment.