http: process 100, 102-199 according to specs.
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: https://github.com/nodejs/node/pull/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>
This commit is contained in:
parent
cfad44105d
commit
baf8495078
@ -400,6 +400,37 @@ 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: REPLACEME
|
||||
-->
|
||||
|
||||
Emitted when the server sends a 1xx response (excluding 101 Upgrade). This
|
||||
event is emitted with a callback containing an object with a status code.
|
||||
|
||||
```js
|
||||
const http = require('http');
|
||||
|
||||
const options = {
|
||||
hostname: '127.0.0.1',
|
||||
port: 8080,
|
||||
path: '/length_request'
|
||||
};
|
||||
|
||||
// Make a request
|
||||
const req = http.request(options);
|
||||
req.end();
|
||||
|
||||
req.on('information', (res) => {
|
||||
console.log('got information prior to main response: ' + res.statusCode);
|
||||
});
|
||||
```
|
||||
|
||||
101 Upgrade statuses do not fire this event due to their break from the
|
||||
traditional HTTP request/response chain, such as web sockets, in-place TLS
|
||||
upgrades, or HTTP 2.0. To be notified of 101 Upgrade notices, listen for the
|
||||
[`'upgrade'`][] event instead.
|
||||
|
||||
### Event: 'response'
|
||||
<!-- YAML
|
||||
added: v0.1.0
|
||||
@ -1384,6 +1415,14 @@ which has been transmitted are equal or not.
|
||||
Attempting to set a header field name or value that contains invalid characters
|
||||
will result in a [`TypeError`][] being thrown.
|
||||
|
||||
### response.writeProcessing()
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Sends a HTTP/1.1 102 Processing message to the client, indicating that
|
||||
the request body should be sent.
|
||||
|
||||
## Class: http.IncomingMessage
|
||||
<!-- YAML
|
||||
added: v0.1.17
|
||||
@ -1937,6 +1976,7 @@ not abort the request or do anything besides add a `timeout` event.
|
||||
[`'checkContinue'`]: #http_event_checkcontinue
|
||||
[`'request'`]: #http_event_request
|
||||
[`'response'`]: #http_event_response
|
||||
[`'upgrade'`]: #http_event_upgrade
|
||||
[`Agent`]: #http_class_http_agent
|
||||
[`Duplex`]: stream.html#stream_class_stream_duplex
|
||||
[`EventEmitter`]: events.html#events_class_eventemitter
|
||||
|
@ -447,16 +447,25 @@ function socketOnData(d) {
|
||||
socket.destroy();
|
||||
}
|
||||
} else if (parser.incoming && parser.incoming.complete &&
|
||||
// When the status code is 100 (Continue), the server will
|
||||
// send a final response after this client sends a request
|
||||
// body. So, we must not free the parser.
|
||||
parser.incoming.statusCode !== 100) {
|
||||
// When the status code is informational (100, 102-199),
|
||||
// the server will send a final response after this client
|
||||
// sends a request body, so we must not free the parser.
|
||||
// 101 (Switching Protocols) and all other status codes
|
||||
// should be processed normally.
|
||||
!statusIsInformational(parser.incoming.statusCode)) {
|
||||
socket.removeListener('data', socketOnData);
|
||||
socket.removeListener('end', socketOnEnd);
|
||||
freeParser(parser, req, socket);
|
||||
}
|
||||
}
|
||||
|
||||
function statusIsInformational(status) {
|
||||
// 100 (Continue) RFC7231 Section 6.2.1
|
||||
// 102 (Processing) RFC2518
|
||||
// 103 (Early Hints) RFC8297
|
||||
// 104-199 (Unassigned)
|
||||
return (status < 200 && status >= 100 && status !== 101);
|
||||
}
|
||||
|
||||
// client
|
||||
function parserOnIncomingClient(res, shouldKeepAlive) {
|
||||
@ -480,10 +489,16 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
|
||||
return 2; // Skip body and treat as Upgrade.
|
||||
}
|
||||
|
||||
if (res.statusCode === 100) {
|
||||
// restart the parser, as this is a continue message.
|
||||
if (statusIsInformational(res.statusCode)) {
|
||||
// Restart the parser, as this is a 1xx informational message.
|
||||
req.res = null; // Clear res so that we don't hit double-responses.
|
||||
// Maintain compatibility by sending 100-specific events
|
||||
if (res.statusCode === 100) {
|
||||
req.emit('continue');
|
||||
}
|
||||
// Send information events to all 1xx responses except 101 Upgrade.
|
||||
req.emit('information', { statusCode: res.statusCode });
|
||||
|
||||
return 1; // Skip body but don't treat as Upgrade.
|
||||
}
|
||||
|
||||
|
@ -188,6 +188,10 @@ ServerResponse.prototype.writeContinue = function writeContinue(cb) {
|
||||
this._sent100 = true;
|
||||
};
|
||||
|
||||
ServerResponse.prototype.writeProcessing = function writeProcessing(cb) {
|
||||
this._writeRaw(`HTTP/1.1 102 Processing${CRLF}${CRLF}`, 'ascii', cb);
|
||||
};
|
||||
|
||||
ServerResponse.prototype._implicitHeader = function _implicitHeader() {
|
||||
this.writeHead(this.statusCode);
|
||||
};
|
||||
|
52
test/parallel/test-http-information-processing.js
Normal file
52
test/parallel/test-http-information-processing.js
Normal file
@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
const Countdown = require('../common/countdown');
|
||||
|
||||
const test_res_body = 'other stuff!\n';
|
||||
const countdown = new Countdown(3, () => server.close());
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
console.error('Server sending informational message #1...');
|
||||
res.writeProcessing();
|
||||
console.error('Server sending informational message #2...');
|
||||
res.writeProcessing();
|
||||
console.error('Server sending full response...');
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/plain',
|
||||
'ABCD': '1'
|
||||
});
|
||||
res.end(test_res_body);
|
||||
});
|
||||
|
||||
server.listen(0, function() {
|
||||
const req = http.request({
|
||||
port: this.address().port,
|
||||
path: '/world'
|
||||
});
|
||||
req.end();
|
||||
console.error('Client sending request...');
|
||||
|
||||
let body = '';
|
||||
|
||||
req.on('information', function(res) {
|
||||
console.error('Client got 102 Processing...');
|
||||
countdown.dec();
|
||||
});
|
||||
|
||||
req.on('response', function(res) {
|
||||
assert.strictEqual(countdown.remaining, 1,
|
||||
'Full response received before all 102 Processing');
|
||||
assert.strictEqual(200, res.statusCode,
|
||||
`Final status code was ${res.statusCode}, not 200.`);
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', function(chunk) { body += chunk; });
|
||||
res.on('end', function() {
|
||||
console.error('Got full response.');
|
||||
assert.strictEqual(body, test_res_body);
|
||||
assert.ok('abcd' in res.headers);
|
||||
countdown.dec();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user