From 8e375d0ce92c837bf3da9e0784a0991c37c7aaa5 Mon Sep 17 00:00:00 2001 From: MaleDong Date: Sun, 30 Sep 2018 19:04:21 +0800 Subject: [PATCH] docs (Controller.md): Add new feat description Due to https://github.com/eggjs/egg-multipart/pull/19, we have to add this new feature into Egg's main doc in controller.md to notify clients. --- docs/source/en/basics/controller.md | 156 +++++++++++++++++++---- docs/source/zh-cn/basics/controller.md | 116 +++++++++++++++-- test/lib/core/loader/load_plugin.test.js | 2 +- 3 files changed, 238 insertions(+), 36 deletions(-) diff --git a/docs/source/en/basics/controller.md b/docs/source/en/basics/controller.md index d35a1dfb70..08f3e39e07 100644 --- a/docs/source/en/basics/controller.md +++ b/docs/source/en/basics/controller.md @@ -310,19 +310,115 @@ If user request exceeds the maximum length for parsing that we configured, the f **A common mistake is to confuse `ctx.request.body` and `ctx.body`(which is alias for `ctx.response.body`).** -### Acquire Uploaded Files +### Acquire the submitted files -Request body not only can take parameters, but also send files. and browsers usually send file in `Multipart/form-data` type. The uploaded files can be acquired by the framework built-in plugin [Multipart](https://github.com/eggjs/egg-multipart). +The `body` in the request can carry parameters as well as files. Generally speaking, our browsers always send files in `multipart/form-data`, and we now have two kinds of ways supporting submitting and acquiring files with the help of Egg's Multipart as a plug-in. -For full example, see [eggjs/examples/multipart](https://github.com/eggjs/examples/tree/master/multipart). +- #### `File` Mode: +If you have no ideas about Nodejs's Stream at all, the `File` mode suits you well: -In Controller, we can acquire the file stream of the uploaded file through interface `ctx.getFileStream()`. +1) In your config file, enable `file` mode first: +```js +// config/config.default.js +exports.multipart = { + mode: 'file', +}; +``` +2) Submitting / Acquiring files: + +1. For single file: + +Your HTML static front-end codes should look like this below: ```html
title: file: - + +
+``` +The corresponding backend codes are: +```js +// app/controller/upload.js +const Controller = require('egg').Controller; +const fs = require('mz/fs'); + +module.exports = class extends Controller { + async upload() { + const { ctx } = this; + const file = ctx.request.files[0]; + const name = 'egg-multipart-test/' + path.basename(file.filename); + let result; + try { + // process file (e.g: upload to cloud storage) + result = await ctx.oss.put(name, file.filepath); + } finally { + // need to remove the tmp file + await fs.unlink(file.filepath); + } + + ctx.body = { + url: result.url, + // get all field values + requestBody: ctx.request.body, + }; + } +}; +``` + +2. For multiple files: + +For multiple files, with the help of `ctx.request.files`, we can loop each of them and do what process we like: + +Your HTML static front-end codes should look like this below: +```html +
+ title: + file1: + file2: + +
+``` +The corresponding backend codes are: +```js +// app/controller/upload.js +const Controller = require('egg').Controller; +const fs = require('mz/fs'); + +module.exports = class extends Controller { + async upload() { + const { ctx } = this; + console.log(ctx.request.body); + console.log('got %d files', ctx.request.files.length); + for (const file of ctx.request.files) { + console.log('field: ' + file.fieldname); + console.log('filename: ' + file.filename); + console.log('encoding: ' + file.encoding); + console.log('mime: ' + file.mime); + console.log('tmp filepath: ' + file.filepath); + let result; + try { + // process file (e.g: upload to cloud storage) + result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath); + } finally { + // need to remove the tmp file + await fs.unlink(file.filepath); + } + console.log(result); + } + } +}; +``` +- #### `Stream` Mode +If you are very familiar with `Stream` in Nodejs, you can choose this way. In a controller, we can fetch the uploaded files through `ctx.getFileStream()`. + +1. For single file: + +```html +
+ title: + file: +
``` @@ -336,33 +432,35 @@ class UploaderController extends Controller { const ctx = this.ctx; const stream = await ctx.getFileStream(); const name = 'egg-multipart-test/' + path.basename(stream.filename); - // file processing, e.g. uploading to the cloud storage etc. let result; try { + // process file (e.g: upload to cloud storage) result = await ctx.oss.put(name, stream); } catch (err) { - // must consume the file stream, or the browser will get stuck + // You MUST consume the file stream, otherwises the browser cannot response any more await sendToWormhole(stream); throw err; } ctx.body = { url: result.url, - // all form fields can be acquired by `stream.fields` + // All the fields in the form can be fetched through `stream.fields` fields: stream.fields, }; } -}; +} module.exports = UploaderController; ``` -To acquire user uploaded files conveniently by `ctx.getFileStream`, two conditions must be matched: +To acquire the uploaded files easily, there're two conditions at least: + +- Only ONE file per time. +- The field of uploading file MUST be after the other fields in a form, otherwise you cannot get other fields after getting the file stream. -- only one file can be uploaded at the same time. -- file uploading must appear after other fields, otherwise we may can't access fields when we got file stream. +2. For multiple files: -If more than one files are to be uploaded at the same time, `ctx.getFileStream()` is no longer the way but the following: +For multiple files, you should do the following instead of using `ctx.getFileStream()`: ```js const sendToWormhole = require('stream-wormhole'); @@ -376,28 +474,30 @@ class UploaderController extends Controller { // parts() return a promise while ((part = await parts()) != null) { if (part.length) { - // it is field in case of arrays + // arrays are busboy fields console.log('field: ' + part[0]); console.log('value: ' + part[1]); console.log('valueTruncated: ' + part[2]); console.log('fieldnameTruncated: ' + part[3]); } else { if (!part.filename) { - // it occurs when user clicks on the upload without selecting the ile(part represents file, while part.filename is empty) - // more process should be taken, like giving an error message + // When a user clicks `upload` before choosing a file, + // `part` will be file stream, but `part.filename` is empty. + // We must handler this by notifying the user that he/she should + // choose a file before submitting return; } - // part represents the file stream uploaded + // otherwise, it's a fully-filled stream console.log('field: ' + part.fieldname); console.log('filename: ' + part.filename); console.log('encoding: ' + part.encoding); console.log('mime: ' + part.mime); - // file processing, e.g. uploading to the cloud storage etc. let result; try { + // process file (e.g: upload to cloud storage) result = await ctx.oss.put('egg-multipart-test/' + part.filename, part); } catch (err) { - // must consume the file stream, or the browser will get stuck + // You MUST consume the file stream, otherwises the browser cannot response any more await sendToWormhole(part); throw err; } @@ -406,12 +506,12 @@ class UploaderController extends Controller { } console.log('and we are done parsing the form!'); } -}; +} module.exports = UploaderController; ``` -To ensure the security of uploading files, the framework limits the formats of supported file and the whitelist supported by default is below: +The framework also has the limits for the safety of uploading files, the default white list is: ```js // images @@ -439,29 +539,31 @@ To ensure the security of uploading files, the framework limits the formats of s '.avi', ``` -New file extensions can be added by configuring the `config/config.default.js` file or rewriting the whole whitelist. +Users can add new file extensions in `config/config.default.js`, or rewrite a whole white list: -- add new file extensions +- Newly-added a file extension: ```js module.exports = { multipart: { - fileExtensions: [ '.apk' ], // supports .apk file extension + fileExtensions: [ '.apk' ], // Add support for apk files }, }; ``` -- overwrite the whole whitelist +- Overwriting a whole white list: ```js module.exports = { multipart: { - whitelist: [ '.png' ], // overwrite the whole whitelist, only '.png' is allowed to be uploaded + whitelist: [ '.png' ] // ONLY files of png is allowed }, }; ``` -**Note: when the whitelist attribute is used, the fileExtensions attribute will be discarded.** +**Notice:`fileExtensions` will be IGNORED when `whitelist` is overwritten.** + +For more tech details about this, please refer [Egg-Multipart](https://github.com/eggjs/egg-multipart). ### header diff --git a/docs/source/zh-cn/basics/controller.md b/docs/source/zh-cn/basics/controller.md index 46ef5890de..f6067409f1 100644 --- a/docs/source/zh-cn/basics/controller.md +++ b/docs/source/zh-cn/basics/controller.md @@ -314,17 +314,113 @@ module.exports = { ### 获取上传的文件 -请求 body 除了可以带参数之外,还可以发送文件,一般来说,浏览器上都是通过 `Multipart/form-data` 格式发送文件的,框架通过内置 [Multipart](https://github.com/eggjs/egg-multipart) 插件来支持获取用户上传的文件。 +请求 body 除了可以带参数之外,还可以发送文件。一般来说,浏览器上都是通过 `multipart/form-data` 格式发送文件的,在 Egg 的 Multipart 插件中,我们为你提供了两种方式上传和接受文件: -完整的上传示例参见:[eggjs/examples/multipart](https://github.com/eggjs/examples/tree/master/multipart)。 +- #### File 模式: +如果你完全不知道 Nodejs 中的 Stream 用法,那么 File 模式非常合适你: -在 Controller 中,我们可以通过 `ctx.getFileStream()` 接口能获取到上传的文件流。 +1)在 config 文件中启用 `file` 模式: +```js +// config/config.default.js +exports.multipart = { + mode: 'file', +}; +``` + +2)上传 / 接收文件: + +1. 上传 / 接收单个文件: +你的前端静态页面代码应该看上去如下样子: ```html
title: file: - + +
+``` +对应的后端代码如下: +```js +// app/controller/upload.js +const Controller = require('egg').Controller; +const fs = require('mz/fs'); + +module.exports = class extends Controller { + async upload() { + const { ctx } = this; + const file = ctx.request.files[0]; + const name = 'egg-multipart-test/' + path.basename(file.filename); + let result; + try { + // 处理文件,比如上传到云端 + result = await ctx.oss.put(name, file.filepath); + } finally { + // 需要删除临时文件 + await fs.unlink(file.filepath); + } + + ctx.body = { + url: result.url, + // 获取所有的字段值 + requestBody: ctx.request.body, + }; + } +}; +``` + +2. 上传 / 接收多个文件: + +对于多个文件,我们借助 `ctx.request.files` 属性进行遍历,然后分别进行处理: + +你的前端静态页面代码应该看上去如下样子: +```html +
+ title: + file1: + file2: + +
+``` +对应的后端代码: +```js +// app/controller/upload.js +const Controller = require('egg').Controller; +const fs = require('mz/fs'); + +module.exports = class extends Controller { + async upload() { + const { ctx } = this; + console.log(ctx.request.body); + console.log('got %d files', ctx.request.files.length); + for (const file of ctx.request.files) { + console.log('field: ' + file.fieldname); + console.log('filename: ' + file.filename); + console.log('encoding: ' + file.encoding); + console.log('mime: ' + file.mime); + console.log('tmp filepath: ' + file.filepath); + let result; + try { + // 处理文件,比如上传到云端 + result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath); + } finally { + // 需要删除临时文件 + await fs.unlink(file.filepath); + } + console.log(result); + } + } +}; +``` +- #### Stream 模式: +如果你对于 Node 中的 Stream 模式非常熟悉,那么你可以选择此模式。在 Controller 中,我们可以通过 `ctx.getFileStream()` 接口能获取到上传的文件流。 + +1. 上传 / 接受单个文件: + +```html +
+ title: + file: +
``` @@ -364,6 +460,8 @@ module.exports = UploaderController; - 只支持上传一个文件。 - 上传文件必须在所有其他的 fields 后面,否则在拿到文件流时可能还获取不到 fields。 +2. 上传 / 接受多个文件: + 如果要获取同时上传的多个文件,不能通过 `ctx.getFileStream()` 来获取,只能通过下面这种方式: ```js @@ -375,10 +473,10 @@ class UploaderController extends Controller { const ctx = this.ctx; const parts = ctx.multipart(); let part; - // parts() return a promise + // parts() 返回 promise 对象 while ((part = await parts()) != null) { if (part.length) { - // 如果是数组的话是 filed + // 这是 busboy 的字段 console.log('field: ' + part[0]); console.log('value: ' + part[1]); console.log('valueTruncated: ' + part[2]); @@ -448,7 +546,7 @@ module.exports = UploaderController; ```js module.exports = { multipart: { - fileExtensions: [ '.apk' ], // 增加对 .apk 扩展名的支持 + fileExtensions: [ '.apk' ] // 增加对 apk 扩展名的文件支持 }, }; ``` @@ -463,7 +561,9 @@ module.exports = { }; ``` -**注意:当传递了 whitelist 属性时,fileExtensions 属性不生效。** +**注意:当重写了 whitelist 时,fileExtensions 不生效。** + +欲了解更多相关此技术细节和详情,请参阅 [Egg-Multipart](https://github.com/eggjs/egg-multipart)。 ### header diff --git a/test/lib/core/loader/load_plugin.test.js b/test/lib/core/loader/load_plugin.test.js index cb7947aaf2..15f54cf786 100644 --- a/test/lib/core/loader/load_plugin.test.js +++ b/test/lib/core/loader/load_plugin.test.js @@ -226,9 +226,9 @@ describe('test/lib/core/loader/load_plugin.test.js', () => { 'onerror', 'i18n', 'watcher', + 'schedule', 'multipart', 'development', - 'schedule', 'logrotator', 'static', 'view',