mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-08-25 21:04:04 +02:00
Address response bodies by response id instead of a filesystem path (#550)
This commit is contained in:
+14
-3
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tauri::{AppHandle, Manager, Runtime, WebviewWindow};
|
||||
use tokio::sync::watch::Receiver;
|
||||
use yaak::send::{SendHttpRequestWithPluginsParams, send_http_request_with_plugins};
|
||||
use yaak::send::{ResponseBody, SendHttpRequestWithPluginsParams, send_http_request_with_plugins};
|
||||
use yaak_crypto::manager::EncryptionManager;
|
||||
use yaak_http::manager::HttpConnectionManager;
|
||||
use yaak_models::models::{CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseState};
|
||||
@@ -62,6 +62,12 @@ impl<R: Runtime> ResponseContext<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// What a send produced: the response, and where its body went.
|
||||
pub struct SentHttpRequest {
|
||||
pub response: HttpResponse,
|
||||
pub body: ResponseBody,
|
||||
}
|
||||
|
||||
pub async fn send_http_request<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
unrendered_request: &HttpRequest,
|
||||
@@ -69,7 +75,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
environment: Option<Environment>,
|
||||
cookie_jar: Option<CookieJar>,
|
||||
cancelled_rx: &mut Receiver<bool>,
|
||||
) -> Result<HttpResponse> {
|
||||
) -> Result<SentHttpRequest> {
|
||||
send_http_request_with_context(
|
||||
window,
|
||||
unrendered_request,
|
||||
@@ -90,7 +96,7 @@ pub async fn send_http_request_with_context<R: Runtime>(
|
||||
cookie_jar: Option<CookieJar>,
|
||||
cancelled_rx: &Receiver<bool>,
|
||||
plugin_context: &PluginContext,
|
||||
) -> Result<HttpResponse> {
|
||||
) -> Result<SentHttpRequest> {
|
||||
let app_handle = window.app_handle().clone();
|
||||
let update_source = UpdateSource::from_window_label(window.label());
|
||||
let mut response_ctx =
|
||||
@@ -110,7 +116,7 @@ pub async fn send_http_request_with_context<R: Runtime>(
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(response) => Ok(response),
|
||||
Ok(sent) => Ok(sent),
|
||||
Err(e) => {
|
||||
let error = e.to_string();
|
||||
let elapsed = start.elapsed().as_millis() as i32;
|
||||
@@ -123,7 +129,12 @@ pub async fn send_http_request_with_context<R: Runtime>(
|
||||
}
|
||||
r.error = Some(error);
|
||||
});
|
||||
Ok(response_ctx.response().clone())
|
||||
// The send failed, so whatever body exists is the partial one
|
||||
// already on disk under the response's id.
|
||||
Ok(SentHttpRequest {
|
||||
response: response_ctx.response().clone(),
|
||||
body: ResponseBody::Stored,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +147,7 @@ async fn send_http_request_inner<R: Runtime>(
|
||||
cancelled_rx: &Receiver<bool>,
|
||||
plugin_context: &PluginContext,
|
||||
response_ctx: &mut ResponseContext<R>,
|
||||
) -> Result<HttpResponse> {
|
||||
) -> Result<SentHttpRequest> {
|
||||
let app_handle = window.app_handle().clone();
|
||||
let plugin_manager = Arc::new((*app_handle.state::<PluginManager>()).clone());
|
||||
let encryption_manager = Arc::new((*app_handle.state::<EncryptionManager>()).clone());
|
||||
@@ -165,7 +176,7 @@ async fn send_http_request_inner<R: Runtime>(
|
||||
.await
|
||||
.map_err(|e| GenericError(e.to_string()))?;
|
||||
|
||||
Ok(result.response)
|
||||
Ok(SentHttpRequest { response: result.response, body: result.response_body })
|
||||
}
|
||||
|
||||
pub fn resolve_http_request<R: Runtime>(
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::import::import_data;
|
||||
use crate::models_ext::{BlobManagerExt, QueryManagerExt};
|
||||
use crate::notifications::YaakNotifier;
|
||||
use crate::render::{render_grpc_request, render_json_value, render_template};
|
||||
use crate::rpc_ext::EphemeralHttpResponse;
|
||||
use crate::updates::{UpdateMode, UpdateTrigger, YaakUpdater};
|
||||
use crate::uri_scheme::handle_deep_link;
|
||||
use error::Result as YaakResult;
|
||||
@@ -30,6 +31,7 @@ use tokio::sync::Mutex;
|
||||
use tokio::task::block_in_place;
|
||||
use tokio::time;
|
||||
use yaak::export::{self, ExportDataParams};
|
||||
use yaak::send::ResponseBody;
|
||||
use yaak_common::command::new_checked_command;
|
||||
use yaak_crypto::manager::EncryptionManager;
|
||||
use yaak_grpc::manager::{GrpcConfig, GrpcHandle};
|
||||
@@ -981,13 +983,18 @@ async fn cmd_restart<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send without saving anything.
|
||||
///
|
||||
/// The response never reaches the database, so its body cannot be read back by
|
||||
/// id later the way a saved response's can. It comes back here instead, which
|
||||
/// is the only copy the caller gets.
|
||||
async fn cmd_send_ephemeral_request<R: Runtime>(
|
||||
mut request: HttpRequest,
|
||||
environment_id: Option<&str>,
|
||||
cookie_jar_id: Option<&str>,
|
||||
window: WebviewWindow<R>,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> YaakResult<HttpResponse> {
|
||||
) -> YaakResult<EphemeralHttpResponse> {
|
||||
let response = HttpResponse::default();
|
||||
request.id = "".to_string();
|
||||
let environment = match environment_id {
|
||||
@@ -1006,7 +1013,18 @@ async fn cmd_send_ephemeral_request<R: Runtime>(
|
||||
}
|
||||
});
|
||||
|
||||
send_http_request(&window, &request, &response, environment, cookie_jar, &mut cancel_rx).await
|
||||
let sent =
|
||||
send_http_request(&window, &request, &response, environment, cookie_jar, &mut cancel_rx)
|
||||
.await?;
|
||||
|
||||
// Blanking the request id above is what makes this send unsaved, so the
|
||||
// engine always hands the body back. Failing loudly beats returning an
|
||||
// empty body that reads as "the server sent nothing".
|
||||
let ResponseBody::Returned(body) = sent.body else {
|
||||
return Err(GenericError("Unsaved response did not return a body".to_string()));
|
||||
};
|
||||
|
||||
Ok(EphemeralHttpResponse { response: sent.response, body })
|
||||
}
|
||||
|
||||
async fn cmd_format_json(text: &str) -> YaakResult<String> {
|
||||
@@ -1020,27 +1038,49 @@ async fn cmd_format_graphql(text: &str) -> YaakResult<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a response's body is, and what it is meant to be read as.
|
||||
struct ResponseBodyLocation {
|
||||
/// None when the response has no stored body.
|
||||
path: Option<PathBuf>,
|
||||
/// The response's declared `Content-Type`, empty when it has none.
|
||||
content_type: String,
|
||||
}
|
||||
|
||||
/// Find a response's body from its id alone.
|
||||
///
|
||||
/// The frontend hands back an id and never a path, so the only bodies reachable
|
||||
/// here are ones the engine wrote and the database still knows about. A
|
||||
/// response that was never saved has no entry, and its body came back from the
|
||||
/// send that made it.
|
||||
fn locate_response_body<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
response_id: &str,
|
||||
) -> YaakResult<ResponseBodyLocation> {
|
||||
let response = app_handle.db().get_http_response(response_id)?;
|
||||
|
||||
Ok(ResponseBodyLocation {
|
||||
path: response.body_path.map(PathBuf::from),
|
||||
content_type: response
|
||||
.headers
|
||||
.iter()
|
||||
.find(|h| h.name.eq_ignore_ascii_case("content-type"))
|
||||
.map(|h| h.value.clone())
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cmd_http_response_body<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
response: HttpResponse,
|
||||
response_id: &str,
|
||||
filter: Option<&str>,
|
||||
) -> YaakResult<FilterResponse> {
|
||||
let body_path = match response.body_path {
|
||||
None => {
|
||||
return Ok(FilterResponse { content: String::new(), error: None });
|
||||
}
|
||||
Some(p) => p,
|
||||
let location = locate_response_body(window.app_handle(), response_id)?;
|
||||
let Some(body_path) = location.path else {
|
||||
return Ok(FilterResponse { content: String::new(), error: None });
|
||||
};
|
||||
|
||||
let content_type = response
|
||||
.headers
|
||||
.iter()
|
||||
.find_map(|h| {
|
||||
if h.name.eq_ignore_ascii_case("content-type") { Some(h.value.as_str()) } else { None }
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let content_type = location.content_type.as_str();
|
||||
let body = read_response_body(&body_path, content_type)
|
||||
.await
|
||||
.ok_or(GenericError("Failed to find response body".to_string()))?;
|
||||
@@ -1053,6 +1093,20 @@ async fn cmd_http_response_body<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
/// The body's path on this machine, for the desktop host to read or hand to the
|
||||
/// webview's asset protocol.
|
||||
///
|
||||
/// The frontend holds response ids; only `packages/platform`'s Tauri host sees
|
||||
/// the path, and only because it is about to open the file itself. Hosts
|
||||
/// without a filesystem serve the same bytes over HTTP instead.
|
||||
async fn cmd_http_response_body_path<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
response_id: &str,
|
||||
) -> YaakResult<Option<String>> {
|
||||
let location = locate_response_body(&app_handle, response_id)?;
|
||||
Ok(location.path.map(|p| p.to_string_lossy().to_string()))
|
||||
}
|
||||
|
||||
async fn cmd_http_request_body<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
response_id: &str,
|
||||
@@ -1069,8 +1123,15 @@ async fn cmd_http_request_body<R: Runtime>(
|
||||
Ok(Some(body))
|
||||
}
|
||||
|
||||
async fn cmd_get_sse_events(file_path: &str) -> YaakResult<Vec<ServerSentEvent>> {
|
||||
let body = fs::read(file_path)?;
|
||||
async fn cmd_get_sse_events<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
response_id: &str,
|
||||
) -> YaakResult<Vec<ServerSentEvent>> {
|
||||
let Some(body_path) = locate_response_body(&app_handle, response_id)?.path else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
|
||||
let body = fs::read(body_path)?;
|
||||
let mut event_parser = EventParser::new();
|
||||
event_parser.process_bytes(body.into())?;
|
||||
|
||||
@@ -1488,7 +1549,7 @@ async fn cmd_send_http_request<R: Runtime>(
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(r) => r,
|
||||
Ok(sent) => sent.response,
|
||||
Err(e) => {
|
||||
let resp = app_handle.db().get_http_response(&response.id)?;
|
||||
app_handle.db().upsert_http_response(
|
||||
|
||||
@@ -314,7 +314,7 @@ async fn handle_host_plugin_request<R: Runtime>(
|
||||
.await?;
|
||||
|
||||
Ok(Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
|
||||
http_response,
|
||||
http_response: http_response.response,
|
||||
})))
|
||||
}
|
||||
HostRequest::OpenWindow(req) => {
|
||||
|
||||
@@ -250,6 +250,19 @@ pub(crate) struct CmdSendEphemeralRequestReq {
|
||||
pub cookie_jar_id: Option<String>,
|
||||
}
|
||||
|
||||
/// An unsaved response and its body.
|
||||
///
|
||||
/// The body rides along because nothing stored it: there is no database row to
|
||||
/// look up later and no file for a host to read, so this is the caller's only
|
||||
/// copy.
|
||||
#[derive(Debug, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_rpc.ts")]
|
||||
pub struct EphemeralHttpResponse {
|
||||
pub response: HttpResponse,
|
||||
pub body: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_rpc.ts")]
|
||||
@@ -268,10 +281,17 @@ pub(crate) struct CmdFormatGraphqlReq {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_rpc.ts")]
|
||||
pub(crate) struct CmdHttpResponseBodyReq {
|
||||
pub response: HttpResponse,
|
||||
pub response_id: String,
|
||||
pub filter: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_rpc.ts")]
|
||||
pub(crate) struct CmdHttpResponseBodyPathReq {
|
||||
pub response_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_rpc.ts")]
|
||||
@@ -283,7 +303,7 @@ pub(crate) struct CmdHttpRequestBodyReq {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_rpc.ts")]
|
||||
pub(crate) struct CmdGetSseEventsReq {
|
||||
pub file_path: String,
|
||||
pub response_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, TS)]
|
||||
@@ -970,7 +990,7 @@ async fn cmd_restart<R: Runtime>(ctx: ClientCtx<R>, _req: CmdRestartReq) -> Resu
|
||||
Ok(crate::cmd_restart(ctx.window.app_handle().clone()).await?)
|
||||
}
|
||||
|
||||
async fn cmd_send_ephemeral_request<R: Runtime>(ctx: ClientCtx<R>, req: CmdSendEphemeralRequestReq) -> Result<HttpResponse> {
|
||||
async fn cmd_send_ephemeral_request<R: Runtime>(ctx: ClientCtx<R>, req: CmdSendEphemeralRequestReq) -> Result<EphemeralHttpResponse> {
|
||||
Ok(crate::cmd_send_ephemeral_request(req.request, req.environment_id.as_deref(), req.cookie_jar_id.as_deref(), ctx.window.clone(), ctx.window.app_handle().clone()).await?)
|
||||
}
|
||||
|
||||
@@ -983,15 +1003,19 @@ async fn cmd_format_graphql<R: Runtime>(_ctx: ClientCtx<R>, req: CmdFormatGraphq
|
||||
}
|
||||
|
||||
async fn cmd_http_response_body<R: Runtime>(ctx: ClientCtx<R>, req: CmdHttpResponseBodyReq) -> Result<FilterResponse> {
|
||||
Ok(crate::cmd_http_response_body(ctx.window.clone(), ctx.window.app_handle().state::<PluginManager>(), req.response, req.filter.as_deref()).await?)
|
||||
Ok(crate::cmd_http_response_body(ctx.window.clone(), ctx.window.app_handle().state::<PluginManager>(), &req.response_id, req.filter.as_deref()).await?)
|
||||
}
|
||||
|
||||
async fn cmd_http_response_body_path<R: Runtime>(ctx: ClientCtx<R>, req: CmdHttpResponseBodyPathReq) -> Result<Option<String>> {
|
||||
Ok(crate::cmd_http_response_body_path(ctx.window.app_handle().clone(), &req.response_id).await?)
|
||||
}
|
||||
|
||||
async fn cmd_http_request_body<R: Runtime>(ctx: ClientCtx<R>, req: CmdHttpRequestBodyReq) -> Result<Option<Vec<u8>>> {
|
||||
Ok(crate::cmd_http_request_body(ctx.window.app_handle().clone(), &req.response_id).await?)
|
||||
}
|
||||
|
||||
async fn cmd_get_sse_events<R: Runtime>(_ctx: ClientCtx<R>, req: CmdGetSseEventsReq) -> Result<Vec<ServerSentEvent>> {
|
||||
Ok(crate::cmd_get_sse_events(&req.file_path).await?)
|
||||
async fn cmd_get_sse_events<R: Runtime>(ctx: ClientCtx<R>, req: CmdGetSseEventsReq) -> Result<Vec<ServerSentEvent>> {
|
||||
Ok(crate::cmd_get_sse_events(ctx.window.app_handle().clone(), &req.response_id).await?)
|
||||
}
|
||||
|
||||
async fn cmd_get_http_response_events<R: Runtime>(ctx: ClientCtx<R>, req: CmdGetHttpResponseEventsReq) -> Result<Vec<HttpResponseEvent>> {
|
||||
@@ -1367,10 +1391,11 @@ rpc_commands! {
|
||||
cmd_grpc_reflect(CmdGrpcReflectReq) -> Vec<ServiceDefinition>,
|
||||
cmd_grpc_go(CmdGrpcGoReq) -> String,
|
||||
cmd_restart(CmdRestartReq) -> (),
|
||||
cmd_send_ephemeral_request(CmdSendEphemeralRequestReq) -> HttpResponse,
|
||||
cmd_send_ephemeral_request(CmdSendEphemeralRequestReq) -> EphemeralHttpResponse,
|
||||
cmd_format_json(CmdFormatJsonReq) -> String,
|
||||
cmd_format_graphql(CmdFormatGraphqlReq) -> String,
|
||||
cmd_http_response_body(CmdHttpResponseBodyReq) -> FilterResponse,
|
||||
cmd_http_response_body_path(CmdHttpResponseBodyPathReq) -> Option<String>,
|
||||
cmd_http_request_body(CmdHttpRequestBodyReq) -> Option<Vec<u8>>,
|
||||
cmd_get_sse_events(CmdGetSseEventsReq) -> Vec<ServerSentEvent>,
|
||||
cmd_get_http_response_events(CmdGetHttpResponseEventsReq) -> Vec<HttpResponseEvent>,
|
||||
|
||||
Reference in New Issue
Block a user