Google Cloud Function调用AWS S3 getObject请求超时无响应
在Google Cloud Function(以下简称GCF)中调用AWS SDK的S3接口时,无论使用SDK v2的s3.getObject方法还是SDK v3的.send方法,请求都会持续超时无返回。本地开发阶段使用Node/Express服务测试同一段逻辑运行完全正常,本地可正常运行的代码片段如下:
通用请求参数定义:
const params = { Bucket: process.env.AWS_BUCKET_NAME, Key: key, };
- SDK v3版本调用实现:
const createParams = new GetObjectCommand(params); const response = await client.send(createParams);
- SDK v2版本调用实现:
const s3Asset = await s3.getObject(params).promise();
两种实现本地都可以正常拉取目标文件:单文件场景直接返回Buffer格式,多文件场景打包为Zip后返回。
代码部署到GCF后,S3请求始终无响应最终触发函数超时。已完成的排查验证项:
- 函数内发起请求访问google.com可正常返回200状态码,确认公网访问能力正常
- 已配置VPC连接器允许所有流量出站
- 相关环境变量(AWS密钥、桶名、区域等)均已正确配置
部署使用的GCF完整代码如下:
const s3 = new S3Client({ credentials: { secretAccessKey: process.env.AWS_ACCESS_KEY, accessKeyId: process.env.AWS_ACCESS_ID, }, region: process.env.AWS_REGION, correctClockSkew: true, }); exports.downloadAssets = async (req, res) => { res.set('Access-Control-Allow-Origin', '*'); if (req.method === 'OPTIONS') { console.log('Hit OPTIONS'); res.set('Access-Control-Allow-Methods', 'POST'); res.set('Access-Control-Allow-Headers', 'Content-Type'); res.set('Access-Control-Max-Age', '3600'); res.status(204).send(''); } else { console.log('***** Assets to download\n', req.body?.assets, '*****'); try { if (req.body?.assets) { var params = { Bucket: process.env.AWS_BUCKET_NAME, Key: key, }; const s3Asset = await s3.getObject(params).promise(); // 执行到此处卡住 console.log('*** I have asset *** \n',s3Asset) // 该日志永远不会打印 res.send({ buffer: s3Asset.Body, name: 'nameOfFile', mimeType: s3Asset.ContentType }); } else { return res.status(400).json({ error: 'No Assets Sent' }); } } catch (e) { console.log('Error downloading assets: \n', e); res.status(400).json({ error: e.message }); } } };
按以下优先级逐一排查修复:
1. 修正AWS SDK版本混用问题
你部署代码中初始化的是SDK v3版本的S3Client,该类不存在实例方法getObject()——v3版本的所有S3操作都需要通过send()方法传入对应Command执行。本地能跑通通常是因为本地开发环境同时安装了v2版本的aws-sdk依赖,初始化的是v2版S3实例;部署到GCF时依赖安装逻辑和本地不一致,要么是v3实例调用不存在的方法导致Promise一直处于pending状态,要么是依赖版本冲突导致请求逻辑异常。
如果使用v3版本SDK,将S3调用逻辑改为v3标准写法:
// 顶部补充对应依赖导入 const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3"); // 调用部分替换原有v2风格代码 const command = new GetObjectCommand(params); const s3Response = await s3.send(command); // 注意v3返回的Body是ReadableStream类型,需要手动转换为Buffer const fileBuffer = Buffer.from(await s3Response.Body.transformToByteArray());
如果使用v2版本SDK,将初始化逻辑替换为v2标准写法,不要混用v3的S3Client:
const AWS = require('aws-sdk'); const s3 = new AWS.S3({ accessKeyId: process.env.AWS_ACCESS_ID, secretAccessKey: process.env.AWS_ACCESS_KEY, region: process.env.AWS_REGION, correctClockSkew: true, }); // 原有s3.getObject(params).promise()的写法可以正常保留
2. 强制SDK使用IPv4协议栈
这是GCF调用AWS服务的高频已知问题:GCF的Node.js运行时默认优先解析域名的AAAA(IPv6)记录,但GCF默认网络、VPC连接器默认都不支持IPv6出站。你能正常访问google.com是因为该域名可自动回退到IPv4连接,而部分区域的S3端点会卡在IPv6连接握手阶段、不会自动回退,最终导致请求超时。
在SDK初始化时强制指定使用IPv4栈即可修复:
- v3版本配置方式:
const http = require('http'); const https = require('https'); const { NodeHttpHandler } = require("@aws-sdk/node-http-handler"); const s3 = new S3Client({ // 原有credentials、region等配置保留 requestHandler: new NodeHttpHandler({ httpAgent: new http.Agent({ family: 4 }), httpsAgent: new https.Agent({ family: 4 }), connectionTimeout: 10000, socketTimeout: 10000 }) });
- v2版本配置方式:
const https = require('https'); const s3 = new AWS.S3({ // 原有配置保留 httpOptions: { agent: new https.Agent({ family: 4 }), timeout: 10000 }, maxRetries: 3 });
3. 补全代码中未定义的key变量
当前部署代码里params的Key字段直接使用了未定义的key变量,正常逻辑应该从req.body.assets中提取待下载的文件Key,未定义变量虽然会直接抛出ReferenceError,但如果上层有吞错逻辑也可能表现为请求卡住,需要补全这部分业务逻辑。
内容的提问来源于stack exchange,提问作者Alex S

