返回博客

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,同时允许 POSTauthorizationcontent-type。无论 Origin 是 tauri://localhosthttp://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 错误。

坑:把 DeepSeek 和 OpenAI 的 CORS 策略等同

OpenAI (api.openai.com) 不向浏览器放行 CORS,直连必报 "TypeError: Failed to fetch",所以桌面/SPA 项目里确实需要 Native 代理。但 DeepSeek 不一样——它的端点允许浏览器直连。如果不验证就直接照抄 OpenAI 的集成方案,会多写一层完全不必要的 Rust/主进程代理,白增加维护负担。

判断标准只有一条:curl -X OPTIONSaccess-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()

有两个前提需要检查:

  1. CSP:Tauri 的 tauri.conf.json 里如果设置了 app.security.csp,需要加 connect-src https://api.deepseek.com;若 csp: null 则无需操作。
  2. API Key 安全:桌面应用把用户自己填写的 Key 存在本地并从 Webview 发出,没有安全问题(Key 是用户自己的,在用户机器上)。多用户 Web 产品需要服务端代理不是为了 CORS,而是为了不把你的 Key 暴露给客户端。
正解:OPTIONS preflight 验证先于编码决策

接任何新 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 代理。