request.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. pub mod ctype;
  2. pub mod url;
  3. use std::str::FromStr;
  4. use crate::{
  5. auth::{Auth, BasicAuth, OAuth},
  6. error::AppError,
  7. request::{ctype::ContentType, url::RequestUrl},
  8. workspace::WorkspaceEntryBase,
  9. AppResult,
  10. };
  11. use base64::{prelude::BASE64_STANDARD, Engine};
  12. use reqwest::{
  13. header::{self, HeaderMap, HeaderValue},
  14. Body, Method,
  15. };
  16. use serde::{Deserialize, Serialize};
  17. use sqlx::prelude::FromRow;
  18. use tauri_plugin_log::log;
  19. pub const DEFAULT_HEADERS: &'static [(&'static str, &'static str)] = &[
  20. ("user-agent", "rquest/0.0.1"),
  21. ("accept", "*/*"),
  22. ("accept-encoding", "gzip, defalte, br"),
  23. ];
  24. pub async fn send(client: reqwest::Client, req: HttpRequestParameters) -> AppResult<HttpResponse> {
  25. let HttpRequestParameters {
  26. url,
  27. method,
  28. mut headers,
  29. body,
  30. } = req;
  31. fn insert_ct_if_missing(headers: &mut HeaderMap, value: &'static str) {
  32. if !headers.contains_key(header::CONTENT_TYPE) {
  33. headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(value));
  34. }
  35. }
  36. let body = match body {
  37. Some(body) => {
  38. match body.ty {
  39. ContentType::Text => insert_ct_if_missing(&mut headers, "text/plain"),
  40. ContentType::Json => {
  41. insert_ct_if_missing(&mut headers, "application/json");
  42. serde_json::from_str::<serde_json::Value>(&body.path)?;
  43. }
  44. ContentType::Xml => {
  45. insert_ct_if_missing(&mut headers, "application/xml");
  46. roxmltree::Document::parse(&body.path)?;
  47. }
  48. ContentType::FormUrlEncoded => {
  49. serde_urlencoded::from_str::<Vec<(&str, &str)>>(&body.path)
  50. .map_err(|e| AppError::SerdeUrl(e.to_string()))?;
  51. }
  52. // Handled by reqwest
  53. ContentType::FormData => {}
  54. };
  55. Some(Body::from(body.path))
  56. }
  57. None => None,
  58. };
  59. let mut req = client.request(method, url).headers(headers);
  60. if let Some(body) = body {
  61. req = req.body(body);
  62. }
  63. let req = req.build()?;
  64. dbg!(&req);
  65. let res = match client.execute(req).await {
  66. Ok(res) => {
  67. log::debug!(
  68. "{} {} {:?} {:#?}",
  69. res.remote_addr()
  70. .map(|addr| addr.to_string())
  71. .unwrap_or(String::new()),
  72. res.status(),
  73. res.content_length(),
  74. res.headers()
  75. );
  76. let status = res.status();
  77. let headers = res.headers().clone();
  78. let body = ResponseBody::try_from_response(res).await?;
  79. HttpResponse::new(status.as_u16() as usize, headers, body)
  80. }
  81. Err(e) => return Err(e.into()),
  82. };
  83. Ok(res)
  84. }
  85. /// Load the request body into a vec, validating it beforehand to ensure no syntactic errors are
  86. /// present in bodies that need valid syntax.
  87. pub async fn get_valid_request_body(path: &str, ty: ContentType) -> AppResult<String> {
  88. let body = tokio::fs::read_to_string(path).await?;
  89. match ty {
  90. ContentType::Text => {}
  91. ContentType::Json => {
  92. serde_json::from_str::<serde_json::Value>(&body)?;
  93. }
  94. ContentType::Xml => {
  95. roxmltree::Document::parse(&body)?;
  96. }
  97. ContentType::FormUrlEncoded => {
  98. serde_urlencoded::from_str::<Vec<(&str, &str)>>(&body)
  99. .map_err(|e| AppError::SerdeUrl(e.to_string()))?;
  100. }
  101. // Handled by reqwest
  102. ContentType::FormData => {}
  103. };
  104. Ok(body)
  105. }
  106. #[derive(Debug, Serialize)]
  107. pub struct WorkspaceRequest {
  108. /// Workspace entry representing this request.
  109. pub entry: WorkspaceEntryBase,
  110. /// Request method.
  111. pub method: String,
  112. /// The request URL
  113. pub url: String,
  114. /// Request HTTP body.
  115. pub body: Option<EntryRequestBody>,
  116. /// HTTP header names => values.
  117. pub headers: Vec<RequestHeader>,
  118. /// URL path keys => values.
  119. pub path_params: Vec<RequestPathParam>,
  120. }
  121. impl WorkspaceRequest {
  122. pub fn new(entry: WorkspaceEntryBase, method: String, url: String) -> Self {
  123. Self {
  124. entry,
  125. method,
  126. url,
  127. body: None,
  128. headers: vec![],
  129. path_params: vec![],
  130. }
  131. }
  132. pub fn from_params_and_headers(
  133. entry: WorkspaceEntryBase,
  134. params: RequestParams,
  135. headers: Vec<RequestHeader>,
  136. path_params: Vec<RequestPathParam>,
  137. ) -> Self {
  138. let body = match (params.body_id, params.body, params.content_type) {
  139. (Some(id), Some(body), Some(content_type)) => Some(EntryRequestBody {
  140. id,
  141. body,
  142. content_type,
  143. }),
  144. (None, None, None) => None,
  145. _ => panic!("id, body and content_type must all be present"),
  146. };
  147. WorkspaceRequest {
  148. entry,
  149. method: params.method,
  150. url: params.url,
  151. body,
  152. headers,
  153. path_params,
  154. }
  155. }
  156. pub fn resolve_auth(&mut self, auth: Auth) -> AppResult<()> {
  157. match auth {
  158. crate::auth::Auth::Token(token) => match token.placement {
  159. crate::auth::TokenPlacement::Query => {
  160. let mut url = RequestUrl::parse(&self.url)?;
  161. url.query_params.push((&token.name, &token.value));
  162. self.url = url.to_string();
  163. }
  164. crate::auth::TokenPlacement::Header => {
  165. self.headers.push(RequestHeader {
  166. id: -1,
  167. name: token.name,
  168. value: token.value,
  169. });
  170. }
  171. },
  172. crate::auth::Auth::Basic(BasicAuth { user, password }) => {
  173. let value = format!(
  174. "Basic {}",
  175. BASE64_STANDARD.encode(format!("{user}:{password}"))
  176. );
  177. self.headers.push(RequestHeader {
  178. id: -1,
  179. name: "Authorization".to_string(),
  180. value,
  181. });
  182. }
  183. crate::auth::Auth::OAuth(OAuth {
  184. token_name,
  185. callback_url,
  186. auth_url,
  187. token_url,
  188. refresh_url,
  189. client_id,
  190. client_secret,
  191. scope,
  192. state,
  193. grant_type,
  194. }) => {
  195. todo!()
  196. }
  197. }
  198. Ok(())
  199. }
  200. }
  201. /// Finalized request parameters obtained from a [WorkspaceRequest].
  202. #[derive(Debug)]
  203. pub struct HttpRequestParameters {
  204. pub url: String,
  205. pub method: reqwest::Method,
  206. pub headers: HeaderMap,
  207. pub body: Option<RequestBody>,
  208. }
  209. impl TryFrom<WorkspaceRequest> for HttpRequestParameters {
  210. type Error = String;
  211. fn try_from(value: WorkspaceRequest) -> Result<Self, String> {
  212. let method = match Method::from_str(&value.method) {
  213. Ok(m) => m,
  214. Err(e) => return Err(e.to_string()),
  215. };
  216. let mut headers = HeaderMap::new();
  217. for header in value.headers {
  218. headers.insert(
  219. reqwest::header::HeaderName::from_str(&header.name).map_err(|e| e.to_string())?,
  220. HeaderValue::from_str(&header.value).map_err(|e| e.to_string())?,
  221. );
  222. }
  223. Ok(Self {
  224. url: value.url,
  225. method,
  226. headers,
  227. body: value.body.map(|body| RequestBody {
  228. path: body.body,
  229. ty: body.content_type,
  230. }),
  231. })
  232. }
  233. }
  234. #[derive(Debug, Serialize)]
  235. pub struct HttpResponse {
  236. pub status: usize,
  237. // pub headers: HeaderMap,
  238. pub body: Option<ResponseBody>,
  239. }
  240. impl HttpResponse {
  241. pub fn new(status: usize, headers: HeaderMap, body: Option<ResponseBody>) -> Self {
  242. Self {
  243. status,
  244. // headers,
  245. body,
  246. }
  247. }
  248. }
  249. #[derive(Debug, Clone, Serialize)]
  250. pub enum ResponseBody {
  251. Text(String),
  252. /// A pretty printed JSON string
  253. Json(String),
  254. // TODO:
  255. // Xml(String),
  256. // HTML
  257. // Binary
  258. }
  259. impl ResponseBody {
  260. pub fn len(&self) -> usize {
  261. match self {
  262. ResponseBody::Text(t) => t.len(),
  263. ResponseBody::Json(t) => t.len(),
  264. }
  265. }
  266. pub async fn try_from_response(res: reqwest::Response) -> AppResult<Option<Self>> {
  267. if res.content_length().is_none() {
  268. log::debug!("Response no content");
  269. }
  270. let Some(ct) = res.headers().get(header::CONTENT_TYPE) else {
  271. log::warn!("Response does not contain content-type header, attempting to read as text");
  272. return Ok(Some(Self::Text(res.text().await?)));
  273. };
  274. let ct = match ct.to_str() {
  275. Ok(ct) => ct,
  276. Err(e) => {
  277. log::warn!("Unable to parse content-type header: {e}");
  278. return Err(e.into());
  279. }
  280. };
  281. let ct: mime::Mime = ct.parse()?;
  282. if ct.subtype() == mime::JSON || ct.suffix().is_some_and(|s| s == mime::JSON) {
  283. log::debug!("reading body");
  284. let json = serde_json::to_string_pretty(&res.json::<serde_json::Value>().await?)?;
  285. log::debug!("body read");
  286. return Ok(Some(Self::Json(json)));
  287. }
  288. if ct.type_() == mime::TEXT {
  289. log::debug!("reading body");
  290. let text = res.text().await?;
  291. log::debug!("body read");
  292. return Ok(Some(Self::Text(text)));
  293. }
  294. log::warn!("Body did not match anything!");
  295. Ok(None)
  296. }
  297. }
  298. #[derive(Debug)]
  299. pub struct RequestParams {
  300. /// ID of the workspace entry representing this request.
  301. pub id: i64,
  302. pub method: String,
  303. pub url: String,
  304. pub content_type: Option<ContentType>,
  305. pub body: Option<String>,
  306. pub body_id: Option<i64>,
  307. }
  308. #[derive(Debug, Deserialize, Serialize)]
  309. pub struct RequestPathParam {
  310. pub position: i64,
  311. pub name: String,
  312. pub value: String,
  313. }
  314. #[derive(Debug, Deserialize)]
  315. pub struct RequestPathUpdate {
  316. pub position: usize,
  317. pub name: String,
  318. pub value: Option<String>,
  319. }
  320. #[derive(Debug, Serialize, FromRow)]
  321. pub struct RequestHeader {
  322. pub id: i64,
  323. pub name: String,
  324. pub value: String,
  325. }
  326. #[derive(Debug, Deserialize)]
  327. pub struct RequestHeaderInsert {
  328. pub name: String,
  329. pub value: String,
  330. }
  331. #[derive(Debug, Deserialize)]
  332. pub struct RequestHeaderUpdate {
  333. pub id: i64,
  334. pub name: Option<String>,
  335. pub value: Option<String>,
  336. }
  337. #[derive(Debug, Serialize, Deserialize)]
  338. pub struct RequestBody {
  339. pub path: String,
  340. pub ty: ContentType,
  341. }
  342. #[derive(Debug, Serialize)]
  343. pub struct EntryRequestBody {
  344. pub id: i64,
  345. pub body: String,
  346. pub content_type: ContentType,
  347. }