hur/http/
request.rs

1use std::convert::TryFrom;
2use std::net::{SocketAddr, ToSocketAddrs};
3
4use super::headers::Headers;
5use super::{Method, Scheme};
6use serde::Serialize;
7use url::Url;
8
9use crate::error::Error;
10use crate::proxy::should_proxy;
11
12#[derive(Serialize)]
13pub struct Request {
14    #[serde(skip)]
15    pub url: Url,
16    #[serde(skip)]
17    pub proxy: bool,
18    #[serde(skip)]
19    pub servers: Vec<SocketAddr>,
20    #[serde(rename = "url")]
21    pub full_path: String,
22    pub scheme: Scheme,
23    pub protocol: String,
24    pub method: Method,
25    path: String,
26    pub headers: Headers,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    body: Option<String>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    query: Option<String>,
31}
32
33impl Request {
34    pub fn new(url: Url, method: Method, headers: Headers) -> Result<Request, Error> {
35        let scheme = Scheme::try_from(url.scheme())?;
36        let url_servers = find_socket_addresses(&url, &scheme)?;
37        let (servers, proxy) = match should_proxy(&url, &url_servers, &scheme)? {
38            Some(servers) => (servers, true),
39            None => (url_servers, false),
40        };
41
42        Ok(Request {
43            proxy,
44            servers,
45            full_path: url.to_string(),
46            scheme,
47            protocol: String::from("HTTP/1.1"),
48            method,
49            path: String::from(url.path()),
50            headers: standard_headers(headers, &url.host().unwrap().to_string()),
51            body: None,
52            query: url.query().map_or_else(|| None, |s| Some(String::from(s))),
53            url,
54        })
55    }
56
57    pub fn with_body(
58        url: Url,
59        method: Method,
60        headers: Headers,
61        body: &str,
62    ) -> Result<Request, Error> {
63        let mut request = Request::new(url, method, headers)?;
64        request.body = Some(body.to_string());
65        request
66            .headers
67            .add("Content-Length", &body.as_bytes().len().to_string());
68        Ok(request)
69    }
70
71    fn build_request(&self, path: &str) -> String {
72        let mut message = self.make_status_line(path);
73        self.add_headers(&mut message);
74        self.add_body(&mut message);
75        message.push_str("\r\n\r\n");
76        message
77    }
78
79    pub fn build(&self) -> String {
80        let path = match (self.proxy, &self.scheme) {
81            (true, Scheme::Http) => &self.full_path,
82            _ => &self.path,
83        };
84        self.build_request(path)
85    }
86
87    fn make_status_line(&self, path: &str) -> String {
88        let path = match &self.query {
89            Some(query) => format!("{path}?{query}"),
90            None => path.to_string(),
91        };
92        format!(
93            "{method} {path} {protocol}\r\n",
94            method = self.method.to_string().to_uppercase(),
95            path = path,
96            protocol = self.protocol,
97        )
98    }
99
100    fn add_body(&self, message: &mut String) {
101        if let Some(body) = &self.body {
102            message.push_str("\r\n");
103            message.push_str(body);
104        }
105    }
106
107    fn add_headers(&self, message: &mut String) {
108        for (key, value_vec) in self.headers.iter() {
109            for val in value_vec {
110                message.push_str(&format!(
111                    "{key}: {value}\r\n",
112                    key = key,
113                    value = val.trim(),
114                ));
115            }
116        }
117    }
118}
119
120fn find_socket_addresses(url: &Url, scheme: &Scheme) -> Result<Vec<SocketAddr>, Error> {
121    let mut server_details = String::new();
122    match url.domain() {
123        Some(domain) => server_details.push_str(domain),
124        None => server_details.push_str(&url.host().unwrap().to_string()),
125    };
126    server_details.push(':');
127    match url.port() {
128        Some(port) => server_details.push_str(&port.to_string()),
129        None => match scheme {
130            Scheme::Https => server_details.push_str("443"),
131            Scheme::Http => server_details.push_str("80"),
132        },
133    }
134    Ok(server_details.to_socket_addrs()?.collect())
135}
136
137fn standard_headers(input_headers: Headers, host: &str) -> Headers {
138    let mut hs = Headers::new();
139    hs.add(
140        "User-Agent",
141        &format!("{}/{}", clap::crate_name!(), clap::crate_version!()),
142    );
143    hs.add("Host", host);
144    hs.add("Connection", "close");
145    hs.append(input_headers);
146    hs
147}