1
1
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' ;
3
7
4
8
import * as grpc from '@grpc/grpc-js' ;
5
9
import * as protoLoader from '@grpc/proto-loader' ;
@@ -40,6 +44,9 @@ export class ReflectionV1Implementation {
40
44
/** A graph of file dependencies */
41
45
private readonly fileDependencies = new Map < IFileDescriptorProto , IFileDescriptorProto [ ] > ( ) ;
42
46
47
+ /** Pre-computed encoded-versions of each file */
48
+ private readonly fileEncodings = new Map < IFileDescriptorProto , Uint8Array > ( ) ;
49
+
43
50
/** An index of proto files by type extension relationship
44
51
*
45
52
* extensionIndex[<pkg>.<msg>][<field#>] contains a reference to the file containing an
@@ -50,12 +57,11 @@ export class ReflectionV1Implementation {
50
57
/** An index of fully qualified symbol names (eg. 'sample.Message') to the files that contain them */
51
58
private readonly symbols : Record < string , IFileDescriptorProto > = { } ;
52
59
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 > = { } ;
55
62
56
- constructor ( root : protoLoader . PackageDefinition , options ?: ReflectionServerOptions ) {
57
- this . options = options ;
58
63
64
+ constructor ( root : protoLoader . PackageDefinition , options ?: ReflectionServerOptions ) {
59
65
Object . values ( root ) . forEach ( ( { fileDescriptorProtos } ) => {
60
66
if ( Array . isArray ( fileDescriptorProtos ) ) { // we use an array check to narrow the type
61
67
fileDescriptorProtos . forEach ( ( bin ) => {
@@ -69,16 +75,23 @@ export class ReflectionV1Implementation {
69
75
} ) ;
70
76
71
77
// Pass 1: Index Values
78
+ const serviceWhitelist = new Set ( options ?. services ) ;
72
79
const index = ( fqn : string , file : IFileDescriptorProto ) => ( this . symbols [ fqn ] = file ) ;
73
80
Object . values ( this . files ) . forEach ( ( file ) =>
74
81
visit ( file , {
75
82
field : index ,
76
83
oneOf : index ,
77
84
message : index ,
78
- service : index ,
79
85
method : index ,
80
86
enum : index ,
81
87
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
+ } ,
82
95
extension : ( fqn , file , ext ) => {
83
96
index ( fqn , file ) ;
84
97
@@ -137,6 +150,11 @@ export class ReflectionV1Implementation {
137
150
} ,
138
151
} ) ,
139
152
) ;
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
+ } ) ;
140
158
}
141
159
142
160
addToServer ( server : Pick < grpc . Server , 'addService' > ) {
@@ -217,19 +235,7 @@ export class ReflectionV1Implementation {
217
235
* @returns full-qualified service names (eg. 'sample.SampleService')
218
236
*/
219
237
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 } ) ) } ;
233
239
}
234
240
235
241
/** Find the proto file(s) that declares the given fully-qualified symbol name
@@ -249,7 +255,7 @@ export class ReflectionV1Implementation {
249
255
const deps = this . getFileDependencies ( file ) ;
250
256
251
257
return {
252
- fileDescriptorProto : [ file , ...deps ] . map ( ( proto ) => FileDescriptorProto . encode ( proto ) . finish ( ) ) ,
258
+ fileDescriptorProto : [ file , ...deps ] . map ( ( file ) => this . fileEncodings . get ( file ) || new Uint8Array ( ) )
253
259
} ;
254
260
}
255
261
@@ -267,7 +273,7 @@ export class ReflectionV1Implementation {
267
273
const deps = this . getFileDependencies ( file ) ;
268
274
269
275
return {
270
- fileDescriptorProto : [ file , ...deps ] . map ( ( f ) => FileDescriptorProto . encode ( f ) . finish ( ) ) ,
276
+ fileDescriptorProto : [ file , ...deps ] . map ( ( file ) => this . fileEncodings . get ( file ) || new Uint8Array ) ,
271
277
} ;
272
278
}
273
279
@@ -289,7 +295,7 @@ export class ReflectionV1Implementation {
289
295
const deps = this . getFileDependencies ( file ) ;
290
296
291
297
return {
292
- fileDescriptorProto : [ file , ...deps ] . map ( ( f ) => FileDescriptorProto . encode ( f ) . finish ( ) ) ,
298
+ fileDescriptorProto : [ file , ...deps ] . map ( ( file ) => this . fileEncodings . get ( file ) || new Uint8Array ( ) ) ,
293
299
} ;
294
300
}
295
301
0 commit comments