Skip to content

Commit c53656d

Browse files
committed
refactor(grpc-reflection): precompute service list and file encodings
1 parent 234f7f0 commit c53656d

File tree

1 file changed

+28
-22
lines changed

1 file changed

+28
-22
lines changed

packages/grpc-reflection/src/implementations/reflection-v1.ts

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import * as path from 'path';
2-
import { FileDescriptorProto, IFileDescriptorProto } from 'protobufjs/ext/descriptor';
2+
import {
3+
FileDescriptorProto,
4+
IFileDescriptorProto,
5+
IServiceDescriptorProto
6+
} from 'protobufjs/ext/descriptor';
37

48
import * as grpc from '@grpc/grpc-js';
59
import * as protoLoader from '@grpc/proto-loader';
@@ -40,6 +44,9 @@ export class ReflectionV1Implementation {
4044
/** A graph of file dependencies */
4145
private readonly fileDependencies = new Map<IFileDescriptorProto, IFileDescriptorProto[]>();
4246

47+
/** Pre-computed encoded-versions of each file */
48+
private readonly fileEncodings = new Map<IFileDescriptorProto, Uint8Array>();
49+
4350
/** An index of proto files by type extension relationship
4451
*
4552
* extensionIndex[<pkg>.<msg>][<field#>] contains a reference to the file containing an
@@ -50,12 +57,11 @@ export class ReflectionV1Implementation {
5057
/** An index of fully qualified symbol names (eg. 'sample.Message') to the files that contain them */
5158
private readonly symbols: Record<string, IFileDescriptorProto> = {};
5259

53-
/** Options that the user provided for this service */
54-
private readonly options?: ReflectionServerOptions;
60+
/** An index of the services in the analyzed package(s) */
61+
private readonly services: Record<string, IServiceDescriptorProto> = {};
5562

56-
constructor(root: protoLoader.PackageDefinition, options?: ReflectionServerOptions) {
57-
this.options = options;
5863

64+
constructor(root: protoLoader.PackageDefinition, options?: ReflectionServerOptions) {
5965
Object.values(root).forEach(({ fileDescriptorProtos }) => {
6066
if (Array.isArray(fileDescriptorProtos)) { // we use an array check to narrow the type
6167
fileDescriptorProtos.forEach((bin) => {
@@ -69,16 +75,23 @@ export class ReflectionV1Implementation {
6975
});
7076

7177
// Pass 1: Index Values
78+
const serviceWhitelist = new Set(options?.services);
7279
const index = (fqn: string, file: IFileDescriptorProto) => (this.symbols[fqn] = file);
7380
Object.values(this.files).forEach((file) =>
7481
visit(file, {
7582
field: index,
7683
oneOf: index,
7784
message: index,
78-
service: index,
7985
method: index,
8086
enum: index,
8187
enumValue: index,
88+
service: (fqn, file, service) => {
89+
index(fqn, file);
90+
91+
if (options?.services === undefined || serviceWhitelist.has(fqn)) {
92+
this.services[fqn] = service;
93+
}
94+
},
8295
extension: (fqn, file, ext) => {
8396
index(fqn, file);
8497

@@ -137,6 +150,11 @@ export class ReflectionV1Implementation {
137150
},
138151
}),
139152
);
153+
154+
// Pass 3: pre-compute file encoding since that can be slow and is done frequently
155+
Object.values(this.files).forEach(file => {
156+
this.fileEncodings.set(file, FileDescriptorProto.encode(file).finish())
157+
});
140158
}
141159

142160
addToServer(server: Pick<grpc.Server, 'addService'>) {
@@ -217,19 +235,7 @@ export class ReflectionV1Implementation {
217235
* @returns full-qualified service names (eg. 'sample.SampleService')
218236
*/
219237
listServices(listServices: string): ListServiceResponse__Output {
220-
const services = Object.values(this.files)
221-
.map((file) =>
222-
file.service?.map((service) => `${file.package}.${service.name}`),
223-
)
224-
.flat()
225-
.filter((service): service is string => !!service);
226-
227-
const whitelist = new Set(this.options?.services ?? undefined);
228-
const exposedServices = whitelist.size ?
229-
services.filter(service => whitelist.has(service))
230-
: services;
231-
232-
return { service: exposedServices.map((service) => ({ name: service })) };
238+
return { service: Object.keys(this.services).map((service) => ({ name: service })) };
233239
}
234240

235241
/** Find the proto file(s) that declares the given fully-qualified symbol name
@@ -249,7 +255,7 @@ export class ReflectionV1Implementation {
249255
const deps = this.getFileDependencies(file);
250256

251257
return {
252-
fileDescriptorProto: [file, ...deps].map((proto) => FileDescriptorProto.encode(proto).finish()),
258+
fileDescriptorProto: [file, ...deps].map((file) => this.fileEncodings.get(file) || new Uint8Array())
253259
};
254260
}
255261

@@ -267,7 +273,7 @@ export class ReflectionV1Implementation {
267273
const deps = this.getFileDependencies(file);
268274

269275
return {
270-
fileDescriptorProto: [file, ...deps].map((f) => FileDescriptorProto.encode(f).finish()),
276+
fileDescriptorProto: [file, ...deps].map((file) => this.fileEncodings.get(file) || new Uint8Array),
271277
};
272278
}
273279

@@ -289,7 +295,7 @@ export class ReflectionV1Implementation {
289295
const deps = this.getFileDependencies(file);
290296

291297
return {
292-
fileDescriptorProto: [file, ...deps].map((f) => FileDescriptorProto.encode(f).finish()),
298+
fileDescriptorProto: [file, ...deps].map((file) => this.fileEncodings.get(file) || new Uint8Array()),
293299
};
294300
}
295301

0 commit comments

Comments
 (0)