Skip to content

Commit

Permalink
refactor: use async function and support egg@2 (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
dead-horse committed Nov 10, 2017
1 parent a198e0e commit 6a7fa06
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 141 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
@@ -1,8 +1,8 @@
sudo: false
language: node_js
node_js:
- '6'
- '8'
- '9'
install:
- npm i npminstall && npminstall
script:
Expand Down
106 changes: 57 additions & 49 deletions README.md
Expand Up @@ -127,25 +127,29 @@ Controller which hanlder `POST /upload`:
// app/controller/upload.js
const path = require('path');
const sendToWormhole = require('stream-wormhole');
const Controller = require('egg').Controller;

module.exports = Class UploadController extends Controller {
async upload() {
const ctx = this.ctx;
const stream = await ctx.getFileStream();
const name = 'egg-multipart-test/' + path.basename(stream.filename);
let result;
try {
// process file or upload to cloud storage
result = await ctx.oss.put(name, stream);
} catch (err) {
// must consume the stream, otherwise browser will be stuck.
await sendToWormhole(stream);
throw err;
}

module.exports = function* (ctx) {
const stream = yield ctx.getFileStream();
const name = 'egg-multipart-test/' + path.basename(stream.filename);
let result;
try {
// process file or upload to cloud storage
result = yield ctx.oss.put(name, stream);
} catch (err) {
// must consume the stream, otherwise browser will be stuck.
yield sendToWormhole(stream);
throw err;
ctx.body = {
url: result.url,
// process form fields by `stream.fields`
fields: stream.fields,
};
}

ctx.body = {
url: result.url,
// process form fields by `stream.fields`
fields: stream.fields,
};
};
```

Expand All @@ -164,39 +168,43 @@ Controller which hanlder `POST /upload`:
```js
// app/controller/upload.js
const sendToWormhole = require('stream-wormhole');

module.exports = function* (ctx) {
const parts = ctx.multipart();
let part;
while ((part = yield parts) != null) {
if (part.length) {
// 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) {
// user click `upload` before choose a file,
// `part` will be file stream, but `part.filename` is empty
// must handler this, such as log error.
return;
const Controller = require('egg').Controller;

module.exports = Class UploadController extends Controller {
async upload() {
const ctx = this.ctx;
const parts = ctx.multipart();
let part;
while ((part = await parts()) != null) {
if (part.length) {
// 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) {
// user click `upload` before choose a file,
// `part` will be file stream, but `part.filename` is empty
// must handler this, such as log error.
return;
}
// otherwise, it's a stream
console.log('field: ' + part.fieldname);
console.log('filename: ' + part.filename);
console.log('encoding: ' + part.encoding);
console.log('mime: ' + part.mime);
let result;
try {
result = await ctx.oss.put('egg-multipart-test/' + part.filename, part);
} catch (err) {
await sendToWormhole(part);
throw err;
}
console.log(result);
}
// otherwise, it's a stream
console.log('field: ' + part.fieldname);
console.log('filename: ' + part.filename);
console.log('encoding: ' + part.encoding);
console.log('mime: ' + part.mime);
let result;
try {
result = yield ctx.oss.put('egg-multipart-test/' + part.filename, part);
} catch (err) {
yield sendToWormhole(part);
throw err;
}
console.log(result);
}
console.log('and we are done parsing the form!');
}
console.log('and we are done parsing the form!');
}
};
```
54 changes: 25 additions & 29 deletions app/extend/context.js
@@ -1,7 +1,6 @@
'use strict';

const parse = require('co-busboy');
const co = require('co');

module.exports = {
/**
Expand All @@ -24,42 +23,39 @@ module.exports = {
* get upload file stream
* @example
* ```js
* const stream = yield this.getFileStream();
* const stream = await ctx.getFileStream();
* // get other fields
* console.log(stream.fields);
* ```
* @method Context#getFileStream
* @return {ReadStream} stream
* @since 1.0.0
*/
getFileStream() {
const ctx = this;
return co(function* () {
const parts = ctx.multipart({ autoFields: true });
const stream = yield parts;
// stream not exists, treat as an exception
if (!stream || !stream.filename) {
ctx.throw(400, 'Can\'t found upload file');
async getFileStream() {
const parts = this.multipart({ autoFields: true });
const stream = await parts();
// stream not exists, treat as an exception
if (!stream || !stream.filename) {
this.throw(400, 'Can\'t found upload file');
}
stream.fields = parts.field;
stream.once('limit', () => {
const err = new Error('Request file too large');
err.name = 'MultipartFileTooLargeError';
err.status = 413;
err.fields = stream.fields;
err.filename = stream.filename;
if (stream.listenerCount('error') > 0) {
stream.emit('error', err);
this.coreLogger.warn(err);
} else {
this.coreLogger.error(err);
// ignore next error event
stream.on('error', () => {});
}
stream.fields = parts.field;
stream.once('limit', () => {
const err = new Error('Request file too large');
err.name = 'MultipartFileTooLargeError';
err.status = 413;
err.fields = stream.fields;
err.filename = stream.filename;
if (stream.listenerCount('error') > 0) {
stream.emit('error', err);
ctx.coreLogger.warn(err);
} else {
ctx.coreLogger.error(err);
// ignore next error event
stream.on('error', () => {});
}
// ignore all data
stream.resume();
});
return stream;
// ignore all data
stream.resume();
});
return stream;
},
};
2 changes: 1 addition & 1 deletion appveyor.yml
@@ -1,7 +1,7 @@
environment:
matrix:
- nodejs_version: '6'
- nodejs_version: '8'
- nodejs_version: '9'

install:
- ps: Install-Product node $env:nodejs_version
Expand Down
23 changes: 11 additions & 12 deletions package.json
Expand Up @@ -32,37 +32,36 @@
},
"homepage": "https://github.com/eggjs/egg-multipart#readme",
"engines": {
"node": ">= 6.0.0"
"node": ">= 8.0.0"
},
"files": [
"app",
"config",
"app.js"
],
"ci": {
"version": "6, 8",
"version": "8, 9",
"license": true
},
"dependencies": {
"co": "^4.6.0",
"co-busboy": "^1.3.1",
"co-busboy": "^1.4.0",
"humanize-bytes": "^1.0.1"
},
"devDependencies": {
"autod": "^2.8.0",
"egg": "^1.5.0",
"egg-bin": "^4.0.4",
"autod": "^2.10.1",
"egg": "next",
"egg-bin": "^4.3.5",
"egg-ci": "^1.8.0",
"egg-mock": "^3.8.0",
"eslint": "^4.1.0",
"eslint-config-egg": "^4.2.1",
"egg-mock": "^3.13.1",
"eslint": "^4.10.0",
"eslint-config-egg": "^5.1.1",
"formstream": "^1.1.0",
"is-type-of": "^1.0.0",
"mkdirp": "^0.5.1",
"mz": "^2.6.0",
"mz": "^2.7.0",
"stream-wormhole": "^1.0.3",
"supertest": "^3.0.0",
"urllib": "^2.22.0",
"urllib": "^2.25.1",
"webstorm-disable-index": "^1.2.0"
}
}
Expand Up @@ -3,20 +3,20 @@
const path = require('path');
const fs = require('fs');

module.exports = function* () {
const parts = this.multipart();
module.exports = async ctx => {
const parts = ctx.multipart();
let part;
while ((part = yield parts) != null) {
while ((part = await parts()) != null) {
if (Array.isArray(part)) {
continue;
} else {
break;
}
}

const ws = fs.createWriteStream(path.join(this.app.config.logger.dir, 'multipart-test-file'));
const ws = fs.createWriteStream(path.join(ctx.app.config.logger.dir, 'multipart-test-file'));
part.pipe(ws);
this.body = {
ctx.body = {
filename: part.filename,
};
};
27 changes: 12 additions & 15 deletions test/fixtures/apps/multipart/app/controller/upload.js
Expand Up @@ -4,45 +4,42 @@ const path = require('path');
const fs = require('fs');
const sendToWormhole = require('stream-wormhole');

module.exports = function* () {
const parts = this.multipart();
module.exports = async ctx => {
const parts = ctx.multipart();
let part;
while ((part = yield parts) != null) {
while ((part = await parts()) != null) {
if (Array.isArray(part)) {
continue;
} else {
break;
}
}

// 并没有文件被上传,这时候需要根据业务需要做针对性的处理
// 例如 文件是必须字段,那么就报错
// 这里只是给出提示
if (!part || !part.filename) {
this.body = {
ctx.body = {
message: 'no file',
};
return;
}

if (this.query.mock_stream_error) {
if (ctx.query.mock_stream_error) {
// mock save stream error
const filepath = path.join(this.app.config.logger.dir, 'not-exists-dir/dir2/testfile');
const filepath = path.join(ctx.app.config.logger.dir, 'not-exists-dir/dir2/testfile');
try {
yield saveStream(part, filepath);
await saveStream(part, filepath);
} catch (err) {
yield sendToWormhole(part);
await sendToWormhole(part);
throw err;
}

this.body = {
ctx.body = {
filename: part.filename,
};
}

const filepath = path.join(this.app.config.logger.dir, 'multipart-test-file');
yield saveStream(part, filepath);
this.body = {
const filepath = path.join(ctx.app.config.logger.dir, 'multipart-test-file');
await saveStream(part, filepath);
ctx.body = {
filename: part.filename,
};
};
Expand Down
14 changes: 7 additions & 7 deletions test/fixtures/apps/upload-limit/app/router.js
Expand Up @@ -40,19 +40,19 @@ module.exports = app => {
},
};

app.get('/upload', function* () {
this.set('x-csrf', this.csrf);
this.body = 'hi';
app.get('/upload', async ctx => {
ctx.set('x-csrf', ctx.csrf);
ctx.body = 'hi';
});

app.post('/upload', function* () {
const stream = yield this.getFileStream();
app.post('/upload', async ctx => {
const stream = await ctx.getFileStream();
const name = 'egg-multipart-test/' + process.version + '-' + Date.now() + '-' + path.basename(stream.filename);
const result = yield this.oss.put(name, stream);
const result = await ctx.oss.put(name, stream);
if (name.includes('not-handle-error-event-and-mock-stream-error')) {
process.nextTick(() => stream.emit('error', new Error('mock stream unhandle error')));
}
this.body = {
ctx.body = {
name: result.name,
url: result.url,
status: result.res.status,
Expand Down

0 comments on commit 6a7fa06

Please sign in to comment.