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 +4 -4
- data/README.md +2 -1
- data/lib/grpc_server_reflection/request_handler.rb +146 -0
- data/lib/grpc_server_reflection/service.rb +3 -146
- data/lib/grpc_server_reflection/v1alpha_service.rb +11 -0
- data/lib/grpc_server_reflection/version.rb +1 -1
- data/lib/grpc_server_reflection.rb +15 -0
- metadata +11 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 21336865d9ba96adae1592262834991d29077233e343347ae086e7ad7f0da8de
|
|
4
|
+
data.tar.gz: c06426b8f3e81ad7399a2f15c0ed7afd2fdb8322b2325845981e9cd4830ae671
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
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
|
|
50
|
-
|
|
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
|
|
@@ -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.
|
|
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-
|
|
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
|
|
98
|
-
|
|
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
|
|
146
|
+
summary: gRPC Server Reflection Protocol for Ruby
|
|
145
147
|
test_files: []
|