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