ensure that res.status is not called multiple times
Categories
(Taskcluster :: Services, defect)
Tracking
(Not tracked)
People
(Reporter: dustin, Unassigned, Mentored)
Details
(Keywords: good-first-bug)
Attachments
(1 file)
|
1.62 KB,
text/plain
|
Details |
| Reporter | ||
Comment 1•7 years ago
|
||
| Reporter | ||
Comment 3•7 years ago
|
||
| Reporter | ||
Comment 6•7 years ago
|
||
| Reporter | ||
Comment 8•7 years ago
|
||
| Reporter | ||
Comment 10•7 years ago
|
||
Comment 11•7 years ago
|
||
Comment 12•7 years ago
|
||
Comment 13•7 years ago
|
||
Comment 14•7 years ago
|
||
| Reporter | ||
Updated•7 years ago
|
| Reporter | ||
Comment 15•7 years ago
|
||
Comment 16•7 years ago
|
||
| Reporter | ||
Updated•7 years ago
|
| Reporter | ||
Comment 17•7 years ago
|
||
| Reporter | ||
Updated•7 years ago
|
Updated•7 years ago
|
Comment 18•7 years ago
|
||
| Reporter | ||
Comment 19•7 years ago
|
||
Comment 20•7 years ago
|
||
Comment 21•7 years ago
|
||
| Reporter | ||
Comment 22•7 years ago
|
||
| Reporter | ||
Updated•7 years ago
|
| Assignee | ||
Updated•6 years ago
|
| Reporter | ||
Updated•6 years ago
|
Comment 24•6 years ago
|
||
Hi Dustin!
Can I work on this bug?
| Reporter | ||
Comment 25•6 years ago
|
||
Sure! You can find res.reply defined at
https://github.com/taskcluster/taskcluster/blob/73730cd7bf12b86df82d3dd17a46d2186b4cac9f/libraries/api/src/middleware/schema.js#L66
and you'll want to look at the README in
https://github.com/taskcluster/taskcluster/blob/73730cd7bf12b86df82d3dd17a46d2186b4cac9f/libraries/api
to understand what the api library does.
A good place to start is to write a test case for the api library where the test case defines an API method that calls res.reply twice, and see what happens. Once that's done, we can work in improving it.
| Reporter | ||
Updated•6 years ago
|
Comment 26•6 years ago
|
||
Thank you for the references, Dustin!
So I did as you suggested. I tested the following scenarios:
-
Call res.reply twice, both without parameters. like res.reply() --> No errors
-
Call res.reply twice, both with parameters. like res.reply({a: 1}) --> Errors at console
-
Call res.reply twice, with(1st call) and without(2nd call) parameters. --> Errors at console
-
Call res.reply twice, without(1st call) and with(2nd call) parameters. --> Errors at console
-
Call res.reply twice, with empty json(1st call - like res.reply({});) and without(2nd call) parameters. --> Errors at console
-
Call res.reply twice, without(1st call) parameters and with empty json(2nd call - like res.reply({});). --> Errors at console
There are errors in the console for the second res.reply. Below is the error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (/home/himanshu/open-source-projects/taskcluster/node_modules/express/lib/response.js:767:10)
at ServerResponse.send (/home/himanshu/open-source-projects/taskcluster/node_modules/express/lib/response.js:170:12)
at ServerResponse.json (/home/himanshu/open-source-projects/taskcluster/node_modules/express/lib/response.js:267:15)
at /home/himanshu/open-source-projects/taskcluster/libraries/api/src/middleware/express-error.js:99:41
at Layer.handle_error (/home/himanshu/open-source-projects/taskcluster/node_modules/express/lib/router/layer.js:71:5)
at next (/home/himanshu/open-source-projects/taskcluster/node_modules/express/lib/router/route.js:135:13)
at Promise.resolve.then.then.catch (/home/himanshu/open-source-projects/taskcluster/libraries/api/src/middleware/handle.js:28:14)
at process._tickCallback (internal/process/next_tick.js:68:7)
I've also tried to test res.send calling them twice for a simple API. The behavior for res.send is also similar for the above scenarios except for scenario 5 where there were no errors.
Kindly, let me know the next steps.
| Reporter | ||
Comment 27•6 years ago
|
||
It looks like express is giving an error for most of those situations. However, #1 concerns me -- that should be an error too!
When you tested these things, did you write tests to do so, or were you doing the testing by hand? I'd like to have unit tests that check all six situations.
Comment 28•6 years ago
|
||
I had written the test cases as specified in Comment 10.
Regarding case #1, I found an interesting thing. The errors also appear for #1, but it's failing intermittently. Tracing the error, I found that express is not throwing the error, instead nodejs is throwing the error.
When the second res.reply is passed empty, null or undefined as parameter:
https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js#L541
When the second res.reply is passed a valid object:
https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js#L483
When express tries to set/remove header for the second res.reply, it is rejected by node since the header has already been set.
https://github.com/expressjs/express/blob/master/lib/response.js#L767
https://github.com/expressjs/express/blob/master/lib/response.js#L210
Analyzing further, since the two res.reply are async, if the second res.reply reaches the setHeader/removeHeader in nodejs before the header has been set for first res.reply, this may behave unexpectedly.
Thus, for all the test cases, the error may or may not be thrown. Until now I've not encoured this but it's a possibility.
Please let me know how to proceed on this.
| Reporter | ||
Comment 29•6 years ago
|
||
Hm, good research, I'm impressed!
I think that we could probably make this all a lot more explicit by including our own only-once logic in res.reply, to catch the issue before express and Node's http implementation get a chance to do so. That should allow us to catch case #1 reliably, as well as all of the other cases. What do you think?
Comment 30•6 years ago
|
||
Thank you! :)
Sounds good to me. I think we can keep track of whether 'res.reply' has been called before or not by using a local boolean variable 'executed'. This can be achieved by closures. So, 'res.reply' if called for the first time will invert the variable and all subsequent calls to it will enter the if condition when 'executed' is true. Then, we can handle this the way we want. What do you suggest?
Another question, do we plan to throw an error when encountered with multiple calls to res.reply or silent ignore them with a 'return;'?
| Reporter | ||
Comment 31•6 years ago
|
||
A boolean sounds good. And definitely throw an error -- calling res.reply twice is a sign of a serious programming error in the api method implementation, and hiding that would not be good.
Comment 32•6 years ago
|
||
So can I implement a solution for this as well as add the tests and raise a PR?
Also, should I add the tests in someplace existing or create a new test file for this?
| Reporter | ||
Comment 33•6 years ago
|
||
Yep, a PR sounds good.
https://github.com/taskcluster/taskcluster/blob/master/libraries/api/test/validate_test.js might be a good place for the new tests. Feel free to add a new sub-suite to that file if that makes sense to you.
Comment 34•6 years ago
|
||
I have implemented the solution to throw an error. But writing the tests are tricky since the response from the first res.reply() is sent successfully to the client(in our case the test). The error is thrown for second res.reply() and appears in the console.
I have looked up the express project for references and found out they have not written tests for such a scenario.
| Reporter | ||
Comment 35•6 years ago
|
||
Errors should be handled by calling monitor.reportError. There is a "mock" mode for the monitor that allows you to make assertions on what it was called with. "Reporting an error" is really just a special form of log message. You can see some assertions about log messages here. Hopefully that helps!
Updated•6 years ago
|
Comment 37•6 years ago
|
||
Hello Dustin, Can outreachy applicant take up issues from Bugzilla too. If yes, I can try working on it.
| Reporter | ||
Comment 38•6 years ago
|
||
Sure thing! There's a lot of context in the above comments, so have a careful look there.
Comment 39•6 years ago
|
||
Great, I will take a look and ensure a PR :+1
:
Comment 40•6 years ago
|
||
(In reply to Vishakha Nihore from comment #39)
Great, I will take a look and ensure a PR :+1
Vishakha: are you still working on this?
Updated•6 years ago
|
Updated•6 years ago
|
Comment 41•6 years ago
|
||
Hi,
I think I have implemented the tests for this but they seem to contradict this comment
https://github.com/taskcluster/taskcluster-lib-api/pull/80#issuecomment-367839970
here is the test for the first test case that, according to the comment doesn't trigger an error:
const builder = new APIBuilder({
title: 'Test Api',
description: 'Another test api',
serviceName: 'test',
apiVersion: 'v1',
});
builder.declare({
method: 'get',
route: '/test-double-json-send',
name: 'testDoubleSend',
output: 'test-schema.yml',
category: 'API Library',
title: 'Test End-Point',
description: 'place to call to trigger a double send',
}, function(req, res) {
res.status(400).json({error: 'yep'});
res.status(200).send('OK');
});
const url = u('/test-double-json-send');
return request.get(url).then((res) => {
assert(false, 'Error didn\'t trigger');
}).catch(() => {
assert(true, 'Error was triggered');
});
And this test passes.
I think I'm missing something but after re-reading the comments I can't seem to find it.
Any help would be appreciated.
Comment 42•6 years ago
|
||
Comment 43•6 years ago
|
||
I should clarify,
I also tested the other 3 scenarios with the code in 'two other tests'
| Reporter | ||
Comment 44•6 years ago
|
||
Ah, I think there's some confusion over two uses of "error". All three of those test cases should both
- return a 400 status to the
request.getcall - log a JS error via the monitor
Here's how that breaks down:
res.status(400).json({error: 'yep'});
this sends an HTTP response to the caller with the 400 status (1).
But the real bug we want to prevent here is subsequently calling
res.status(200).send('OK');
By the time that call occurs, we've already sent the HTTP response, so it's too late to change it. The best we can do is call monitor.reportError, which will end up logging an error to the service's logs. We have systems in place to pick up those errors and email us about them in production. And in tests for services, tc-lib-testing will cause the test to fail if such an error is logged -- exactly what we want.
So in addition to the tests you've written so far, you'll want to add some assertions that an error is logged. You can see some examples of testing for logging here. Note that if you run the tests with DEBUG=*, you can also see the logging in the output (with lots of other stuff). I'd also suggest using -b to bail out when the first test fails, and --grep to run only one test:
DEBUG=* mocha test/auth_test.js -b --grep 'calling send twice with status json'
(assuming your new test cases are in auth_test.js - otherwise change the filename in the above).
Comment 45•6 years ago
|
||
I see.
I'll add the code, modify the tests and come back with a PR.
Thank you for your help.
Comment 46•5 years ago
|
||
I'm currently preparing my PR and there doesn't seem to be an issue associated with this bug.
Would you prefer it if I added one or should I not add a changelog entry?
| Reporter | ||
Comment 47•5 years ago
|
||
You can reference this bug in the changelog entry with yarn changelog --bug 1437193 --patch
Comment 48•5 years ago
|
||
Louis: are you still working on this? It's the final blocker for bug 1436000.
| Reporter | ||
Comment 49•5 years ago
|
||
Comment 50•5 years ago
|
||
Hi, Yes I'm still on this. I'm trying to capture res.send to trigger the error if it's called after another function that uses it, but have not been able to do it so far.
| Reporter | ||
Comment 51•5 years ago
|
||
THanks @louiscontant!
Comment 52•5 years ago
|
||
Released in taskcluster 25.3.0.
Description
•