feat(flow): 新增流式执行模式与SSE支持
新增流式执行模式,通过SSE实时推送节点执行事件与日志 重构HTTP执行器与中间件,提取通用HTTP客户端组件 优化前端测试面板,支持流式模式切换与实时日志展示 更新依赖版本并修复密码哈希的随机数生成器问题 修复前端节点类型映射问题,确保Code节点表单可用
This commit is contained in:
@ -2,8 +2,7 @@ use async_trait::async_trait;
|
||||
use serde_json::{Value, json, Map};
|
||||
use tracing::info;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use reqwest::Certificate;
|
||||
use crate::middlewares::http_client::{execute_http, HttpClientOptions, HttpRequest};
|
||||
|
||||
use crate::flow::task::Executor;
|
||||
use crate::flow::domain::{NodeDef, NodeId};
|
||||
@ -34,64 +33,29 @@ impl Executor for HttpTask {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// 3) 解析配置
|
||||
// 3) 解析配置 -> 转换为中间件请求参数
|
||||
let (method, url, headers, query, body, opts) = parse_http_config(cfg)?;
|
||||
info!(target = "udmin.flow", "http task: {} {}", method, url);
|
||||
|
||||
// 4) 发送请求(支持 HTTPS 相关选项)
|
||||
let client = {
|
||||
let mut builder = reqwest::Client::builder();
|
||||
if let Some(ms) = opts.timeout_ms { builder = builder.timeout(Duration::from_millis(ms)); }
|
||||
if opts.insecure { builder = builder.danger_accept_invalid_certs(true); }
|
||||
if opts.http1_only { builder = builder.http1_only(); }
|
||||
if let Some(pem) = opts.ca_pem {
|
||||
if let Ok(cert) = Certificate::from_pem(pem.as_bytes()) {
|
||||
builder = builder.add_root_certificate(cert);
|
||||
}
|
||||
}
|
||||
builder.build()?
|
||||
let req = HttpRequest {
|
||||
method,
|
||||
url,
|
||||
headers,
|
||||
query,
|
||||
body,
|
||||
};
|
||||
let client_opts = HttpClientOptions {
|
||||
timeout_ms: opts.timeout_ms,
|
||||
insecure: opts.insecure,
|
||||
ca_pem: opts.ca_pem,
|
||||
http1_only: opts.http1_only,
|
||||
};
|
||||
let mut req = client.request(method.parse()?, url);
|
||||
|
||||
if let Some(hs) = headers {
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
let mut map = HeaderMap::new();
|
||||
for (k, v) in hs {
|
||||
if let (Ok(name), Ok(value)) = (HeaderName::from_bytes(k.as_bytes()), HeaderValue::from_str(&v)) {
|
||||
map.insert(name, value);
|
||||
}
|
||||
}
|
||||
req = req.headers(map);
|
||||
}
|
||||
|
||||
if let Some(qs) = query {
|
||||
// 将查询参数转成 (String, String) 列表,便于 reqwest 序列化
|
||||
let mut pairs: Vec<(String, String)> = Vec::new();
|
||||
for (k, v) in qs {
|
||||
let s = match v {
|
||||
Value::String(s) => s,
|
||||
Value::Number(n) => n.to_string(),
|
||||
Value::Bool(b) => b.to_string(),
|
||||
other => other.to_string(),
|
||||
};
|
||||
pairs.push((k, s));
|
||||
}
|
||||
req = req.query(&pairs);
|
||||
}
|
||||
|
||||
if let Some(b) = body { req = req.json(&b); }
|
||||
|
||||
let resp = req.send().await?;
|
||||
let status = resp.status().as_u16();
|
||||
let headers_out: Map<String, Value> = resp
|
||||
.headers()
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), Value::String(v.to_str().unwrap_or("").to_string())))
|
||||
.collect();
|
||||
|
||||
// 尝试以 JSON 解析,否则退回文本
|
||||
let text = resp.text().await?;
|
||||
let parsed_body: Value = serde_json::from_str(&text).unwrap_or_else(|_| Value::String(text));
|
||||
// 4) 调用中间件发送请求
|
||||
let out = execute_http(req, client_opts).await?;
|
||||
let status = out.status;
|
||||
let headers_out = out.headers;
|
||||
let parsed_body = out.body;
|
||||
|
||||
// 5) 将结果写回 ctx
|
||||
let result = json!({
|
||||
@ -138,8 +102,15 @@ fn parse_http_config(cfg: Value) -> anyhow::Result<(
|
||||
let query = m.remove("query").and_then(|v| v.as_object().cloned());
|
||||
let body = m.remove("body");
|
||||
|
||||
// 可选 HTTPS/超时/HTTP 版本配置
|
||||
let timeout_ms = m.remove("timeout_ms").and_then(|v| v.as_u64());
|
||||
// 统一解析超时配置(内联)
|
||||
let timeout_ms = if let Some(ms) = m.remove("timeout_ms").and_then(|v| v.as_u64()) {
|
||||
Some(ms)
|
||||
} else if let Some(Value::Object(mut to)) = m.remove("timeout") {
|
||||
to.remove("timeout").and_then(|v| v.as_u64())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let insecure = m.remove("insecure").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let http1_only = m.remove("http1_only").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
let ca_pem = m.remove("ca_pem").and_then(|v| v.as_str().map(|s| s.to_string()));
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
pub mod http;
|
||||
pub mod db;
|
||||
// removed: pub mod expr;
|
||||
pub mod variable;
|
||||
pub mod script_rhai;
|
||||
pub mod script_js;
|
||||
|
||||
Reference in New Issue
Block a user