update request connecting and adding timeouts wow
This commit is contained in:
parent
38d4f30f55
commit
18f012835b
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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::*;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user