wsd/
http.rs

1//! Provides most simple ways to make http request, simple as what JavaScript dose!
2use reqwest::blocking::{Body, Client, RequestBuilder, Response};
3use reqwest::header::HeaderMap;
4use std::collections::HashMap;
5use std::fmt;
6use std::str::FromStr;
7use std::time::Duration;
8
9pub use reqwest::Method;
10
11#[allow(rustdoc::bare_urls)]
12
13/// Most simple way to make http request, using keep-alive connection pooling.
14///```rust
15/// use wsd::http::*;
16//
17/// fn test() {
18///    let mut c = Request::new(Method::POST, "https://docs.rs");
19///    c.gzip(true);
20///    c.timeout(5);
21///    c.header("TOKEN", "1234567890");
22///    c.send("{id: 100}", |data| {
23///        println!("Data: {}", data.text());
24///        println!("Headers: {:#?}", data.headers());
25///     });
26///}
27///```
28pub struct Request {
29    url: String,
30    method: Method,
31    inner: Option<Client>,
32    headers: HashMap<String, String>,
33    error: String,
34    timeout: f32,
35    gzip: bool,
36}
37
38pub struct Data {
39    status: u16,
40    data: String,
41    headers: HashMap<String, String>,
42}
43
44fn get_headers(input: &HeaderMap) -> HashMap<String, String> {
45    let mut headers: HashMap<String, String> = HashMap::new();
46    for (key, value) in input {
47        let k = String::from(key.as_str());
48        let v = String::from(value.to_str().unwrap_or(""));
49        headers.insert(k, v);
50    }
51
52    return headers;
53}
54
55impl fmt::Debug for Request {
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        f.debug_struct("Request")
58            .field("method", &self.method)
59            .field("url", &self.url)
60            .field("headers", &self.headers)
61            .finish()
62    }
63}
64
65impl Request {
66    pub fn new<T: AsRef<str>>(method: Method, url: T) -> Self {
67        return Self {
68            url: url.as_ref().to_string(),
69            method: method,
70            inner: None,
71            headers: HashMap::new(),
72            error: "".into(),
73            timeout: 10.0,
74            gzip: false,
75        };
76    }
77
78    /// Whether to enable gzip
79    pub fn gzip(&mut self, zip: bool) -> &mut Self {
80        self.gzip = zip;
81        return self;
82    }
83
84    /// Set timeout as seconds
85    pub fn timeout(&mut self, seconds: f32) -> &mut Self {
86        self.timeout = seconds;
87        return self;
88    }
89
90    /// Insert header before send
91    pub fn header<K: Into<String>>(&mut self, key: K, value: K) -> &mut Self {
92        self.headers.insert(key.into(), value.into());
93        return self;
94    }
95
96    fn build(&self) -> RequestBuilder {
97        let c = self.inner.as_ref().unwrap();
98        let mut x = c.request(self.method.clone(), self.url.clone());
99        for (key, value) in &self.headers {
100            x = x.header(key, value);
101        }
102        return x;
103    }
104
105    /// Send the request
106    pub fn send<DATA: Into<Body>, F: FnMut(Data)>(&mut self, data: DATA, mut f: F) -> i32 {
107        let duration = Duration::from_millis((self.timeout * 1000.0) as u64);
108        // build client once
109        if self.inner.is_none() {
110            let c = Client::builder()
111                .gzip(self.gzip)
112                .timeout(duration)
113                .build();
114            match c {
115                Ok(client) => {
116                    self.inner = Some(client);
117                }
118                Err(e) => {
119                    self.error = e.to_string();
120                    return -1;
121                }
122            }
123        }
124
125        // build request
126        let mut x = self.build();
127        x = x.body(data);
128
129        // send
130        let ret = x.send();
131        if let Err(e) = ret {
132            self.error = e.to_string();
133            return -1;
134        }
135
136        let response = ret.unwrap();
137        let status = response.status().as_u16();
138        let headers = get_headers(response.headers());
139
140        // moved occured inside of the text()
141        let t = response.text();
142        if let Err(e) = t {
143            self.error = e.to_string();
144            return -1;
145        }
146
147        let upcall = Data {
148            status: status,
149            data: t.unwrap(),
150            headers: headers,
151        };
152
153        f(upcall);
154
155        return 0;
156    }
157}
158
159impl Data {
160    /// Get HTTP status code
161    pub fn status(&self) -> u16 {
162        return self.status;
163    }
164
165    /// Get reponse as text
166    pub fn text(&self) -> &String {
167        return &self.data;
168    }
169
170    /// Get reponse as JSON
171    pub fn json(&self) -> serde_json::Value {
172        let value = serde_json::Value::from_str(self.data.as_str());
173        return value.unwrap_or(serde_json::Value::Null);
174    }
175
176    /// Get reponse headers
177    pub fn headers(&self) -> &HashMap<String, String> {
178        return &self.headers;
179    }
180}
181
182/// Most simple way to make a http get request, the gzip was enabled by default
183///
184/// ```rust,no_run
185/// fn test() {
186///     wsd::http::get("https://docs.rs/", |data| {
187///         println!("status = {}, data = {}", data.status(), data.text());
188///     });
189/// }
190/// ```
191pub fn get<URL, F>(url: URL, mut f: F)
192where
193    URL: AsRef<str>,
194    F: FnMut(Data),
195{
196    let g = || -> Result<Response, Box<dyn std::error::Error>> {
197        let client = Client::builder().gzip(true).build()?;
198        let ret = client.get(url.as_ref()).send()?;
199        return Ok(ret);
200    };
201
202    // default as connection timed out
203    let mut data = Data {
204        status: 522,
205        data: "".to_string(),
206        headers: HashMap::new(),
207    };
208
209    if let Ok(response) = g() {
210        data.status = response.status().as_u16();
211        data.headers = get_headers(response.headers());
212        data.data = response.text().unwrap_or("".to_string());
213    }
214
215    // result
216    f(data);
217}
218
219/// Most simple way to make a http post request, the gzip will be enabled if data greater than 1 KB.
220///
221/// ```rust
222/// fn test() {
223///     wsd::http::post("https://docs.rs/", "{id: 100}", |data| {
224///         println!("status = {}, data = {}", data.status(), data.text());
225///     });
226/// }
227/// ```
228pub fn post<URL, BODY, F>(url: URL, body: BODY, mut f: F)
229where
230    URL: AsRef<str>,
231    BODY: Into<Body> + AsRef<[u8]>,
232    F: FnMut(Data),
233{
234    let g = |x: BODY| -> Result<Response, Box<dyn std::error::Error>> {
235        // enable zip if data reaches MTU (1300 - 1500)
236        let zip = x.as_ref().len() > 1024;
237        let client = Client::builder().gzip(zip).build()?;
238        let ret = client.post(url.as_ref()).body(x).send()?;
239        return Ok(ret);
240    };
241
242    // default as connection timed out
243    let mut data = Data {
244        status: 522,
245        data: "".to_string(),
246        headers: HashMap::new(),
247    };
248
249    if let Ok(response) = g(body) {
250        data.status = response.status().as_u16();
251        data.headers = get_headers(response.headers());
252        data.data = response.text().unwrap_or("".to_string());
253    }
254
255    // result
256    f(data);
257}