jlcodes99/cockpit-tools · error
网关 {} 响应解析失败: {} (url={})
Error message
网关 {} 响应解析失败: {} (url={}) What it means
post_gateway_json got a 2xx response but serde_json::from_str failed to parse the body as JSON, so it returns '网关 {op} 响应解析失败: {parse_err} (url={url})'. The gateway returned a non-JSON body (or invalid JSON) where a JSON payload was expected, e.g. an HTML error page or empty body.
Source
Thrown at src-tauri/src/modules/wakeup.rs:776
crate::modules::logger::log_error(&format!(
"[Wakeup] 网关 {} 返回错误: url={}, status={}, body_len={}, body={}",
op_name,
url,
status,
summarize_body_len(&text),
truncate_log_text(&text, 512)
));
return Err(format!(
"网关 {} 返回错误: status={}, body_len={}",
op_name,
status,
summarize_body_len(&text)
));
}
serde_json::from_str::<serde_json::Value>(&text).map_err(|e| {
let message = format!("网关 {} 响应解析失败: {} (url={})", op_name, e, url);
crate::modules::logger::log_error(&format!(
"[Wakeup] {},body_len={}",
message,
summarize_body_len(&text)
));
message
})
}
async fn resolve_requested_model_for_official_ls(
account_id: &str,
model: &str,
cancel_rx: Option<&watch::Receiver<bool>>,
) -> Result<serde_json::Value, String> {
let trimmed = model.trim();
if let Ok(num) = trimmed.parse::<i64>() {
return Ok(json!({ "model": num }));
}
View on GitHub (pinned to 1ed8b77992)
Solutions
- Log/inspect the truncated body (already truncated to 512 chars) to see what was actually returned
- Check for a proxy/VPN injecting HTML error pages into 200 responses
- Confirm the gateway endpoint version matches what this client expects
- Retry — transient truncation or gateway glitch may resolve
- Handle the empty-body case explicitly before parsing
Example fix
// before
serde_json::from_str::<serde_json::Value>(&text).map_err(|e| {
let message = format!("网关 {} 响应解析失败: {} (url={})", op_name, e, url);
...
})
// after
if text.trim().is_empty() {
return Err(format!("网关 {} 响应为空 (url={})", op_name, url));
}
serde_json::from_str::<serde_json::Value>(&text).map_err(|e| {
let message = format!("网关 {} 响应解析失败: {} (url={})", op_name, e, url);
...
}) Defensive patterns
Strategy: validation
Validate before calling
// Guard the response before parsing
fn is_parseable_json(text: &str) -> bool {
let t = text.trim();
!t.is_empty() && (t.starts_with('{') || t.starts_with('[')) && serde_json::from_str::<serde_json::Value>(t).is_ok()
} Type guard
fn as_json(text: &str) -> Option<serde_json::Value> {
serde_json::from_str::<serde_json::Value>(text).ok()
} Try / catch
match post_gateway_json(...).await {
Err(e) if e.contains("响应解析失败") => {
// inspect logged body snippet; likely proxy HTML or empty body — retry via alternate base
try_next_base_url().await;
}
other => handle(other),
} Prevention
- Check Content-Type is application/json before parsing
- Explicitly handle empty bodies instead of attempting JSON parse
- Bypass/inspect corporate proxies that inject HTML into responses
- Fuzz/contract-test against real gateway responses on version upgrades
When it happens
Trigger: trigger_wakeup_via_client_gateway_once → post_gateway_json receives text that isn't valid JSON — a proxy/HTML error page served with 200, an empty body, a truncated response, or a gateway returning plain text.
Common situations: Captive portal/corporate proxy injecting HTML; gateway returning empty 200 on internal error; compression/charset mismatch corrupting the body; gateway API version returning a different content type; response cut off by timeout mid-body.
Related errors
- 网关 {} 返回错误: status={}, body_len={}
- invalidJsonMessage
- messages.invalidJson
- messages.empty
- errorCode (parseJsonOrThrow caller-supplied)
AI-assisted analysis of jlcodes99/cockpit-tools@1ed8b77992 (2026-09-05).
Data as JSON: /api/errors/def341f15d071b51.
Report an issue: GitHub.