From 38d4f30f55612edbbe893399c555d92b2ba5018c Mon Sep 17 00:00:00 2001 From: MeexReay Date: Sun, 19 Jan 2025 11:25:38 +0300 Subject: [PATCH] refactor + docs --- src/{ezhttp => }/body.rs | 11 ++- src/{ezhttp => }/client/client.rs | 12 ++- src/{ezhttp => }/client/mod.rs | 2 - src/{ezhttp => }/client/proxy.rs | 0 src/{ezhttp => }/client/req_builder.rs | 31 +++++++ src/{ezhttp => }/error.rs | 2 +- src/ezhttp/mod.rs | 120 ------------------------ src/{ezhttp => }/headers.rs | 0 src/lib.rs | 121 ++++++++++++++++++++++++- src/{ezhttp => }/request.rs | 14 +++ src/{ezhttp => }/response.rs | 0 src/{ezhttp => }/server/handler.rs | 0 src/{ezhttp => }/server/mod.rs | 0 src/{ezhttp => }/server/starter.rs | 0 14 files changed, 184 insertions(+), 129 deletions(-) rename src/{ezhttp => }/body.rs (98%) rename src/{ezhttp => }/client/client.rs (80%) rename src/{ezhttp => }/client/mod.rs (99%) rename src/{ezhttp => }/client/proxy.rs (100%) rename src/{ezhttp => }/client/req_builder.rs (80%) rename src/{ezhttp => }/error.rs (95%) delete mode 100755 src/ezhttp/mod.rs rename src/{ezhttp => }/headers.rs (100%) rename src/{ezhttp => }/request.rs (91%) rename src/{ezhttp => }/response.rs (100%) rename src/{ezhttp => }/server/handler.rs (100%) rename src/{ezhttp => }/server/mod.rs (100%) rename src/{ezhttp => }/server/starter.rs (100%) diff --git a/src/ezhttp/body.rs b/src/body.rs similarity index 98% rename from src/ezhttp/body.rs rename to src/body.rs index 9b11a07..740235f 100755 --- a/src/ezhttp/body.rs +++ b/src/body.rs @@ -4,9 +4,14 @@ use async_trait::async_trait; use serde_json::Value; use tokio::{fs, io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}}; -use crate::ezhttp::{split_bytes, split_bytes_once}; - -use super::{error::HttpError, headers::Headers, read_line_crlf, Sendable}; +use super::{ + split_bytes, + split_bytes_once, + error::HttpError, + headers::Headers, + read_line_crlf, + Sendable +}; #[derive(Debug, Clone)] pub struct Body { diff --git a/src/ezhttp/client/client.rs b/src/client/client.rs similarity index 80% rename from src/ezhttp/client/client.rs rename to src/client/client.rs index f101040..24da97b 100755 --- a/src/ezhttp/client/client.rs +++ b/src/client/client.rs @@ -2,12 +2,14 @@ use crate::{error::HttpError, headers::Headers, prelude::HttpResponse, request:: use super::{send_request, Proxy}; +/// Client that sends http requests pub struct HttpClient { proxy: Proxy, verify: bool, headers: Headers } +/// [`HttpClient`](HttpClient) builder pub struct ClientBuilder { proxy: Proxy, verify: bool, @@ -15,6 +17,7 @@ pub struct ClientBuilder { } impl ClientBuilder { + /// Create a client builder pub fn new() -> ClientBuilder { ClientBuilder { proxy: Proxy::None, @@ -23,6 +26,7 @@ impl ClientBuilder { } } + /// Build a client pub fn build(self) -> HttpClient { HttpClient { proxy: self.proxy, @@ -31,22 +35,25 @@ impl ClientBuilder { } } + /// Set client proxy pub fn proxy(mut self, proxy: Proxy) -> Self { self.proxy = proxy; self } + /// Set is client have to verify ssl certificate pub fn verify(mut self, verify: bool) -> Self { self.verify = verify; self } - + /// Set default headers pub fn headers(mut self, headers: Headers) -> Self { self.headers = headers; self } + /// Set default header pub fn header(mut self, name: impl ToString, value: impl ToString) -> Self { self.headers.put(name, value.to_string()); self @@ -54,16 +61,19 @@ impl ClientBuilder { } impl HttpClient { + /// Get new HttpClient builder pub fn builder() -> ClientBuilder { ClientBuilder::new() } + /// Sends a request and receives a response pub async fn send(&self, request: HttpRequest) -> Result { send_request(request, self.verify, self.proxy.clone(), self.headers.clone()).await } } impl Default for HttpClient { + /// Create default HttpClient fn default() -> Self { ClientBuilder::new().build() } diff --git a/src/ezhttp/client/mod.rs b/src/client/mod.rs similarity index 99% rename from src/ezhttp/client/mod.rs rename to src/client/mod.rs index a723180..651567c 100755 --- a/src/ezhttp/client/mod.rs +++ b/src/client/mod.rs @@ -18,8 +18,6 @@ pub use req_builder::*; pub use client::*; pub use proxy::*; - -// TODO: proxy support async fn send_request(request: HttpRequest, ssl_verify: bool, proxy: Proxy, headers: Headers) -> Result { let mut request = request.clone(); diff --git a/src/ezhttp/client/proxy.rs b/src/client/proxy.rs similarity index 100% rename from src/ezhttp/client/proxy.rs rename to src/client/proxy.rs diff --git a/src/ezhttp/client/req_builder.rs b/src/client/req_builder.rs similarity index 80% rename from src/ezhttp/client/req_builder.rs rename to src/client/req_builder.rs index 7f24e98..19fa9ad 100755 --- a/src/ezhttp/client/req_builder.rs +++ b/src/client/req_builder.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::{super::body::{Body, Part}, gen_multipart_boundary, super::headers::Headers, super::request::{HttpRequest, URL}}; +/// Builder for [`HttpRequest`](HttpRequest) pub struct RequestBuilder { method: String, url: URL, @@ -12,6 +13,7 @@ pub struct RequestBuilder { } impl RequestBuilder { + /// Create builder with a custom method pub fn new(method: String, url: URL) -> Self { RequestBuilder { method, @@ -21,56 +23,82 @@ impl RequestBuilder { } } + /// Create builder for a GET request pub fn get(url: URL) -> Self { Self::new("GET".to_string(), url) } + + /// Create builder for a HEAD request pub fn head(url: URL) -> Self { Self::new("HEAD".to_string(), url) } + + /// Create builder for a POST request pub fn post(url: URL) -> Self { Self::new("POST".to_string(), url) } + + /// Create builder for a PUT request pub fn put(url: URL) -> Self { Self::new("PUT".to_string(), url) } + + /// Create builder for a DELETE request pub fn delete(url: URL) -> Self { Self::new("DELETE".to_string(), url) } + + /// Create builder for a CONNECT request pub fn connect(url: URL) -> Self { Self::new("CONNECT".to_string(), url) } + + /// Create builder for a OPTIONS request pub fn options(url: URL) -> Self { Self::new("OPTIONS".to_string(), url) } + + /// Create builder for a TRACE request pub fn trace(url: URL) -> Self { Self::new("TRACE".to_string(), url) } + + /// Create builder for a PATCH request pub fn patch(url: URL) -> Self { Self::new("PATCH".to_string(), url) } + /// Set request url pub fn url(mut self, url: URL) -> Self { self.url = url; self } + /// Set request method pub fn method(mut self, method: String) -> Self { self.method = method; self } + /// Set headers pub fn headers(mut self, headers: Headers) -> Self { self.headers = headers; self } + /// Set header pub fn header(mut self, name: impl ToString, value: impl ToString) -> Self { self.headers.put(name, value.to_string()); self } + /// Set body pub fn body(mut self, body: Body) -> Self { self.body = Some(body); self } + /// Set text as body pub fn text(mut self, text: impl ToString) -> Self { self.body = Some(Body::from_text(text.to_string().as_str())); self } + /// Set json as body pub fn json(mut self, json: Value) -> Self { self.body = Some(Body::from_json(json)); self } + /// Set raw bytes as body pub fn bytes(mut self, bytes: &[u8]) -> Self { self.body = Some(Body::from_bytes(bytes)); self } + /// Set multipart as body pub fn multipart(mut self, parts: &[Part]) -> Self { let boundary = gen_multipart_boundary(); self.headers.put("Content-Type", format!("multipart/form-data; boundary={}", boundary.clone())); @@ -78,16 +106,19 @@ impl RequestBuilder { self } + /// Set query in url pub fn url_query(mut self, query: &[(impl ToString, impl ToString)]) -> Self { self.url.query = HashMap::from_iter(query.iter().map(|o| (o.0.to_string(), o.1.to_string()))); self } + /// Set query as body pub fn body_query(mut self, query: &[(impl ToString, impl ToString)]) -> Self { self.body = Some(Body::from_query(HashMap::from_iter(query.iter().map(|o| (o.0.to_string(), o.1.to_string()))))); self } + /// Build request pub fn build(self) -> HttpRequest { HttpRequest { url: self.url, diff --git a/src/ezhttp/error.rs b/src/error.rs similarity index 95% rename from src/ezhttp/error.rs rename to src/error.rs index 03db81c..e8b0f52 100755 --- a/src/ezhttp/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ use std::error::Error; -/// Http error +/// Http library errors #[derive(Debug)] pub enum HttpError { ReadLineEof, diff --git a/src/ezhttp/mod.rs b/src/ezhttp/mod.rs deleted file mode 100755 index a325890..0000000 --- a/src/ezhttp/mod.rs +++ /dev/null @@ -1,120 +0,0 @@ -pub mod error; -pub mod headers; -pub mod request; -pub mod response; -pub mod body; -pub mod server; -pub mod client; - -pub mod prelude { - pub use super::error::*; - pub use super::headers::*; - pub use super::request::*; - pub use super::response::*; - pub use super::response::status_code::*; - pub use super::body::*; - pub use super::server::*; - pub use super::server::handler::*; - pub use super::server::starter::*; - // pub use super::client::*; - pub use super::*; -} - -use error::HttpError; -use rand::Rng; -use tokio::{io::{AsyncReadExt, AsyncWrite}, net::TcpStream}; -use tokio_io_timeout::TimeoutStream; -use async_trait::async_trait; - -const CHARS: &str = "qwertyuiopasdfghjklzxcvbnm0123456789QWERTYUIOPASDFGHJKLZXCVBNM'()+_,-./:=?"; - -pub fn gen_multipart_boundary() -> String { - let range = 20..40; - let length: usize = rand::thread_rng().gen_range(range); - [0..length].iter().map(|_| - String::from(CHARS.chars() - .collect::>() - .get(rand::thread_rng() - .gen_range(0..CHARS.len()) - ).unwrap().clone() - ) - ).collect::>().join("") -} - -fn split_bytes_once(bytes: &[u8], sep: &[u8]) -> (Vec, Vec) { - if let Some(index) = bytes.windows(sep.len()) - .enumerate() - .filter(|o| o.1 == sep) - .map(|o| o.0) - .next() { - let t = bytes.split_at(index); - (t.0.to_vec(), t.1.split_at(sep.len()).1.to_vec()) - } else { - (Vec::from(bytes), Vec::new()) - } -} - -fn split_bytes(bytes: &[u8], sep: &[u8]) -> Vec> { - if bytes.len() >= sep.len() { - let indexes: Vec = bytes.windows(sep.len()) - .enumerate() - .filter(|o| o.1 == sep) - .map(|o| o.0) - .collect(); - let mut parts: Vec> = Vec::new(); - let mut now_part: Vec = Vec::new(); - let mut i = 0usize; - loop { - if i >= bytes.len() { - break; - } - - if indexes.contains(&i) { - parts.push(now_part.clone()); - now_part.clear(); - i += sep.len(); - continue; - } - - now_part.push(bytes[i]); - - i += 1; - } - parts.push(now_part); - parts - } else { - vec![Vec::from(bytes)] - } -} - -async fn read_line(data: &mut (impl AsyncReadExt + Unpin)) -> Result { - let mut line = Vec::new(); - loop { - let mut buffer = vec![0;1]; - data.read_exact(&mut buffer).await.or(Err(HttpError::ReadLineEof))?; - let char = buffer[0]; - line.push(char); - if char == 0x0a { - break; - } - } - String::from_utf8(line).or(Err(HttpError::ReadLineUnknown)) -} - -async fn read_line_crlf(data: &mut (impl AsyncReadExt + Unpin)) -> Result { - match read_line(data).await { - Ok(i) => Ok(i[..i.len() - 2].to_string()), - Err(e) => Err(e), - } -} - -#[async_trait] -pub trait Sendable: Send + Sync { - async fn send( - &self, - stream: &mut (dyn AsyncWrite + Unpin + Send + Sync), - ) -> Result<(), HttpError>; - fn as_box(self) -> Box; -} - -pub type Stream = TimeoutStream; \ No newline at end of file diff --git a/src/ezhttp/headers.rs b/src/headers.rs similarity index 100% rename from src/ezhttp/headers.rs rename to src/headers.rs diff --git a/src/lib.rs b/src/lib.rs index 6ac547c..4a968d0 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,120 @@ -pub mod ezhttp; +pub mod error; +pub mod headers; +pub mod request; +pub mod response; +pub mod body; +pub mod server; +pub mod client; -pub use ezhttp::*; +pub mod prelude { + pub use super::error::*; + pub use super::headers::*; + pub use super::request::*; + pub use super::response::*; + pub use super::response::status_code::*; + pub use super::body::*; + pub use super::server::*; + pub use super::server::handler::*; + pub use super::server::starter::*; + pub use super::client::*; + pub use super::*; +} + +use error::HttpError; +use rand::Rng; +use tokio::{io::{AsyncReadExt, AsyncWrite}, net::TcpStream}; +use tokio_io_timeout::TimeoutStream; +use async_trait::async_trait; + +const CHARS: &str = "qwertyuiopasdfghjklzxcvbnm0123456789QWERTYUIOPASDFGHJKLZXCVBNM'()+_,-./:=?"; + +pub fn gen_multipart_boundary() -> String { + let range = 20..40; + let length: usize = rand::thread_rng().gen_range(range); + [0..length].iter().map(|_| + String::from(CHARS.chars() + .collect::>() + .get(rand::thread_rng() + .gen_range(0..CHARS.len()) + ).unwrap().clone() + ) + ).collect::>().join("") +} + +fn split_bytes_once(bytes: &[u8], sep: &[u8]) -> (Vec, Vec) { + if let Some(index) = bytes.windows(sep.len()) + .enumerate() + .filter(|o| o.1 == sep) + .map(|o| o.0) + .next() { + let t = bytes.split_at(index); + (t.0.to_vec(), t.1.split_at(sep.len()).1.to_vec()) + } else { + (Vec::from(bytes), Vec::new()) + } +} + +fn split_bytes(bytes: &[u8], sep: &[u8]) -> Vec> { + if bytes.len() >= sep.len() { + let indexes: Vec = bytes.windows(sep.len()) + .enumerate() + .filter(|o| o.1 == sep) + .map(|o| o.0) + .collect(); + let mut parts: Vec> = Vec::new(); + let mut now_part: Vec = Vec::new(); + let mut i = 0usize; + loop { + if i >= bytes.len() { + break; + } + + if indexes.contains(&i) { + parts.push(now_part.clone()); + now_part.clear(); + i += sep.len(); + continue; + } + + now_part.push(bytes[i]); + + i += 1; + } + parts.push(now_part); + parts + } else { + vec![Vec::from(bytes)] + } +} + +async fn read_line(data: &mut (impl AsyncReadExt + Unpin)) -> Result { + let mut line = Vec::new(); + loop { + let mut buffer = vec![0;1]; + data.read_exact(&mut buffer).await.or(Err(HttpError::ReadLineEof))?; + let char = buffer[0]; + line.push(char); + if char == 0x0a { + break; + } + } + String::from_utf8(line).or(Err(HttpError::ReadLineUnknown)) +} + +async fn read_line_crlf(data: &mut (impl AsyncReadExt + Unpin)) -> Result { + match read_line(data).await { + Ok(i) => Ok(i[..i.len() - 2].to_string()), + Err(e) => Err(e), + } +} + +#[async_trait] +pub trait Sendable: Send + Sync { + async fn send( + &self, + stream: &mut (dyn AsyncWrite + Unpin + Send + Sync), + ) -> Result<(), HttpError>; + fn as_box(self) -> Box; +} + +pub type Stream = TimeoutStream; \ No newline at end of file diff --git a/src/ezhttp/request.rs b/src/request.rs similarity index 91% rename from src/ezhttp/request.rs rename to src/request.rs index ddd3928..f063bc5 100755 --- a/src/ezhttp/request.rs +++ b/src/request.rs @@ -6,6 +6,7 @@ use std::{ use async_trait::async_trait; use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; +/// Request URL #[derive(Clone, Debug)] pub struct URL { pub path: String, @@ -35,6 +36,8 @@ impl URL { } } + /// Turns URL object to url string without scheme, domain, port + /// Example: /123.html?k=v#anc pub fn to_path_string(&self) -> String { format!("{}{}{}", self.path, if self.query.is_empty() { String::new() @@ -49,6 +52,8 @@ impl URL { }) } + /// Turns string without scheme, domain, port to URL object + /// Example of string: /123.html?k=v#anc pub fn from_path_string(s: &str, scheme: String, domain: String, port: u16) -> Option { let (s, anchor) = s.split_once("#").unwrap_or((s, "")); let (path, query) = s.split_once("?").unwrap_or((s, "")); @@ -69,6 +74,9 @@ impl URL { impl FromStr for URL { type Err = HttpError; + /// Turns url string to URL object + /// Example: https://domain.com:999/123.html?k=v#anc + /// Example 2: http://exampl.eu/sing fn from_str(s: &str) -> Result { let (scheme, s) = s.split_once("://").ok_or(HttpError::UrlError)?; let (host, s) = s.split_once("/").unwrap_or((s, "")); @@ -96,6 +104,9 @@ impl FromStr for URL { } impl ToString for URL { + /// Turns URL object to string + /// Example: https://domain.com:999/123.html?k=v#anc + /// Example 2: http://exampl.eu/sing fn to_string(&self) -> String { format!("{}://{}{}", self.scheme, { if (self.scheme == "http" && self.port != 80) || (self.scheme == "https" && self.port != 443) { @@ -173,6 +184,7 @@ impl HttpRequest { )) } + /// Get multipart parts (requires Content-Type header) pub fn get_multipart(&self) -> Option> { let boundary = self.headers.get("content-type")? .split(";") @@ -182,6 +194,7 @@ impl HttpRequest { Some(self.body.as_multipart(boundary)) } + /// Set multipart parts (modifies Content-Type header) pub fn set_multipart(&mut self, parts: Vec) -> Option<()> { let boundary = gen_multipart_boundary(); self.headers.put("Content-Type", format!("multipart/form-data; boundary={}", boundary.clone())); @@ -189,6 +202,7 @@ impl HttpRequest { Some(()) } + /// Create new request builder pub fn builder(method: String, url: URL) -> RequestBuilder { RequestBuilder::new(method, url) } diff --git a/src/ezhttp/response.rs b/src/response.rs similarity index 100% rename from src/ezhttp/response.rs rename to src/response.rs diff --git a/src/ezhttp/server/handler.rs b/src/server/handler.rs similarity index 100% rename from src/ezhttp/server/handler.rs rename to src/server/handler.rs diff --git a/src/ezhttp/server/mod.rs b/src/server/mod.rs similarity index 100% rename from src/ezhttp/server/mod.rs rename to src/server/mod.rs diff --git a/src/ezhttp/server/starter.rs b/src/server/starter.rs similarity index 100% rename from src/ezhttp/server/starter.rs rename to src/server/starter.rs