http_stat/
http_request.rs1use super::error::{Error, Result};
19use super::stats::{HttpStat, ALPN_HTTP1, ALPN_HTTP2};
20use bytes::Bytes;
21use http::request::Builder;
22use http::HeaderValue;
23use http::Request;
24use http::Uri;
25use http::{HeaderMap, Method};
26use http_body_util::Full;
27use std::net::IpAddr;
28use std::str::FromStr;
29use std::time::Duration;
30use std::time::Instant;
31
32const VERSION: &str = env!("CARGO_PKG_VERSION");
34
35pub(crate) fn finish_with_error(
37 mut stat: HttpStat,
38 error: impl ToString,
39 start: Instant,
40) -> HttpStat {
41 stat.error = Some(error.to_string());
42 stat.total = Some(start.elapsed());
43 stat
44}
45
46#[derive(Default, Debug, Clone)]
48pub struct HttpRequest {
49 pub uri: Uri, pub method: Option<String>, pub alpn_protocols: Vec<String>, pub resolve: Option<IpAddr>, pub headers: Option<HeaderMap<HeaderValue>>, pub ip_version: Option<i32>, pub skip_verify: bool, pub body: Option<Bytes>, pub dns_servers: Option<Vec<String>>, pub dns_timeout: Option<Duration>, pub tcp_timeout: Option<Duration>, pub tls_timeout: Option<Duration>, pub request_timeout: Option<Duration>, pub quic_timeout: Option<Duration>, }
64
65impl HttpRequest {
66 pub fn get_port(&self) -> u16 {
67 let schema = if let Some(scheme) = self.uri.scheme() {
68 scheme.to_string()
69 } else {
70 "".to_string()
71 };
72
73 let default_port = if ["https", "grpcs"].contains(&schema.as_str()) {
74 443
75 } else {
76 80
77 };
78 self.uri.port_u16().unwrap_or(default_port)
79 }
80 pub fn builder(&self, is_http1: bool) -> Builder {
82 let uri = &self.uri;
83 let method = if let Some(method) = &self.method {
84 Method::from_str(method).unwrap_or(Method::GET)
85 } else {
86 Method::GET
87 };
88 let mut builder = if is_http1 {
89 if let Some(value) = uri.path_and_query() {
90 Request::builder().uri(value.to_string())
91 } else {
92 Request::builder().uri(uri)
93 }
94 } else {
95 Request::builder().uri(uri)
96 };
97 builder = builder.method(method);
98 let mut set_host = false;
99 let mut set_user_agent = false;
100
101 if let Some(headers) = &self.headers {
103 for (key, value) in headers.iter() {
104 builder = builder.header(key, value);
105 match key.to_string().to_lowercase().as_str() {
106 "host" => set_host = true,
107 "user-agent" => set_user_agent = true,
108 _ => {}
109 }
110 }
111 }
112
113 if !set_host {
115 if let Some(host) = uri.host() {
116 builder = builder.header("Host", host);
117 }
118 }
119
120 if !set_user_agent {
122 builder = builder.header("User-Agent", format!("httpstat.rs/{VERSION}"));
123 }
124 builder
125 }
126}
127
128impl TryFrom<&str> for HttpRequest {
130 type Error = Error;
131
132 fn try_from(url: &str) -> Result<Self> {
133 let prefixes = ["http://", "https://", "grpc://", "grpcs://"];
134
135 let value = if prefixes.iter().any(|prefix| url.starts_with(prefix)) {
136 url.to_string()
137 } else {
138 format!("http://{url}")
139 };
140 let uri = value.parse::<Uri>().map_err(|e| Error::Uri { source: e })?;
141 Ok(Self {
142 uri,
143 alpn_protocols: vec![ALPN_HTTP2.to_string(), ALPN_HTTP1.to_string()],
144 ..Default::default()
145 })
146 }
147}
148
149impl TryFrom<&HttpRequest> for Request<Full<Bytes>> {
151 type Error = Error;
152 fn try_from(req: &HttpRequest) -> Result<Self> {
153 req.builder(true)
154 .body(Full::new(req.body.clone().unwrap_or_default()))
155 .map_err(|e| Error::Http { source: e })
156 }
157}
158
159pub(crate) fn build_http_request(
160 req: &HttpRequest,
161 is_http1: bool,
162) -> Result<Request<Full<Bytes>>> {
163 req.builder(is_http1)
164 .body(Full::new(req.body.clone().unwrap_or_default()))
165 .map_err(|e| Error::Http { source: e })
166}