update request connecting and adding timeouts wow

This commit is contained in:
MeexReay 2025-01-25 17:09:09 +03:00
parent 38d4f30f55
commit 18f012835b
6 changed files with 153 additions and 150 deletions

6
Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@ -448,9 +448,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.136"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "336a0c23cf42a38d9eaa7cd22c7040d04e1228a19a933890805ffd00a16437d2"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
dependencies = [
"itoa",
"memchr",

View File

@ -4,14 +4,14 @@ version = "0.2.1"
edition = "2021"
repository = "https://git.meex.lol/MeexReay/ezhttp"
description = "easy http server for small sites"
description = "Simple async http library with client and server"
license-file = "LICENSE"
readme = "README.md"
keywords = ["http", "server", "site", "async"]
[dependencies]
urlencoding = "2.1.3"
serde_json = "1.0.135"
serde_json = "1.0.137"
tokio = { version = "1.43.0", features = ["full"] }
tokio-io-timeout = "1.2.0"
threadpool = "1.8.1"

View File

@ -1,8 +1,6 @@
# EzHttp
Simple async http library with client and server
This library is under developement, so if you found any bugs, please write them to [Issues](https://git.meex.lol/MeexReay/ezhttp/issues)
## Setup
```toml

View File

@ -1,3 +1,5 @@
use std::time::Duration;
use crate::{error::HttpError, headers::Headers, prelude::HttpResponse, request::HttpRequest};
use super::{send_request, Proxy};
@ -5,15 +7,21 @@ use super::{send_request, Proxy};
/// Client that sends http requests
pub struct HttpClient {
proxy: Proxy,
verify: bool,
headers: Headers
ssl_verify: bool,
headers: Headers,
connect_timeout: Option<Duration>,
write_timeout: Option<Duration>,
read_timeout: Option<Duration>
}
/// [`HttpClient`](HttpClient) builder
pub struct ClientBuilder {
proxy: Proxy,
verify: bool,
headers: Headers
ssl_verify: bool,
headers: Headers,
connect_timeout: Option<Duration>,
write_timeout: Option<Duration>,
read_timeout: Option<Duration>
}
impl ClientBuilder {
@ -21,8 +29,11 @@ impl ClientBuilder {
pub fn new() -> ClientBuilder {
ClientBuilder {
proxy: Proxy::None,
verify: false,
headers: Headers::new()
ssl_verify: false,
headers: Headers::new(),
connect_timeout: None,
write_timeout: None,
read_timeout: None
}
}
@ -30,11 +41,40 @@ impl ClientBuilder {
pub fn build(self) -> HttpClient {
HttpClient {
proxy: self.proxy,
verify: self.verify,
headers: self.headers
ssl_verify: self.ssl_verify,
headers: self.headers,
connect_timeout: self.connect_timeout,
write_timeout: self.write_timeout,
read_timeout: self.read_timeout
}
}
/// Set request timeouts0
pub fn timeout(mut self, connect: Option<Duration>, read: Option<Duration>, write: Option<Duration>) -> Self {
self.connect_timeout = connect;
self.read_timeout = read;
self.write_timeout = write;
self
}
/// Set connect timeout
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.connect_timeout = Some(timeout);
self
}
/// Set read timeout
pub fn read_timeout(mut self, timeout: Duration) -> Self {
self.read_timeout = Some(timeout);
self
}
/// Set write timeout
pub fn write_timeout(mut self, timeout: Duration) -> Self {
self.write_timeout = Some(timeout);
self
}
/// Set client proxy
pub fn proxy(mut self, proxy: Proxy) -> Self {
self.proxy = proxy;
@ -42,8 +82,8 @@ impl ClientBuilder {
}
/// Set is client have to verify ssl certificate
pub fn verify(mut self, verify: bool) -> Self {
self.verify = verify;
pub fn ssl_verify(mut self, verify: bool) -> Self {
self.ssl_verify = verify;
self
}
@ -68,7 +108,45 @@ impl HttpClient {
/// 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
send_request(
request,
self.ssl_verify,
self.proxy.clone(),
self.headers.clone(),
self.connect_timeout,
self.write_timeout,
self.read_timeout
).await
}
/// Get connect timeout
pub fn connect_timeout(&self) -> Option<Duration> {
self.connect_timeout.clone()
}
/// Get read timeout
pub fn read_timeout(&self) -> Option<Duration> {
self.read_timeout.clone()
}
/// Get write timeout
pub fn write_timeout(&self) -> Option<Duration> {
self.write_timeout.clone()
}
/// Get client proxy
pub fn proxy(&self) -> Proxy {
self.proxy.clone()
}
/// Get is client have to verify ssl certificate
pub fn ssl_verify(&self) -> bool {
self.ssl_verify
}
/// Get default headers
pub fn headers(&self) -> Headers {
self.headers.clone()
}
}

View File

@ -1,8 +1,9 @@
use std::pin::Pin;
use std::{pin::Pin, time::Duration};
use base64::Engine;
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream};
use tokio::{io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::TcpStream};
use tokio_io_timeout::TimeoutStream;
use tokio_openssl::SslStream;
use tokio_socks::tcp::{Socks4Stream, Socks5Stream};
@ -18,139 +19,66 @@ pub use req_builder::*;
pub use client::*;
pub use proxy::*;
async fn send_request(request: HttpRequest, ssl_verify: bool, proxy: Proxy, headers: Headers) -> Result<HttpResponse, HttpError> {
let mut request = request.clone();
trait RequestStream: AsyncRead + AsyncWrite + Unpin + Send + Sync {}
impl<T: AsyncRead + AsyncWrite + Unpin + Send + Sync> RequestStream for T {}
async fn connect_stream(proxy: Proxy, site_host: &str) -> Result<Box<dyn RequestStream>, HttpError> {
Ok(match proxy {
Proxy::Http { host, auth } | Proxy::Https { host, auth } => {
let mut stream = TcpStream::connect(host).await.map_err(|_| HttpError::ConnectError)?;
let auth_header = auth.map(|(u, p)| format!("Proxy-Authorization: basic {}\r\n", BASE64_STANDARD.encode(format!("{u}:{p}"))));
let connect_request = format!("CONNECT {site_host} HTTP/1.1\r\nHost: {site_host}\r\n{}\r\n", auth_header.unwrap_or_default());
stream.write_all(connect_request.as_bytes()).await.map_err(|_| HttpError::ConnectError)?;
HttpResponse::recv(&mut stream).await.map_err(|_| HttpError::ConnectError)?;
Box::new(stream)
}
Proxy::Socks4 { host, user } => Box::new(match user {
Some(user) => Socks4Stream::connect_with_userid(host, site_host, &user).await.map_err(|_| HttpError::ConnectError)?,
None => Socks4Stream::connect(host, site_host).await.map_err(|_| HttpError::ConnectError)?,
}),
Proxy::Socks5 { host, auth } => Box::new(match auth {
Some((u, p)) => Socks5Stream::connect_with_password(host, site_host, &u, &p).await.map_err(|_| HttpError::ConnectError)?,
None => Socks5Stream::connect(host, site_host).await.map_err(|_| HttpError::ConnectError)?,
}),
Proxy::None => Box::new(TcpStream::connect(site_host).await.map_err(|_| HttpError::ConnectError)?),
})
}
async fn send_request(
mut request: HttpRequest, ssl_verify: bool, proxy: Proxy, headers: Headers,
connect_timeout: Option<Duration>, write_timeout: Option<Duration>, read_timeout: Option<Duration>
) -> Result<HttpResponse, HttpError> {
for (key, value) in headers.entries() {
request.headers.put(key, value);
request.headers.put_default(key, value);
}
request.headers.put("Connection", "close".to_string());
request.headers.put("Host", request.url.domain.to_string());
request.headers.put("Content-Length", request.body.as_bytes().len().to_string());
request.headers.put_default("Connection", "close".to_string());
request.headers.put_default("Host", request.url.domain.to_string());
request.headers.put_default("Content-Length", request.body.as_bytes().len().to_string());
let site_host = format!("{}:{}", request.url.domain, request.url.port);
match proxy {
Proxy::Http { host, auth } => {
let mut stream = TcpStream::connect(host).await.map_err(|_| HttpError::ConnectError)?;
match auth {
Some((user,password)) => stream.write_all(&[
b"CONNECT ", site_host.as_bytes(), b" HTTP/1.1\r\n",
b"Host: ", site_host.as_bytes(), b"\r\n",
b"Proxy-Authorization: basic ", BASE64_STANDARD.encode(format!("{user}:{password}")).as_bytes(), b"\r\n\r\n",
].concat()).await,
None => stream.write_all(&[
b"CONNECT ", site_host.as_bytes(), b" HTTP/1.1\r\n",
b"Host: ", site_host.as_bytes(), b"\r\n\r\n",
].concat()).await
}.map_err(|_| HttpError::ConnectError)?;
HttpResponse::recv(&mut stream).await.map_err(|_| HttpError::ConnectError)?;
if request.url.scheme == "http" {
request.send(&mut stream).await?;
Ok(HttpResponse::recv(&mut stream).await?)
} else if request.url.scheme == "https" {
let mut wrapper = ssl_wrapper(ssl_verify, request.url.domain.clone(), stream).await?;
request.send(&mut wrapper).await?;
Ok(HttpResponse::recv(&mut wrapper).await?)
} else {
Err(HttpError::UnknownScheme)
}
let stream: Box<dyn RequestStream> = match connect_timeout {
Some(connect_timeout) => {
tokio::time::timeout(
connect_timeout,
connect_stream(proxy, &site_host)
).await.map_err(|_| HttpError::ConnectError)??
}, None => {
connect_stream(proxy, &site_host).await?
}
Proxy::Https { host, auth } => {
let mut stream = TcpStream::connect(host).await.map_err(|_| HttpError::ConnectError)?;
};
match auth {
Some((user,password)) => stream.write_all(&[
b"CONNECT ", site_host.as_bytes(), b" HTTP/1.1\r\n",
b"Host: ", site_host.as_bytes(), b"\r\n",
b"Proxy-Authorization: basic ", BASE64_STANDARD.encode(format!("{user}:{password}")).as_bytes(), b"\r\n\r\n",
].concat()).await,
None => stream.write_all(&[
b"CONNECT ", site_host.as_bytes(), b" HTTP/1.1\r\n",
b"Host: ", site_host.as_bytes(), b"\r\n\r\n",
].concat()).await
}.map_err(|_| HttpError::ConnectError)?;
let mut stream = TimeoutStream::new(stream);
stream.set_write_timeout(write_timeout);
stream.set_read_timeout(read_timeout);
let mut stream = Box::pin(stream);
HttpResponse::recv(&mut stream).await.map_err(|_| HttpError::ConnectError)?;
if request.url.scheme == "http" {
request.send(&mut stream).await?;
Ok(HttpResponse::recv(&mut stream).await?)
} else if request.url.scheme == "https" {
let mut wrapper = ssl_wrapper(ssl_verify, request.url.domain.clone(), stream).await?;
request.send(&mut wrapper).await?;
Ok(HttpResponse::recv(&mut wrapper).await?)
} else {
Err(HttpError::UnknownScheme)
}
}
Proxy::Socks4 { host, user } => {
let mut stream = match user {
Some(user) => Socks4Stream::connect_with_userid(host, site_host, &user).await,
None => Socks4Stream::connect(host, site_host).await
}.map_err(|_| HttpError::ConnectError)?;
if request.url.scheme == "http" {
request.send(&mut stream).await?;
Ok(HttpResponse::recv(&mut stream).await?)
} else if request.url.scheme == "https" {
let mut wrapper = ssl_wrapper(ssl_verify, request.url.domain.clone(), stream).await?;
request.send(&mut wrapper).await?;
Ok(HttpResponse::recv(&mut wrapper).await?)
} else {
Err(HttpError::UnknownScheme)
}
}
Proxy::Socks5 { host, auth } => {
let mut stream = match auth {
Some(auth) => Socks5Stream::connect_with_password(host, site_host, &auth.0, &auth.1).await,
None => Socks5Stream::connect(host, site_host).await
}.map_err(|_| HttpError::ConnectError)?;
if request.url.scheme == "http" {
request.send(&mut stream).await?;
Ok(HttpResponse::recv(&mut stream).await?)
} else if request.url.scheme == "https" {
let mut wrapper = ssl_wrapper(ssl_verify, request.url.domain.clone(), stream).await?;
request.send(&mut wrapper).await?;
Ok(HttpResponse::recv(&mut wrapper).await?)
} else {
Err(HttpError::UnknownScheme)
}
}
Proxy::None => {
let mut stream = TcpStream::connect(site_host).await.map_err(|_| HttpError::ConnectError)?;
if request.url.scheme == "http" {
request.send(&mut stream).await?;
Ok(HttpResponse::recv(&mut stream).await?)
} else if request.url.scheme == "https" {
let mut wrapper = ssl_wrapper(ssl_verify, request.url.domain.clone(), stream).await?;
request.send(&mut wrapper).await?;
Ok(HttpResponse::recv(&mut wrapper).await?)
} else {
Err(HttpError::UnknownScheme)
}
}
if request.url.scheme == "https" {
let mut stream = ssl_wrapper(ssl_verify, request.url.domain.clone(), stream).await?;
request.send(&mut stream).await?;
Ok(HttpResponse::recv(&mut stream).await?)
} else {
request.send(&mut stream).await?;
Ok(HttpResponse::recv(&mut stream).await?)
}
}

View File

@ -16,7 +16,6 @@ pub mod prelude {
pub use super::server::*;
pub use super::server::handler::*;
pub use super::server::starter::*;
pub use super::client::*;
pub use super::*;
}