diff --git a/lib/internal/inspector/network_http.js b/lib/internal/inspector/network_http.js index 8d324c8c544eea..46bdd827c094a1 100644 --- a/lib/internal/inspector/network_http.js +++ b/lib/internal/inspector/network_http.js @@ -5,6 +5,7 @@ const { DateNow, ObjectEntries, String, + StringPrototypeStartsWith, Symbol, } = primordials; @@ -21,6 +22,19 @@ const EventEmitter = require('events'); const kRequestUrl = Symbol('kRequestUrl'); +function isAbsoluteURLPath(path) { + return typeof path === 'string' && + (StringPrototypeStartsWith(path, 'http://') || + StringPrototypeStartsWith(path, 'https://')); +} + +function getRequestURL(request, host) { + if (isAbsoluteURLPath(request.path)) { + return request.path; + } + return `${request.protocol}//${host}${request.path}`; +} + // Convert a Headers object (Map) to a plain object (Map) const convertHeaderObject = (headers = {}) => { // The 'host' header that contains the host and port of the URL. @@ -62,7 +76,7 @@ function onClientRequestCreated({ request }) { request[kInspectorRequestId] = getNextRequestId(); const { 0: headers, 1: host, 2: charset } = convertHeaderObject(request.getHeaders()); - const url = `${request.protocol}//${host}${request.path}`; + const url = getRequestURL(request, host); request[kRequestUrl] = url; Network.requestWillBeSent({ diff --git a/test/parallel/test-inspector-network-http.js b/test/parallel/test-inspector-network-http.js index 3f0014f2459253..a90f9d9d21cc37 100644 --- a/test/parallel/test-inspector-network-http.js +++ b/test/parallel/test-inspector-network-http.js @@ -32,9 +32,28 @@ const setResponseHeaders = (res) => { const kTimeout = 1000; const kDelta = 200; +const kDefaultResponseHeaders = { + server: 'node', + etag: '12345', + setCookie: 'key1=value1\nkey2=value2', + xHeader2: 'value1, value2', +}; + +function getDefaultResponseExpect(url) { + return { + url, + mimeType: 'text/plain', + charset: 'utf-8', + responseHeaders: kDefaultResponseHeaders, + }; +} + +function getPathName(req) { + return new URL(req.url, `http://${req.headers.host}`).pathname; +} const handleRequest = (req, res) => { - const path = req.url; + const path = getPathName(req); switch (path) { case '/hello-world': setResponseHeaders(res); @@ -46,6 +65,22 @@ const handleRequest = (req, res) => { res.end('hello world\n'); }, kTimeout); break; + case '/echo-post': { + const chunks = []; + req.on('data', (chunk) => { + chunks.push(chunk); + }); + req.on('end', () => { + const body = Buffer.concat(chunks).toString(); + res.setHeader('Content-Type', 'application/json; charset=utf-8'); + res.writeHead(200); + res.end(JSON.stringify({ + method: req.method, + body, + })); + }); + break; + } default: assert.fail(`Unexpected path: ${path}`); } @@ -77,7 +112,7 @@ function verifyRequestWillBeSent({ method, params }, expect) { assert.ok(params.requestId.startsWith('node-network-event-')); assert.strictEqual(params.request.url, expect.url); - assert.strictEqual(params.request.method, 'GET'); + assert.strictEqual(params.request.method, expect.method ?? 'GET'); assert.strictEqual(typeof params.request.headers, 'object'); assert.strictEqual(params.request.headers['accept-language'], 'en-US'); assert.strictEqual(params.request.headers.cookie, 'k1=v1; k2=v2'); @@ -103,12 +138,20 @@ function verifyResponseReceived({ method, params }, expect) { assert.strictEqual(params.response.statusText, 'OK'); assert.strictEqual(params.response.url, expect.url); assert.strictEqual(typeof params.response.headers, 'object'); - assert.strictEqual(params.response.headers.server, 'node'); - assert.strictEqual(params.response.headers.etag, '12345'); - assert.strictEqual(params.response.headers['set-cookie'], 'key1=value1\nkey2=value2'); - assert.strictEqual(params.response.headers['x-header2'], 'value1, value2'); - assert.strictEqual(params.response.mimeType, 'text/plain'); - assert.strictEqual(params.response.charset, 'utf-8'); + if (expect.responseHeaders?.server) { + assert.strictEqual(params.response.headers.server, expect.responseHeaders.server); + } + if (expect.responseHeaders?.etag) { + assert.strictEqual(params.response.headers.etag, expect.responseHeaders.etag); + } + if (expect.responseHeaders?.setCookie) { + assert.strictEqual(params.response.headers['set-cookie'], expect.responseHeaders.setCookie); + } + if (expect.responseHeaders?.xHeader2) { + assert.strictEqual(params.response.headers['x-header2'], expect.responseHeaders.xHeader2); + } + assert.strictEqual(params.response.mimeType, expect.mimeType); + assert.strictEqual(params.response.charset, expect.charset); return params; } @@ -151,17 +194,46 @@ function verifyHttpResponse(response) { })); } -async function testHttpGet() { - const url = `http://127.0.0.1:${httpServer.address().port}/hello-world`; +function drainHttpResponse(response) { + response.resume(); +} + +function createRequestTracker(url, responseExpect, requestExpect = {}) { const requestWillBeSentFuture = once(session, 'Network.requestWillBeSent') - .then(([event]) => verifyRequestWillBeSent(event, { url })); + .then(([event]) => verifyRequestWillBeSent(event, { + url, + method: requestExpect.method, + })); const responseReceivedFuture = once(session, 'Network.responseReceived') - .then(([event]) => verifyResponseReceived(event, { url })); + .then(([event]) => verifyResponseReceived(event, responseExpect)); const loadingFinishedFuture = once(session, 'Network.loadingFinished') .then(([event]) => verifyLoadingFinished(event)); + return { + requestWillBeSentFuture, + responseReceivedFuture, + loadingFinishedFuture, + }; +} + +async function assertResponseBody(responseReceived, expectedBody, expectedBase64Encoded = false) { + const responseBody = await session.post('Network.getResponseBody', { + requestId: responseReceived.requestId, + }); + assert.strictEqual(responseBody.base64Encoded, expectedBase64Encoded); + assert.strictEqual(responseBody.body, expectedBody); +} + +async function testHttpGet() { + const url = `http://127.0.0.1:${httpServer.address().port}/hello-world`; + const { + requestWillBeSentFuture, + responseReceivedFuture, + loadingFinishedFuture, + } = createRequestTracker(url, getDefaultResponseExpect(url)); + http.get({ host: '127.0.0.1', port: httpServer.address().port, @@ -175,24 +247,80 @@ async function testHttpGet() { const delta = (loadingFinished.timestamp - responseReceived.timestamp) * 1000; assert.ok(delta > kDelta); + await assertResponseBody(responseReceived, '\nhello world\n'); +} - const responseBody = await session.post('Network.getResponseBody', { - requestId: responseReceived.requestId, +async function testHttpGetWithAbsoluteUrlPath() { + const url = `http://127.0.0.1:${httpServer.address().port}/hello-world`; + const { + requestWillBeSentFuture, + responseReceivedFuture, + loadingFinishedFuture, + } = createRequestTracker(url, getDefaultResponseExpect(url)); + + http.get({ + host: '127.0.0.1', + port: httpServer.address().port, + path: url, + headers: requestHeaders, + }, common.mustCall(verifyHttpResponse)); + + await requestWillBeSentFuture; + const responseReceived = await responseReceivedFuture; + const loadingFinished = await loadingFinishedFuture; + + const delta = (loadingFinished.timestamp - responseReceived.timestamp) * 1000; + assert.ok(delta > kDelta); + await assertResponseBody(responseReceived, '\nhello world\n'); +} + +async function testHttpPostWithAbsoluteUrlPath() { + const requestBody = JSON.stringify({ title: 'foo', type: 'post' }); + const url = `http://127.0.0.1:${httpServer.address().port}/echo-post`; + const { + requestWillBeSentFuture, + responseReceivedFuture, + loadingFinishedFuture, + } = createRequestTracker(url, { + url, + mimeType: 'application/json', + charset: 'utf-8', + }, { + method: 'POST', + }); + + const responsePromise = new Promise((resolve, reject) => { + const req = http.request({ + host: '127.0.0.1', + port: httpServer.address().port, + path: url, + method: 'POST', + headers: { + ...requestHeaders, + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(requestBody), + }, + }, resolve); + req.on('error', reject); + req.end(requestBody); }); - assert.strictEqual(responseBody.base64Encoded, false); - assert.strictEqual(responseBody.body, '\nhello world\n'); + + const response = await responsePromise; + drainHttpResponse(response); + + await requestWillBeSentFuture; + const responseReceived = await responseReceivedFuture; + await loadingFinishedFuture; + await assertResponseBody(responseReceived, `{"method":"POST","body":"${requestBody}"}`); } async function testHttpsGet() { const url = `https://127.0.0.1:${httpsServer.address().port}/hello-world`; - const requestWillBeSentFuture = once(session, 'Network.requestWillBeSent') - .then(([event]) => verifyRequestWillBeSent(event, { url })); - - const responseReceivedFuture = once(session, 'Network.responseReceived') - .then(([event]) => verifyResponseReceived(event, { url })); - - const loadingFinishedFuture = once(session, 'Network.loadingFinished') - .then(([event]) => verifyLoadingFinished(event)); + const { + requestWillBeSentFuture, + responseReceivedFuture, + loadingFinishedFuture, + } = createRequestTracker(url, getDefaultResponseExpect(url)); https.get({ host: '127.0.0.1', @@ -208,12 +336,7 @@ async function testHttpsGet() { const delta = (loadingFinished.timestamp - responseReceived.timestamp) * 1000; assert.ok(delta > kDelta); - - const responseBody = await session.post('Network.getResponseBody', { - requestId: responseReceived.requestId, - }); - assert.strictEqual(responseBody.base64Encoded, false); - assert.strictEqual(responseBody.body, '\nhello world\n'); + await assertResponseBody(responseReceived, '\nhello world\n'); } async function testHttpError() { @@ -257,6 +380,10 @@ async function testHttpsError() { const testNetworkInspection = async () => { await testHttpGet(); session.removeAllListeners(); + await testHttpGetWithAbsoluteUrlPath(); + session.removeAllListeners(); + await testHttpPostWithAbsoluteUrlPath(); + session.removeAllListeners(); await testHttpsGet(); session.removeAllListeners(); await testHttpError();