尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Sails `req.file()` 完整指南:基于 Skipper 的流式文件上传
后端【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址https://gitcode.com/gh_mirrors/sa/sails点击查看免费下载导读req.file()是 Sails 框架中处理 multipart 文件上传的核心 API它返回一个基于 Skipper 的Upstream上游流让控制器与策略policy能够以流式、高性能的方式接收客户端上传的二进制文件。读完本文你将掌握req.file(field)的参数约定、text 参数必须先于 file 参数的硬性约束、Upstream 的流式工作原理以及如何通过.upload()将文件落盘、上传到 S3 / GridFS 等自定义接收器并了解 Sails 底层对超时、缓冲与 DoS 防护的可配置策略。req.file()是什么req.file()构建并返回一个表示指定field上到来的 multipart 文件上传的 Skipper Upstreamreq.file(field);它来自 Skipper 中当未通过sails.config.http.bodyParser自定义解析器时Sails 会执行fn require(skipper)并将其挂载到 HTTP 中间件栈同时 package.json 中声明了skipper: ^0.9.5作为直接依赖。参数表参数类型说明1field((string))要监听上传的文件参数的名称例如avatar。所谓field与表单 POST 中name / email / password这类文本参数并列即input typefile nameavatar中的name值。你可以用它监听任意多个不同的文件字段例如req.file(avatar)、req.file(newSong)。使用场景req.file()在哪里可用req.file()用于处理真正的 HTTP 请求。在 Sails 中它最常见的调用位置是控制器 action 或策略policy内部例如// 在控制器 action 或策略中 req.file(avatar).upload(function (err, uploadedFiles){ if (err) return res.serverError(err); return res.json({ message: uploadedFiles.length file(s) uploaded successfully!, files: uploadedFiles }); });.upload()的完整用法与选项列表如maxBytes、dirname请参阅 Skipper 的 README 文档下文会给出常用选项的实战示例。值得注意的是虚拟请求virtual requests上没有req.file()。在 lib/router/index.js 中可以看到Sails 为基于 socket.io 的虚拟请求装配了一个替身实现req.file被替换为一个直接返回 500 错误的函数错误信息为Streaming file uploads viareq.file()are only available over HTTP with Skipper.。这说明流式文件上传仅通过 HTTP Skipper 可用套接字请求无法使用req.file()。关键约束text 参数必须先于 file 参数发送这种简化带来一个需要注意的小问题请求体中的文本参数必须位于文件之前。通常这些文本参数承载着关于文件上传的字符串元数据例如标题、备注、所属用户 ID 等。发送给 Sails 的 multipart 请求应先发送全部 text 参数再发送任意 file 参数。例如如果你正在构建与 Sails 通信的 Web 前端就应该在任何表单上传或 AJAX 文件上传请求中先包含文本参数。这里的text 参数指的是你随文件一起发送、用于提供上传附加信息的元数据参数。从源码层面看这一约束是由 Skipper 的流式处理模型决定的Skipper 允许 Sails 在文件仍在上传途中就运行你的 action 代码而不必等文件全部传完。要做到这一点解析器必须能先确定地拿到 body 中的文本参数若把文件放在最前Sails 将无法在不缓冲完整文件的前提下提取元数据。这也正是 docs/concepts/File Uploads/File Uploads.md 中反复强调文本参数必须在文件之前的根因。与文本参数同传的两种做法把文本参数放进 URLquery string——最简单的方式必须放 body 时使用官方 Web app 模板自带的 Cloud SDK基于 Parasails它能正确处理伴随文件上传的额外参数服务端 action 中仍需保证文本参数在文件之前。工作原理一切皆是流StreamSkipper 将所有文件上传视为流。这意味着用户可以上传超大体量的单体文件而只带来极小的性能开销、零磁盘占用同时还能保护你的应用免受针对 TMP 文件的恶意 DoS 攻击。当一个 multipart 请求到达服务器时Skipper不会把临时文件写入磁盘而是只把请求缓冲到足够运行你的应用代码的程度允许你插接plug in一个兼容的 blob 接收器receiver。如果你没有把某个字段的数据插接出去那么该 Upstream 就会触及其高水位标记high water mark缓冲区被清空之后该字段上到达的字节都会被忽略。换句话说Sails 的策略与控制器 action 可以先运行例如做数据库查询、权限校验在此期间 Skipper 通过 Node.js 流内置的缓冲 TCP 背压机制暂停 Upstream一旦你在 action 中调用req.file(avatar).upload(...)把 Upstream 接到接收器上数据流随即恢复并持续落盘/上传。Upstream 的本质流的流req.file()支持同一字段携带多个文件因此它返回的 Upstream 实际上是一个**缓冲的事件发射器流其中包含潜在的二进制流文件**。具体来说Upstream 是一个处于object mode的 Node.js Readable 流其中的每个对象本身又是一个到来的 multipart 文件上传流。因此如果你更愿意直接以流的流的方式操作 Upstream可以省略.upload()改为自行绑定finish与error事件或使用.pipe()var avatarUpstream req.file(avatar); avatarUpstream.on(finish, function () { // 所有文件流都已被消费完毕 return res.ok(); }); avatarUpstream.on(error, function (err) { return res.serverError(err); });从实现上看.upload()所做的无非是把Upstream pipe 进指定的 receiver 实例然后在 Upstream 发出finish或error事件时执行你传入的回调。实战示例从最小可跑到完整业务闭环最小示例接收并返回上传结果在控制器 action 或策略中req.file(avatar).upload(function (err, uploadedFiles){ if (err) return res.serverError(err); return res.json({ message: uploadedFiles.length file(s) uploaded successfully!, files: uploadedFiles }); });完整示例头像上传 下载下面的示例展示了如何允许用户上传头像图片并将其关联到账号。它假设访问控制已由策略完成登录用户 ID 存放在req.session.userId中// api/controllers/UserController.js module.exports { /** * 为当前登录用户上传头像 * * (POST /user/avatar) */ uploadAvatar: function (req, res) { req.file(avatar).upload({ // 不允许总上传体积超过 ~10MB maxBytes: 10000000 }, function whenDone(err, uploadedFiles) { if (err) { return res.serverError(err); } // 如果没有文件被上传返回错误 if (uploadedFiles.length 0){ return res.badRequest(No file was uploaded); } // 从自定义配置中获取部署应用的 base URL var baseUrl sails.config.custom.baseUrl; // 保存 fd 以及头像可被访问的 url User.update(req.session.userId, { // 生成唯一的头像下载地址 avatarUrl: require(util).format(%s/user/avatar/%s, baseUrl, req.session.userId), // 取第一个文件并使用它的 fdfile descriptor avatarFd: uploadedFiles[0].fd }) .exec(function (err){ if (err) return res.serverError(err); return res.ok(); }); }); }, /** * 下载指定 id 用户的头像 * * (GET /user/avatar/:id) */ avatar: function (req, res){ User.findOne(req.param(id)).exec(function (err, user){ if (err) return res.serverError(err); if (!user) return res.notFound(); // 该用户未上传头像 if (!user.avatarFd) { return res.notFound(); } var SkipperDisk require(skipper-disk); var fileAdapter SkipperDisk(/* optional opts */); // 将下载文件名设置为用户上传时的文件名 res.set(Content-disposition, attachment; filename file.name ); // 以流的方式把文件下发 fileAdapter.read(user.avatarFd) .on(error, function (err){ return res.serverError(err); }) .pipe(res); }); } };文件被存在哪里使用默认的receiver时上传文件会进入myApp/.tmp/uploads/目录。你可以通过dirname选项覆盖它。注意在调用.upload()时和调用 skipper-disk 适配器时都需要指定该选项以确保上传与下载指向同一位置。例如上传到assets/imagesreq.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! }); });快速上手用 CLI 生成 api 并编写 action先生成一个用于服务/存储文件的api$ sails generate api file debug: Generated a new controller file at api/controllers/FileController.js! debug: Generated a new model File at api/models/File.js! info: REST API generated http://localhost:1337/file info: and will be available the next time you run sails lift.然后编写一个提供上传表单的indexaction 和一个接收文件的uploadaction// myApp/api/controllers/FileController.js module.exports { index: function (req,res){ res.writeHead(200, {content-type: text/html}); res.end( form actionhttp://localhost:1337/file/upload enctypemultipart/form-data methodpost input typetext nametitlebr input typefile nameavatar multiplemultiplebr input typesubmit valueUpload /form ) }, upload: function (req, res) { req.file(avatar).upload(function (err, files) { if (err) return res.serverError(err); return res.json({ message: files.length file(s) uploaded successfully!, files: files }); }); } };注意表单中input typetext nametitle位于input typefile之前——这正是前面强调的text 参数先于 file 参数约束的直观体现。底层调优sails.config.http中与req.file()相关的 Skipper 选项Sails 允许你通过sails.config.http.middleware.bodyParser自定义或包装 Skipper 实例从而对req.file()的底层行为做性能调优与安全加固。典型写法如下// config/http.js module.exports { middleware: { bodyParser: (function () { var skipper require(skipper); var middlewareFn skipper({ strict: true, // ... 更多 Skipper 选项 ... }); return middlewareFn; })(), }, };可在上表中传入的选项均与req.file()背后的 Upstream 生命周期直接相关属性类型默认值说明maxWaitTimeBeforePassingControlToApp((number))500处理一个到来的 multipart 请求时在把控制权移交给应用的策略与控制器之前最多等待的毫秒数。若该毫秒数流逝后仍无任何文件上传、且请求尚未发送完文本参数等其他数据即表单发出 close则会不再等待而直接移交控制权。在特定负载均衡器 / 代理 / SSL 组合后运行的应用可能需要增大该值。maxTimeToWaitForFirstFile((number))10000等待任意给定 upstream 上第一个文件上传到达的最长毫秒数超过则触发.upload()回调并抛出ETIMEOUT错误。maxTimeToBuffer((number))4500在开始接收文件上传后等待任意给定活动 upstream 被插接到 receiver 上的最长毫秒数。Skipper 会暂停 upstream以便应用策略与控制器代码先运行例如做数据库查询然后你再把传入的文件上传例如req.file(avatar).upload(...)插接到目标本地磁盘、S3、gridfs 等。超时后会触发EMAXBUFFER错误。这是针对用待处理文件上传洪水淹没服务器类 DoS 攻击的可配置防御层——最有效的防御是尽早在控制器 action 顶部把上传插接到 receiver。strict((boolean))true启用时HTTP 请求体仅在看起来像数组或字典普通 JS 对象时才按 JSON 解析禁用时 body parser 接受JSON.parse()能解析的任何内容。默认启用。extended((boolean))true是否理解 URL 编码请求体中括号记法的多个文本参数例如courseId[]ARY%20301courseId[]PSY%20420并将其解析为数组。默认启用。onBodyParserError((function))见说明可选函数在 Skipper 解析请求体出错例如畸形 JSON时被调用接收err、req、res、next四个参数。Sails 提供默认实现以 400 状态码和错误详情响应请求。需要注意通过这种方式传给 Skipper 的选项也会透传给底层的 Express body parser以便做更底层的性能调优完整低层选项参见 body-parser 文档。另外这个 HTTP 中间件栈只作用于真正的 HTTP 请求处理虚拟请求如 socket时会被忽略——这与上文req.file()仅在 HTTP Skipper 下可用的结论一致。扩展到其他接收器S3 与 GridFSreq.file()的价值在于其插接式plug-in设计接收器不限于本地磁盘。你可以在.upload()中传入任意兼容的 Skipper 接收器例如上传到 Amazon S3 或 MongoDB GridFS详见仓库文档 docs/concepts/File Uploads/uploading-to-amazon-s3.md 与 docs/concepts/File Uploads/uploading-to-mongo-gridfs.md// 以 S3 为例示意 req.file(avatar).upload({ adapter: require(skipper-s3), key: ..., secret: ..., bucket: ... }, function (err, uploadedFiles) { // ... });其原理与本地磁盘完全一致.upload()仍只是把 Upstream 管道接到指定的 receiver 实例上receiver 负责把每个文件流写入目标存储。安全注意事项Sails不会自动对上传做病毒扫描也不会检测上传文件是否损坏或异常。如果你允许用户互相上传、共享文件你有责任保护用户免受彼此侵害任何接收不受信任文件上传并将其存盘的 Node.js 应用都不应把文件上传到 Java web root 或任何传统 web 服务器可能递归进入并执行其中任意代码文件的目录。最佳实践是把文件上传到 S3 或磁盘上的安全目录文件上传请求头中的 MIME 类型永远不应被信赖虽然现代浏览器中把不受信任的 JS 作为img src…加载已不再是 XSS 漏洞但 MIME 类型仍可被伪造始终假设任何进入服务器的请求都可能是恶意的或自我伪装misrepresent的。小结与延伸阅读req.file(field)是 Sails 文件上传体系的入口它以流式处理换取性能与磁盘零占用以文本参数先于文件参数的约束换取文件仍在传输时即可执行应用代码的体验并通过可插拔 receiver 架构覆盖本地磁盘、S3、GridFS 等多种存储目标。如果你需要深入了解仓库内可继续阅读docs/concepts/File Uploads/File Uploads.md —— 文件上传概念的完整讲解docs/reference/sails.config/sails.config.http.md ——bodyParser与全部 Skipper 调优选项lib/hooks/http/get-configured-http-middleware-fns.js —— Skipper 作为默认 body parser 的装配源码lib/router/index.js —— 虚拟请求下req.file()的降级实现返回 500。赞分享后端【免费下载链接】sailsRealtime MVC Framework for Node.js项目地址https://gitcode.com/gh_mirrors/sa/sails点击查看免费下载相关推荐Sails 文件上传完全指南基于 Skipper 的流式上传、自定义目录与 S3/GridFS 适配器Sails 文件上传完全指南基于 Skipper 的流式上传、自定义目录与 S3/GridFS 适配器 导读 文件上传是 Web 应用的高频需求但在 Sai后端Sails 文件上传到 MongoDB GridFS 实战指南基于 skipper-gridfs 的流式存储方案Sails 文件上传到 MongoDB GridFS 实战指南基于 skipper gridfs 的流式存储方案 在 Sails 应用中文件上传默认由内置的后端Sails 文件上传到 Amazon S3使用 Skipper-S3 适配器实现流式上传Sails 文件上传到 Amazon S3使用 Skipper S3 适配器实现流式上传 导读 本文讲解如何在 Sails 应用中借助 Skipper 生态后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

