nano_get/
request.rs

1use std::collections::HashMap;
2use std::error::Error;
3
4use super::{ToUrl, Url};
5use super::errors::NanoGetError;
6use super::http::request_http_get;
7#[cfg(feature = "https")]
8use super::https::request_https_get;
9use super::Response;
10
11/// This is the basic HTTP Request Object.
12///
13/// This is self-containing and you can execute the request using its execute method.
14/// It invokes a http or https version depending on the protocol of the embedded url and
15/// based on the `"https"` feature flag.
16///
17/// The major difference between this and the usual `get` method in the crate is the
18/// more fine grained control you get in the request and response.
19///
20/// Running the HTTP(s) method is as simple as calling `Request.execute()` on the
21/// constructed request. This returns a `Response` object instead of the body of the response as
22/// a String.
23///
24/// ## Request Body
25/// Although, the standard doesn't recommend sending a body with a get request, you can provide an
26/// optional body for the request.
27/// ### Example
28/// ```rust
29/// let mut request = nano_get::Request::default_get_request("http://example.com/").unwrap();
30/// request.body = Some("Hello World!".to_string());
31/// ```
32///
33/// ## Additional Request Headers
34/// You can provide additional headers as part of your request by using the `add_header(key: &str, value: &str)`
35/// method. These will be sent along with the default headers as part of the request.
36/// ### Example
37/// ```rust
38/// let mut request = nano_get::Request::default_get_request("http://example.com/").unwrap();
39/// request.add_header("test", "value testing");
40/// ```
41///
42/// ## Executing the Request
43/// As mentioned earlier, executing the request is as simple as calling `Request.execute()`.
44///
45/// This is similar to the basic unified HTTP GET in this crate `nano_get::get()`, in the way it
46/// handles http/https.
47///
48/// If the protocol of the embedded url is https and if the `"https"` feature flag is present,
49/// the https version of get, based on the [openssl](https://crates.io/crates/openssl) crate is executed.
50///
51/// The fall-back is the regular HTTP GET.
52///
53/// ### Example
54/// For regular HTTP GET requests,
55/// ```rust
56/// use nano_get::Response;
57/// let mut request = nano_get::Request::default_get_request("http://example.com/").unwrap();
58/// request.add_header("test", "value testing");
59/// let response: Response = request.execute().unwrap();
60/// ```
61#[derive(Debug)]
62pub struct Request {
63    /// The embedded Url that is part of the request. This is used while executing the HTTP Request.
64    pub url: Url,
65    request_type: RequestType,
66    headers: Option<HashMap<String, String>>,
67    /// The optional body of the request, that is sent while executing the request.
68    pub body: Option<String>,
69}
70
71#[allow(dead_code)]
72#[derive(Debug)]
73enum RequestType {
74    HEAD,
75    GET,
76    PUT,
77    POST,
78    DELETE,
79    OPTIONS,
80    CUSTOM(String),
81}
82
83impl RequestType {
84    fn value(&self) -> &'static str {
85        match self {
86            RequestType::GET => "GET",
87            RequestType::HEAD => "HEAD",
88            RequestType::POST => "POST",
89            RequestType::PUT => "PUT",
90            RequestType::DELETE => "DELETE",
91            RequestType::OPTIONS => "OPTIONS",
92            RequestType::CUSTOM(_) => "CUSTOM",
93        }
94    }
95}
96
97/// Coveneince wrapper for a tuple of (key: &str, value: &str) that is to be sent as a HTTP header.
98pub type Header<'a> = (&'a str, &'a str);
99
100impl Request {
101    /// Creates a new Request object, based on the url, and optional headers.
102    ///
103    /// ## Examples
104    /// ```rust
105    /// use nano_get::Request;
106    /// let request = Request::new("http://example.com", None, None);
107    /// ```
108    ///
109    /// To include custom headers,
110    /// ```rust
111    /// use nano_get::Request;
112    /// let request_headers = vec![("header1", "value1"), ("header2", "value2")];
113    /// let request = Request::new("http://example.com", Some(request_headers), None);
114    /// ```
115    ///
116    /// To include custom headers and body
117    /// ```rust
118    /// use nano_get::Request;
119    /// let request_headers = vec![("header1", "value1"), ("header2", "value2")];
120    /// let request_body = "Hello World!!".to_string();
121    /// let request = Request::new("http://example.com", Some(request_headers), Some(request_body));
122    /// ```
123    pub fn new<A: ToUrl>(url: A, headers: Option<Vec<Header>>, body: Option<String>) -> Result<Self, Box<dyn Error>> {
124        let url = url.to_url()?;
125        let mut request = Request {
126            url,
127            request_type: RequestType::GET,
128            headers: None,
129            body,
130        };
131        request.headers = Some(Self::get_default_headers(&request.url));
132        let addnl_headers = process_headers(headers);
133        request.merge_addnl_headers(addnl_headers);
134        Ok(request)
135    }
136
137    fn merge_addnl_headers(&mut self, addnl_headers: Option<HashMap<String, String>>) {
138        if self.headers.is_some() {
139            let headers = self.headers.as_mut().unwrap();
140            if let Some(extra_headers) = addnl_headers {
141                for (k, v) in extra_headers {
142                    headers.insert(k, v);
143                }
144            }
145        } else {
146            self.headers = addnl_headers;
147        }
148    }
149
150    /// Simplified version to create a Request based only on the given Url.
151    ///
152    /// Default Headers are inserted and the Body is set to `None`.
153    /// The values can be modified if required later.
154    ///
155    /// ## Example
156    ///
157    /// ```rust
158    /// use nano_get::Request;
159    /// let request = Request::default_get_request("http://example.com");
160    /// ```
161    pub fn default_get_request<A: ToUrl>(url: A) -> Result<Self, Box<dyn Error>> {
162        Self::new(url, None, None)
163    }
164
165    fn get_default_headers(url: &Url) -> HashMap<String, String> {
166        let mut headers = HashMap::with_capacity(4);
167        headers.insert("user-agent".to_string(), "mini-get/0.1.0".to_string());
168        headers.insert("accept".to_string(), "*/*".to_string());
169        headers.insert("host".to_string(), url.host.clone());
170        headers.insert("connection".to_string(), "close".to_string());
171        headers
172    }
173
174    /// Executes the request and returns a `nano_get::Response` object based `std::result::Result`.
175    ///
176    /// If the protocol of the embedded url is https and if the `"https"` feature flag is present,
177    /// the https version of get, based on the [openssl](https://crates.io/crates/openssl) crate is executed.
178    ///
179    /// ## Example
180    ///
181    /// ```rust
182    /// use nano_get::Response;
183    ///
184    /// let mut request = nano_get::Request::default_get_request("http://example.com/").unwrap();
185    /// request.add_header("test", "value testing");
186    /// let response: Response = request.execute().unwrap();
187    /// println!("{}", response.status);
188    /// println!("{}", response.body);
189    /// ```
190    pub fn execute(&self) -> Result<Response, NanoGetError> {
191        #[cfg(feature = "https")] {
192            if self.is_https() {
193                return request_https_get(&self);
194            }
195        }
196        request_http_get(&self)
197    }
198
199    /// Returns the headers as an Iterator over the key-value pairs.
200    ///
201    /// ## Example
202    ///
203    /// ```rust
204    /// use nano_get::Response;
205    ///
206    /// let mut request = nano_get::Request::default_get_request("http://example.com/").unwrap();
207    /// request.add_header("test", "value testing");
208    /// for (k, v) in request.get_request_headers() {
209    ///     println!("{}, {}", k, v);
210    /// }
211    /// ```
212    pub fn get_request_headers(&self) -> impl Iterator<Item=(&str, &str)> {
213        self.headers.as_ref().unwrap().iter().map(|(k, v)| {
214            (k.as_str(), v.as_str())
215        })
216    }
217
218    /// Convenience method to check if the request is a https request based
219    /// on the embedded url's protocol.
220    pub fn is_https(&self) -> bool {
221        self.url.protocol.as_str() == "https"
222    }
223
224    /// Returns the type of HTTP Request.
225    ///
226    /// Currently only returns `"GET"`. For Future Use.
227    pub fn get_request_type(&self) -> &str {
228        self.request_type.value()
229    }
230
231    /// Add an additional header to the request.
232    ///
233    /// You can overwrite existing values by adding the header with the new value.
234    ///
235    /// You cannot however remove the presence of a header.
236    pub fn add_header(&mut self, key: &str, value: &str) {
237        if self.headers.is_some() {
238            self.headers.as_mut().unwrap().insert((*key).to_string(), (*value).to_string());
239        } else {
240            let mut headers = HashMap::new();
241            headers.insert((*key).to_string(), (*value).to_string());
242            self.headers = Some(headers);
243        }
244    }
245}
246
247fn process_headers(headers: Option<Vec<Header>>) -> Option<HashMap<String, String>> {
248    headers.map(|vec| {
249        vec.iter().cloned().map(|(k, v)| (k.to_string(), v.to_string())).collect()
250    })
251}