GeezerVison is showing github.com — inspector: report loadingFinished until the response data is consumed · nodejs/node@f185e8a
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +31Lines changed: 31 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +const { | | 4 | + NumberMAX_SAFE_INTEGER, | | 5 | + Symbol, | | 6 | +} = primordials; | | 7 | + | | 8 | +const { now } = require('internal/perf/utils'); | | 9 | +const kInspectorRequestId = Symbol('kInspectorRequestId'); | | 10 | + | | 11 | +/** | | 12 | + * Return a monotonically increasing time in seconds since an arbitrary point in the past. | | 13 | + * @returns {number} | | 14 | + */ | | 15 | +function getMonotonicTime() { | | 16 | + return now() / 1000; | | 17 | +} | | 18 | + | | 19 | +let requestId = 0; | | 20 | +function getNextRequestId() { | | 21 | + if (requestId === NumberMAX_SAFE_INTEGER) { | | 22 | + requestId = 0; | | 23 | + } | | 24 | + return `node-network-event-${++requestId}`; | | 25 | +}; | | 26 | + | | 27 | +module.exports = { | | 28 | + kInspectorRequestId, | | 29 | + getMonotonicTime, | | 30 | + getNextRequestId, | | 31 | +}; |
Collapse file Copy file name to clipboard +132Lines changed: 132 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | + | | 3 | +const { | | 4 | + ArrayIsArray, | | 5 | + DateNow, | | 6 | + ObjectEntries, | | 7 | + String, | | 8 | + Symbol, | | 9 | +} = primordials; | | 10 | + | | 11 | +const { | | 12 | + kInspectorRequestId, | | 13 | + getMonotonicTime, | | 14 | + getNextRequestId, | | 15 | +} = require('internal/inspector/network'); | | 16 | +const dc = require('diagnostics_channel'); | | 17 | +const { Network } = require('inspector'); | | 18 | + | | 19 | +const kResourceType = 'Other'; | | 20 | +const kRequestUrl = Symbol('kRequestUrl'); | | 21 | + | | 22 | +// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>) | | 23 | +const convertHeaderObject = (headers = {}) => { | | 24 | + // The 'host' header that contains the host and port of the URL. | | 25 | + let host; | | 26 | + const dict = {}; | | 27 | + for (const { 0: key, 1: value } of ObjectEntries(headers)) { | | 28 | + if (key.toLowerCase() === 'host') { | | 29 | + host = value; | | 30 | + } | | 31 | + if (typeof value === 'string') { | | 32 | + dict[key] = value; | | 33 | + } else if (ArrayIsArray(value)) { | | 34 | + if (key.toLowerCase() === 'cookie') dict[key] = value.join('; '); | | 35 | + // ChromeDevTools frontend treats 'set-cookie' as a special case | | 36 | + // https://github.com/ChromeDevTools/devtools-frontend/blob/4275917f84266ef40613db3c1784a25f902ea74e/front_end/core/sdk/NetworkRequest.ts#L1368 | | 37 | + else if (key.toLowerCase() === 'set-cookie') dict[key] = value.join('\n'); | | 38 | + else dict[key] = value.join(', '); | | 39 | + } else { | | 40 | + dict[key] = String(value); | | 41 | + } | | 42 | + } | | 43 | + return [host, dict]; | | 44 | +}; | | 45 | + | | 46 | +/** | | 47 | + * When a client request starts, emit Network.requestWillBeSent event. | | 48 | + * https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-requestWillBeSent | | 49 | + * @param {{ request: import('http').ClientRequest }} event | | 50 | + */ | | 51 | +function onClientRequestStart({ request }) { | | 52 | + request[kInspectorRequestId] = getNextRequestId(); | | 53 | + | | 54 | + const { 0: host, 1: headers } = convertHeaderObject(request.getHeaders()); | | 55 | + const url = `${request.protocol}//${host}${request.path}`; | | 56 | + request[kRequestUrl] = url; | | 57 | + | | 58 | + Network.requestWillBeSent({ | | 59 | + requestId: request[kInspectorRequestId], | | 60 | + timestamp: getMonotonicTime(), | | 61 | + wallTime: DateNow(), | | 62 | + request: { | | 63 | + url, | | 64 | + method: request.method, | | 65 | + headers, | | 66 | + }, | | 67 | + }); | | 68 | +} | | 69 | + | | 70 | +/** | | 71 | + * When a client request errors, emit Network.loadingFailed event. | | 72 | + * https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-loadingFailed | | 73 | + * @param {{ request: import('http').ClientRequest, error: any }} event | | 74 | + */ | | 75 | +function onClientRequestError({ request, error }) { | | 76 | + if (typeof request[kInspectorRequestId] !== 'string') { | | 77 | + return; | | 78 | + } | | 79 | + Network.loadingFailed({ | | 80 | + requestId: request[kInspectorRequestId], | | 81 | + timestamp: getMonotonicTime(), | | 82 | + type: kResourceType, | | 83 | + errorText: error.message, | | 84 | + }); | | 85 | +} | | 86 | + | | 87 | +/** | | 88 | + * When response headers are received, emit Network.responseReceived event. | | 89 | + * https://chromedevtools.github.io/devtools-protocol/1-3/Network/#event-responseReceived | | 90 | + * @param {{ request: import('http').ClientRequest, error: any }} event | | 91 | + */ | | 92 | +function onClientResponseFinish({ request, response }) { | | 93 | + if (typeof request[kInspectorRequestId] !== 'string') { | | 94 | + return; | | 95 | + } | | 96 | + Network.responseReceived({ | | 97 | + requestId: request[kInspectorRequestId], | | 98 | + timestamp: getMonotonicTime(), | | 99 | + type: kResourceType, | | 100 | + response: { | | 101 | + url: request[kRequestUrl], | | 102 | + status: response.statusCode, | | 103 | + statusText: response.statusMessage ?? '', | | 104 | + headers: convertHeaderObject(response.headers)[1], | | 105 | + }, | | 106 | + }); | | 107 | + | | 108 | + // Wait until the response body is consumed by user code. | | 109 | + response.once('end', () => { | | 110 | + Network.loadingFinished({ | | 111 | + requestId: request[kInspectorRequestId], | | 112 | + timestamp: getMonotonicTime(), | | 113 | + }); | | 114 | + }); | | 115 | +} | | 116 | + | | 117 | +function enable() { | | 118 | + dc.subscribe('http.client.request.start', onClientRequestStart); | | 119 | + dc.subscribe('http.client.request.error', onClientRequestError); | | 120 | + dc.subscribe('http.client.response.finish', onClientResponseFinish); | | 121 | +} | | 122 | + | | 123 | +function disable() { | | 124 | + dc.unsubscribe('http.client.request.start', onClientRequestStart); | | 125 | + dc.unsubscribe('http.client.request.error', onClientRequestError); | | 126 | + dc.unsubscribe('http.client.response.finish', onClientResponseFinish); | | 127 | +} | | 128 | + | | 129 | +module.exports = { | | 130 | + enable, | | 131 | + disable, | | 132 | +}; |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/inspector_network_tracking.js +6-93Lines changed: 6 additions & 93 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | 'use strict'; | 2 | 2 |
| 3 | | -const { | 4 | | - ArrayIsArray, | 5 | | - DateNow, | 6 | | - ObjectEntries, | 7 | | - String, | 8 | | -} = primordials; | 9 | | - | 10 | | -let dc; | 11 | | -let Network; | 12 | | - | 13 | | -let requestId = 0; | 14 | | -const getNextRequestId = () => `node-network-event-${++requestId}`; | 15 | | - | 16 | | -// Convert a Headers object (Map<string, number | string | string[]>) to a plain object (Map<string, string>) | 17 | | -const headerObjectToDictionary = (headers = {}) => { | 18 | | - const dict = {}; | 19 | | - for (const { 0: key, 1: value } of ObjectEntries(headers)) { | 20 | | - if (typeof value === 'string') { | 21 | | - dict[key] = value; | 22 | | - } else if (ArrayIsArray(value)) { | 23 | | - if (key.toLowerCase() === 'cookie') dict[key] = value.join('; '); | 24 | | - // ChromeDevTools frontend treats 'set-cookie' as a special case | 25 | | - // https://github.com/ChromeDevTools/devtools-frontend/blob/4275917f84266ef40613db3c1784a25f902ea74e/front_end/core/sdk/NetworkRequest.ts#L1368 | 26 | | - else if (key.toLowerCase() === 'set-cookie') dict[key] = value.join('\n'); | 27 | | - else dict[key] = value.join(', '); | 28 | | - } else { | 29 | | - dict[key] = String(value); | 30 | | - } | 31 | | - } | 32 | | - return dict; | 33 | | -}; | 34 | | - | 35 | | -function onClientRequestStart({ request }) { | 36 | | - const url = `${request.protocol}//${request.host}${request.path}`; | 37 | | - const wallTime = DateNow(); | 38 | | - const timestamp = wallTime / 1000; | 39 | | - request._inspectorRequestId = getNextRequestId(); | 40 | | - Network.requestWillBeSent({ | 41 | | - requestId: request._inspectorRequestId, | 42 | | - timestamp, | 43 | | - wallTime, | 44 | | - request: { | 45 | | - url, | 46 | | - method: request.method, | 47 | | - headers: headerObjectToDictionary(request.getHeaders()), | 48 | | - }, | 49 | | - }); | 50 | | -} | 51 | | - | 52 | | -function onClientRequestError({ request, error }) { | 53 | | - if (typeof request._inspectorRequestId !== 'string') { | 54 | | - return; | 55 | | - } | 56 | | - const timestamp = DateNow() / 1000; | 57 | | - Network.loadingFailed({ | 58 | | - requestId: request._inspectorRequestId, | 59 | | - timestamp, | 60 | | - type: 'Other', | 61 | | - errorText: error.message, | 62 | | - }); | 63 | | -} | 64 | | - | 65 | | -function onClientResponseFinish({ request, response }) { | 66 | | - if (typeof request._inspectorRequestId !== 'string') { | 67 | | - return; | 68 | | - } | 69 | | - const url = `${request.protocol}//${request.host}${request.path}`; | 70 | | - const timestamp = DateNow() / 1000; | 71 | | - Network.responseReceived({ | 72 | | - requestId: request._inspectorRequestId, | 73 | | - timestamp, | 74 | | - type: 'Other', | 75 | | - response: { | 76 | | - url, | 77 | | - status: response.statusCode, | 78 | | - statusText: response.statusMessage ?? '', | 79 | | - headers: headerObjectToDictionary(response.headers), | 80 | | - }, | 81 | | - }); | 82 | | - Network.loadingFinished({ | 83 | | - requestId: request._inspectorRequestId, | 84 | | - timestamp, | 85 | | - }); | 86 | | -} | 87 | | - | 88 | 3 | function enable() { | 89 | | - dc ??= require('diagnostics_channel'); | 90 | | - Network ??= require('inspector').Network; | 91 | | - dc.subscribe('http.client.request.start', onClientRequestStart); | 92 | | - dc.subscribe('http.client.request.error', onClientRequestError); | 93 | | - dc.subscribe('http.client.response.finish', onClientResponseFinish); | | 4 | + require('internal/inspector/network_http').enable(); | | 5 | + // TODO: add undici request/websocket tracking. | | 6 | + // https://github.com/nodejs/node/issues/53946 | 94 | 7 | } | 95 | 8 |
| 96 | 9 | function disable() { | 97 | | - dc.unsubscribe('http.client.request.start', onClientRequestStart); | 98 | | - dc.unsubscribe('http.client.request.error', onClientRequestError); | 99 | | - dc.unsubscribe('http.client.response.finish', onClientResponseFinish); | | 10 | + require('internal/inspector/network_http').disable(); | | 11 | + // TODO: add undici request/websocket tracking. | | 12 | + // https://github.com/nodejs/node/issues/53946 | 100 | 13 | } | 101 | 14 |
| 102 | 15 | module.exports = { | |
Collapse file Copy file name to clipboardExpand all lines: src/node_builtins.cc +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -119,6 +119,8 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const { | 119 | 119 | builtin_categories.cannot_be_required = std::set<std::string> { | 120 | 120 | #if !HAVE_INSPECTOR | 121 | 121 | "inspector", "inspector/promises", "internal/util/inspector", | | 122 | + "internal/inspector/network", "internal/inspector/network_http", | | 123 | + "internal/inspector_async_hook", "internal/inspector_network_tracking", | 122 | 124 | #endif // !HAVE_INSPECTOR | 123 | 125 |
| 124 | 126 | #if !NODE_USE_V8_PLATFORM || !defined(NODE_HAVE_I18N_SUPPORT) | |
|