From a62999ac7039b22aca08ad978aa09228f03385ad Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Wed, 29 Jun 2016 23:20:06 +0200 Subject: [PATCH] src: add /json/protocol endpoint to inspector Embed the compressed and minified protocol.json from the bundled v8_inspector and make it available through the /json/protocol endpoint. Refs: https://github.com/nodejs/diagnostics/issues/52 PR-URL: https://github.com/nodejs/node/pull/7491 Reviewed-By: Colin Ihrig Reviewed-By: James M Snell --- deps/zlib/zlib.gyp | 4 +++ node.gyp | 29 +++++++++++++++++++ src/inspector_agent.cc | 28 ++++++++++++++++++ test/common.js | 1 + .../test-v8-inspector-json-protocol.js | 22 ++++++++++++++ test/testpy/__init__.py | 5 +++- tools/compress_json.py | 25 ++++++++++++++++ 7 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-v8-inspector-json-protocol.js create mode 100644 tools/compress_json.py diff --git a/deps/zlib/zlib.gyp b/deps/zlib/zlib.gyp index cf0b090d9e..65133d5fce 100644 --- a/deps/zlib/zlib.gyp +++ b/deps/zlib/zlib.gyp @@ -12,6 +12,7 @@ { 'target_name': 'zlib', 'type': 'static_library', + 'defines': [ 'ZLIB_CONST' ], 'sources': [ 'adler32.c', 'compress.c', @@ -44,6 +45,7 @@ '.', ], 'direct_dependent_settings': { + 'defines': [ 'ZLIB_CONST' ], 'include_dirs': [ '.', ], @@ -72,10 +74,12 @@ 'direct_dependent_settings': { 'defines': [ 'USE_SYSTEM_ZLIB', + 'ZLIB_CONST', ], }, 'defines': [ 'USE_SYSTEM_ZLIB', + 'ZLIB_CONST', ], 'link_settings': { 'libraries': [ diff --git a/node.gyp b/node.gyp index 0f263d1346..4043269930 100644 --- a/node.gyp +++ b/node.gyp @@ -322,6 +322,7 @@ 'dependencies': [ 'deps/v8_inspector/third_party/v8_inspector/platform/' 'v8_inspector/v8_inspector.gyp:v8_inspector_stl', + 'v8_inspector_compress_protocol_json#host', ], 'include_dirs': [ 'deps/v8_inspector/third_party/v8_inspector', @@ -650,6 +651,34 @@ } ] ] }, + { + 'target_name': 'v8_inspector_compress_protocol_json', + 'type': 'none', + 'toolsets': ['host'], + 'conditions': [ + [ 'v8_inspector=="true"', { + 'actions': [ + { + 'action_name': 'v8_inspector_compress_protocol_json', + 'process_outputs_as_sources': 1, + 'inputs': [ + 'deps/v8_inspector/third_party/' + 'v8_inspector/platform/v8_inspector/js_protocol.json', + ], + 'outputs': [ + '<(SHARED_INTERMEDIATE_DIR)/v8_inspector_protocol_json.h', + ], + 'action': [ + 'python', + 'tools/compress_json.py', + '<@(_inputs)', + '<@(_outputs)', + ], + }, + ], + }], + ], + }, { 'target_name': 'node_js2c', 'type': 'none', diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 774f001143..7dd83c7eaf 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -8,6 +8,7 @@ #include "node_version.h" #include "v8-platform.h" #include "util.h" +#include "zlib.h" #include "platform/v8_inspector/public/InspectorVersion.h" #include "platform/v8_inspector/public/V8Inspector.h" @@ -41,6 +42,10 @@ const char TAG_DISCONNECT[] = "#disconnect"; const char DEVTOOLS_PATH[] = "/node"; const char DEVTOOLS_HASH[] = V8_INSPECTOR_REVISION; +static const uint8_t PROTOCOL_JSON[] = { +#include "v8_inspector_protocol_json.h" // NOLINT(build/include_order) +}; + void PrintDebuggerReadyMessage(int port) { fprintf(stderr, "Debugger listening on port %d.\n" "Warning: This is an experimental feature and could change at any time.\n" @@ -161,6 +166,27 @@ void SendTargentsListResponse(InspectorSocket* socket, SendHttpResponse(socket, buffer.data(), len); } +void SendProtocolJson(InspectorSocket* socket) { + z_stream strm; + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + CHECK_EQ(Z_OK, inflateInit(&strm)); + static const size_t kDecompressedSize = + PROTOCOL_JSON[0] * 0x10000u + + PROTOCOL_JSON[1] * 0x100u + + PROTOCOL_JSON[2]; + strm.next_in = PROTOCOL_JSON + 3; + strm.avail_in = sizeof(PROTOCOL_JSON) - 3; + std::vector data(kDecompressedSize); + strm.next_out = reinterpret_cast(&data[0]); + strm.avail_out = data.size(); + CHECK_EQ(Z_STREAM_END, inflate(&strm, Z_FINISH)); + CHECK_EQ(0, strm.avail_out); + CHECK_EQ(Z_OK, inflateEnd(&strm)); + SendHttpResponse(socket, &data[0], data.size()); +} + const char* match_path_segment(const char* path, const char* expected) { size_t len = strlen(expected); if (StringEqualNoCaseN(path, expected, len)) { @@ -179,6 +205,8 @@ bool RespondToGet(InspectorSocket* socket, const std::string& script_name_, if (match_path_segment(command, "list") || command[0] == '\0') { SendTargentsListResponse(socket, script_name_, script_path_, port); + } else if (match_path_segment(command, "protocol")) { + SendProtocolJson(socket); } else if (match_path_segment(command, "version")) { SendVersionResponse(socket); } else { diff --git a/test/common.js b/test/common.js index 3eaaccacc9..5d226e29c5 100644 --- a/test/common.js +++ b/test/common.js @@ -16,6 +16,7 @@ exports.testDir = __dirname; exports.fixturesDir = path.join(exports.testDir, 'fixtures'); exports.libDir = path.join(exports.testDir, '../lib'); exports.tmpDirName = 'tmp'; +// PORT should match the definition in test/testpy/__init__.py. exports.PORT = +process.env.NODE_COMMON_PORT || 12346; exports.isWindows = process.platform === 'win32'; exports.isWOW64 = exports.isWindows && diff --git a/test/parallel/test-v8-inspector-json-protocol.js b/test/parallel/test-v8-inspector-json-protocol.js new file mode 100644 index 0000000000..762c4386bb --- /dev/null +++ b/test/parallel/test-v8-inspector-json-protocol.js @@ -0,0 +1,22 @@ +// Flags: --inspect={PORT} +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const http = require('http'); + +const options = { + path: '/json/protocol', + port: common.PORT, + host: common.localhostIPv4, +}; + +http.get(options, common.mustCall((res) => { + let body = ''; + res.setEncoding('utf8'); + res.on('data', (data) => body += data); + res.on('end', common.mustCall(() => { + assert(body.length > 0); + assert.deepStrictEqual(JSON.stringify(JSON.parse(body)), body); + })); +})); diff --git a/test/testpy/__init__.py b/test/testpy/__init__.py index 9fff0b969c..367346f6e1 100644 --- a/test/testpy/__init__.py +++ b/test/testpy/__init__.py @@ -61,7 +61,10 @@ class SimpleTestCase(test.TestCase): source = open(self.file).read() flags_match = FLAGS_PATTERN.search(source) if flags_match: - result += flags_match.group(1).strip().split() + # PORT should match the definition in test/common.js. + env = { 'PORT': int(os.getenv('NODE_COMMON_PORT', '12346')) } + env['PORT'] += self.thread_id * 100 + result += flags_match.group(1).strip().format(**env).split() files_match = FILES_PATTERN.search(source); additional_files = [] if files_match: diff --git a/tools/compress_json.py b/tools/compress_json.py new file mode 100644 index 0000000000..34dbb878c4 --- /dev/null +++ b/tools/compress_json.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +import json +import struct +import sys +import zlib + +if __name__ == '__main__': + fp = open(sys.argv[1]) + obj = json.load(fp) + text = json.dumps(obj, separators=(',', ':')) + data = zlib.compress(text, zlib.Z_BEST_COMPRESSION) + + # To make decompression a little easier, we prepend the compressed data + # with the size of the uncompressed data as a 24 bits BE unsigned integer. + assert len(text) < 1 << 24, 'Uncompressed JSON must be < 16 MB.' + data = struct.pack('>I', len(text))[1:4] + data + + step = 20 + slices = (data[i:i+step] for i in xrange(0, len(data), step)) + slices = map(lambda s: ','.join(str(ord(c)) for c in s), slices) + text = ',\n'.join(slices) + + fp = open(sys.argv[2], 'w') + fp.write(text)