Repository: nodejs/node
Pull request: inspector: inspect HTTP response body

This adds support for inspecting HTTP response body. (Request body is not supported yet).

Refs: https://github.com/nodejs/node/issues/53946

Changed files:
filename: lib/internal/inspector/network_http.js
patch: @@ -17,6 +17,7 @@ const {
   sniffMimeType,
 } = require('internal/inspector/network');
 const { Network } = require('inspector');
+const EventEmitter = require('events');
 
 const kRequestUrl = Symbol('kRequestUrl');
 
@@ -120,6 +121,17 @@ function onClientResponseFinish({ request, response }) {
     },
   });
 
+  // Unlike response.on('data', ...), this does not put the stream into flowing mode.
+  EventEmitter.prototype.on.call(response, 'data', (chunk) => {
+    Network.dataReceived({
+      requestId: request[kInspectorRequestId],
+      timestamp: getMonotonicTime(),
+      dataLength: chunk.byteLength,
+      encodedDataLength: chunk.byteLength,
+      data: chunk,
+    });
+  });
+
   // Wait until the response body is consumed by user code.
   response.once('end', () => {
     Network.loadingFinished({

filename: test/parallel/test-inspector-network-http.js
patch: @@ -130,6 +130,27 @@ function verifyLoadingFailed({ method, params }) {
   assert.strictEqual(typeof params.errorText, 'string');
 }
 
+function verifyHttpResponse(response) {
+  assert.strictEqual(response.statusCode, 200);
+  const chunks = [];
+
+  // Verifies that the inspector does not put the response into flowing mode.
+  assert.strictEqual(response.readableFlowing, null);
+  // Verifies that the data listener may be added at a later time, and it can
+  // still observe the data in full.
+  queueMicrotask(() => {
+    response.on('data', (chunk) => {
+      chunks.push(chunk);
+    });
+    assert.strictEqual(response.readableFlowing, true);
+  });
+
+  response.on('end', common.mustCall(() => {
+    const body = Buffer.concat(chunks).toString();
+    assert.strictEqual(body, '\nhello world\n');
+  }));
+}
+
 async function testHttpGet() {
   const url = `http://127.0.0.1:${httpServer.address().port}/hello-world`;
   const requestWillBeSentFuture = once(session, 'Network.requestWillBeSent')
@@ -146,18 +167,20 @@ async function testHttpGet() {
     port: httpServer.address().port,
     path: '/hello-world',
     headers: requestHeaders
-  }, common.mustCall((res) => {
-    // Dump the response.
-    res.on('data', () => {});
-    res.on('end', () => {});
-  }));
+  }, common.mustCall(verifyHttpResponse));
 
   await requestWillBeSentFuture;
   const responseReceived = await responseReceivedFuture;
   const loadingFinished = await loadingFinishedFuture;
 
   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');
 }
 
 async function testHttpsGet() {
@@ -177,18 +200,20 @@ async function testHttpsGet() {
     path: '/hello-world',
     rejectUnauthorized: false,
     headers: requestHeaders,
-  }, common.mustCall((res) => {
-    // Dump the response.
-    res.on('data', () => {});
-    res.on('end', () => {});
-  }));
+  }, common.mustCall(verifyHttpResponse));
 
   await requestWillBeSentFuture;
   const responseReceived = await responseReceivedFuture;
   const loadingFinished = await loadingFinishedFuture;
 
   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');
 }
 
 async function testHttpError() {