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}