grpc-server-reflection 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ea2d3f5a48656af40960816f018562d38151cc067899fcd1ff22863c62b4cb9
4
- data.tar.gz: 6f4abd7208859c1e297ce89f965e273414c25439b06dd2ac7e5600bd26ebf875
3
+ metadata.gz: 21336865d9ba96adae1592262834991d29077233e343347ae086e7ad7f0da8de
4
+ data.tar.gz: c06426b8f3e81ad7399a2f15c0ed7afd2fdb8322b2325845981e9cd4830ae671
5
5
  SHA512:
6
- metadata.gz: aee60ebbad98713df2cda9d31fc63e52741d5740b19046ba13cdc4e3cfb9d397df2a7ec3f64766baac1471652eb71700abe9ff045c197c64ceac70a780bde1e4
7
- data.tar.gz: 9681fa4ff9aedeb9f73693e92b3bd478e6e8f8a8bc29f080b6a792d2899d1efc3cc7d3d89a2bc0f336c6c246abc49c79ef0138f848d64381090e902d38b97d0f
6
+ metadata.gz: b282d30924471a6e0010a5d0eb9df38865f65bd7287afcd771b22afa4cec6612e51830de2f3873f5a92a8cfaf76bc12f1414888ce52f7027155ff2eade0f41ff
7
+ data.tar.gz: 854f8bd7f859dcce794bb4180069cc56969d17efcefc38f05e4e036dccafe6bd43f1fc7410240da0b9d193fc4c21e3d36564e21486c6df9a5476bac3a8f49828
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # grpc-server-reflection
2
2
 
3
- Ruby gem implementing the [gRPC Server Reflection Protocol v1](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md). Enables tools like `grpcurl`, `grpcui`, and Postman to introspect your gRPC services without `.proto` files.
3
+ Ruby gem implementing the [gRPC Server Reflection Protocol](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md). Enables tools like `grpcurl`, `grpcui`, and Postman to introspect your gRPC services without `.proto` files.
4
4
 
5
5
  ## Installation
6
6
 
@@ -19,6 +19,7 @@ server = GRPC::RpcServer.new
19
19
  server.add_http2_port('0.0.0.0:50051', :this_port_is_insecure)
20
20
  server.handle(MyApp::GreeterService)
21
21
  server.handle(GrpcServerReflection::Service) # Add reflection
22
+ server.handle(GrpcServerReflection::V1AlphaService) # Add v1alpha reflection
22
23
  server.run
