为何Safari向Node.js API POST上传react-video-recorder录制视频时偶发CORS错误
我们团队正在开发一款网站,支持用户上传已有视频或在浏览器内录制新视频文件。本地文件上传通过标准HTML表单实现,录制功能基于react-video-recorder开发。两种场景下文件准备完成后,均会通过ky发起POST请求提交表单,代码如下:
const response = await ky.post(`https://xxx.xxx.xxx.xxx:xxxx/upload`, { timeout: false, body: formData, headers: { 'Authorization': `Bearer ${sessionStorage.getItem('token')}` } }).json();
两种添加文件的方式调用同一个提交函数,因此无论用户通过哪种方式获取视频,POST请求的请求头完全一致。
但实际测试发现,仅当上传react-video-recorder录制的视频时,Safari会抛出CORS错误,而同页面在Mac端Chrome上执行录制上传操作则不会出现该问题。
已核对服务端配置,明确设置了允许对应站点的跨域头,为避免拼写错误,甚至试过直接将请求的origin头值赋值给Access-Control-Allow-Origin,服务端核心代码如下:
app.post('/upload', passport.authenticate('jwt', {session: false}), function(req,res) { res.setHeader('Access-Control-Allow-Origin','https://my-website.com') res.setHeader('Access-Control-Allow-Methods','POST, GET, OPTIONS') // 业务逻辑 ... })
目前怀疑这根本不是CORS问题,可能是其他环节出错,但Safari给出的错误提示对排查没有帮助。需要排查思路或解决方案。
补充说明:已导出两次请求的HTTP报文,发现二者存在差异,但不清楚出现差异的原因:
成功请求的报文如下:
POST /upload HTTP/1.1 Accept: application/json Origin: https://my-website.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryGRcSY52tKpQVBLIb Authorization: Bearer _redacted Referer: https://my-website.com/ Content-Length: 51199 Host: my-api.com:3004 Accept-Language: en-ca User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15 Accept-Encoding: gzip, deflate, br Connection: keep-alive
失败请求的报文如下:
POST /upload Authorization: Bearer _redacted Content-Type: multipart/form-data; boundary=----WebKitFormBoundary0Uzp1dEaDyHuIhEw Accept: application/json Origin: https://my-website.com Referer: https://my-website.com/Upload User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15
疑问:这是否说明录制的视频文件没有成功挂载到POST请求中?
问题解答
首先可以明确:失败请求报文缺失了Host、Content-Length、HTTP/1.1版本声明等核心字段,并非录制文件未挂载到请求,而是Safari在处理特殊格式的Blob/File类型表单字段时触发了底层请求构造异常,看起来的CORS错误本质是请求未正确发送到服务端,服务端自然不会返回合法跨域头,最终Safari抛出跨域错误。
排查和解决方案可按以下顺序执行:
- 调整录制输出格式:
react-video-recorder默认输出webm格式视频,Safari对webm格式的录制和读取兼容性极差,生成的Blob头部存在异常会导致表单构造失败。可手动指定录制输出为mp4格式,验证问题是否消失。 - 修复服务端跨域配置:你当前的跨域头仅在POST路由的业务逻辑中返回,而跨域POST请求前浏览器会先发送OPTIONS预检请求,你的服务端未处理预检请求的跨域头返回,Safari对预检请求的校验严格度远高于Chrome,这是触发CORS报错的核心诱因之一。需要把跨域头设置提到所有路由之前,单独处理OPTIONS请求直接返回204状态码,示例代码如下:
// 所有请求前先设置跨域头 app.use(function(req, res, next) { res.setHeader('Access-Control-Allow-Origin','https://my-website.com') res.setHeader('Access-Control-Allow-Methods','POST, GET, OPTIONS') res.setHeader('Access-Control-Allow-Headers','Content-Type, Authorization') if (req.method === 'OPTIONS') { return res.sendStatus(204) } next() })
- 优化ky请求配置:不要手动指定
Content-Type头,让浏览器自动处理multipart/form-data的boundary生成;同时添加retry: 0配置,避免Safari的失败重试触发重复跨域校验。 - 验证表单合法性:发起请求前打印formData的所有字段,确认录制生成的File对象已正确append到formData中,同时检查File对象的
size、name属性是否正常,Safari下未正常结束录制生成的文件size会为0,也会导致请求构造失败。
内容的提问来源于stack exchange,提问作者Chris

