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