Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove error-inject and fix error handling #1409

Merged
merged 6 commits into from May 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 6 additions & 6 deletions lib/response.js
Expand Up @@ -6,7 +6,6 @@
*/

const contentDisposition = require('content-disposition');
const ensureErrorHandler = require('error-inject');
const getType = require('cache-content-type');
const onFinish = require('on-finished');
const escape = require('escape-html');
Expand Down Expand Up @@ -167,12 +166,13 @@ module.exports = {
}

// stream
if ('function' === typeof val.pipe) {
if (val instanceof Stream) {
onFinish(this.res, destroy.bind(null, val));
ensureErrorHandler(val, err => this.ctx.onerror(err));

// overwriting
if (null != original && original !== val) this.remove('Content-Length');
if (original != val) {
val.once('error', err => this.ctx.onerror(err));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two implementations seem to be not equal. We should only add error event listener when the stream has no other error event listener, which aim to avoid uncaught exception.

Also IMO koa's code philosophy is to keep code simple, so we don't treat 'one-liner module' as a bad thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In current implementation the check in the error-inject does nothing (as two inline created functions never the same) and just always add .on('error') handler. Do you mean that was desired behaviour?
I'm proposing to remove error-inject here not because it's one line, but because even you (module author) forgot that you are breaking it functionality while refactored to inline function. It just does nothing in current version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't understand what you mean, can you explain in a test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonathanong Sorry for my English. I've added a failing test to this PR, it's pretty simple:

it('should add error handler to the stream, but only once', () => {
      const res = response();
      const body = new Stream.PassThrough();
      assert.strictEqual(body.listenerCount('error'), 0);
      res.body = body;
      assert.strictEqual(body.listenerCount('error'), 1);
      res.body = body;
      assert.strictEqual(body.listenerCount('error'), 1);
    });

This fails in Koa master and passing after merging this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a developer error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, but the most probable explanation here is that by hiding the actual logic in a one-line module even the author of the module forgot the logic on a next refactoring and broke the functionality. That's what this PR is all about - fix the error and bring the logic inside to prevent such a problem in the future.

// overwriting
if (null != original) this.remove('Content-Length');
}

if (setType) this.type = 'bin';
return;
Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -32,7 +32,6 @@
"depd": "^1.1.2",
"destroy": "^1.0.4",
"encodeurl": "^1.0.2",
"error-inject": "^1.0.0",
"escape-html": "^1.0.3",
"fresh": "~0.5.2",
"http-assert": "^1.3.0",
Expand Down
11 changes: 11 additions & 0 deletions test/response/body.js
Expand Up @@ -4,6 +4,7 @@
const response = require('../helpers/context').response;
const assert = require('assert');
const fs = require('fs');
const Stream = require('stream');

describe('res.body=', () => {
describe('when Content-Type is set', () => {
Expand Down Expand Up @@ -108,6 +109,16 @@ describe('res.body=', () => {
res.body = fs.createReadStream('LICENSE');
assert.equal('application/octet-stream', res.header['content-type']);
});

it('should add error handler to the stream, but only once', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case clearly shows where the problem is, LGTM

const res = response();
const body = new Stream.PassThrough();
assert.strictEqual(body.listenerCount('error'), 0);
res.body = body;
assert.strictEqual(body.listenerCount('error'), 1);
res.body = body;
assert.strictEqual(body.listenerCount('error'), 1);
});
});

describe('when a buffer is given', () => {
Expand Down