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(Debug, Clone)]
55pub struct ConnectTo {
56 src_host: String,
57 src_port: Option<u16>,
58 pub dst_host: String,
59 pub dst_port: Option<u16>,
60}
61
62fn parse_host_segment(s: &str) -> (String, &str) {
63 if let Some(rest) = s.strip_prefix('[') {
64 if let Some(end) = rest.find(']') {
66 return (rest[..end].to_string(), &rest[end + 1..]);
67 }
68 }
69 let colon = s.find(':').unwrap_or(s.len());
71 (s[..colon].to_string(), &s[colon..])
72}
73
74impl ConnectTo {
75 pub fn parse(s: &str) -> Option<Self> {
77 let (src_host, rest) = parse_host_segment(s);
78 let rest = rest.strip_prefix(':')?; let colon = rest.find(':')?;
82 let src_port = if rest[..colon].is_empty() {
83 None
84 } else {
85 Some(rest[..colon].parse().ok()?)
86 };
87 let rest = &rest[colon + 1..];
88
89 let (dst_host, rest) = parse_host_segment(rest);
91
92 let port2_str = rest.strip_prefix(':').unwrap_or(rest);
94 let dst_port = if port2_str.is_empty() {
95 None
96 } else {
97 Some(port2_str.parse().ok()?)
98 };
99
100 Some(ConnectTo {
101 src_host,
102 src_port,
103 dst_host,
104 dst_port,
105 })
106 }
107
108 pub fn matches(&self, host: &str, port: u16) -> bool {
110 let host_ok = self.src_host.is_empty() || self.src_host.eq_ignore_ascii_case(host);
111 let port_ok = self.src_port.is_none() || self.src_port == Some(port);
112 host_ok && port_ok
113 }
114}
115
116#[derive(Default, Debug, Clone)]
118pub struct HttpRequest {
119 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>, pub client_cert: Option<Vec<u8>>, pub client_key: Option<Vec<u8>>, pub proxy: Option<String>, pub use_absolute_uri: bool, pub connect_to: Vec<String>, pub bind_addr: Option<IpAddr>, }
140
141impl HttpRequest {
142 pub fn get_port(&self) -> u16 {
143 let schema = if let Some(scheme) = self.uri.scheme() {
144 scheme.to_string()
145 } else {
146 "".to_string()
147 };
148
149 let default_port = if ["https", "grpcs"].contains(&schema.as_str()) {
150 443
151 } else {
152 80
153 };
154 self.uri.port_u16().unwrap_or(default_port)
155 }
156 pub fn builder(&self, is_http1: bool) -> Builder {
158 let uri = &self.uri;
159 let method = if let Some(method) = &self.method {
160 Method::from_str(method).unwrap_or(Method::GET)
161 } else {
162 Method::GET
163 };
164 let mut builder = if is_http1 && !self.use_absolute_uri {
165 if let Some(value) = uri.path_and_query() {
166 Request::builder().uri(value.to_string())
167 } else {
168 Request::builder().uri(uri)
169 }
170 } else {
171 Request::builder().uri(uri)
172 };
173 builder = builder.method(method);
174 let mut set_host = false;
175 let mut set_user_agent = false;
176
177 if let Some(headers) = &self.headers {
179 for (key, value) in headers.iter() {
180 builder = builder.header(key, value);
181 match key.to_string().to_lowercase().as_str() {
182 "host" => set_host = true,
183 "user-agent" => set_user_agent = true,
184 _ => {}
185 }
186 }
187 }
188
189 if !set_host {
191 if let Some(host) = uri.host() {
192 let port = self.get_port();
193 if port != 80 && port != 443 {
194 builder = builder.header("Host", format!("{host}:{port}"));
195 } else {
196 builder = builder.header("Host", host);
197 }
198 }
199 }
200
201 if !set_user_agent {
203 builder = builder.header("User-Agent", format!("httpstat.rs/{VERSION}"));
204 }
205 builder
206 }
207}
208
209impl TryFrom<&str> for HttpRequest {
211 type Error = Error;
212
213 fn try_from(url: &str) -> Result<Self> {
214 let prefixes = ["http://", "https://", "grpc://", "grpcs://"];
215
216 let value = if prefixes.iter().any(|prefix| url.starts_with(prefix)) {
217 url.to_string()
218 } else {
219 format!("http://{url}")
220 };
221 let uri = value.parse::<Uri>().map_err(|e| Error::Uri { source: e })?;
222 Ok(Self {
223 uri,
224 alpn_protocols: vec![ALPN_HTTP2.to_string(), ALPN_HTTP1.to_string()],
225 ..Default::default()
226 })
227 }
228}
229
230impl TryFrom<&HttpRequest> for Request<Full<Bytes>> {
232 type Error = Error;
233 fn try_from(req: &HttpRequest) -> Result<Self> {
234 req.builder(true)
235 .body(Full::new(req.body.clone().unwrap_or_default()))
236 .map_err(|e| Error::Http { source: e })
237 }
238}
239
240pub(crate) fn build_http_request(
241 req: &HttpRequest,
242 is_http1: bool,
243) -> Result<Request<Full<Bytes>>> {
244 req.builder(is_http1)
245 .body(Full::new(req.body.clone().unwrap_or_default()))
246 .map_err(|e| Error::Http { source: e })
247}