request.rs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. pub mod ctype;
  2. pub mod url;
  3. use std::str::FromStr;
  4. use crate::{db::Update, request::ctype::ContentType, workspace::WorkspaceEntryBase, AppResult};
  5. use reqwest::{
  6. header::{self, HeaderMap, HeaderValue},
  7. Body, Method,
  8. };
  9. use serde::{Deserialize, Serialize};
  10. use sqlx::prelude::FromRow;
  11. use tauri_plugin_log::log;
  12. pub const DEFAULT_HEADERS: &'static [(&'static str, &'static str)] = &[
  13. ("user-agent", "rquest/0.0.1"),
  14. ("accept", "*/*"),
  15. ("accept-encoding", "gzip, defalte, br"),
  16. ];
  17. pub async fn send(client: reqwest::Client, req: HttpRequestParameters) -> AppResult<HttpResponse> {
  18. let HttpRequestParameters {
  19. url,
  20. method,
  21. mut headers,
  22. body,
  23. } = req;
  24. fn insert_ct_if_missing(headers: &mut HeaderMap, value: &'static str) {
  25. if !headers.contains_key(header::CONTENT_TYPE) {
  26. headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(value));
  27. }
  28. }
  29. let body = match body {
  30. Some(body) => {
  31. match body.ty {
  32. ContentType::Text => insert_ct_if_missing(&mut headers, "text/plain"),
  33. ContentType::Json => insert_ct_if_missing(&mut headers, "application/json"),
  34. ContentType::Xml => insert_ct_if_missing(&mut headers, "application/xml"),
  35. // Handled by reqwest
  36. ContentType::FormData | ContentType::FormUrlEncoded => {}
  37. };
  38. Some(Body::from(body.content))
  39. }
  40. None => None,
  41. };
  42. let mut req = client.request(method, url).headers(headers);
  43. if let Some(body) = body {
  44. req = req.body(body)
  45. }
  46. let res = match req.send().await {
  47. Ok(res) => {
  48. log::debug!(
  49. "{} {} {:?} {:#?}",
  50. res.remote_addr()
  51. .map(|addr| addr.to_string())
  52. .unwrap_or(String::new()),
  53. res.status(),
  54. res.content_length(),
  55. res.headers()
  56. );
  57. let status = res.status();
  58. let headers = res.headers().clone();
  59. let body = ResponseBody::try_from_response(res).await?;
  60. HttpResponse::new(status.as_u16() as usize, headers, body)
  61. }
  62. Err(e) => return Err(e.into()),
  63. };
  64. Ok(res)
  65. }
  66. #[derive(Debug, Serialize)]
  67. pub struct WorkspaceRequest {
  68. /// Workspace entry representing this request.
  69. pub entry: WorkspaceEntryBase,
  70. /// Request method.
  71. pub method: String,
  72. /// The request URL
  73. pub url: String,
  74. /// Request HTTP body.
  75. pub body: Option<EntryRequestBody>,
  76. /// HTTP header names => values.
  77. pub headers: Vec<RequestHeader>,
  78. /// URL path keys => values.
  79. pub path_params: Vec<RequestPathParam>,
  80. }
  81. impl WorkspaceRequest {
  82. pub fn new(entry: WorkspaceEntryBase, method: String, url: String) -> Self {
  83. Self {
  84. entry,
  85. method,
  86. url,
  87. body: None,
  88. headers: vec![],
  89. path_params: vec![],
  90. }
  91. }
  92. pub fn from_params_and_headers(
  93. entry: WorkspaceEntryBase,
  94. params: RequestParams,
  95. headers: Vec<RequestHeader>,
  96. path_params: Vec<RequestPathParam>,
  97. ) -> Self {
  98. let body = match (params.body_id, params.body, params.content_type) {
  99. (Some(id), Some(body), Some(content_type)) => Some(EntryRequestBody {
  100. id,
  101. body,
  102. content_type,
  103. }),
  104. (None, None, None) => None,
  105. _ => panic!("id, body and content_type must all be present"),
  106. };
  107. WorkspaceRequest {
  108. entry,
  109. method: params.method,
  110. url: params.url,
  111. body,
  112. headers,
  113. path_params,
  114. }
  115. }
  116. }
  117. /// Finalized request parameters obtained from a [WorkspaceRequest].
  118. #[derive(Debug)]
  119. pub struct HttpRequestParameters {
  120. pub url: String,
  121. pub method: reqwest::Method,
  122. pub headers: HeaderMap,
  123. pub body: Option<RequestBody>,
  124. }
  125. impl TryFrom<WorkspaceRequest> for HttpRequestParameters {
  126. type Error = String;
  127. fn try_from(value: WorkspaceRequest) -> Result<Self, String> {
  128. let method = match Method::from_str(&value.method) {
  129. Ok(m) => m,
  130. Err(e) => return Err(e.to_string()),
  131. };
  132. let mut headers = HeaderMap::new();
  133. for header in value.headers {
  134. headers.insert(
  135. reqwest::header::HeaderName::from_str(&header.name).map_err(|e| e.to_string())?,
  136. HeaderValue::from_str(&header.value).map_err(|e| e.to_string())?,
  137. );
  138. }
  139. Ok(Self {
  140. url: value.url,
  141. method,
  142. headers,
  143. body: value.body.map(|body| RequestBody {
  144. content: body.body,
  145. ty: body.content_type,
  146. }),
  147. })
  148. }
  149. }
  150. #[derive(Debug, Serialize)]
  151. pub struct HttpResponse {
  152. pub status: usize,
  153. // pub headers: HeaderMap,
  154. pub body: Option<ResponseBody>,
  155. }
  156. impl HttpResponse {
  157. pub fn new(status: usize, headers: HeaderMap, body: Option<ResponseBody>) -> Self {
  158. Self {
  159. status,
  160. // headers,
  161. body,
  162. }
  163. }
  164. }
  165. #[derive(Debug, Clone, Serialize)]
  166. pub enum ResponseBody {
  167. Text(String),
  168. /// A pretty printed JSON string
  169. Json(String),
  170. // TODO:
  171. // Xml(String),
  172. // HTML
  173. // Binary
  174. }
  175. impl ResponseBody {
  176. pub fn len(&self) -> usize {
  177. match self {
  178. ResponseBody::Text(t) => t.len(),
  179. ResponseBody::Json(t) => t.len(),
  180. }
  181. }
  182. pub async fn try_from_response(res: reqwest::Response) -> AppResult<Option<Self>> {
  183. if res.content_length().is_none() {
  184. log::debug!("Response no content");
  185. }
  186. let Some(ct) = res.headers().get(header::CONTENT_TYPE) else {
  187. log::warn!("Response does not contain content-type header, attempting to read as text");
  188. return Ok(Some(Self::Text(res.text().await?)));
  189. };
  190. let ct = match ct.to_str() {
  191. Ok(ct) => ct,
  192. Err(e) => {
  193. log::warn!("Unable to parse content-type header: {e}");
  194. return Err(e.into());
  195. }
  196. };
  197. let ct: mime::Mime = ct.parse()?;
  198. if ct.subtype() == mime::JSON || ct.suffix().is_some_and(|s| s == mime::JSON) {
  199. log::debug!("reading body");
  200. let json = serde_json::to_string_pretty(&res.json::<serde_json::Value>().await?)?;
  201. log::debug!("body read");
  202. return Ok(Some(Self::Json(json)));
  203. }
  204. if ct.type_() == mime::TEXT {
  205. log::debug!("reading body");
  206. let text = res.text().await?;
  207. log::debug!("body read");
  208. return Ok(Some(Self::Text(text)));
  209. }
  210. log::warn!("Body did not match anything!");
  211. Ok(None)
  212. }
  213. }
  214. #[derive(Debug)]
  215. pub struct RequestParams {
  216. /// ID of the workspace entry representing this request.
  217. pub id: i64,
  218. pub method: String,
  219. pub url: String,
  220. pub content_type: Option<ContentType>,
  221. pub body: Option<String>,
  222. pub body_id: Option<i64>,
  223. }
  224. #[derive(Debug, Deserialize, Serialize)]
  225. pub struct RequestPathParam {
  226. pub position: i64,
  227. pub name: String,
  228. pub value: String,
  229. }
  230. #[derive(Debug, Deserialize)]
  231. pub struct RequestPathUpdate {
  232. pub position: usize,
  233. pub name: String,
  234. pub value: Option<String>,
  235. }
  236. #[derive(Debug, Serialize, FromRow)]
  237. pub struct RequestHeader {
  238. pub id: i64,
  239. pub name: String,
  240. pub value: String,
  241. }
  242. #[derive(Debug, Deserialize)]
  243. pub struct RequestHeaderInsert {
  244. pub name: String,
  245. pub value: String,
  246. }
  247. #[derive(Debug, Deserialize)]
  248. pub struct RequestHeaderUpdate {
  249. pub id: i64,
  250. pub name: Option<String>,
  251. pub value: Option<String>,
  252. }
  253. #[derive(Debug, Serialize, Deserialize)]
  254. pub struct RequestBody {
  255. pub content: String,
  256. pub ty: ContentType,
  257. }
  258. #[derive(Debug, Serialize)]
  259. pub struct EntryRequestBody {
  260. pub id: i64,
  261. pub body: String,
  262. pub content_type: ContentType,
  263. }