23
24
  ```
24
25
 
@@ -0,0 +1,146 @@
1
+ module GrpcServerReflection
2
+ module RequestHandler
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def registry_for(allowed)
9
+ @mutex ||= Mutex.new
10
+ @mutex.synchronize do
11
+ cache_key = allowed ? allowed.sort : nil
12
+ if @registry_cache_key != cache_key
13
+ @registry = nil
14
+ @registry_cache_key = cache_key
15
+ end
16
+ @registry ||= DescriptorRegistry.new(allowed_service_names: allowed)
17
+ end
18
+ end
19
+
20
+ def reset_registry!
21
+ @mutex ||= Mutex.new
22
+ @mutex.synchronize do
23
+ @registry = nil
24
+ @registry_cache_key = nil
25
+ end
26
+ end
27
+ end
28
+
29
+ def server_reflection_info(requests, call)
30
+ registry = self.class.registry_for(allowed_service_names(call))
31
+
32
+ Enumerator.new do |yielder|
33
+ requests.each do |request|
34
+ response = handle_request(request, registry)
35
+ yielder << response
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def proto_module
43
+ raise NotImplementedError
44
+ end
45
+
46
+ def allowed_service_names(call)
47
+ if GrpcServerReflection.services
48
+ return GrpcServerReflection.services.map(&:service_name).compact
49
+ end
50
+
51
+ server = find_server(call)
52
+ return nil unless server
53
+
54
+ rpc_descs = server.instance_variable_get(:@rpc_descs)
55
+ return nil unless rpc_descs
56
+
57
+ rpc_descs.keys.map do |key|
58
+ parts = key.to_s.split('/')
59
+ parts[1] if parts.length >= 3
60
+ end.compact.uniq
61
+ end
62
+
63
+ def find_server(call)
64
+ return nil unless call
65
+ ObjectSpace.each_object(GRPC::RpcServer).first
66
+ rescue
67
+ nil
68
+ end
69
+
70
+ def handle_request(request, registry)
71
+ response = proto_module::ServerReflectionResponse.new(
72
+ valid_host: request.host,
73
+ original_request: request
74
+ )
75
+
76
+ case request.message_request
77
+ when :list_services
78
+ handle_list_services(response, registry)
79
+ when :file_by_filename
80
+ handle_file_by_filename(response, request.file_by_filename, registry)
81
+ when :file_containing_symbol
82
+ handle_file_containing_symbol(response, request.file_containing_symbol, registry)
83
+ when :file_containing_extension
84
+ handle_file_containing_extension(response, request.file_containing_extension, registry)
85
+ when :all_extension_numbers_of_type
86
+ handle_all_extension_numbers(response, request.all_extension_numbers_of_type, registry)
87
+ else
88
+ handle_error(response, GRPC::Core::StatusCodes::UNIMPLEMENTED, 'Request not implemented')
89
+ end
90
+
91
+ response
92
+ end
93
+
94
+ def handle_list_services(response, registry)
95
+ services = registry.list_services.map do |name|
96
+ proto_module::ServiceResponse.new(name: name)
97
+ end
98
+ response.list_services_response = proto_module::ListServiceResponse.new(
99
+ service: services
100
+ )
101
+ end
102
+
103
+ def handle_file_by_filename(response, filename, registry)
104
+ descriptors = registry.file_descriptors_with_dependencies(filename)
105
+ if descriptors.empty?
106
+ handle_error(response, GRPC::Core::StatusCodes::NOT_FOUND, "File not found: #{filename}")
107
+ else
108
+ response.file_descriptor_response = proto_module::FileDescriptorResponse.new(
109
+ file_descriptor_proto: descriptors
110
+ )
111
+ end
112
+ end
113
+
114
+ def handle_file_containing_symbol(response, symbol, registry)
115
+ filename = registry.find_filename_by_symbol(symbol)
116
+ if filename.nil?
117
+ handle_error(response, GRPC::Core::StatusCodes::NOT_FOUND, "Symbol not found: #{symbol}")
118
+ else
119
+ descriptors = registry.file_descriptors_with_dependencies(filename)
120
+ response.file_descriptor_response = proto_module::FileDescriptorResponse.new(
121
+ file_descriptor_proto: descriptors
122
+ )
123
+ end
124
+ end
125
+
126
+ def handle_file_containing_extension(response, ext_request, registry)
127
+ handle_error(response, GRPC::Core::StatusCodes::NOT_FOUND,
128
+ "Extension not found for type: #{ext_request.containing_type}, number: #{ext_request.extension_number}")
129
+ end
130
+
131
+ def handle_all_extension_numbers(response, type_name, registry)
132
+ numbers = registry.find_extension_numbers(type_name)
133
+ response.all_extension_numbers_response = proto_module::ExtensionNumberResponse.new(
134
+ base_type_name: type_name,
135
+ extension_number: numbers
136
+ )
137
+ end
138
+
139
+ def handle_error(response, code, message)
140
+ response.error_response = proto_module::ErrorResponse.new(
141
+ error_code: code,
142
+ error_message: message
143
+ )
144
+ end
145
+ end
146
+ end
@@ -1,154 +1,11 @@
1
1
  module GrpcServerReflection
2
- class << self
3
- # Manually set which services to reflect.
4
- # If not set, auto-detects from the server's registered handlers.
5
- #
6
- # GrpcServerReflection.services = [MyService, OtherService]
7
- # s.handle(GrpcServerReflection::Service)
8
- #
9
- attr_accessor :services
10
- end
11
-
12
2
  class Service < Grpc::Reflection::V1::ServerReflection::Service
13
- def server_reflection_info(requests, call)
14
- registry = self.class.registry_for(allowed_service_names(call))
15
-
16
- Enumerator.new do |yielder|
17
- requests.each do |request|
18
- response = handle_request(request, registry)
19
- yielder << response
20
- end
21
- end
22
- end
23
-
24
- # Class-level registry cache. Reset with `GrpcServerReflection::Service.reset_registry!`
25
- class << self
26
- def registry_for(allowed)
27
- @mutex ||= Mutex.new
28
- @mutex.synchronize do
29
- cache_key = allowed ? allowed.sort : nil
30
- if @registry_cache_key != cache_key
31
- @registry = nil
32
- @registry_cache_key = cache_key
33
- end
34
- @registry ||= DescriptorRegistry.new(allowed_service_names: allowed)
35
- end
36
- end
37
-
38
- def reset_registry!
39
- @mutex ||= Mutex.new
40
- @mutex.synchronize do
41
- @registry = nil
42
- @registry_cache_key = nil
43
- end
44
- end
45
- end
3
+ include RequestHandler
46
4
 
47
5
  private
48
6
 
49
- def allowed_service_names(call)
50
- # Manual override takes priority
51
- if GrpcServerReflection.services
52
- return GrpcServerReflection.services.map(&:service_name).compact
53
- end
54
-
55
- # Auto-detect from server's registered RPCs
56
- server = find_server(call)
57
- return nil unless server
58
-
59
- rpc_descs = server.instance_variable_get(:@rpc_descs)
60
- return nil unless rpc_descs
61
-
62
- rpc_descs.keys.map do |key|
63
- parts = key.to_s.split('/')
64
- parts[1] if parts.length >= 3
65
- end.compact.uniq
66
- end
67
-
68
- def find_server(call)
69
- return nil unless call
70
-
71
- # Try to find the server via ObjectSpace
72
- # In a running gRPC server, there's typically one RpcServer instance
73
- ObjectSpace.each_object(GRPC::RpcServer).first
74
- rescue
75
- nil
76
- end
77
-
78
- def handle_request(request, registry)
79
- response = Grpc::Reflection::V1::ServerReflectionResponse.new(
80
- valid_host: request.host,
81
- original_request: request
82
- )
83
-
84
- case request.message_request
85
- when :list_services
86
- handle_list_services(response, registry)
87
- when :file_by_filename
88
- handle_file_by_filename(response, request.file_by_filename, registry)
89
- when :file_containing_symbol
90
- handle_file_containing_symbol(response, request.file_containing_symbol, registry)
91
- when :file_containing_extension
92
- handle_file_containing_extension(response, request.file_containing_extension, registry)
93
- when :all_extension_numbers_of_type
94
- handle_all_extension_numbers(response, request.all_extension_numbers_of_type, registry)
95
- else
96
- handle_error(response, GRPC::Core::StatusCodes::UNIMPLEMENTED, 'Request not implemented')
97
- end
98
-
99
- response
100
- end
101
-
102
- def handle_list_services(response, registry)
103
- services = registry.list_services.map do |name|
104
- Grpc::Reflection::V1::ServiceResponse.new(name: name)
105
- end
106
- response.list_services_response = Grpc::Reflection::V1::ListServiceResponse.new(
107
- service: services
108
- )
109
- end
110
-
111
- def handle_file_by_filename(response, filename, registry)
112
- descriptors = registry.file_descriptors_with_dependencies(filename)
113
- if descriptors.empty?
114
- handle_error(response, GRPC::Core::StatusCodes::NOT_FOUND, "File not found: #{filename}")
115
- else
116
- response.file_descriptor_response = Grpc::Reflection::V1::FileDescriptorResponse.new(
117
- file_descriptor_proto: descriptors
118
- )
119
- end
120
- end
121
-
122
- def handle_file_containing_symbol(response, symbol, registry)
123
- filename = registry.find_filename_by_symbol(symbol)
124
- if filename.nil?
125
- handle_error(response, GRPC::Core::StatusCodes::NOT_FOUND, "Symbol not found: #{symbol}")
126
- else
127
- descriptors = registry.file_descriptors_with_dependencies(filename)
128
- response.file_descriptor_response = Grpc::Reflection::V1::FileDescriptorResponse.new(
129
- file_descriptor_proto: descriptors
130
- )
131
- end
132
- end
133
-
134
- def handle_file_containing_extension(response, ext_request, registry)
135
- handle_error(response, GRPC::Core::StatusCodes::NOT_FOUND,
136
- "Extension not found for type: #{ext_request.containing_type}, number: #{ext_request.extension_number}")
137
- end
138
-
139
- def handle_all_extension_numbers(response, type_name, registry)
140
- numbers = registry.find_extension_numbers(type_name)
141
- response.all_extension_numbers_response = Grpc::Reflection::V1::ExtensionNumberResponse.new(
142
- base_type_name: type_name,
143
- extension_number: numbers
144
- )
145
- end
146
-
147
- def handle_error(response, code, message)
148
- response.error_response = Grpc::Reflection::V1::ErrorResponse.new(
149
- error_code: code,
150
- error_message: message
151
- )
7
+ def proto_module
8
+ Grpc::Reflection::V1
152
9
  end
153
10
  end
154
11
  end
@@ -0,0 +1,11 @@
1
+ module GrpcServerReflection
2
+ class V1AlphaService < Grpc::Reflection::V1alpha::ServerReflection::Service
3
+ include RequestHandler
4
+
5
+ private
6
+
7
+ def proto_module
8
+ Grpc::Reflection::V1alpha
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module GrpcServerReflection
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
@@ -1,6 +1,21 @@
1
1
  require 'grpc'
2
2
  require 'google/protobuf'
3
3
  require 'grpc/reflection/v1/reflection_services_pb'
4
+ require 'grpc/reflection/v1alpha/reflection_services_pb'
4
5
  require_relative 'grpc_server_reflection/version'
5
6
  require_relative 'grpc_server_reflection/descriptor_registry'
7
+ require_relative 'grpc_server_reflection/request_handler'
6
8
  require_relative 'grpc_server_reflection/service'
9
+ require_relative 'grpc_server_reflection/v1alpha_service'
10
+
11
+ module GrpcServerReflection
12
+ class << self
13
+ # Manually set which services to reflect.
14
+ # If not set, auto-detects from the server's registered handlers.
15
+ #
16
+ # GrpcServerReflection.services = [MyService, OtherService]
17
+ # s.handle(GrpcServerReflection::Service)
18
+ #
19
+ attr_accessor :services
20
+ end
21
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grpc-server-reflection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - zbokostya
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-04 00:00:00.000000000 Z
11
+ date: 2026-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grpc
@@ -94,9 +94,9 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.0'
97
- description: Implements the gRPC Server Reflection Protocol v1, enabling tools like
98
- grpcurl and Postman to introspect gRPC services.
99
- email:
97
+ description: Implements the gRPC Server Reflection Protocol, enabling tools like grpcurl
98
+ and Postman to introspect gRPC services.
99
+ email:
100
100
  executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
@@ -116,13 +116,15 @@ files:
116
116
  - lib/grpc_server_reflection/descriptor_registry/object_space_indexer.rb
117
117
  - lib/grpc_server_reflection/descriptor_registry/proto_builder.rb
118
118
  - lib/grpc_server_reflection/descriptor_registry/type_mapping.rb
119
+ - lib/grpc_server_reflection/request_handler.rb
119
120
  - lib/grpc_server_reflection/service.rb
121
+ - lib/grpc_server_reflection/v1alpha_service.rb
120
122
  - lib/grpc_server_reflection/version.rb
121
123
  homepage: https://github.com/zbokostya/ruby-grpc-server-reflection
122
124
  licenses:
123
125
  - MIT
124
126
  metadata: {}
125
- post_install_message:
127
+ post_install_message:
126
128
  rdoc_options: []
127
129
  require_paths:
128
130
  - lib
@@ -139,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
141
  version: '0'
140
142
  requirements: []
141
143
  rubygems_version: 3.1.6
142
- signing_key:
144
+ signing_key:
143
145
  specification_version: 4
144
- summary: gRPC Server Reflection Protocol v1 for Ruby
146
+ summary: gRPC Server Reflection Protocol for Ruby
145
147
  test_files: []