refactor + docs
This commit is contained in:
parent
bda82c9c7c
commit
38d4f30f55
@ -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 {
|
@ -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<HttpResponse, HttpError> {
|
||||
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()
|
||||
}
|
@ -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<HttpResponse, HttpError> {
|
||||
let mut request = request.clone();
|
||||
|
@ -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,
|
@ -1,6 +1,6 @@
|
||||
use std::error::Error;
|
||||
|
||||
/// Http error
|
||||
/// Http library errors
|
||||
#[derive(Debug)]
|
||||
pub enum HttpError {
|
||||
ReadLineEof,
|
@ -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::<Vec<char>>()
|
||||
.get(rand::thread_rng()
|
||||
.gen_range(0..CHARS.len())
|
||||
).unwrap().clone()
|
||||
)
|
||||
).collect::<Vec<String>>().join("")
|
||||
}
|
||||
|
||||
fn split_bytes_once(bytes: &[u8], sep: &[u8]) -> (Vec<u8>, Vec<u8>) {
|
||||
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<Vec<u8>> {
|
||||
if bytes.len() >= sep.len() {
|
||||
let indexes: Vec<usize> = bytes.windows(sep.len())
|
||||
.enumerate()
|
||||
.filter(|o| o.1 == sep)
|
||||
.map(|o| o.0)
|
||||
.collect();
|
||||
let mut parts: Vec<Vec<u8>> = Vec::new();
|
||||
let mut now_part: Vec<u8> = 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<String, HttpError> {
|
||||
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<String, HttpError> {
|
||||
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<dyn Sendable>;
|
||||
}
|
||||
|
||||
pub type Stream = TimeoutStream<TcpStream>;
|
121
src/lib.rs
121
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::<Vec<char>>()
|
||||
.get(rand::thread_rng()
|
||||
.gen_range(0..CHARS.len())
|
||||
).unwrap().clone()
|
||||
)
|
||||
).collect::<Vec<String>>().join("")
|
||||
}
|
||||
|
||||
fn split_bytes_once(bytes: &[u8], sep: &[u8]) -> (Vec<u8>, Vec<u8>) {
|
||||
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<Vec<u8>> {
|
||||
if bytes.len() >= sep.len() {
|
||||
let indexes: Vec<usize> = bytes.windows(sep.len())
|
||||
.enumerate()
|
||||
.filter(|o| o.1 == sep)
|
||||
.map(|o| o.0)
|
||||
.collect();
|
||||
let mut parts: Vec<Vec<u8>> = Vec::new();
|
||||
let mut now_part: Vec<u8> = 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<String, HttpError> {
|
||||
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<String, HttpError> {
|
||||
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<dyn Sendable>;
|
||||
}
|
||||
|
||||
pub type Stream = TimeoutStream<TcpStream>;
|
@ -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<Self> {
|
||||
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<Self, Self::Err> {
|
||||
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<Vec<Part>> {
|
||||
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<Part>) -> 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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user