#[cfg(all(feature = "tokio-native-tls", feature = "http3"))]
compile_error!("HTTP3 is not supported within tokio-native-tls runtime.");
#[cfg(all(feature = "smol-native-tls", feature = "http3"))]
compile_error!("HTTP3 is not supported within smol-native-tls runtime.");
#[cfg(all(feature = "tokio-rt", feature = "smol-rt"))]
compile_error!("Only one runtime feature can be enabled at a time.");
#[cfg(not(any(feature = "http1", feature = "http2", feature = "http3")))]
compile_error!("At least one HTTP version feature must be enabled.");
pub(crate) const MAX_ERROR_MESSAGE_SIZE: usize = 50000;
#[cfg(any(feature = "tokio-rust-tls", feature = "smol-rust-tls"))]
#[inline]
pub(crate) fn alpn() -> Vec<Vec<u8>> {
vec![
#[cfg(feature = "http2")]
b"h2".to_vec(),
#[cfg(feature = "http1")]
b"http/1.1".to_vec(),
#[cfg(feature = "http3")]
b"h3".to_vec(),
]
}
#[cfg(any(feature = "tokio-native-tls", feature = "smol-native-tls"))]
#[inline]
pub(crate) fn alpn() -> &'static [&'static str] {
&[
#[cfg(feature = "http2")]
"h2",
#[cfg(feature = "http1")]
"http/1.1",
#[cfg(feature = "http3")]
"h3",
]
}
use cfg_if::cfg_if;
#[cfg(feature = "tokio-rt")]
use tokio::sync::{RwLock, RwLockWriteGuard};
#[cfg(feature = "smol-rt")]
use smol::lock::RwLock;
use std::fmt::{Debug, Display};
use std::net::IpAddr;
use std::ops::Shl;
use http::{header, HeaderValue, Request};
use log::{error, info};
use crate::cert::{Certificate, Identity};
use crate::client::conn::{ConnectionConfig, ConnectionFactory};
use crate::catcher::DeboaCatcher;
use crate::client::conn::pool::{DeboaHttpConnectionPool, HttpConnectionPool};
use crate::errors::{DeboaError, RequestError};
use crate::request::{DeboaRequest, IntoRequest};
use crate::response::DeboaResponse;
pub use async_trait::async_trait;
#[cfg(feature = "tokio-rt")]
pub type File = tokio::fs::File;
#[cfg(feature = "smol-rt")]
pub type File = smol::fs::File;
pub mod cache;
pub mod catcher;
pub mod cert;
pub mod client;
pub mod cookie;
pub mod errors;
pub mod form;
pub mod fs;
pub mod request;
pub mod response;
pub mod rt;
pub mod url;
#[cfg(test)]
mod tests;
pub type Result<T> = std::result::Result<T, DeboaError>;
impl Shl<&str> for &Client {
type Output = DeboaRequest;
fn shl(self, other: &str) -> Self::Output {
DeboaRequest::get(other)
.expect("Invalid URL!")
.build()
.expect("Invalid request!")
}
}
#[derive(PartialEq, Debug, Clone)]
pub enum HttpVersion {
#[cfg(feature = "http1")]
Http1,
#[cfg(feature = "http2")]
Http2,
#[cfg(feature = "http3")]
Http3,
}
impl Display for HttpVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "http1")]
HttpVersion::Http1 => write!(f, "HTTP/1.1"),
#[cfg(feature = "http2")]
HttpVersion::Http2 => write!(f, "HTTP/2"),
#[cfg(feature = "http3")]
HttpVersion::Http3 => write!(f, "HTTP/3"),
}
}
}
#[deprecated(note = "DeboaBuilder is now ClientBuilder")]
pub type DeboaBuilder = ClientBuilder;
pub struct ClientBuilder {
connection_timeout: u64,
request_timeout: u64,
identity: Option<Identity>,
certificate: Option<Certificate>,
catchers: Option<Vec<Box<dyn DeboaCatcher>>>,
protocol: HttpVersion,
skip_cert_verification: bool,
pool: RwLock<HttpConnectionPool>,
bind_addr: IpAddr,
}
impl ClientBuilder {
#[inline]
pub fn connection_timeout(mut self, connection_timeout: u64) -> Self {
self.connection_timeout = connection_timeout;
self
}
#[inline]
pub fn request_timeout(mut self, request_timeout: u64) -> Self {
self.request_timeout = request_timeout;
self
}
#[inline]
pub fn client_cert(mut self, client_cert: Identity) -> Self {
self.identity = Some(client_cert);
self
}
#[inline]
pub fn identity(mut self, identity: Identity) -> Self {
self.identity = Some(identity);
self
}
#[inline]
pub fn certificate(mut self, certificate: Certificate) -> Self {
self.certificate = Some(certificate);
self
}
pub fn catch<C: DeboaCatcher>(mut self, catcher: C) -> Self {
if let Some(catchers) = &mut self.catchers {
catchers.push(Box::new(catcher));
} else {
self.catchers = Some(vec![Box::new(catcher)]);
}
self
}
#[inline]
pub fn protocol(mut self, protocol: HttpVersion) -> Self {
self.protocol = protocol;
self
}
#[inline]
pub fn skip_cert_verification(mut self, skip: bool) -> Self {
self.skip_cert_verification = skip;
self
}
#[inline]
pub fn pool(mut self, pool: HttpConnectionPool) -> Self {
self.pool = RwLock::new(pool);
self
}
#[inline]
pub fn bind_addr(mut self, bind_addr: IpAddr) -> Self {
self.bind_addr = bind_addr;
self
}
#[inline]
pub fn build(self) -> Client {
Client {
connection_timeout: self.connection_timeout,
request_timeout: self.request_timeout,
identity: self.identity,
certificate: self.certificate,
catchers: self.catchers,
protocol: self.protocol,
skip_cert_verification: self.skip_cert_verification,
pool: self.pool,
bind_addr: self.bind_addr,
}
}
}
#[deprecated(note = "Deboa is now Client")]
pub type Deboa = Client;
pub struct Client {
connection_timeout: u64,
request_timeout: u64,
identity: Option<Identity>,
certificate: Option<Certificate>,
catchers: Option<Vec<Box<dyn DeboaCatcher>>>,
protocol: HttpVersion,
skip_cert_verification: bool,
pool: RwLock<HttpConnectionPool>,
bind_addr: IpAddr,
}
impl AsRef<Client> for Client {
fn as_ref(&self) -> &Client {
self
}
}
impl AsMut<Client> for Client {
fn as_mut(&mut self) -> &mut Client {
self
}
}
impl Debug for Client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Client")
.field("connection_timeout", &self.connection_timeout)
.field("request_timeout", &self.request_timeout)
.field("protocol", &self.protocol)
.finish()
}
}
pub(crate) const fn default_protocol() -> HttpVersion {
cfg_if! {
if #[cfg(feature = "http1")] {
HttpVersion::Http1
} else if #[cfg(feature = "http2")] {
HttpVersion::Http2
} else {
HttpVersion::Http3
}
}
}
impl Default for Client {
fn default() -> Self {
Self {
connection_timeout: 0,
request_timeout: 0,
identity: None,
certificate: None,
catchers: None,
protocol: default_protocol(),
skip_cert_verification: false,
pool: RwLock::new(HttpConnectionPool::default()),
bind_addr: "0.0.0.0"
.parse()
.unwrap(),
}
}
}
impl Client {
#[deprecated(note = "Use Client::default() instead", since = "0.0.9")]
pub fn new() -> Self {
Self {
connection_timeout: 0,
request_timeout: 0,
identity: None,
certificate: None,
catchers: None,
protocol: default_protocol(),
skip_cert_verification: false,
pool: RwLock::new(HttpConnectionPool::default()),
bind_addr: "0.0.0.0"
.parse()
.unwrap(),
}
}
#[inline]
pub fn builder() -> ClientBuilder {
ClientBuilder {
connection_timeout: 0,
request_timeout: 0,
identity: None,
certificate: None,
catchers: None,
protocol: default_protocol(),
skip_cert_verification: false,
pool: RwLock::new(HttpConnectionPool::default()),
bind_addr: "0.0.0.0"
.parse()
.unwrap(),
}
}
#[inline]
pub fn skip_cert_verification(&self) -> bool {
self.skip_cert_verification
}
#[inline]
pub fn protocol(&self) -> &HttpVersion {
&self.protocol
}
#[inline]
pub fn connection_timeout(&self) -> u64 {
self.connection_timeout
}
#[inline]
#[cfg(feature = "tokio-rt")]
pub async fn connection_pool(&self) -> &tokio::sync::RwLock<HttpConnectionPool> {
&self.pool
}
#[inline]
#[cfg(feature = "smol-rt")]
pub async fn connection_pool(&self) -> &smol::lock::RwLock<HttpConnectionPool> {
&self.pool
}
#[inline]
pub fn bind_addr(&self) -> IpAddr {
self.bind_addr
}
#[inline]
pub fn request_timeout(&self) -> u64 {
self.request_timeout
}
#[inline]
#[deprecated(note = "Use identity instead", since = "0.0.8")]
pub fn client_cert(&self) -> Option<&Identity> {
self.identity
.as_ref()
}
#[inline]
pub fn identity(&self) -> Option<&Identity> {
self.identity
.as_ref()
}
pub async fn execute<R>(&self, request: R) -> Result<DeboaResponse>
where
R: IntoRequest,
{
let request = request.into_request()?;
let url = request
.as_ref()
.url();
let mut uri = url
.path()
.to_string();
if let Some(query) = url.query() {
uri.push('?');
uri.push_str(query);
}
let method = request
.as_ref()
.method();
info!("Building request: {} {}", method, uri);
let mut builder = Request::builder()
.uri(uri)
.method(
method
.to_string()
.as_str(),
);
{
let req_headers = builder
.headers_mut()
.unwrap();
request
.as_ref()
.headers()
.into_iter()
.fold(&mut *req_headers, |acc, (key, value)| {
acc.insert(key, value.into());
acc
});
if let Some(deboa_cookies) = request
.as_ref()
.cookies()
{
let mut cookies = Vec::<String>::new();
for cookie in deboa_cookies.values() {
cookies.push(cookie.to_string());
}
if let Ok(cookie_header) = HeaderValue::from_str(&cookies.join("; ")) {
req_headers.insert(header::COOKIE, cookie_header);
}
}
}
let request = builder.body(request.body());
if let Err(err) = request {
error!("Failed to send request: {}", err);
return Err(DeboaError::Request(RequestError::Send {
url: url.to_string(),
message: err.to_string(),
}));
}
let request = request.unwrap();
let scheme = url.scheme();
let host = url
.host_str()
.unwrap_or("localhost");
let (port, is_secure) = if let Some(port) = url.port() {
(port, scheme == "https" || scheme == "wss")
} else {
match scheme {
"http" | "ws" => (80, false),
"https" | "wss" => (443, true),
_ => panic!("Unsupported scheme: {}", scheme),
}
};
let config = ConnectionConfig::builder()
.is_secure(is_secure)
.host(host)
.port(port)
.protocol(
self.protocol
.clone(),
)
.identity(
self.identity
.clone(),
)
.certificate(
self.certificate
.clone(),
)
.skip_cert_verification(self.skip_cert_verification)
.client_bind_addr(self.bind_addr)
.build();
#[cfg(feature = "tokio-rt")]
let mut pool = RwLockWriteGuard::map(self.pool.write().await, |f| f);
#[cfg(feature = "smol-rt")]
let mut pool = self.pool.write().await;
let conn = pool
.create_connection(&config)
.await?;
let response = conn.send_request(url.clone(), request)
.await?;
Ok(response)
}
}