| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 3abb607 commit 4c869c8
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2120,10 +2120,41 @@ buffer. Returns `false` if all or part of the data was queued in user memory. | |||
| 2120 | 2120 | added: v0.3.0 | |
| 2121 | 2121 | --> | |
| 2122 | 2122 | ||
| 2123 | - Sends a HTTP/1.1 100 Continue message to the client, indicating that | ||
| 2123 | + Sends an HTTP/1.1 100 Continue message to the client, indicating that | ||
| 2124 | 2124 | the request body should be sent. See the [`'checkContinue'`][] event on | |
| 2125 | 2125 | `Server`. | |
| 2126 | 2126 | ||
| 2127 | + ### `response.writeEarlyHints(links[, callback])` | ||
| 2128 | + | ||
| 2129 | + <!-- YAML | ||
| 2130 | + added: REPLACEME | ||
| 2131 | + --> | ||
| 2132 | + | ||
| 2133 | + * `links` {string|Array} | ||
| 2134 | + * `callback` {Function} | ||
| 2135 | + | ||
| 2136 | + Sends an HTTP/1.1 103 Early Hints message to the client with a Link header, | ||
| 2137 | + indicating that the user agent can preload/preconnect the linked resources. | ||
| 2138 | + The `links` can be a string or an array of strings containing the values | ||
| 2139 | + of the `Link` header. The optional `callback` argument will be called when | ||
| 2140 | + the response message has been written. | ||
| 2141 | + | ||
| 2142 | + **Example** | ||
| 2143 | + | ||
| 2144 | + ```js | ||
| 2145 | + const earlyHintsLink = '</styles.css>; rel=preload; as=style'; | ||
| 2146 | + response.writeEarlyHints(earlyHintsLink); | ||
| 2147 | + | ||
| 2148 | + const earlyHintsLinks = [ | ||
| 2149 | + '</styles.css>; rel=preload; as=style', | ||
| 2150 | + '</scripts.js>; rel=preload; as=script', | ||
| 2151 | + ]; | ||
| 2152 | + response.writeEarlyHints(earlyHintsLinks); | ||
| 2153 | + | ||
| 2154 | + const earlyHintsCallback = () => console.log('early hints message sent'); | ||
| 2155 | + response.writeEarlyHints(earlyHintsLinks, earlyHintsCallback); | ||
| 2156 | + ``` | ||
| 2157 | + | ||
| 2127 | 2158 | ### `response.writeHead(statusCode[, statusMessage][, headers])` | |
| 2128 | 2159 | ||
| 2129 | 2160 | <!-- YAML | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3991,6 +3991,32 @@ Sends a status `100 Continue` to the client, indicating that the request body | |||
| 3991 | 3991 | should be sent. See the [`'checkContinue'`][] event on `Http2Server` and | |
| 3992 | 3992 | `Http2SecureServer`. | |
| 3993 | 3993 | ||
| 3994 | + ### `response.writeEarlyHints(links)` | ||
| 3995 | + | ||
| 3996 | + <!-- YAML | ||
| 3997 | + added: REPLACEME | ||
| 3998 | + --> | ||
| 3999 | + | ||
| 4000 | + * `links` {string|Array} | ||
| 4001 | + | ||
| 4002 | + Sends a status `103 Early Hints` to the client with a Link header, | ||
| 4003 | + indicating that the user agent can preload/preconnect the linked resources. | ||
| 4004 | + The `links` can be a string or an array of strings containing the values | ||
| 4005 | + of the `Link` header. | ||
| 4006 | + | ||
| 4007 | + **Example** | ||
| 4008 | + | ||
| 4009 | + ```js | ||
| 4010 | + const earlyHintsLink = '</styles.css>; rel=preload; as=style'; | ||
| 4011 | + response.writeEarlyHints(earlyHintsLink); | ||
| 4012 | + | ||
| 4013 | + const earlyHintsLinks = [ | ||
| 4014 | + '</styles.css>; rel=preload; as=style', | ||
| 4015 | + '</scripts.js>; rel=preload; as=script', | ||
| 4016 | + ]; | ||
| 4017 | + response.writeEarlyHints(earlyHintsLinks); | ||
| 4018 | + ``` | ||
| 4019 | + | ||
| 3994 | 4020 | #### `response.writeHead(statusCode[, statusMessage][, headers])` | |
| 3995 | 4021 | ||
| 3996 | 4022 | <!-- YAML | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -80,7 +80,8 @@ const { | |||
| 80 | 80 | } = codes; | |
| 81 | 81 | const { | |
| 82 | 82 | validateInteger, | |
| 83 | - validateBoolean | ||
| 83 | + validateBoolean, | ||
| 84 | + validateLinkHeaderValue | ||
| 84 | 85 | } = require('internal/validators'); | |
| 85 | 86 | const Buffer = require('buffer').Buffer; | |
| 86 | 87 | const { | |
@@ -300,6 +301,43 @@ ServerResponse.prototype.writeProcessing = function writeProcessing(cb) { | |||
| 300 | 301 | this._writeRaw('HTTP/1.1 102 Processing\r\n\r\n', 'ascii', cb); | |
| 301 | 302 | }; | |
| 302 | 303 | ||
| 304 | + ServerResponse.prototype.writeEarlyHints = function writeEarlyHints(links, cb) { | ||
| 305 | + let head = 'HTTP/1.1 103 Early Hints\r\n'; | ||
| 306 | + | ||
| 307 | + if (typeof links === 'string') { | ||
| 308 | + validateLinkHeaderValue(links, 'links'); | ||
| 309 | + head += 'Link: ' + links + '\r\n'; | ||
| 310 | + } else if (ArrayIsArray(links)) { | ||
| 311 | + if (!links.length) { | ||
| 312 | + return; | ||
| 313 | + } | ||
| 314 | + | ||
| 315 | + head += 'Link: '; | ||
| 316 | + | ||
| 317 | + for (let i = 0; i < links.length; i++) { | ||
| 318 | + const link = links[i]; | ||
| 319 | + validateLinkHeaderValue(link, 'links'); | ||
| 320 | + head += link; | ||
| 321 | + | ||
| 322 | + if (i !== links.length - 1) { | ||
| 323 | + head += ', '; | ||
| 324 | + } | ||
| 325 | + } | ||
| 326 | + | ||
| 327 | + head += '\r\n'; | ||
| 328 | + } else { | ||
| 329 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 330 | + 'links', | ||
| 331 | + links, | ||
| 332 | + 'must be an array or string of format "</styles.css>; rel=preload; as=style"' | ||
| 333 | + ); | ||
| 334 | + } | ||
| 335 | + | ||
| 336 | + head += '\r\n'; | ||
| 337 | + | ||
| 338 | + this._writeRaw(head, 'ascii', cb); | ||
| 339 | + }; | ||
| 340 | + | ||
| 303 | 341 | ServerResponse.prototype._implicitHeader = function _implicitHeader() { | |
| 304 | 342 | this.writeHead(this.statusCode); | |
| 305 | 343 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,6 +32,7 @@ const { | |||
| 32 | 32 | HTTP2_HEADER_STATUS, | |
| 33 | 33 | ||
| 34 | 34 | HTTP_STATUS_CONTINUE, | |
| 35 | + HTTP_STATUS_EARLY_HINTS, | ||
| 35 | 36 | HTTP_STATUS_EXPECTATION_FAILED, | |
| 36 | 37 | HTTP_STATUS_METHOD_NOT_ALLOWED, | |
| 37 | 38 | HTTP_STATUS_OK | |
@@ -55,6 +56,7 @@ const { | |||
| 55 | 56 | const { | |
| 56 | 57 | validateFunction, | |
| 57 | 58 | validateString, | |
| 59 | + validateLinkHeaderValue, | ||
| 58 | 60 | } = require('internal/validators'); | |
| 59 | 61 | const { | |
| 60 | 62 | kSocket, | |
@@ -844,6 +846,49 @@ class Http2ServerResponse extends Stream { | |||
| 844 | 846 | }); | |
| 845 | 847 | return true; | |
| 846 | 848 | } | |
| 849 | + | ||
| 850 | + writeEarlyHints(links) { | ||
| 851 | + let linkHeaderValue = ''; | ||
| 852 | + | ||
| 853 | + if (typeof links === 'string') { | ||
| 854 | + validateLinkHeaderValue(links, 'links'); | ||
| 855 | + linkHeaderValue += links; | ||
| 856 | + } else if (ArrayIsArray(links)) { | ||
| 857 | + if (!links.length) { | ||
| 858 | + return; | ||
| 859 | + } | ||
| 860 | + | ||
| 861 | + linkHeaderValue += ''; | ||
| 862 | + | ||
| 863 | + for (let i = 0; i < links.length; i++) { | ||
| 864 | + const link = links[i]; | ||
| 865 | + validateLinkHeaderValue(link, 'links'); | ||
| 866 | + linkHeaderValue += link; | ||
| 867 | + | ||
| 868 | + if (i !== links.length - 1) { | ||
| 869 | + linkHeaderValue += ', '; | ||
| 870 | + } | ||
| 871 | + } | ||
| 872 | + } else { | ||
| 873 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 874 | + 'links', | ||
| 875 | + links, | ||
| 876 | + 'must be an array or string of format "</styles.css>; rel=preload; as=style"' | ||
| 877 | + ); | ||
| 878 | + } | ||
| 879 | + | ||
| 880 | + const stream = this[kStream]; | ||
| 881 | + | ||
| 882 | + if (stream.headersSent || this[kState].closed) | ||
| 883 | + return false; | ||
| 884 | + | ||
| 885 | + stream.additionalHeaders({ | ||
| 886 | + [HTTP2_HEADER_STATUS]: HTTP_STATUS_EARLY_HINTS, | ||
| 887 | + 'Link': linkHeaderValue | ||
| 888 | + }); | ||
| 889 | + | ||
| 890 | + return true; | ||
| 891 | + } | ||
| 847 | 892 | } | |
| 848 | 893 | ||
| 849 | 894 | function onServerStream(ServerRequest, ServerResponse, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -403,6 +403,21 @@ function validateUnion(value, name, union) { | |||
| 403 | 403 | } | |
| 404 | 404 | } | |
| 405 | 405 | ||
| 406 | + function validateLinkHeaderValue(value, name) { | ||
| 407 | + const linkValueRegExp = /^(?:<[^>]*>;)\s*(?:rel=(")?[^;"]*\1;?)\s*(?:(?:as|anchor|title)=(")?[^;"]*\2)?$/; | ||
| 408 | + | ||
| 409 | + if ( | ||
| 410 | + typeof value === 'undefined' || | ||
| 411 | + !RegExpPrototypeExec(linkValueRegExp, value) | ||
| 412 | + ) { | ||
| 413 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 414 | + name, | ||
| 415 | + value, | ||
| 416 | + 'must be an array or string of format "</styles.css>; rel=preload; as=style"' | ||
| 417 | + ); | ||
| 418 | + } | ||
| 419 | + } | ||
| 420 | + | ||
| 406 | 421 | module.exports = { | |
| 407 | 422 | isInt32, | |
| 408 | 423 | isUint32, | |
@@ -425,4 +440,5 @@ module.exports = { | |||
| 425 | 440 | validateUndefined, | |
| 426 | 441 | validateUnion, | |
| 427 | 442 | validateAbortSignal, | |
| 443 | + validateLinkHeaderValue | ||
| 428 | 444 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,33 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('node:assert'); | ||
| 4 | + const http = require('node:http'); | ||
| 5 | + const debug = require('node:util').debuglog('test'); | ||
| 6 | + | ||
| 7 | + const testResBody = 'response content\n'; | ||
| 8 | + | ||
| 9 | + const server = http.createServer(common.mustCall((req, res) => { | ||
| 10 | + debug('Server sending early hints...'); | ||
| 11 | + res.writeEarlyHints({ links: 'bad argument object' }); | ||
| 12 | + | ||
| 13 | + debug('Server sending full response...'); | ||
| 14 | + res.end(testResBody); | ||
| 15 | + })); | ||
| 16 | + | ||
| 17 | + server.listen(0, common.mustCall(() => { | ||
| 18 | + const req = http.request({ | ||
| 19 | + port: server.address().port, path: '/' | ||
| 20 | + }); | ||
| 21 | + | ||
| 22 | + req.end(); | ||
| 23 | + debug('Client sending request...'); | ||
| 24 | + | ||
| 25 | + req.on('information', common.mustNotCall()); | ||
| 26 | + | ||
| 27 | + process.on('uncaughtException', (err) => { | ||
| 28 | + debug(`Caught an exception: ${JSON.stringify(err)}`); | ||
| 29 | + if (err.name === 'AssertionError') throw err; | ||
| 30 | + assert.strictEqual(err.code, 'ERR_INVALID_ARG_VALUE'); | ||
| 31 | + process.exit(0); | ||
| 32 | + }); | ||
| 33 | + })); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,33 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('node:assert'); | ||
| 4 | + const http = require('node:http'); | ||
| 5 | + const debug = require('node:util').debuglog('test'); | ||
| 6 | + | ||
| 7 | + const testResBody = 'response content\n'; | ||
| 8 | + | ||
| 9 | + const server = http.createServer(common.mustCall((req, res) => { | ||
| 10 | + debug('Server sending early hints...'); | ||
| 11 | + res.writeEarlyHints('bad argument value'); | ||
| 12 | + | ||
| 13 | + debug('Server sending full response...'); | ||
| 14 | + res.end(testResBody); | ||
| 15 | + })); | ||
| 16 | + | ||
| 17 | + server.listen(0, common.mustCall(() => { | ||
| 18 | + const req = http.request({ | ||
| 19 | + port: server.address().port, path: '/' | ||
| 20 | + }); | ||
| 21 | + | ||
| 22 | + req.end(); | ||
| 23 | + debug('Client sending request...'); | ||
| 24 | + | ||
| 25 | + req.on('information', common.mustNotCall()); | ||
| 26 | + | ||
| 27 | + process.on('uncaughtException', (err) => { | ||
| 28 | + debug(`Caught an exception: ${JSON.stringify(err)}`); | ||
| 29 | + if (err.name === 'AssertionError') throw err; | ||
| 30 | + assert.strictEqual(err.code, 'ERR_INVALID_ARG_VALUE'); | ||
| 31 | + process.exit(0); | ||
| 32 | + }); | ||
| 33 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments