Sails 文件上传到 Amazon S3:使用 Skipper-S3 适配器实现流式上传
【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails
导读
本文讲解如何在 Sails 应用中,借助 Skipper 生态的skipper-s3文件上传适配器,以流式方式将用户上传的文件直接传输到 Amazon S3 对象存储,无需先将文件落盘到本地文件系统。读完本文你将掌握:S3 桶的区域硬性要求、req.file(...).upload({...})的完整配置写法、上传回调与响应组织方式,以及 Sails 内置 Skipper 底层流式处理机制与安全注意事项。
背景:Sails 的文件上传机制
Sails 内置了名为 Skipper 的"body parser",它让流式文件上传变得非常简单——上传目标不局限于服务器本地磁盘,还可以是 Amazon S3、MongoDB GridFS 或其他受支持的存储适配器(参见 docs/concepts/File Uploads/File Uploads.md 中的整体介绍)。
这一机制在仓库源码中有直接体现:在 lib/hooks/http/get-configured-http-middleware-fns.js 中,默认的bodyParser中间件在未显式配置时会require('skipper');而 package.json 中skipper: "^0.9.5"也确认了它是 Sails 运行时默认依赖。也就是说,一个常规的 Sails 应用开箱即可在 action 中调用req.file()接收 multipart 上传。
需要注意:req.file()只在 HTTP 请求上下文中可用。在 lib/router/index.js 中,Sails 为虚拟请求(例如 socket.io 请求)设置了req.file的 mock 实现,直接返回 500 并提示 "Streaming file uploads viareq.file()are only available over HTTP with Skipper."——因此 S3 上传同样仅适用于 HTTP 上传场景。
前置条件:S3 桶必须位于 US East (N. Virginia) 区域
skipper-s3适配器存在一个容易踩坑的硬性约束:
你的 Amazon S3 bucket 必须创建在US East (N. Virginia)区域(即区域代码
us-east-1)。如果桶创建在其他区域,上传将无法工作,AWS 会返回InvalidRequest错误。
这是文档开篇就强调的关键前提(见 docs/concepts/File Uploads/uploading-to-amazon-s3.md)。在创建桶时务必选择该区域,否则后续所有上传请求都会失败,且错误信息来自 AWS 服务端,排查成本较高。
此外你还需要准备好 AWS 凭据:一组拥有该桶写权限的Access Key与Secret Key(即下文代码中的key与secret)。
安装 skipper-s3 适配器
在 Sails 项目根目录执行:
npm install skipper-s3 --save该命令会把skipper-s3加入项目package.json的dependencies。它是 Skipper 生态中的一个"blob receiver"(存储接收器),负责把 Skipper 解析出的上传流写入 S3。
在控制器中使用适配器上传
安装完成后,在任意 controller action 中通过req.file('<字段名>')拿到 Skipper Upstream,再调用.upload()并传入 S3 配置。以下示例直接来自官方文档(见 docs/concepts/File Uploads/uploading-to-amazon-s3.md):
uploadFile: function (req, res) { req.file('avatar').upload({ adapter: require('skipper-s3'), key: 'S3 Key', secret: 'S3 Secret', bucket: 'Bucket Name' }, function (err, filesUploaded) { if (err) return res.serverError(err); return res.ok({ files: filesUploaded, textParams: req.allParams() }); }); }配置项说明:
| 选项 | 类型 | 说明 |
|---|---|---|
adapter | ((function)) | 指定上传接收适配器,这里为require('skipper-s3')返回的适配器构造器 |
key | ((string)) | AWS Access Key ID,用于鉴权 |
secret | ((string)) | AWS Secret Access Key,用于鉴权 |
bucket | ((string)) | 目标 S3 桶名称(必须位于us-east-1区域) |
.upload()的第二个参数是完成回调(err, filesUploaded):
- 上传过程中出错时,
err非空,此时应返回res.serverError(err); - 成功时,
filesUploaded是本次上传产生的文件描述数组,可返回给前端使用; - 示例中同时通过
req.allParams()取回请求中的文本参数一并返回。req.allParams()由 lib/hooks/request/params.all.js 注入,它合并了 query string、request body 与路由参数。
关于安全硬编码凭据的提醒
示例中的key/secret是占位写法。生产环境中切勿把 AWS 凭据硬编码进控制器代码,更不要提交到版本库。建议将凭据放入 Sails 的环境变量或config/local.js(该文件通常已被.gitignore忽略),再在 action 中通过process.env或sails.config读取。从源码结构看,Sails 的配置体系(如config/local.js与环境变量)天然支持这种注入方式。
底层原理:Skipper 如何流式处理上传
理解req.file()的行为有助于排查 S3 上传问题。根据 docs/reference/req/req.file.md 的说明:
req.file(field)返回一个 SkipperUpstream,表示来自指定字段(如avatar)的 multipart 文件上传。Upstream 本质上是一个 Node.js Readable 流(object mode),其中的每个对象都是一次传入的 multipart 文件上传流。- Skipper 把所有文件上传都当作流处理。当 multipart 请求到达服务器时,Skipper 并不会把临时文件写入磁盘,而是只缓冲请求足够长的时间来运行你的应用代码,让你"插入"一个兼容的 blob receiver(在这里就是
skipper-s3)。 - 如果某个字段的数据没有"插入"到任何 receiver,Upstream 会达到其 high water mark,缓冲区被刷新,之后到达的字节将被忽略。
- 底层实现上,
.upload()所做的工作就是把 Upstream 管道(pipe)到指定的 receiver 实例,并在 Upstream 触发finish或error事件时执行回调。
这套流式设计带来的好处是:用户可以上传体积很大的文件,而对内存与磁盘占用影响很小,同时还能抵御利用 TMP 文件发起的拒绝服务攻击。
文本参数必须在文件参数之前
由于 Skipper 是流式处理,multipart 请求体中所有文本参数必须位于任何文件参数之前。这样 Sails 可以在文件仍在上传时就开始执行你的 action 代码,而不必等待上传全部完成(这是为了避免 Express 系应用中常见的 body parser DDoS 漏洞)。如果你通过req.allParams()或req.body读取表单文本字段时发现取不到值,请先检查客户端提交顺序:文本字段在前,<input type="file">在后。
从本地磁盘上传迁移到 S3
作为对照,Sails 默认(不指定adapter)时使用磁盘接收器(skipper-disk),文件会写入myApp/.tmp/uploads/目录,可通过dirname选项自定义(参见 docs/concepts/File Uploads/File Uploads.md)。例如:
req.file('avatar').upload({ dirname: require('path').resolve(sails.config.appPath, 'assets/images') }, function (err, uploadedFiles) { if (err) return res.serverError(err); return res.json({ message: uploadedFiles.length + ' file(s) uploaded successfully!' }); });迁移到 S3 时,你只需在.upload()的选项对象中把adapter换成require('skipper-s3')并提供key/secret/bucket,即可把落盘目标整体替换为对象存储。两者共用同一套回调签名(err, filesUploaded),因此返回给前端的文件描述结构保持一致;同时由于文件直接进入 S3,也规避了将不可信文件存放在 Web 根目录下的安全隐患。
安全注意事项
- 不要信任请求中的 MIME 类型:文件上传请求头里的 MIME 类型不可靠,永远不要依赖它来判断文件真实内容。
- 不自动病毒扫描:Sails/Skipper 不会对上传做病毒扫描或异常检测。若你的应用允许用户互相上传、共享文件,保护用户免受恶意文件侵害是你自己的责任。
- 永远假设请求是恶意的:进入服务器的任何请求都可能被伪造或伪装,涉及文件存储的路径、命名与访问控制都要按不可信输入处理。
完整实战流程小结
- 在 AWS 控制台创建 S3 桶,区域选择 US East (N. Virginia)(us-east-1),否则上传会报
InvalidRequest; - 准备拥有该桶写权限的 AWS Access Key / Secret Key;
- 在项目根目录执行
npm install skipper-s3 --save; - 在 controller action 中调用
req.file('avatar').upload({ adapter: require('skipper-s3'), key, secret, bucket }, callback); - 回调中处理
err(返回res.serverError)与filesUploaded(返回给前端),并可通过req.allParams()一并返回文本参数; - 生产环境将凭据收敛到环境变量或
config/local.js,避免硬编码。
【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址: https://gitcode.com/gh_mirrors/sa/sails
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考