第一部分:问题背景
1.1 错误现象复现
javascript
// 浏览器控制台报错示例
Access to fetch at 'https://chat.qwenlm.ai/api/v1/files/' from origin 'https://ocr.doublefenzhuan.me'
has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
1.2 CORS 机制的核心原理
- 同源策略 (Same-Origin Policy):浏览器的安全沙箱规则。
- 预检请求 (Preflight Request) :
OPTIONS
方法的作用与触发条件。 - 服务端责任 :
Access-Control-Allow-Origin
等响应头的重要性。
1.3 逆向工程场景的特殊性
- 无服务器控制权:无法修改目标服务器的 CORS 配置。
- 突发性策略变更:目标服务器可能动态调整安全规则(如案例中的突然限制)。
第二部分:代理方案的技术实现
2.1 整体架构图
[浏览器] --> [同域代理接口 /proxy/upload] --> [目标服务器 chat.qwenlm.ai]
2.2 代码实现详解(以 Cloudflare Worker 为例)
javascript
// Worker 路由配置
addEventListener('fetch', event => {
const url = new URL(event.request.url);
switch(url.pathname) {
case '/proxy/upload':
if (event.request.method === 'POST') {
return event.respondWith(handleProxyUpload(event.request));
}
break;
// ...其他路由...
}
});
// 代理请求处理函数
async function handleProxyUpload(request) {
try {
// 1. 提取客户端请求内容
const formData = await request.formData();
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
// 2. 转发到目标服务器
const targetResponse = await fetch('https://chat.qwenlm.ai/api/v1/files/', {
method: 'POST',
headers: {
'accept': 'application/json',
'authorization': `Bearer ${token}`,
},
body: formData,
});
// 3. 返回处理后的响应
const data = await targetResponse.json();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
} catch (error) {
// ...错误处理...
}
}
2.3 关键代码解析
代码片段 | 作用说明 |
---|---|
case '/proxy/upload' |
定义代理路由入口,匹配特定路径的请求 |
await request.formData() |
提取客户端提交的表单数据(含文件上传) |
fetch('https://chat.qwenlm.ai') |
服务器端发起跨域请求,不受浏览器策略限制 |
Access-Control-Allow-Origin: * |
确保浏览器接受代理返回的响应 |
第三部分:技术原理深度解析
3.1 为什么代理方案有效?
- 同源策略的规避 :浏览器只感知到与页面同域的
/proxy/upload
请求。 - 服务端特权:服务器间通信不受 CORS 限制(Node.js/Python/Worker 等)。
- 请求头传递 :代理可透传/修改敏感头信息(如
Authorization
)。
3.2 与传统方案的对比
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
修改服务器 CORS 配置 | 原生支持、性能最佳 | 需要目标服务器控制权 | 自有 API 服务 |
代理方案 | 无需目标服务器配合 | 增加中间层、略影响性能 | 第三方 API/逆向工程 |
JSONP | 兼容老旧浏览器 | 仅支持 GET、存在安全风险 | 历史遗留系统 |
no-cors 模式 | 快速实现 | 无法读取完整响应 | 简单请求且无需响应内容 |
问题
https://ocr.doublefenzhuan.me/proxy/upload
为什么加了proxy/upload就变成了后端服务器呢?
- 关于「后端服务器」的定义
前端和后端的本质区别在于代码执行的位置:
- 前端:运行在用户浏览器中的代码(HTML/CSS/JavaScript)。
- 后端:运行在服务器上的代码(如 Node.js、Python、Java 等)。
URL 路径(如 /proxy/upload
)本身不决定前后端,而是看该路径的请求由谁处理:
- 如果请求由浏览器直接处理(如静态文件请求),属于前端。
- 如果请求由服务器端代码处理(如动态接口),属于后端。
- 为什么 /proxy/upload 是后端服务器?
js
case '/proxy/upload':
if (request.method === 'POST') {
return handleProxyUpload(request); // 由服务器端代码处理
}
break;
路径 /proxy/upload
只是一个路由标识:
当客户端访问 https://ocr.doublefenzhuan.me/proxy/upload
时,请求会被 Worker 脚本捕获。
Worker 脚本根据路由规则(case '/proxy/upload')调用 handleProxyUpload
函数。
handleProxyUpload
是服务器端代码(运行在 Cloudflare Worker
的服务器环境中),因此这个路径对应的逻辑属于后端。
- 为什么说「服务器之间没有跨域问题」?
- CORS 是浏览器强制的安全策略,只影响浏览器发起的请求。
- 服务器之间的 HTTP 请求(如 Worker → chat.qwenlm.ai)不受 CORS 限制,因为:
- 服务器没有「同源策略」的概念。
- 服务器可以自由向任何域名发起请求(除非目标服务器主动封禁)。
4.前端直接发起跨域请求的代码
创建 FormData
对象:前端通过 FormData
对象将文件数据包装成表单格式。 发起跨域请求:使用 fetch 直接向目标服务器 (https://chat.qwenlm.ai/api/v1/files/
) 发起 POST
请求。 添加Authorization
头:将用户的认证令牌添加到请求头中。 处理响应:如果请求成功,解析并返回响应数据;如果失败,抛出错误。因为目标服务器不支持 CORS,前端需要通过代理服务器(或 Worker)绕过同源策略限制。
js
async function uploadFile(file, token) {
try {
// 创建 FormData 对象
const formData = new FormData();
formData.append('file', file); // 添加文件
// 发起 POST 请求
const response = await fetch('https://chat.qwenlm.ai/api/v1/files/', {
method: 'POST',
headers: {
'accept': 'application/json',
'authorization': `Bearer ${token}`, // 添加 Authorization 头
},
body: formData, // 发送 FormData
});
// 处理响应
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Upload failed:', error);
throw error;
}
}
附录 :