使用AWS Node.js SDK的s3.upload在Vercel环境间歇性出现NetworkError
问题分析与解决方案
问题核心
在Vercel API路由中使用AWS Node.js SDK的s3.upload接口上传图片时,间歇性触发NetworkError,错误本质为ERR_HTTP_HEADERS_SENT(向客户端发送响应后无法再设置请求头)。相同代码在CLI或其他Lambda环境中运行正常,CORS已排查排除影响,怀疑与Vercel环境特性或AWS SDK版本有关。
错误详情
{ "data": { "errorMessage": "NetworkingError [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client", "errorType": "Runtime.UnhandledPromiseRejection", "reason": { "code": "NetworkingError", "errorMessage": "Cannot set headers after they are sent to the client", "errorType": "NetworkingError", "hostname": "bucket.s3.amazonaws.com", "message": "Cannot set headers after they are sent to the client", "region": "us-east-1", "retryable": true, "stack": [ "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client", " at new NodeError (internal/errors.js:322:7)", " at ClientRequest.setHeader (_http_outgoing.js:561:11)", " at Object.mod.request (/var/task/___vc/__launcher.js:90:17)", " at features.constructor.handleRequest (/var/task/node_modules/aws-sdk/lib/http/node.js:45:23)", " at executeSend (/var/task/node_modules/aws-sdk/lib/event_listeners.js:452:29)", " at Request.SEND (/var/task/node_modules/aws-sdk/lib/event_listeners.js:466:9)", " at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:102:18)", " at Request.emit (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:78:10)", " at Request.emit (/var/task/node_modules/aws-sdk/lib/request.js:686:14)", " at Request.transition (/var/task/node_modules/aws-sdk/lib/request.js:22:10)" ], "time": "2023-04-27T11:03:16.773Z" }, "stack": [ "Runtime.UnhandledPromiseRejection: NetworkingError [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client", " at process.<anonymous> (/var/runtime/index.js:35:15)", " at process.emit (events.js:412:35)", " at process.emit (domain.js:475:12)", " at processPromiseRejections (internal/process/promises.js:245:33)", " at processTicksAndRejections (internal/process/task_queues.js:96:32)" ] } }
问题根源
从错误栈可见,问题出在Vercel的___vc/__launcher.js中尝试设置请求头时,发现响应已发送。主要原因包括:
- API路由提前终止响应:代码可能在
s3.upload完成前就调用了res.send()/res.json(),导致Vercel结束请求上下文,而AWS SDK后续的重试或回调操作仍试图修改请求头。 - AWS SDK v2回调/重试机制与Vercel环境不兼容:V2的回调模式可能在Vercel函数运行时中出现Promise未捕获,或重试时上下文已被销毁。
- Vercel函数超时:若上传耗时接近Vercel默认超时(10秒),函数被提前终止后,SDK后续操作会触发错误。
解决方案
- 确保响应仅在上传完成后发送:改用
async/await替代回调,等待s3.upload的Promise完成后再返回响应:import AWS from 'aws-sdk'; const s3 = new AWS.S3(); export default async function handler(req, res) { try { const uploadResult = await s3.upload({ Bucket: 'your-bucket', Key: 'file-key', Body: req.body }).promise(); res.status(200).json({ success: true, data: uploadResult }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } } - 升级到AWS SDK v3:V3采用模块化设计,兼容性更好,可避免V2中部分回调与Serverless环境的冲突:
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'; const s3Client = new S3Client({ region: 'us-east-1' }); export default async function handler(req, res) { try { const command = new PutObjectCommand({ Bucket: 'your-bucket', Key: 'file-key', Body: req.body }); const uploadResult = await s3Client.send(command); res.status(200).json({ success: true, data: uploadResult }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } } - 调整AWS SDK重试配置:若重试是触发问题的原因,可禁用重试或调整重试次数,避免响应发送后仍有后续操作:
const s3 = new AWS.S3({ maxRetries: 0, retryDelayOptions: { base: 0 } }); - 延长Vercel函数超时:在
vercel.json中设置更长的超时时间,确保上传操作能完成:{ "functions": { "api/**/*.js": { "timeout": 30 } } }
内容的提问来源于stack exchange,提问作者Dylan Pierce
相关产品推荐
相关产品推荐

