mod tls_connect;
use std::env;
use std::error::Error;
use std::time::Duration;
use crate::tls_connect::connect_rustls;
use httpio::enums::char_set::CharSet;
use httpio::enums::header::http_header::HttpHeader;
use httpio::enums::http_body::HttpBody;
use httpio::enums::http_request_method::HttpRequestMethod;
use httpio::enums::http_version::HttpVersion;
use httpio::structures::connection::http_connection::HttpConnection;
use httpio::structures::header::header_list::HttpHeaderList;
use httpio::utils::http_header_field_name;
use httpio::utils::url::Url;
use log::debug_with;
use log::enums::log_level::LogLevel;
use log::structs::logger::Logger;
use httpio::compression::set_compression_provider;
#[path = "../shared/mod.rs"]
mod shared;
use shared::Flate2Decoder;
const TARGET: &str = "https://www.example.com";
const USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15";
const ACCEPT: &str = "text/html, application/json, */*;q=0.8";
const ACCEPT_LANGUAGE: &str =
"en,es-ES;q=0.9,es;q=0.8,it;q=0.7,de;q=0.6,cs;q=0.5,ru;q=0.4,ja;q=0.3,uk;q=0.2";
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10);
fn main() -> Result<(), Box<dyn Error>> {
set_compression_provider(Box::new(Flate2Decoder))
.map_err(|e| format!("Failed to set compression provider: {}", e))?;
let use_smol = env::args().any(|arg| arg == "--smol");
if use_smol {
println!("Using smol runtime");
smol_main()
} else {
println!("Using async-foundation runtime");
async_foundation_main()
}
}
fn async_foundation_main() -> Result<(), Box<dyn Error>> {
use async_foundation::net::tcp_stream::TcpStream;
use futures::executor::block_on;
block_on(async {
let url: Url = TARGET.into();
let addr = url.socket_address()?;
let host_name = url.domain.to_string();
let logger = logger("HTTP");
debug_with!(logger, "Connecting to {}...", addr);
let stream = TcpStream::connect(addr)?;
let (reader, writer) =
connect_rustls(stream, Some(host_name.clone())).await?;
let mut connection = HttpConnection::new(
reader,
writer,
HttpVersion::Http11,
&host_name,
request_headers().into(),
logger,
)?;
connection
.send_request(
HttpRequestMethod::Get,
&url.path_query(),
HttpHeaderList::default(),
HttpBody::None,
)
.await?;
let message = connection.read_response().await?;
let charset = message.headers.get_charset(CharSet::Iso88591);
let _message: String = message.body.to_string(charset)?;
Ok(())
})
}
fn smol_main() -> Result<(), Box<dyn Error>> {
use smol::net::TcpStream;
use smol::Timer;
use futures::future::{select, Either};
smol::block_on(async {
let url: Url = TARGET.into();
let addr = url.socket_address()?;
let host_name = url.domain.to_string();
let logger = logger("HTTP");
debug_with!(logger, "Connecting to {}...", addr);
let connect_future = Box::pin(TcpStream::connect(addr));
let timeout_future = Box::pin(Timer::after(CONNECTION_TIMEOUT));
let tcp_stream = match select(connect_future, timeout_future).await {
Either::Left((Ok(stream), _)) => stream,
Either::Left((Err(e), _)) => return Err(e.into()),
Either::Right(_) => return Err(format!("TCP connection timed out after {}s", CONNECTION_TIMEOUT.as_secs()).into()),
};
let handshake_future = Box::pin(connect_rustls(tcp_stream, Some(host_name.clone())));
let timeout_future = Box::pin(Timer::after(CONNECTION_TIMEOUT));
let (reader, writer) = match select(handshake_future, timeout_future).await {
Either::Left((Ok(res), _)) => res,
Either::Left((Err(e), _)) => return Err(e.into()),
Either::Right(_) => return Err(format!("TLS handshake timed out after {}s", CONNECTION_TIMEOUT.as_secs()).into()),
};
let mut connection = HttpConnection::new(
reader,
writer,
HttpVersion::Http11,
&host_name,
request_headers().into(),
logger,
)?;
connection
.send_request(
HttpRequestMethod::Get,
&url.path_query(),
HttpHeaderList::default(),
HttpBody::None,
)
.await?;
let message = connection.read_response().await?;
let charset = message.headers.get_charset(CharSet::Iso88591);
let _message: String = message.body.to_string(charset)?;
Ok(())
})
}
fn request_headers() -> Vec<HttpHeader> {
vec![
HttpHeader::new(http_header_field_name::USER_AGENT, USER_AGENT),
HttpHeader::new(http_header_field_name::ACCEPT, ACCEPT),
HttpHeader::Generic {
key: "Cache-Control".to_string(),
value: "no-cache".to_string(),
},
HttpHeader::Generic {
key: "Accept-Language".to_string(),
value: ACCEPT_LANGUAGE.to_string(),
},
]
}
fn logger(_subject: &'static str) -> Logger {
Logger::with_level(LogLevel::Debug)
.stdout()
.time_format("%H:%M:%S%.3f")
}