DeepSeek 不需要 Native 代理:浏览器/Tauri Webview 可直接 fetch
在一个 Tauri 2 桌面应用里集成 DeepSeek 大模型对话功能,习惯性地打开了 Rust 侧的 reqwest,准备写一个 #[tauri::command] 做 HTTP 代理——毕竟 OpenAI 的 API 众所周知会拦截浏览器直连请求,搭一层 Native 代理几乎是标准动作。写了一半忽然想到:这个假设验证过吗?
现象
在 Vite dev 模式下(http://localhost:5173)直接用 fetch 调 DeepSeek,没有任何 CORS 报错,返回正常的 401(API Key 未设置)。同样在 Tauri webview(origin 为 tauri://localhost)下测试,请求也顺利到达服务端。
根因
DeepSeek 的 OpenAI 兼容端点 https://api.deepseek.com/v1/chat/completions 实现了宽松的 CORS 策略:它会把请求里的 Origin 原样反射到响应头 access-control-allow-origin,同时允许 POST、authorization、content-type。无论 Origin 是 tauri://localhost、http://localhost:5173 还是任何 webview 来源,服务端都会放行。
用 curl 可以一秒验证:
curl -s -i -X OPTIONS https://api.deepseek.com/v1/chat/completions \
-H "Origin: tauri://localhost" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: authorization,content-type"
# HTTP/2 200
# access-control-allow-origin: tauri://localhost ← 原样反射
# access-control-allow-methods: POST
# access-control-allow-headers: authorization,content-type这与 OpenAI 截然不同——OpenAI 不返回 access-control-allow-origin,浏览器直连必然报 CORS 错误。
OpenAI (api.openai.com) 不向浏览器放行 CORS,直连必报 "TypeError: Failed to fetch",所以桌面/SPA 项目里确实需要 Native 代理。但 DeepSeek 不一样——它的端点允许浏览器直连。如果不验证就直接照抄 OpenAI 的集成方案,会多写一层完全不必要的 Rust/主进程代理,白增加维护负担。
判断标准只有一条:curl -X OPTIONS 看 access-control-allow-origin 是否存在。有就直连,没有才代理。
正解
直接在 Webview 前端用原生 fetch 调用,不需要任何 Native 桥接:
const res = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: 'deepseek-chat',
messages,
stream: false,
}),
})
const data = await res.json()有两个前提需要检查:
- CSP:Tauri 的
tauri.conf.json里如果设置了app.security.csp,需要加connect-src https://api.deepseek.com;若csp: null则无需操作。 - API Key 安全:桌面应用把用户自己填写的 Key 存在本地并从 Webview 发出,没有安全问题(Key 是用户自己的,在用户机器上)。多用户 Web 产品需要服务端代理不是为了 CORS,而是为了不把你的 Key 暴露给客户端。
接任何新 LLM API 前,先跑一条 curl -X OPTIONS 探测 CORS,30 秒得出结论,不要凭经验类比。DeepSeek 可直连,阿里云百炼(*.aliyuncs.com)不行,需走 tauri-plugin-http——同是国内 LLM 服务,策略可以完全不同。
一个额外细节
验证直连是否真的通了,最可靠的信号是:用一个错误的 API Key 发起真实 POST 请求,若返回 HTTP 401 + JSON body,说明请求已穿越网络并通过了 CORS 检查(CORS 失败的特征是 "TypeError: Failed to fetch",没有 HTTP 状态码)。
一句话外卖
每个 LLM 服务商的 CORS 策略独立设定,30 秒
curl OPTIONS验证优于任何经验类比——先探针,再决定是否需要 Native 代理。