proxy request and readme update

This commit is contained in:
MeexReay 2024-11-30 00:28:50 +03:00
parent 304a14ccc8
commit 64d945b104
5 changed files with 229 additions and 35 deletions

46
Cargo.lock generated
View File

@ -38,6 +38,12 @@ dependencies = [
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.6.0"
@ -86,10 +92,17 @@ version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "ezhttp"
version = "0.2.0"
dependencies = [
"base64",
"lazy_static",
"mime_guess",
"openssl",
@ -100,6 +113,7 @@ dependencies = [
"tokio",
"tokio-io-timeout",
"tokio-openssl",
"tokio-socks",
"urlencoding",
]
@ -585,6 +599,26 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "threadpool"
version = "1.8.1"
@ -644,6 +678,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-socks"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
dependencies = [
"either",
"futures-util",
"thiserror",
"tokio",
]
[[package]]
name = "unicase"
version = "2.8.0"

View File

@ -21,3 +21,5 @@ rand = "0.8.5"
mime_guess = "2.0.5"
openssl = "0.10.68"
tokio-openssl = "0.6.5"
tokio-socks = "0.5.2"
base64 = "0.22.1"

View File

@ -1,5 +1,5 @@
# EzHttp
Easy http server for small sites
Simple http library with client and server
This library is under developement, so if you found any bugs, please write them to [Issues](https://github.com/MeexReay/ezhttp/issues)
@ -12,7 +12,27 @@ ezhttp = { git = "https://github.com/MeexReay/ezhttp" } # unstable
## Examples
Hello world example:
Client example:
```rust
use ezhttp::prelude::*;
#[tokio::main]
async fn main() -> Result<(), HttpError> {
let client = HttpClient::builder().build(); // or HttpClient::default()
let url = URL::from_str("https://google.com")?;
let request: HttpRequest = RequestBuilder::get(url).build();
let response: HttpResponse = client.send(request).await?;
println!("response status: {}", response.status_code);
println!("response body: {} bytes", response.body.as_text().unwrap().len());
Ok(())
}
```
Site example:
```rust
use ezhttp::prelude::*;
@ -46,7 +66,14 @@ impl HttpServer for EzSite {
#[tokio::main]
async fn main() {
start_server(EzSite("Hello World!".to_string()), "localhost:8080").await.expect("http server error");
HttpServerStarter::new(
EzSite("Hello World!".to_string()),
"localhost:8080"
).timeout(Some(Duration::from_secs(5)))
.threads(5)
.start_forever()
.await
.expect("http server error");
}
```

View File

@ -1,11 +1,14 @@
use std::pin::Pin;
use base64::Engine;
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
use tokio::net::TcpStream;
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream};
use tokio_openssl::SslStream;
use tokio_socks::tcp::{Socks4Stream, Socks5Stream};
use super::{error::HttpError, gen_multipart_boundary, headers::Headers, prelude::HttpResponse, request::HttpRequest};
use base64::prelude::BASE64_STANDARD;
pub mod req_builder;
pub mod client;
@ -17,12 +20,8 @@ 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;
let mut stream = TcpStream::connect(
format!("{}:{}", request.url.domain, request.url.port)
).await.map_err(|_| HttpError::ConnectError)?;
async fn send_request(request: HttpRequest, ssl_verify: bool, proxy: Proxy, headers: Headers) -> Result<HttpResponse, HttpError> {
let mut request = request.clone();
for (key, value) in headers.entries() {
request.headers.put(key, value);
@ -32,35 +31,151 @@ async fn send_request(request: HttpRequest, ssl_verify: bool, _proxy: Proxy, hea
request.headers.put("Host", request.url.domain.to_string());
request.headers.put("Content-Length", request.body.as_bytes().len().to_string());
if request.url.scheme == "http" {
request.send(&mut stream).await?;
let site_host = format!("{}:{}", request.url.domain, request.url.port);
Ok(HttpResponse::recv(&mut stream).await?)
} else if request.url.scheme == "https" {
let mut ssl_connector = SslConnector::builder(SslMethod::tls())
.map_err(|_| HttpError::SslError)?;
match proxy {
Proxy::Http { host, auth } => {
let mut stream = TcpStream::connect(host).await.map_err(|_| HttpError::ConnectError)?;
ssl_connector.set_verify(if ssl_verify { SslVerifyMode::PEER } else { SslVerifyMode::NONE });
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 ssl_connector = ssl_connector.build();
HttpResponse::recv(&mut stream).await.map_err(|_| HttpError::ConnectError)?;
let ssl = ssl_connector
.configure()
.map_err(|_| HttpError::SslError)?
.into_ssl(&request.url.domain)
.map_err(|_| HttpError::SslError)?;
if request.url.scheme == "http" {
request.send(&mut stream).await?;
let mut wrapper = SslStream::new(ssl, stream)
.map_err(|_| HttpError::SslError)?;
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?;
let mut wrapper = Pin::new(&mut wrapper);
request.send(&mut wrapper).await?;
wrapper.as_mut().connect().await.map_err(|_| HttpError::SslError)?;
Ok(HttpResponse::recv(&mut wrapper).await?)
} else {
Err(HttpError::UnknownScheme)
}
}
Proxy::Https { host, auth } => {
let mut stream = TcpStream::connect(host).await.map_err(|_| HttpError::ConnectError)?;
request.send(&mut wrapper).await?;
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)?;
Ok(HttpResponse::recv(&mut wrapper).await?)
} else {
Err(HttpError::UnknownScheme)
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)
}
}
}
}
async fn ssl_wrapper<S: AsyncReadExt + AsyncWriteExt>(ssl_verify: bool, domain: String, stream: S) -> Result<Pin<Box<SslStream<S>>>, HttpError> {
let mut ssl_connector = SslConnector::builder(SslMethod::tls())
.map_err(|_| HttpError::SslError)?;
ssl_connector.set_verify(if ssl_verify { SslVerifyMode::PEER } else { SslVerifyMode::NONE });
let ssl_connector = ssl_connector.build();
let ssl = ssl_connector
.configure()
.map_err(|_| HttpError::SslError)?
.into_ssl(&domain)
.map_err(|_| HttpError::SslError)?;
let wrapper = SslStream::new(ssl, stream)
.map_err(|_| HttpError::SslError)?;
let mut wrapper = Box::pin(wrapper);
wrapper.as_mut().connect().await.map_err(|_| HttpError::SslError)?;
Ok(wrapper)
}

View File

@ -4,7 +4,7 @@ use std::net::{ToSocketAddrs, SocketAddr};
pub enum Proxy {
None,
Socks5 { host: SocketAddr, auth: Option<(String, String)> },
Socks4 { host: SocketAddr, user: String },
Socks4 { host: SocketAddr, user: Option<String> },
Http { host: SocketAddr, auth: Option<(String, String)> },
Https { host: SocketAddr, auth: Option<(String, String)> },
}
@ -22,8 +22,12 @@ impl Proxy {
Self::Socks5 { host: host.to_socket_addrs().unwrap().next().unwrap(), auth: Some((user, password)) }
}
pub fn socks4(host: impl ToSocketAddrs, user_id: String) -> Self {
Self::Socks4 { host: host.to_socket_addrs().unwrap().next().unwrap(), user: user_id }
pub fn socks4(host: impl ToSocketAddrs) -> Self {
Self::Socks4 { host: host.to_socket_addrs().unwrap().next().unwrap(), user: None }
}
pub fn socks4_with_auth(host: impl ToSocketAddrs, user_id: String) -> Self {
Self::Socks4 { host: host.to_socket_addrs().unwrap().next().unwrap(), user: Some(user_id) }
}
pub fn http(host: impl ToSocketAddrs) -> Self {