| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 50c8ff3 commit 4c9251f
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2675,6 +2675,35 @@ been transmitted are equal or not. | |||
| 2675 | 2675 | Attempting to set a header field name or value that contains invalid characters | |
| 2676 | 2676 | will result in a [`TypeError`][] being thrown. | |
| 2677 | 2677 | ||
| 2678 | + ### `response.writeInformation(statusCode[, headers][, callback])` | ||
| 2679 | + | ||
| 2680 | + <!-- YAML | ||
| 2681 | + added: REPLACEME | ||
| 2682 | + --> | ||
| 2683 | + | ||
| 2684 | + * `statusCode` {number} An HTTP 1xx informational status code, between `100` | ||
| 2685 | + and `199` inclusive, excluding `101` (Switching Protocols) which is only | ||
| 2686 | + available through the [`'upgrade'`][] event. | ||
| 2687 | + * `headers` {Object|Array} An optional set of headers to send with the | ||
| 2688 | + informational response. Accepts the same shapes as | ||
| 2689 | + [`response.writeHead()`][]. | ||
| 2690 | + * `callback` {Function} Optional, called once the message has been written | ||
| 2691 | + to the socket. | ||
| 2692 | + | ||
| 2693 | + Sends an arbitrary HTTP/1.1 1xx informational response to the client. This | ||
| 2694 | + is a generic equivalent of [`response.writeContinue()`][], | ||
| 2695 | + [`response.writeProcessing()`][] and [`response.writeEarlyHints()`][], and | ||
| 2696 | + can be called multiple times before the final response. After the final | ||
| 2697 | + response headers have been sent (via [`response.writeHead()`][] or an | ||
| 2698 | + implicit header), calling this method throws `ERR_HTTP_HEADERS_SENT`. | ||
| 2699 | + | ||
| 2700 | + Clients receive these responses via the [`'information'`][information event] | ||
| 2701 | + event on `http.ClientRequest`. | ||
| 2702 | + | ||
| 2703 | + ```js | ||
| 2704 | + response.writeInformation(110, { 'X-Progress': '50%' }); | ||
| 2705 | + ``` | ||
| 2706 | + | ||
| 2678 | 2707 | ### `response.writeProcessing()` | |
| 2679 | 2708 | ||
| 2680 | 2709 | <!-- YAML | |
@@ -4666,7 +4695,9 @@ const agent2 = new http.Agent({ proxyEnv: process.env }); | |||
| 4666 | 4695 | [`response.write()`]: #responsewritechunk-encoding-callback | |
| 4667 | 4696 | [`response.write(data, encoding)`]: #responsewritechunk-encoding-callback | |
| 4668 | 4697 | [`response.writeContinue()`]: #responsewritecontinue | |
| 4698 | + [`response.writeEarlyHints()`]: #responsewriteearlyhintshints-callback | ||
| 4669 | 4699 | [`response.writeHead()`]: #responsewriteheadstatuscode-statusmessage-headers | |
| 4700 | + [`response.writeProcessing()`]: #responsewriteprocessing | ||
| 4670 | 4701 | [`server.close()`]: #serverclosecallback | |
| 4671 | 4702 | [`server.headersTimeout`]: #serverheaderstimeout | |
| 4672 | 4703 | [`server.keepAliveTimeoutBuffer`]: #serverkeepalivetimeoutbuffer | |
@@ -4687,4 +4718,5 @@ const agent2 = new http.Agent({ proxyEnv: process.env }); | |||
| 4687 | 4718 | [`writable.destroyed`]: stream.md#writabledestroyed | |
| 4688 | 4719 | [`writable.uncork()`]: stream.md#writableuncork | |
| 4689 | 4720 | [`writable.write()`]: stream.md#writablewritechunk-encoding-callback | |
| 4721 | + [information event]: #event-information | ||
| 4690 | 4722 | [initial delay]: net.md#socketsetkeepaliveenable-initialdelay | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4824,6 +4824,30 @@ response.writeEarlyHints({ | |||
| 4824 | 4824 | }); | |
| 4825 | 4825 | ``` | |
| 4826 | 4826 | ||
| 4827 | + #### `response.writeInformation(statusCode[, headers])` | ||
| 4828 | + | ||
| 4829 | + <!-- YAML | ||
| 4830 | + added: REPLACEME | ||
| 4831 | + --> | ||
| 4832 | + | ||
| 4833 | + * `statusCode` {number} An HTTP 1xx informational status code, between `100` | ||
| 4834 | + and `199` inclusive, excluding `101` (Switching Protocols) which is not | ||
| 4835 | + allowed in HTTP/2. | ||
| 4836 | + * `headers` {Object} An optional object of headers to send with the | ||
| 4837 | + informational response. | ||
| 4838 | + | ||
| 4839 | + Sends an arbitrary HTTP 1xx informational response, equivalent in HTTP/2 to a | ||
| 4840 | + `HEADERS` frame whose `:status` pseudo-header is a 1xx code. May be called | ||
| 4841 | + multiple times before the final response. After the final response headers | ||
| 4842 | + have been sent, this method is a no-op and returns `false`. | ||
| 4843 | + | ||
| 4844 | + This is the generic equivalent of [`response.writeContinue()`][] and | ||
| 4845 | + [`response.writeEarlyHints()`][]. | ||
| 4846 | + | ||
| 4847 | + ```js | ||
| 4848 | + response.writeInformation(110, { 'X-Progress': '50%' }); | ||
| 4849 | + ``` | ||
| 4850 | + | ||
| 4827 | 4851 | #### `response.writeHead(statusCode[, statusMessage][, headers])` | |
| 4828 | 4852 | ||
| 4829 | 4853 | <!-- YAML | |
@@ -5033,6 +5057,7 @@ you need to implement any fall-back behavior yourself. | |||
| 5033 | 5057 | [`response.write()`]: #responsewritechunk-encoding-callback | |
| 5034 | 5058 | [`response.write(data, encoding)`]: http.md#responsewritechunk-encoding-callback | |
| 5035 | 5059 | [`response.writeContinue()`]: #responsewritecontinue | |
| 5060 | + [`response.writeEarlyHints()`]: #responsewriteearlyhintshints | ||
| 5036 | 5061 | [`response.writeHead()`]: #responsewriteheadstatuscode-statusmessage-headers | |
| 5037 | 5062 | [`server.close()`]: #serverclosecallback | |
| 5038 | 5063 | [`server.maxHeadersCount`]: http.md#servermaxheaderscount | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -308,18 +308,66 @@ ServerResponse.prototype.detachSocket = function detachSocket(socket) { | |||
| 308 | 308 | this.socket = null; | |
| 309 | 309 | }; | |
| 310 | 310 | ||
| 311 | + ServerResponse.prototype.writeInformation = function writeInformation( | ||
| 312 | + statusCode, headers, cb) { | ||
| 313 | + if (this._header) { | ||
| 314 | + throw new ERR_HTTP_HEADERS_SENT('write'); | ||
| 315 | + } | ||
| 316 | + | ||
| 317 | + validateInteger(statusCode, 'statusCode', 100, 199); | ||
| 318 | + if (statusCode === 101) { | ||
| 319 | + throw new ERR_HTTP_INVALID_STATUS_CODE(statusCode); | ||
| 320 | + } | ||
| 321 | + | ||
| 322 | + const statusMessage = STATUS_CODES[statusCode] || 'unknown'; | ||
| 323 | + let head = `HTTP/1.1 ${statusCode} ${statusMessage}\r\n`; | ||
| 324 | + | ||
| 325 | + if (headers !== undefined && headers !== null) { | ||
| 326 | + if (ArrayIsArray(headers)) { | ||
| 327 | + if (headers.length && ArrayIsArray(headers[0])) { | ||
| 328 | + for (let i = 0; i < headers.length; i++) { | ||
| 329 | + const entry = headers[i]; | ||
| 330 | + head += processInformationHeader(entry[0], entry[1]); | ||
| 331 | + } | ||
| 332 | + } else { | ||
| 333 | + if (headers.length % 2 !== 0) { | ||
| 334 | + throw new ERR_INVALID_ARG_VALUE('headers', headers); | ||
| 335 | + } | ||
| 336 | + for (let i = 0; i < headers.length; i += 2) { | ||
| 337 | + head += processInformationHeader(headers[i], headers[i + 1]); | ||
| 338 | + } | ||
| 339 | + } | ||
| 340 | + } else { | ||
| 341 | + validateObject(headers, 'headers'); | ||
| 342 | + const keys = ObjectKeys(headers); | ||
| 343 | + for (let i = 0; i < keys.length; i++) { | ||
| 344 | + const key = keys[i]; | ||
| 345 | + head += processInformationHeader(key, headers[key]); | ||
| 346 | + } | ||
| 347 | + } | ||
| 348 | + } | ||
| 349 | + | ||
| 350 | + head += '\r\n'; | ||
| 351 | + | ||
| 352 | + return this._writeRaw(head, 'ascii', cb); | ||
| 353 | + }; | ||
| 354 | + | ||
| 355 | + function processInformationHeader(name, value) { | ||
| 356 | + validateHeaderName(name); | ||
| 357 | + validateHeaderValue(name, value); | ||
| 358 | + return `${name}: ${value}\r\n`; | ||
| 359 | + } | ||
| 360 | + | ||
| 311 | 361 | ServerResponse.prototype.writeContinue = function writeContinue(cb) { | |
| 312 | - this._writeRaw('HTTP/1.1 100 Continue\r\n\r\n', 'ascii', cb); | ||
| 362 | + this.writeInformation(100, null, cb); | ||
| 313 | 363 | this._sent100 = true; | |
| 314 | 364 | }; | |
| 315 | 365 | ||
| 316 | 366 | ServerResponse.prototype.writeProcessing = function writeProcessing(cb) { | |
| 317 | - this._writeRaw('HTTP/1.1 102 Processing\r\n\r\n', 'ascii', cb); | ||
| 367 | + this.writeInformation(102, null, cb); | ||
| 318 | 368 | }; | |
| 319 | 369 | ||
| 320 | 370 | ServerResponse.prototype.writeEarlyHints = function writeEarlyHints(hints, cb) { | |
| 321 | - let head = 'HTTP/1.1 103 Early Hints\r\n'; | ||
| 322 | - | ||
| 323 | 371 | validateObject(hints, 'hints'); | |
| 324 | 372 | ||
| 325 | 373 | if (hints.link === null || hints.link === undefined) { | |
@@ -336,22 +384,16 @@ ServerResponse.prototype.writeEarlyHints = function writeEarlyHints(hints, cb) { | |||
| 336 | 384 | throw new ERR_INVALID_CHAR('header content', 'Link'); | |
| 337 | 385 | } | |
| 338 | 386 | ||
| 339 | - head += 'Link: ' + link + '\r\n'; | ||
| 340 | - | ||
| 387 | + const headers = { __proto__: null, Link: link }; | ||
| 341 | 388 | const keys = ObjectKeys(hints); | |
| 342 | 389 | for (let i = 0; i < keys.length; i++) { | |
| 343 | 390 | const key = keys[i]; | |
| 344 | 391 | if (key !== 'link') { | |
| 345 | - validateHeaderName(key); | ||
| 346 | - const value = hints[key]; | ||
| 347 | - validateHeaderValue(key, value); | ||
| 348 | - head += key + ': ' + value + '\r\n'; | ||
| 392 | + headers[key] = hints[key]; | ||
| 349 | 393 | } | |
| 350 | 394 | } | |
| 351 | 395 | ||
| 352 | - head += '\r\n'; | ||
| 353 | - | ||
| 354 | - this._writeRaw(head, 'ascii', cb); | ||
| 396 | + this.writeInformation(103, headers, cb); | ||
| 355 | 397 | }; | |
| 356 | 398 | ||
| 357 | 399 | ServerResponse.prototype._implicitHeader = function _implicitHeader() { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -901,17 +901,39 @@ class Http2ServerResponse extends Stream { | |||
| 901 | 901 | this[kStream].respond(headers, options); | |
| 902 | 902 | } | |
| 903 | 903 | ||
| 904 | - // TODO doesn't support callbacks | ||
| 905 | - writeContinue() { | ||
| 904 | + writeInformation(statusCode, headers) { | ||
| 905 | + if (typeof statusCode !== 'number' || | ||
| 906 | + statusCode < 100 || statusCode > 199) { | ||
| 907 | + throw new ERR_HTTP2_STATUS_INVALID(statusCode); | ||
| 908 | + } | ||
| 909 | + if (statusCode === 101) { | ||
| 910 | + throw new ERR_HTTP2_STATUS_INVALID(statusCode); | ||
| 911 | + } | ||
| 912 | + | ||
| 906 | 913 | const stream = this[kStream]; | |
| 914 | + | ||
| 907 | 915 | if (stream.headersSent || this[kState].closed) | |
| 908 | 916 | return false; | |
| 909 | - stream.additionalHeaders({ | ||
| 910 | - [HTTP2_HEADER_STATUS]: HTTP_STATUS_CONTINUE, | ||
| 911 | - }); | ||
| 917 | + | ||
| 918 | + const outHeaders = { __proto__: null }; | ||
| 919 | + if (headers !== undefined && headers !== null) { | ||
| 920 | + validateObject(headers, 'headers'); | ||
| 921 | + const keys = ObjectKeys(headers); | ||
| 922 | + for (let i = 0; i < keys.length; i++) { | ||
| 923 | + outHeaders[keys[i]] = headers[keys[i]]; | ||
| 924 | + } | ||
| 925 | + } | ||
| 926 | + outHeaders[HTTP2_HEADER_STATUS] = statusCode; | ||
| 927 | + | ||
| 928 | + stream.additionalHeaders(outHeaders); | ||
| 912 | 929 | return true; | |
| 913 | 930 | } | |
| 914 | 931 | ||
| 932 | + // TODO doesn't support callbacks | ||
| 933 | + writeContinue() { | ||
| 934 | + return this.writeInformation(HTTP_STATUS_CONTINUE); | ||
| 935 | + } | ||
| 936 | + | ||
| 915 | 937 | writeEarlyHints(hints) { | |
| 916 | 938 | validateObject(hints, 'hints'); | |
| 917 | 939 | ||
@@ -933,18 +955,9 @@ class Http2ServerResponse extends Stream { | |||
| 933 | 955 | return false; | |
| 934 | 956 | } | |
| 935 | 957 | ||
| 936 | - const stream = this[kStream]; | ||
| 958 | + headers.Link = linkHeaderValue; | ||
| 937 | 959 | ||
| 938 | - if (stream.headersSent || this[kState].closed) | ||
| 939 | - return false; | ||
| 940 | - | ||
| 941 | - stream.additionalHeaders({ | ||
| 942 | - ...headers, | ||
| 943 | - [HTTP2_HEADER_STATUS]: HTTP_STATUS_EARLY_HINTS, | ||
| 944 | - 'Link': linkHeaderValue, | ||
| 945 | - }); | ||
| 946 | - | ||
| 947 | - return true; | ||
| 960 | + return this.writeInformation(HTTP_STATUS_EARLY_HINTS, headers); | ||
| 948 | 961 | } | |
| 949 | 962 | } | |
| 950 | 963 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,11 +9,7 @@ const countdown = new Countdown(2, () => server.close()); | |||
| 9 | 9 | ||
| 10 | 10 | const server = http.createServer(common.mustCallAtLeast((req, res) => { | |
| 11 | 11 | console.error('Server sending informational message #1...'); | |
| 12 | - // These function calls may rewritten as necessary | ||
| 13 | - // to call res.writeHead instead | ||
| 14 | - res._writeRaw('HTTP/1.1 102 Processing\r\n'); | ||
| 15 | - res._writeRaw('Foo: Bar\r\n'); | ||
| 16 | - res._writeRaw('\r\n', common.mustCall()); | ||
| 12 | + res.writeInformation(102, { Foo: 'Bar' }, common.mustCall()); | ||
| 17 | 13 | console.error('Server sending full response...'); | |
| 18 | 14 | res.writeHead(200, { | |
| 19 | 15 | 'Content-Type': 'text/plain', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,94 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('node:assert'); | ||
| 4 | + const http = require('node:http'); | ||
| 5 | + | ||
| 6 | + // Happy flow: arbitrary 1xx status with custom headers, observed by the | ||
| 7 | + // client's 'information' event. | ||
| 8 | + { | ||
| 9 | + const server = http.createServer(common.mustCall((req, res) => { | ||
| 10 | + res.writeInformation(110, { 'X-Progress': '50%', 'X-Stage': 'reading' }); | ||
| 11 | + res.writeInformation(199, [['X-Custom', 'one'], ['X-Custom-2', 'two']]); | ||
| 12 | + res.end('done'); | ||
| 13 | + })); | ||
| 14 | + | ||
| 15 | + server.listen(0, common.mustCall(() => { | ||
| 16 | + const req = http.request({ port: server.address().port }); | ||
| 17 | + | ||
| 18 | + const seen = []; | ||
| 19 | + req.on('information', (res) => { | ||
| 20 | + seen.push({ | ||
| 21 | + statusCode: res.statusCode, | ||
| 22 | + headers: res.headers, | ||
| 23 | + }); | ||
| 24 | + }); | ||
| 25 | + | ||
| 26 | + req.on('response', common.mustCall((res) => { | ||
| 27 | + assert.strictEqual(res.statusCode, 200); | ||
| 28 | + | ||
| 29 | + assert.strictEqual(seen.length, 2); | ||
| 30 | + assert.strictEqual(seen[0].statusCode, 110); | ||
| 31 | + assert.strictEqual(seen[0].headers['x-progress'], '50%'); | ||
| 32 | + assert.strictEqual(seen[0].headers['x-stage'], 'reading'); | ||
| 33 | + assert.strictEqual(seen[1].statusCode, 199); | ||
| 34 | + assert.strictEqual(seen[1].headers['x-custom'], 'one'); | ||
| 35 | + assert.strictEqual(seen[1].headers['x-custom-2'], 'two'); | ||
| 36 | + | ||
| 37 | + res.resume(); | ||
| 38 | + res.on('end', common.mustCall(() => server.close())); | ||
| 39 | + })); | ||
| 40 | + | ||
| 41 | + req.end(); | ||
| 42 | + })); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + // Headers argument is optional / nullable. | ||
| 46 | + { | ||
| 47 | + const server = http.createServer(common.mustCall((req, res) => { | ||
| 48 | + res.writeInformation(150); | ||
| 49 | + res.writeInformation(151, null); | ||
| 50 | + res.end(); | ||
| 51 | + })); | ||
| 52 | + | ||
| 53 | + server.listen(0, common.mustCall(() => { | ||
| 54 | + const req = http.request({ port: server.address().port }); | ||
| 55 | + let count = 0; | ||
| 56 | + req.on('information', () => count++); | ||
| 57 | + req.on('response', common.mustCall((res) => { | ||
| 58 | + assert.strictEqual(count, 2); | ||
| 59 | + res.resume(); | ||
| 60 | + res.on('end', common.mustCall(() => server.close())); | ||
| 61 | + })); | ||
| 62 | + req.end(); | ||
| 63 | + })); | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + // Error cases. | ||
| 67 | + { | ||
| 68 | + const server = http.createServer(common.mustCall((req, res) => { | ||
| 69 | + assert.throws(() => res.writeInformation(101), | ||
| 70 | + { code: 'ERR_HTTP_INVALID_STATUS_CODE' }); | ||
| 71 | + assert.throws(() => res.writeInformation(99), | ||
| 72 | + { code: 'ERR_OUT_OF_RANGE' }); | ||
| 73 | + assert.throws(() => res.writeInformation(200), | ||
| 74 | + { code: 'ERR_OUT_OF_RANGE' }); | ||
| 75 | + assert.throws(() => res.writeInformation('100'), | ||
| 76 | + { code: 'ERR_INVALID_ARG_TYPE' }); | ||
| 77 | + assert.throws(() => res.writeInformation(150, { 'X-Bad\n': 'v' }), | ||
| 78 | + { code: 'ERR_INVALID_HTTP_TOKEN' }); | ||
| 79 | + | ||
| 80 | + res.writeHead(200); | ||
| 81 | + assert.throws(() => res.writeInformation(150), | ||
| 82 | + { code: 'ERR_HTTP_HEADERS_SENT' }); | ||
| 83 | + res.end(); | ||
| 84 | + })); | ||
| 85 | + | ||
| 86 | + server.listen(0, common.mustCall(() => { | ||
| 87 | + const req = http.request({ port: server.address().port }); | ||
| 88 | + req.on('response', common.mustCall((res) => { | ||
| 89 | + res.resume(); | ||
| 90 | + res.on('end', common.mustCall(() => server.close())); | ||
| 91 | + })); | ||
| 92 | + req.end(); | ||
| 93 | + })); | ||
| 94 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments