-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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
Process 100, 102-199 status codes according to specs. #18033
Conversation
I did not include documentation as I believe the RFCs cover this adequately, and the aim was to match the specs where Node was currently non-conforming. I admit that the tests are a bit opaque to me. I tried to find a good template for 100 Continue behavior, but it was unclear whether server or client was being tested or where the status code was being provided. Any help in this area is appreciated. |
Generally +1 on this but will need to give it a more thorough review. I'm wondering if this has to be semver-major tho as I can definitely see the possibility of this breaking at least someone. Ping @nodejs/http and @nodejs/tsc, please take a look |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on this change, but it needs a test.
I'd say semver-major is appropriate for this, given people may be depending on the existing (broken) behavior.
lib/_http_client.js
Outdated
socket.removeListener('data', socketOnData); | ||
socket.removeListener('end', socketOnEnd); | ||
freeParser(parser, req, socket); | ||
} | ||
} | ||
|
||
// client | ||
function informational(status) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: name this statusIsInformational
to be a bit more explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
👍 for semver-major. I'm fine with the change itself, but it needs tests. |
@TimothyGu As mentioned earlier, I hit a stumbling block with regard to tests. Node's test setup is not familiar to me and the test code for 100s that I'd use as a template is similarly opaque. I'll help any way I can, but I'm blocked by my lack of familiarity at the moment. |
@miles-po you can run the tests with You can write the test just the same as you would write code anywhere else that you would expect to succeed in a specific way. The only differences is that you have to include |
I think it is about time we plan to do this in a near-term major version. |
@Fishrock123 What are you saying? That this PR should go into 10.0.0? |
lib/_http_client.js
Outdated
// to the content-length of the entity-body had the request | ||
// been a GET. | ||
var isHeadResponse = req.method === 'HEAD'; | ||
debug('AGENT isHeadResponse', isHeadResponse); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed this a while ago, see #17806 (comment). Can you do the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this got back in / was not removed?
lib/_http_client.js
Outdated
debug('AGENT isHeadResponse', isHeadResponse); | ||
|
||
if (statusIsInformational(res.statusCode)) { | ||
// restart the parser, as this is a 1xx informational message. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: please capitalize comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Added test for 102 Processing. Allows for multiple 1xx responses. Updated docs. Added a new method to ServerResponse: The Open questions I had about 103 were whether to allow headers to be passed to it like As this is semver-major, we're in a good time to allow for discussion on the API without harsh deadlines. Suggestions welcome. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The title of this PR is about "102-199" status code, but I see code to handle 102 only.
Regarding 103, I would emit the headers as part of the event. I would not mesh them with the proper response header.
I'm not really fond of the 'information' name, but I don't want to bikeshed it either.
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE | ||
// USE OR OTHER DEALINGS IN THE SOFTWARE. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The header is not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/_http_server.js
Outdated
function _writeInformation(statusCode, statusMessage, cb) { | ||
this._writeRaw(`HTTP/1.1 ${statusCode} ${statusMessage}${CRLF}${CRLF}`, | ||
'ascii', cb); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you make writeInformation
as a top level function? There is no need to attach it to the prototype.
Another option is to just call _writeRaw
in both cases, I do not see much harm in that duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
assert.strictEqual(body, test_res_body, 'Response body doesn\'t match.'); | ||
assert.ok('abcd' in res.headers, 'Response headers missing.'); | ||
server.close(); | ||
process.exit(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this process.exit()
needed? Can we remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
doc/api/http.md
Outdated
@@ -400,6 +400,14 @@ Emitted when the server sends a '100 Continue' HTTP response, usually because | |||
the request contained 'Expect: 100-continue'. This is an instruction that | |||
the client should send the request body. | |||
|
|||
### Event: 'information' | |||
<!-- YAML | |||
added: v10.0.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be REPLACEME
. It will be replaced upon release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
The title of the PR is accurate. While only 102 is supported explicitly in the server, the client now allows for any informational message to be passed without ignoring the final response, which was the original intention of this patch. Unfortunately, there was no way to actually test this without added some basic 102 functionality to the server. I had considered adding 103 support, but as that RFC is still on the "Experimental" track, I decided against it. I guess that was my original thought behind adding that new method, Since 104-199 are not yet defined at all, I thought it best to have the client treat it like one would choose any informational message (allowing for folks with non-standard usage of these codes), but does not affect the client API at all. On the server side, this was not possible. An event can be passed, but how to actually send the headers is not well defined in the RFC. Should all headers defined so far be sent, or just a user-defined subset? When the headers for the 103 are sent, should those be passed on to next response by default or should this be explicit? What about second or third 103 responses? Should they resend all defined headers or just the changed ones? Should two or three (or ten) 103 messages be allowed? Should there be a limit? Rather than make a blind stab on this experimental RFC, I punted for now and just concentrated on the specs I could verify. Comments/suggestions welcome of course. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
doc/api/http.md
Outdated
|
||
Emitted when the server sends a 1xx response (excluding 101 Upgrade). This | ||
event is emitted with a object, which can be used to retrieve the status code. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change should also be added to the http2 compat API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should also explain why 101 Upgrade is not included in this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good spot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also an example illustrating what "This event is emitted with a object" means would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick question: Should 101s actually fire the event too?
I excluded 101 Upgrade since this was typically used to make TLS upgrades (rare) or Web Socket connections (typical). I don't work on the Socket.IO project—for example—so I don't know the potential drawbacks. I suppose performance wouldn't suffer measurably just from firing an event with no listeners.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take that back. 101 Upgrade messages are handled so differently, they can break the request/response model dramatically. Web Sockets being a prime example but TLS as well. Leaving as is.
@miles-po would you be so kind and rebase this PR and force-push-with-lease afterwards? Otherwise it is not possible to start the CI. |
Relevant references: RFC7231 Section 6.2.1 RFC2518 RFC8297 This affects several modules downstream that use the http module, e.g., node-fetch, all of whom violate HTTP RFCs due to this module. As such, this could introduce a breaking change for downstream if HTTP standards were ignored in an ad-hoc fashion. 101 Upgrade is excluded due to its non-informational processing according to RFC7231, Section 6.2.2.
Lint is clean.
Must be distinct from `continue` event due to slightly different behavior/triggering rules from 102/103 status codes. For example, 103 is explicitly made to take headers for pre-processing while 100 status codes never include headers.
Not clear to me where those changes for http2 are to be made. I had assumed (obviously incorrectly) that http2 followed the underscore-prefixed functionality. Could someone narrow down the search please? A line number would suffice. |
Most of the http2 code lives in: https://github.com/nodejs/node/blob/master/lib/internal/http2/core.js I think you should add the functionality to https://github.com/nodejs/node/blob/master/lib/internal/http2/core.js#L1447 (and descending classes, the Server and Client pairs). Also the compat API: https://github.com/nodejs/node/blob/master/lib/internal/http2/compat.js#L212 which will need to surface the information as well. |
I'm okay with separating the HTTP/2 things into a new PR or commit. This will allow easier backporting to releases that do not have the |
👍to two PRs. They can also be two separate commits. |
HTTP/2 changes will be done in a separate PR.
lib/_http_client.js
Outdated
socket.removeListener('data', socketOnData); | ||
socket.removeListener('end', socketOnEnd); | ||
freeParser(parser, req, socket); | ||
} | ||
} | ||
|
||
// client |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what this comment stands for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not either. I was following what looked like existing convention in this source file. Removing.
lib/_http_client.js
Outdated
// to the content-length of the entity-body had the request | ||
// been a GET. | ||
var isHeadResponse = req.method === 'HEAD'; | ||
debug('AGENT isHeadResponse', isHeadResponse); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this got back in / was not removed?
res.on('end', function() { | ||
console.error('Got full response.'); | ||
assert.strictEqual(body, test_res_body, 'Response body doesn\'t match.'); | ||
assert.ok('abcd' in res.headers, 'Response headers missing.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving out the error message is actually better because in case of an error it would say:
The expression assert.ok('abcd' in res.headers) evaluated to a falsy value.
res.on('data', function(chunk) { body += chunk; }); | ||
res.on('end', function() { | ||
console.error('Got full response.'); | ||
assert.strictEqual(body, test_res_body, 'Response body doesn\'t match.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not having any message will probably be even better because it will show what values got in.
}); | ||
|
||
req.on('response', function(res) { | ||
assert.strictEqual(processing_count, 2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use common/countdown.js
. That would be more intuitive.
doc/api/http.md
Outdated
path: '/length_request' | ||
}; | ||
|
||
// make a request |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking nit: Using capital letters as first character would be great :-) And maybe also a dot at the end.
* While rebasing, old content resurfaced. Re-removed. * Removed errant 'client' comment * Removed assert descriptions * Used common/countdown instead of simple int * Capitalized initial letter in example comment
Addressed comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with a nit.
|
||
const test_res_body = 'other stuff!\n'; | ||
let processing_count = 0; | ||
const countdown = new Countdown(3, common.mustCall(() => server.close())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Countdown
is already a mustCall
the wrapping is therefore not necessary :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@mcollina PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Failing on the markdown linter for docs (which is not invoked by Sorry about the hiccup. Markdown warning fixed. |
This is in the failing test |
Landing. |
Landed as baf8495. |
Adding ServerResponse.writeProcessing to send 102 status codes. Added an `'information'` event to ClientRequest to handle 1xx status codes except 101 Upgrade. 101 Upgrade is excluded due to its non-informational processing according to RFC7231, Section 6.2.2. This affects several modules downstream that use the http module, e.g., node-fetch, all of whom violate HTTP RFCs due to this module. As such, this could introduce a breaking change for downstream if HTTP standards were ignored in an ad-hoc fashion. See also RFC2518 RFC8297. PR-URL: #18033 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Adding ServerResponse.writeProcessing to send 102 status codes. Added an `'information'` event to ClientRequest to handle 1xx status codes except 101 Upgrade. 101 Upgrade is excluded due to its non-informational processing according to RFC7231, Section 6.2.2. This affects several modules downstream that use the http module, e.g., node-fetch, all of whom violate HTTP RFCs due to this module. As such, this could introduce a breaking change for downstream if HTTP standards were ignored in an ad-hoc fashion. See also RFC2518 RFC8297. PR-URL: nodejs#18033 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Relevant references:
RFC7231 Section 6.2.1
RFC2518
RFC8297
This affects several modules downstream that use the http
module, e.g., node-fetch, all of whom violate HTTP RFCs
due to this module. As such, this could introduce a
breaking change for downstream if HTTP standards were
ignored in an ad-hoc fashion.
101 Upgrade is excluded due to its non-informational
processing according to RFC7231, Section 6.2.2.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
http, http2, https