裁判文书网数据采集实战:Python爬虫与反爬策略全解析

裁判文书网数据采集实战:Python爬虫与反爬策略全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/21 2:31:56
Vitess v20.0.3 补丁版本解读:关键缺陷修复、稳定性改进与升级建议

Vitess v20.0.3 补丁版本解读:关键缺陷修复、稳定性改进与升级建议

Vitess v20.0.3 补丁版本解读:关键缺陷修复、稳定性改进与升级建议 【免费下载链接】vitess Vitess is a database clustering system for horizontal scaling of MySQL. 项目地址: https://gitcode.com/gh_mirrors/vi/vitess 导读 本文基于 Vitess 20.0 系…

📅 2026/9/21 2:31:56
FoundationDB 中的 POSIX 共享库延迟加载利器:Implib.so 原理与实战指南

FoundationDB 中的 POSIX 共享库延迟加载利器:Implib.so 原理与实战指南

分布式数据库KV存储数据库后端 【免费下载链接】foundationdb FoundationDB - the open source, distributed, transactional key-value store 项目地址: https://gitcode.com/gh_mirrors/fo/foundationdb 点击查看 免费下载 导读 本文围绕 FoundationDB 仓库中 c…

📅 2026/9/21 2:31:56
MORE NEWS

更多资讯

📰

ArcGIS Pro像素编辑器实战:栅格影像修补与地貌伪装技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

成渝智能网联汽车大赛备赛指南:ROS、ADAS与C++/Python实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Mac mini M4上OpenClaw qmd记忆存储sqlite-vec兼容性修复指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Mac虚拟机安装配置与性能优化:Parallels Desktop正版实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Cartographer纯定位模式实战:从仿真到真机部署与故障排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

从零开始学Linux WiFi驱动:mac80211、PCIe与固件加载实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