mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-24 05:05:39 +00:00
Merge pull request #5653 from sanish-bruno/feat/support-grpc-v1-reflection
feat: support v1 reflection for grpc server reflection
This commit is contained in:
54
package-lock.json
generated
54
package-lock.json
generated
@@ -8232,12 +8232,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/google-protobuf": {
|
||||
"version": "3.15.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.15.12.tgz",
|
||||
"integrity": "sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
|
||||
@@ -8373,9 +8367,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
|
||||
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash-es": {
|
||||
@@ -8388,15 +8382,6 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash.set": {
|
||||
"version": "4.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.set/-/lodash.set-4.3.9.tgz",
|
||||
"integrity": "sha512-KOxyNkZpbaggVmqbpr82N2tDVTx05/3/j0f50Es1prxrWB0XYf9p3QNxqcbWb7P1Q9wlvsUSlCFnwlPCIJ46PQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "12.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
|
||||
@@ -15183,9 +15168,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/google-protobuf": {
|
||||
"version": "3.21.4",
|
||||
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz",
|
||||
"integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-4.0.0.tgz",
|
||||
"integrity": "sha512-b8wmenhUMf2WNL+xIJ/slvD/hEE6V3nRnG86O2bzkBrMweM9gnqZE1dfXlDjibY3aXJXDNbAHepevYyQ7qWKsQ==",
|
||||
"license": "(BSD-3-Clause AND Apache-2.0)"
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
@@ -15312,20 +15297,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/grpc-reflection-js": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/grpc-reflection-js/-/grpc-reflection-js-0.3.0.tgz",
|
||||
"integrity": "sha512-3lhTlQluPxVgbowCXA3tAZC3RJW+GSOUkguLNYl1QffYRiutUB3RDfPkQFTcrCFJgNiIIxx+iJkr8s3uSp3zWA==",
|
||||
"node_modules/grpc-js-reflection-client": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/grpc-js-reflection-client/-/grpc-js-reflection-client-1.3.0.tgz",
|
||||
"integrity": "sha512-eJ5/m1pXpcheSjOGExktU69WPUKnL4Su3IxGJYYYjy3/w19vE8dH7Wi46G5T92bpM0eZWftjiM5HduX8CjPq9w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/google-protobuf": "^3.7.2",
|
||||
"@types/lodash.set": "^4.3.6",
|
||||
"google-protobuf": "^3.12.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"protobufjs": "^7.2.2"
|
||||
"@types/lodash": "^4.17.15",
|
||||
"lodash": "^4.17.21",
|
||||
"protobufjs": "^7.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@grpc/grpc-js": "^1.0.0"
|
||||
"@grpc/grpc-js": "^1.12.6"
|
||||
}
|
||||
},
|
||||
"node_modules/har-schema": {
|
||||
@@ -18634,12 +18617,6 @@
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.set": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
|
||||
"integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.uniq": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
|
||||
@@ -32129,7 +32106,8 @@
|
||||
"@types/qs": "^6.9.18",
|
||||
"axios": "^1.9.0",
|
||||
"debug": "^4.4.3",
|
||||
"grpc-reflection-js": "^0.3.0",
|
||||
"google-protobuf": "^4.0.0",
|
||||
"grpc-js-reflection-client": "^1.3.0",
|
||||
"is-ip": "^5.0.1",
|
||||
"system-ca": "^2.0.1",
|
||||
"tough-cookie": "^6.0.0",
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
"@types/qs": "^6.9.18",
|
||||
"axios": "^1.9.0",
|
||||
"debug": "^4.4.3",
|
||||
"grpc-reflection-js": "^0.3.0",
|
||||
"google-protobuf": "^4.0.0",
|
||||
"grpc-js-reflection-client": "^1.3.0",
|
||||
"is-ip": "^5.0.1",
|
||||
"ws": "^8.18.3",
|
||||
"system-ca": "^2.0.1",
|
||||
"tough-cookie": "^6.0.0"
|
||||
"tough-cookie": "^6.0.0",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.22.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { makeGenericClientConstructor, ChannelCredentials, Metadata } from '@grpc/grpc-js';
|
||||
import * as grpcReflection from 'grpc-reflection-js';
|
||||
import { makeGenericClientConstructor, ChannelCredentials, Metadata, status, credentials, CallCredentials } from '@grpc/grpc-js';
|
||||
import { GrpcReflection } from 'grpc-js-reflection-client';
|
||||
import * as protoLoader from '@grpc/proto-loader';
|
||||
import { generateGrpcSampleMessage } from './grpcMessageGenerator';
|
||||
import * as tls from 'tls';
|
||||
@@ -34,6 +34,8 @@ const configOptions = {
|
||||
json: true
|
||||
};
|
||||
|
||||
const reflectionServices = ['grpc.reflection.v1alpha.ServerReflection', 'grpc.reflection.v1.ServerReflection'];
|
||||
|
||||
const replaceTabsWithSpaces = (str, numSpaces = 2) => {
|
||||
if (!str || !str.length || !isString(str)) {
|
||||
return '';
|
||||
@@ -172,6 +174,51 @@ class GrpcClient {
|
||||
this.eventCallback = eventCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates call options from metadata for gRPC calls
|
||||
* @param {grpc.Metadata} metadata - metadata to be sent with calls
|
||||
* @returns {Object} callOptions object with credentials if metadata is provided
|
||||
*/
|
||||
#createCallOptions(metadata) {
|
||||
if (metadata && Object.keys(metadata.getMap()).length > 0) {
|
||||
// Create CallCredentials from metadata generator
|
||||
const callCredentials = CallCredentials.createFromMetadataGenerator((options, callback) => {
|
||||
callback(null, metadata);
|
||||
});
|
||||
return { credentials: callCredentials };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reflection client that works for v1, v1alpha, or both.
|
||||
*
|
||||
* @param {string} host - host:port of the gRPC server
|
||||
* @param {grpc.ChannelCredentials} credentials - defaults to insecure
|
||||
* @param {grpc.Metadata} metadata - metadata to be sent with reflection calls (used for insecure connections where credentials can't include metadata)
|
||||
* @param {grpc.ChannelOptions} options - channel options
|
||||
* @returns {Promise<{ client: GrpcReflection, services: string[], callOptions: Object }>}
|
||||
*/
|
||||
async #getReflectionClient(host, credentials = ChannelCredentials.createInsecure(), metadata = null, options = {}) {
|
||||
const makeClient = (version) => new GrpcReflection(host, credentials, options, version);
|
||||
const callOptions = this.#createCallOptions(metadata);
|
||||
|
||||
let client;
|
||||
let services;
|
||||
|
||||
try {
|
||||
client = makeClient('v1');
|
||||
services = await client.listServices('*', callOptions);
|
||||
return { client, services, callOptions };
|
||||
} catch (e) {
|
||||
console.warn(`gRPC reflection v1 failed:`, e);
|
||||
}
|
||||
|
||||
client = makeClient('v1alpha');
|
||||
services = await client.listServices('*', callOptions);
|
||||
return { client, services, callOptions };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get method type based on streaming configuration
|
||||
*/
|
||||
@@ -241,9 +288,7 @@ class GrpcClient {
|
||||
return ChannelCredentials.createFromSecureContext(secureContext, sslOptions);
|
||||
}
|
||||
|
||||
const credentials = ChannelCredentials.createSsl(rootCertBuffer, privateKeyBuffer, clientCertBuffer, sslOptions);
|
||||
|
||||
return credentials;
|
||||
return ChannelCredentials.createSsl(rootCertBuffer, privateKeyBuffer, clientCertBuffer, sslOptions);
|
||||
} catch (error) {
|
||||
console.error('Error creating channel credentials:', error);
|
||||
// Default to insecure as fallback
|
||||
@@ -566,6 +611,11 @@ class GrpcClient {
|
||||
verifyOptions,
|
||||
sendEvent
|
||||
}) {
|
||||
const { host, path } = getParsedGrpcUrlObject(request.url);
|
||||
const metadata = new Metadata();
|
||||
Object.entries(request.headers).forEach(([name, value]) => {
|
||||
metadata.add(name, value);
|
||||
});
|
||||
const credentials = this.#getChannelCredentials({
|
||||
url: request.url,
|
||||
rootCertificate,
|
||||
@@ -575,38 +625,31 @@ class GrpcClient {
|
||||
pfx,
|
||||
verifyOptions
|
||||
});
|
||||
const { host, path } = getParsedGrpcUrlObject(request.url);
|
||||
const metadata = new Metadata();
|
||||
Object.entries(request.headers).forEach(([name, value]) => {
|
||||
metadata.add(name, value);
|
||||
});
|
||||
|
||||
try {
|
||||
const client = new grpcReflection.Client(host, credentials, {}, metadata);
|
||||
const { client, services, callOptions } = await this.#getReflectionClient(host, credentials, metadata, {});
|
||||
|
||||
const declarations = await client.listServices();
|
||||
const methods = await Promise.all(
|
||||
declarations.map(async (declaration) => {
|
||||
const fileContainingSymbol = await client.fileContainingSymbol(declaration);
|
||||
const descriptor = fileContainingSymbol.toDescriptor('proto3');
|
||||
const protoDefinition = protoLoader.loadFileDescriptorSetFromObject(descriptor, configOptions);
|
||||
const methods = [];
|
||||
for (const service of services) {
|
||||
if (reflectionServices.includes(service)) {
|
||||
continue;
|
||||
}
|
||||
const m = await client.listMethods(service, callOptions);
|
||||
methods.push(...m);
|
||||
}
|
||||
|
||||
const serviceDefinition = protoDefinition[declaration];
|
||||
if (!!serviceDefinition?.format) {
|
||||
return [];
|
||||
}
|
||||
const methods = Object.values(serviceDefinition);
|
||||
methods.forEach((method) => {
|
||||
this.methods.set(method.path, method);
|
||||
});
|
||||
return methods;
|
||||
})
|
||||
);
|
||||
|
||||
const methodsWithType = methods.flat().map((method) => ({
|
||||
...method,
|
||||
type: this.#getMethodType(method)
|
||||
}));
|
||||
const methodsWithType = methods.map((method) => {
|
||||
const { definition, ...rest } = method;
|
||||
const modifiedMethod = {
|
||||
...rest,
|
||||
...definition
|
||||
};
|
||||
modifiedMethod.type = this.#getMethodType(modifiedMethod);
|
||||
return modifiedMethod;
|
||||
});
|
||||
methodsWithType.forEach((method) => {
|
||||
this.methods.set(method.path, method);
|
||||
});
|
||||
return methodsWithType;
|
||||
} catch (error) {
|
||||
console.error('Error in gRPC reflection:', error);
|
||||
@@ -615,9 +658,6 @@ class GrpcClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load methods from proto file
|
||||
*/
|
||||
async loadMethodsFromProtoFile(filePath, includeDirs = []) {
|
||||
const protoDefinition = await protoLoader.load(filePath, { ...configOptions, includeDirs });
|
||||
const methods = Object.values(protoDefinition)
|
||||
|
||||
Reference in New Issue
Block a user