quad_net/
http_request.rs

1//! Async http requests.
2
3#[cfg(target_arch = "wasm32")]
4use sapp_jsutils::JsObject;
5
6#[derive(Debug, Clone, PartialEq, Copy)]
7pub enum Method {
8    Post,
9    Put,
10    Get,
11    Delete,
12}
13
14#[derive(Debug)]
15pub enum HttpError {
16    IOError,
17    #[cfg(not(target_arch = "wasm32"))]
18    UreqError(ureq::Error),
19}
20
21impl std::fmt::Display for HttpError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            HttpError::IOError => write!(f, "IOError"),
25            #[cfg(not(target_arch = "wasm32"))]
26            HttpError::UreqError(error) => write!(f, "Ureq error: {}", error),
27        }
28    }
29}
30impl From<std::io::Error> for HttpError {
31    fn from(_error: std::io::Error) -> HttpError {
32        HttpError::IOError
33    }
34}
35
36#[cfg(not(target_arch = "wasm32"))]
37impl From<ureq::Error> for HttpError {
38    fn from(error: ureq::Error) -> HttpError {
39        HttpError::UreqError(error)
40    }
41}
42
43#[cfg(target_arch = "wasm32")]
44extern "C" {
45    fn http_make_request(scheme: i32, url: JsObject, body: JsObject, headers: JsObject) -> i32;
46    fn http_try_recv(cid: i32) -> JsObject;
47}
48
49#[cfg(not(target_arch = "wasm32"))]
50pub struct Request {
51    rx: std::sync::mpsc::Receiver<Result<String, HttpError>>,
52}
53
54#[cfg(not(target_arch = "wasm32"))]
55impl Request {
56    pub fn try_recv(&mut self) -> Option<Result<String, HttpError>> {
57        self.rx.try_recv().ok()
58    }
59}
60
61#[cfg(target_arch = "wasm32")]
62pub struct Request {
63    cid: i32,
64}
65
66#[cfg(target_arch = "wasm32")]
67impl Request {
68    pub fn try_recv(&mut self) -> Option<Result<String, HttpError>> {
69        let js_obj = unsafe { http_try_recv(self.cid) };
70
71        if js_obj.is_nil() == false {
72            let mut buf = vec![];
73            js_obj.to_byte_buffer(&mut buf);
74
75            let res = std::str::from_utf8(&buf).unwrap().to_owned();
76            return Some(Ok(res));
77        }
78
79        None
80    }
81}
82
83pub struct RequestBuilder {
84    url: String,
85    method: Method,
86    headers: Vec<(String, String)>,
87    body: Option<String>,
88}
89
90impl RequestBuilder {
91    pub fn new(url: &str) -> RequestBuilder {
92        RequestBuilder {
93            url: url.to_owned(),
94            method: Method::Get,
95            headers: vec![],
96            body: None,
97        }
98    }
99
100    pub fn method(self, method: Method) -> RequestBuilder {
101        Self { method, ..self }
102    }
103
104    pub fn header(mut self, header: &str, value: &str) -> RequestBuilder {
105        self.headers.push((header.to_owned(), value.to_owned()));
106
107        Self {
108            headers: self.headers,
109            ..self
110        }
111    }
112
113    pub fn body(self, body: &str) -> RequestBuilder {
114        RequestBuilder {
115            body: Some(body.to_owned()),
116            ..self
117        }
118    }
119
120    #[cfg(not(target_arch = "wasm32"))]
121    pub fn send(self) -> Request {
122        use std::sync::mpsc::channel;
123
124        let (tx, rx) = channel();
125
126        std::thread::spawn(move || {
127            let method = match self.method {
128                Method::Post => ureq::post,
129                Method::Put => ureq::put,
130                Method::Get => ureq::get,
131                Method::Delete => ureq::delete,
132            };
133
134            let mut request = method(&self.url);
135            for (header, value) in self.headers {
136                request = request.set(&header, &value)
137            }
138            let response: Result<String, HttpError> = if let Some(body) = self.body {
139                request.send_string(&body)
140            } else {
141                request.call()
142            }
143            .map_err(|err| err.into())
144            .and_then(|response| response.into_string().map_err(|err| err.into()));
145
146            tx.send(response).unwrap();
147        });
148
149        Request { rx }
150    }
151
152    #[cfg(target_arch = "wasm32")]
153    pub fn send(&self) -> Request {
154        let scheme = match self.method {
155            Method::Post => 0,
156            Method::Put => 1,
157            Method::Get => 2,
158            Method::Delete => 3,
159        };
160
161        let headers = JsObject::object();
162
163        for (header, value) in &self.headers {
164            headers.set_field_string(&header, &value);
165        }
166
167        let cid = unsafe {
168            http_make_request(
169                scheme,
170                JsObject::string(&self.url),
171                JsObject::string(&self.body.as_ref().map(|s| s.as_str()).unwrap_or("")),
172                headers,
173            )
174        };
175        Request { cid }
176    }
177}