blockless_sdk/
http.rs

1use crate::error::HttpErrorKind;
2use json::JsonValue;
3use std::{cmp::Ordering, collections::BTreeMap};
4
5#[cfg(not(feature = "mock-ffi"))]
6#[link(wasm_import_module = "blockless_http")]
7extern "C" {
8    #[link_name = "http_req"]
9    pub(crate) fn http_open(
10        url: *const u8,
11        url_len: u32,
12        opts: *const u8,
13        opts_len: u32,
14        fd: *mut u32,
15        status: *mut u32,
16    ) -> u32;
17
18    #[link_name = "http_read_header"]
19    pub(crate) fn http_read_header(
20        handle: u32,
21        header: *const u8,
22        header_len: u32,
23        buf: *mut u8,
24        buf_len: u32,
25        num: *mut u32,
26    ) -> u32;
27
28    #[link_name = "http_read_body"]
29    pub(crate) fn http_read_body(handle: u32, buf: *mut u8, buf_len: u32, num: *mut u32) -> u32;
30
31    #[link_name = "http_close"]
32    pub(crate) fn http_close(handle: u32) -> u32;
33}
34
35#[cfg(feature = "mock-ffi")]
36#[allow(unused_variables)]
37mod mock_ffi {
38
39    pub unsafe fn http_open(
40        _url: *const u8,
41        _url_len: u32,
42        _opts: *const u8,
43        _opts_len: u32,
44        fd: *mut u32,
45        status: *mut u32,
46    ) -> u32 {
47        unimplemented!()
48    }
49
50    pub unsafe fn http_read_header(
51        _handle: u32,
52        _header: *const u8,
53        _header_len: u32,
54        buf: *mut u8,
55        buf_len: u32,
56        num: *mut u32,
57    ) -> u32 {
58        unimplemented!()
59    }
60
61    pub unsafe fn http_read_body(_handle: u32, buf: *mut u8, buf_len: u32, num: *mut u32) -> u32 {
62        unimplemented!()
63    }
64
65    pub unsafe fn http_close(_handle: u32) -> u32 {
66        unimplemented!()
67    }
68}
69
70#[cfg(feature = "mock-ffi")]
71use mock_ffi::*;
72
73type Handle = u32;
74type ExitCode = u32;
75
76pub struct BlocklessHttp {
77    inner: Handle,
78    code: ExitCode,
79}
80
81pub struct HttpOptions {
82    pub method: String,
83    pub connect_timeout: u32,
84    pub read_timeout: u32,
85    pub body: Option<String>,
86    pub headers: Option<BTreeMap<String, String>>,
87}
88
89impl HttpOptions {
90    pub fn new(method: &str, connect_timeout: u32, read_timeout: u32) -> Self {
91        HttpOptions {
92            method: method.into(),
93            connect_timeout,
94            read_timeout,
95            body: None,
96            headers: None,
97        }
98    }
99
100    pub fn dump(&self) -> String {
101        // convert BTreeMap to json string
102        let mut headers_str = self
103            .headers
104            .clone()
105            .unwrap_or_default()
106            .iter()
107            .map(|(k, v)| format!("\"{}\":\"{}\"", k, v))
108            .collect::<Vec<String>>()
109            .join(",");
110        headers_str = format!("{{{}}}", headers_str);
111
112        let mut json = JsonValue::new_object();
113        json["method"] = self.method.clone().into();
114        json["connectTimeout"] = self.connect_timeout.into();
115        json["readTimeout"] = self.read_timeout.into();
116        json["headers"] = headers_str.into();
117        json["body"] = self.body.clone().into();
118        json.dump()
119    }
120}
121
122impl BlocklessHttp {
123    pub fn open(url: &str, opts: &HttpOptions) -> Result<Self, HttpErrorKind> {
124        let opts = opts.dump();
125        let mut fd = 0;
126        let mut status = 0;
127        let rs = unsafe {
128            http_open(
129                url.as_ptr(),
130                url.len() as _,
131                opts.as_ptr(),
132                opts.len() as _,
133                &mut fd,
134                &mut status,
135            )
136        };
137        if rs != 0 {
138            return Err(HttpErrorKind::from(rs));
139        }
140        Ok(Self {
141            inner: fd,
142            code: status,
143        })
144    }
145
146    pub fn get_code(&self) -> ExitCode {
147        self.code
148    }
149
150    pub fn get_all_body(&self) -> Result<Vec<u8>, HttpErrorKind> {
151        let mut vec = Vec::new();
152        loop {
153            let mut buf = [0u8; 1024];
154            let mut num: u32 = 0;
155            let rs =
156                unsafe { http_read_body(self.inner, buf.as_mut_ptr(), buf.len() as _, &mut num) };
157            if rs != 0 {
158                return Err(HttpErrorKind::from(rs));
159            }
160
161            match num.cmp(&0) {
162                Ordering::Greater => vec.extend_from_slice(&buf[0..num as _]),
163                _ => break,
164            }
165        }
166        Ok(vec)
167    }
168
169    pub fn get_header(&self, header: &str) -> Result<String, HttpErrorKind> {
170        let mut vec = Vec::new();
171        loop {
172            let mut buf = [0u8; 1024];
173            let mut num: u32 = 0;
174            let rs = unsafe {
175                http_read_header(
176                    self.inner,
177                    header.as_ptr(),
178                    header.len() as _,
179                    buf.as_mut_ptr(),
180                    buf.len() as _,
181                    &mut num,
182                )
183            };
184            if rs != 0 {
185                return Err(HttpErrorKind::from(rs));
186            }
187            match num.cmp(&0) {
188                Ordering::Greater => vec.extend_from_slice(&buf[0..num as _]),
189                _ => break,
190            }
191        }
192        String::from_utf8(vec).map_err(|_| HttpErrorKind::Utf8Error)
193    }
194
195    pub fn close(self) {
196        unsafe {
197            http_close(self.inner);
198        }
199    }
200
201    pub fn read_body(&self, buf: &mut [u8]) -> Result<u32, HttpErrorKind> {
202        let mut num: u32 = 0;
203        let rs = unsafe { http_read_body(self.inner, buf.as_mut_ptr(), buf.len() as _, &mut num) };
204        if rs != 0 {
205            return Err(HttpErrorKind::from(rs));
206        }
207        Ok(num)
208    }
209}
210
211impl Drop for BlocklessHttp {
212    fn drop(&mut self) {
213        unsafe {
214            http_close(self.inner);
215        }
216    }
217}