http.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! Types and functions related to HTTP request.
  5. use http::{header::HeaderName, Method};
  6. pub use http::{HeaderMap, StatusCode};
  7. use serde::{Deserialize, Serialize};
  8. use serde_json::Value;
  9. use serde_repr::{Deserialize_repr, Serialize_repr};
  10. use url::Url;
  11. use std::{collections::HashMap, time::Duration};
  12. /// The builder of [`Client`].
  13. #[derive(Debug, Clone, Default, Deserialize)]
  14. #[serde(rename_all = "camelCase")]
  15. pub struct ClientBuilder {
  16. /// Max number of redirections to follow.
  17. pub max_redirections: Option<usize>,
  18. /// Connect timeout for the request.
  19. pub connect_timeout: Option<Duration>,
  20. }
  21. impl ClientBuilder {
  22. /// Creates a new client builder with the default options.
  23. pub fn new() -> Self {
  24. Default::default()
  25. }
  26. /// Sets the maximum number of redirections.
  27. #[must_use]
  28. pub fn max_redirections(mut self, max_redirections: usize) -> Self {
  29. self.max_redirections = Some(max_redirections);
  30. self
  31. }
  32. /// Sets the connection timeout.
  33. #[must_use]
  34. pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
  35. self.connect_timeout.replace(connect_timeout);
  36. self
  37. }
  38. /// Builds the Client.
  39. #[cfg(not(feature = "reqwest-client"))]
  40. pub fn build(self) -> crate::api::Result<Client> {
  41. Ok(Client(self))
  42. }
  43. /// Builds the Client.
  44. #[cfg(feature = "reqwest-client")]
  45. pub fn build(self) -> crate::api::Result<Client> {
  46. let mut client_builder = reqwest::Client::builder();
  47. if let Some(max_redirections) = self.max_redirections {
  48. client_builder = client_builder.redirect(reqwest::redirect::Policy::limited(max_redirections))
  49. }
  50. if let Some(connect_timeout) = self.connect_timeout {
  51. client_builder = client_builder.connect_timeout(connect_timeout);
  52. }
  53. let client = client_builder.build()?;
  54. Ok(Client(client))
  55. }
  56. }
  57. /// The HTTP client based on [`reqwest`].
  58. #[cfg(feature = "reqwest-client")]
  59. #[derive(Debug, Clone)]
  60. pub struct Client(reqwest::Client);
  61. /// The HTTP client.
  62. #[cfg(not(feature = "reqwest-client"))]
  63. #[derive(Debug, Clone)]
  64. pub struct Client(ClientBuilder);
  65. #[cfg(not(feature = "reqwest-client"))]
  66. impl Client {
  67. /// Executes an HTTP request.
  68. ///
  69. /// # Examples
  70. ///
  71. /// ```rust,no_run
  72. /// use tauri::api::http::{ClientBuilder, HttpRequestBuilder, ResponseType};
  73. /// async fn run_request() {
  74. /// let client = ClientBuilder::new().build().unwrap();
  75. /// let response = client.send(
  76. /// HttpRequestBuilder::new("GET", "https://www.rust-lang.org")
  77. /// .unwrap()
  78. /// .response_type(ResponseType::Binary)
  79. /// ).await;
  80. /// if let Ok(response) = response {
  81. /// let bytes = response.bytes();
  82. /// }
  83. /// }
  84. /// ```
  85. pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result<Response> {
  86. let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
  87. let mut request_builder = attohttpc::RequestBuilder::try_new(method, &request.url)?;
  88. if let Some(query) = request.query {
  89. request_builder = request_builder.params(&query);
  90. }
  91. if let Some(headers) = request.headers {
  92. for (header, header_value) in headers.iter() {
  93. request_builder = request_builder.header(
  94. HeaderName::from_bytes(header.as_bytes())?,
  95. header_value.as_bytes(),
  96. );
  97. }
  98. }
  99. if let Some(timeout) = request.timeout {
  100. request_builder = request_builder.timeout(timeout);
  101. }
  102. let response = if let Some(body) = request.body {
  103. match body {
  104. Body::Bytes(data) => request_builder.body(attohttpc::body::Bytes(data)).send()?,
  105. Body::Text(text) => request_builder.body(attohttpc::body::Bytes(text)).send()?,
  106. Body::Json(json) => request_builder.json(&json)?.send()?,
  107. Body::Form(form_body) => {
  108. let mut form = Vec::new();
  109. for (name, part) in form_body.0 {
  110. match part {
  111. FormPart::Bytes(bytes) => form.push((name, serde_json::to_string(&bytes)?)),
  112. FormPart::Text(text) => form.push((name, text)),
  113. }
  114. }
  115. request_builder.form(&form)?.send()?
  116. }
  117. }
  118. } else {
  119. request_builder.send()?
  120. };
  121. Ok(Response(
  122. request.response_type.unwrap_or(ResponseType::Json),
  123. response,
  124. request.url,
  125. ))
  126. }
  127. }
  128. #[cfg(feature = "reqwest-client")]
  129. impl Client {
  130. /// Executes an HTTP request
  131. ///
  132. /// # Examples
  133. pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result<Response> {
  134. let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
  135. let mut request_builder = self.0.request(method, request.url.as_str());
  136. if let Some(query) = request.query {
  137. request_builder = request_builder.query(&query);
  138. }
  139. if let Some(timeout) = request.timeout {
  140. request_builder = request_builder.timeout(timeout);
  141. }
  142. if let Some(body) = request.body {
  143. request_builder = match body {
  144. Body::Bytes(data) => request_builder.body(bytes::Bytes::from(data)),
  145. Body::Text(text) => request_builder.body(bytes::Bytes::from(text)),
  146. Body::Json(json) => request_builder.json(&json),
  147. Body::Form(form_body) => {
  148. let mut form = Vec::new();
  149. for (name, part) in form_body.0 {
  150. match part {
  151. FormPart::Bytes(bytes) => form.push((name, serde_json::to_string(&bytes)?)),
  152. FormPart::Text(text) => form.push((name, text)),
  153. }
  154. }
  155. request_builder.form(&form)
  156. }
  157. };
  158. }
  159. let mut http_request = request_builder.build()?;
  160. if let Some(headers) = request.headers {
  161. for (header, value) in headers.iter() {
  162. http_request.headers_mut().insert(
  163. HeaderName::from_bytes(header.as_bytes())?,
  164. http::header::HeaderValue::from_bytes(value.as_bytes())?,
  165. );
  166. }
  167. }
  168. let response = self.0.execute(http_request).await?;
  169. Ok(Response(
  170. request.response_type.unwrap_or(ResponseType::Json),
  171. response,
  172. ))
  173. }
  174. }
  175. #[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
  176. #[repr(u16)]
  177. #[non_exhaustive]
  178. /// The HTTP response type.
  179. pub enum ResponseType {
  180. /// Read the response as JSON
  181. Json = 1,
  182. /// Read the response as text
  183. Text,
  184. /// Read the response as binary
  185. Binary,
  186. }
  187. /// [`FormBody`] data types.
  188. #[derive(Debug, Deserialize)]
  189. #[serde(untagged)]
  190. #[non_exhaustive]
  191. pub enum FormPart {
  192. /// A string value.
  193. Text(String),
  194. /// A byte array value.
  195. Bytes(Vec<u8>),
  196. }
  197. /// Form body definition.
  198. #[derive(Debug, Deserialize)]
  199. pub struct FormBody(HashMap<String, FormPart>);
  200. impl FormBody {
  201. /// Creates a new form body.
  202. pub fn new(data: HashMap<String, FormPart>) -> Self {
  203. Self(data)
  204. }
  205. }
  206. /// A body for the request.
  207. #[derive(Debug, Deserialize)]
  208. #[serde(tag = "type", content = "payload")]
  209. #[non_exhaustive]
  210. pub enum Body {
  211. /// A multipart formdata body.
  212. Form(FormBody),
  213. /// A JSON body.
  214. Json(Value),
  215. /// A text string body.
  216. Text(String),
  217. /// A byte array body.
  218. Bytes(Vec<u8>),
  219. }
  220. /// The builder for a HTTP request.
  221. ///
  222. /// # Examples
  223. /// ```rust,no_run
  224. /// use tauri::api::http::{HttpRequestBuilder, ResponseType, ClientBuilder};
  225. /// async fn run() {
  226. /// let client = ClientBuilder::new()
  227. /// .max_redirections(3)
  228. /// .build()
  229. /// .unwrap();
  230. /// let request = HttpRequestBuilder::new("GET", "http://example.com").unwrap()
  231. /// .response_type(ResponseType::Text);
  232. /// if let Ok(response) = client.send(request).await {
  233. /// println!("got response");
  234. /// } else {
  235. /// println!("Something Happened!");
  236. /// }
  237. /// }
  238. /// ```
  239. #[derive(Debug, Deserialize)]
  240. #[serde(rename_all = "camelCase")]
  241. pub struct HttpRequestBuilder {
  242. /// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE)
  243. pub method: String,
  244. /// The request URL
  245. pub url: Url,
  246. /// The request query params
  247. pub query: Option<HashMap<String, String>>,
  248. /// The request headers
  249. pub headers: Option<HashMap<String, String>>,
  250. /// The request body
  251. pub body: Option<Body>,
  252. /// Timeout for the whole request
  253. pub timeout: Option<Duration>,
  254. /// The response type (defaults to Json)
  255. pub response_type: Option<ResponseType>,
  256. }
  257. impl HttpRequestBuilder {
  258. /// Initializes a new instance of the HttpRequestrequest_builder.
  259. pub fn new(method: impl Into<String>, url: impl AsRef<str>) -> crate::api::Result<Self> {
  260. Ok(Self {
  261. method: method.into(),
  262. url: Url::parse(url.as_ref())?,
  263. query: None,
  264. headers: None,
  265. body: None,
  266. timeout: None,
  267. response_type: None,
  268. })
  269. }
  270. /// Sets the request parameters.
  271. #[must_use]
  272. pub fn query(mut self, query: HashMap<String, String>) -> Self {
  273. self.query = Some(query);
  274. self
  275. }
  276. /// Sets the request headers.
  277. #[must_use]
  278. pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
  279. self.headers = Some(headers);
  280. self
  281. }
  282. /// Sets the request body.
  283. #[must_use]
  284. pub fn body(mut self, body: Body) -> Self {
  285. self.body = Some(body);
  286. self
  287. }
  288. /// Sets the general request timeout.
  289. #[must_use]
  290. pub fn timeout(mut self, timeout: Duration) -> Self {
  291. self.timeout.replace(timeout);
  292. self
  293. }
  294. /// Sets the type of the response. Interferes with the way we read the response.
  295. #[must_use]
  296. pub fn response_type(mut self, response_type: ResponseType) -> Self {
  297. self.response_type = Some(response_type);
  298. self
  299. }
  300. }
  301. /// The HTTP response.
  302. #[cfg(feature = "reqwest-client")]
  303. #[derive(Debug)]
  304. pub struct Response(ResponseType, reqwest::Response);
  305. /// The HTTP response.
  306. #[cfg(not(feature = "reqwest-client"))]
  307. #[derive(Debug)]
  308. pub struct Response(ResponseType, attohttpc::Response, Url);
  309. impl Response {
  310. /// Get the [`StatusCode`] of this Response.
  311. pub fn status(&self) -> StatusCode {
  312. self.1.status()
  313. }
  314. /// Get the headers of this Response.
  315. pub fn headers(&self) -> &HeaderMap {
  316. self.1.headers()
  317. }
  318. /// Reads the response as raw bytes.
  319. pub async fn bytes(self) -> crate::api::Result<RawResponse> {
  320. let status = self.status().as_u16();
  321. #[cfg(feature = "reqwest-client")]
  322. let data = self.1.bytes().await?.to_vec();
  323. #[cfg(not(feature = "reqwest-client"))]
  324. let data = self.1.bytes()?;
  325. Ok(RawResponse { status, data })
  326. }
  327. #[cfg(not(feature = "reqwest-client"))]
  328. #[allow(dead_code)]
  329. pub(crate) fn reader(self) -> attohttpc::ResponseReader {
  330. let (_, _, reader) = self.1.split();
  331. reader
  332. }
  333. /// Convert the response into a Stream of [`bytes::Bytes`] from the body.
  334. ///
  335. /// # Examples
  336. ///
  337. /// ```no_run
  338. /// use futures::StreamExt;
  339. ///
  340. /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
  341. /// let client = tauri::api::http::ClientBuilder::new().build()?;
  342. /// let mut stream = client.send(tauri::api::http::HttpRequestBuilder::new("GET", "http://httpbin.org/ip")?)
  343. /// .await?
  344. /// .bytes_stream();
  345. ///
  346. /// while let Some(item) = stream.next().await {
  347. /// println!("Chunk: {:?}", item?);
  348. /// }
  349. /// # Ok(())
  350. /// # }
  351. /// ```
  352. #[cfg(feature = "reqwest-client")]
  353. #[allow(dead_code)]
  354. pub(crate) fn bytes_stream(
  355. self,
  356. ) -> impl futures::Stream<Item = crate::api::Result<bytes::Bytes>> {
  357. use futures::StreamExt;
  358. self.1.bytes_stream().map(|res| res.map_err(Into::into))
  359. }
  360. /// Reads the response.
  361. ///
  362. /// Note that the body is serialized to a [`Value`].
  363. pub async fn read(self) -> crate::api::Result<ResponseData> {
  364. #[cfg(feature = "reqwest-client")]
  365. let url = self.1.url().clone();
  366. #[cfg(not(feature = "reqwest-client"))]
  367. let url = self.2;
  368. let mut headers = HashMap::new();
  369. let mut raw_headers = HashMap::new();
  370. for (name, value) in self.1.headers() {
  371. headers.insert(
  372. name.as_str().to_string(),
  373. String::from_utf8(value.as_bytes().to_vec())?,
  374. );
  375. raw_headers.insert(
  376. name.as_str().to_string(),
  377. self
  378. .1
  379. .headers()
  380. .get_all(name)
  381. .into_iter()
  382. .map(|v| String::from_utf8(v.as_bytes().to_vec()).map_err(Into::into))
  383. .collect::<crate::api::Result<Vec<String>>>()?,
  384. );
  385. }
  386. let status = self.1.status().as_u16();
  387. #[cfg(feature = "reqwest-client")]
  388. let data = match self.0 {
  389. ResponseType::Json => self.1.json().await?,
  390. ResponseType::Text => Value::String(self.1.text().await?),
  391. ResponseType::Binary => serde_json::to_value(&self.1.bytes().await?)?,
  392. };
  393. #[cfg(not(feature = "reqwest-client"))]
  394. let data = match self.0 {
  395. ResponseType::Json => self.1.json()?,
  396. ResponseType::Text => Value::String(self.1.text()?),
  397. ResponseType::Binary => serde_json::to_value(&self.1.bytes()?)?,
  398. };
  399. Ok(ResponseData {
  400. url,
  401. status,
  402. headers,
  403. raw_headers,
  404. data,
  405. })
  406. }
  407. }
  408. /// A response with raw bytes.
  409. #[non_exhaustive]
  410. #[derive(Debug)]
  411. pub struct RawResponse {
  412. /// Response status code.
  413. pub status: u16,
  414. /// Response bytes.
  415. pub data: Vec<u8>,
  416. }
  417. /// The response data.
  418. #[derive(Debug, Serialize)]
  419. #[serde(rename_all = "camelCase")]
  420. #[non_exhaustive]
  421. pub struct ResponseData {
  422. /// Response URL. Useful if it followed redirects.
  423. pub url: Url,
  424. /// Response status code.
  425. pub status: u16,
  426. /// Response headers.
  427. pub headers: HashMap<String, String>,
  428. /// Response raw headers.
  429. pub raw_headers: HashMap<String, Vec<String>>,
  430. /// Response data.
  431. pub data: Value,
  432. }
  433. #[cfg(test)]
  434. mod test {
  435. use super::ClientBuilder;
  436. use quickcheck::{Arbitrary, Gen};
  437. impl Arbitrary for ClientBuilder {
  438. fn arbitrary(g: &mut Gen) -> Self {
  439. Self {
  440. max_redirections: Option::arbitrary(g),
  441. connect_timeout: Option::arbitrary(g),
  442. }
  443. }
  444. }
  445. }