Skip to content

Commit c54f247

Browse files
jen20sgg
andauthored
feat: Implement gRPC Reflection Service (#340)
Co-authored-by: Samani G. Gikandi <[email protected]>
1 parent f49d4bd commit c54f247

File tree

15 files changed

+1524
-1
lines changed

15 files changed

+1524
-1
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"tonic-build",
55
"tonic-health",
66
"tonic-types",
7+
"tonic-reflection",
78

89
# Non-published crates
910
"examples",
@@ -18,3 +19,4 @@ members = [
1819
"tests/extern_path/my_application",
1920
"tests/integration_tests"
2021
]
22+

examples/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ path = "src/hyper_warp/server.rs"
130130
name = "health-server"
131131
path = "src/health/server.rs"
132132

133+
[[bin]]
134+
name = "reflection-server"
135+
path = "src/reflection/server.rs"
136+
133137
[[bin]]
134138
name = "autoreload-server"
135139
path = "src/autoreload/server.rs"
@@ -173,6 +177,8 @@ http-body = "0.4"
173177
pin-project = "1.0"
174178
# Health example
175179
tonic-health = { path = "../tonic-health" }
180+
# Reflection example
181+
tonic-reflection = { path = "../tonic-reflection" }
176182
listenfd = "0.3"
177183

178184
[build-dependencies]

examples/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ $ cargo run --bin tls-server
9494
$ cargo run --bin health-server
9595
```
9696

97+
## Server Reflection
98+
99+
### Server
100+
```bash
101+
$ cargo run --bin reflection-server
102+
```
103+
97104
## Tower Middleware
98105

99106
### Server

examples/build.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
14
fn main() {
25
tonic_build::configure()
36
.type_attribute("routeguide.Point", "#[derive(Hash)]")
47
.compile(&["proto/routeguide/route_guide.proto"], &["proto"])
58
.unwrap();
69

7-
tonic_build::compile_protos("proto/helloworld/helloworld.proto").unwrap();
10+
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
11+
tonic_build::configure()
12+
.file_descriptor_set_path(out_dir.join("helloworld_descriptor.bin"))
13+
.compile(&["proto/helloworld/helloworld.proto"], &["proto"])
14+
.unwrap();
15+
816
tonic_build::compile_protos("proto/echo/echo.proto").unwrap();
917

1018
tonic_build::configure()

examples/src/reflection/server.rs

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use tonic::transport::Server;
2+
use tonic::{Request, Response, Status};
3+
4+
mod proto {
5+
tonic::include_proto!("helloworld");
6+
7+
pub(crate) const FILE_DESCRIPTOR_SET: &'static [u8] =
8+
tonic::include_file_descriptor_set!("helloworld_descriptor");
9+
}
10+
11+
#[derive(Default)]
12+
pub struct MyGreeter {}
13+
14+
#[tonic::async_trait]
15+
impl proto::greeter_server::Greeter for MyGreeter {
16+
async fn say_hello(
17+
&self,
18+
request: Request<proto::HelloRequest>,
19+
) -> Result<Response<proto::HelloReply>, Status> {
20+
println!("Got a request from {:?}", request.remote_addr());
21+
22+
let reply = proto::HelloReply {
23+
message: format!("Hello {}!", request.into_inner().name),
24+
};
25+
Ok(Response::new(reply))
26+
}
27+
}
28+
29+
#[tokio::main]
30+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
31+
let service = tonic_reflection::server::Builder::configure()
32+
.register_encoded_file_descriptor_set(proto::FILE_DESCRIPTOR_SET)
33+
.build()
34+
.unwrap();
35+
36+
let addr = "[::1]:50052".parse().unwrap();
37+
let greeter = MyGreeter::default();
38+
39+
Server::builder()
40+
.add_service(service)
41+
.add_service(proto::greeter_server::GreeterServer::new(greeter))
42+
.serve(addr)
43+
.await?;
44+
45+
Ok(())
46+
}

tonic-build/src/prost.rs

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub fn configure() -> Builder {
1212
Builder {
1313
build_client: true,
1414
build_server: true,
15+
file_descriptor_set_path: None,
1516
out_dir: None,
1617
extern_path: Vec::new(),
1718
field_attributes: Vec::new(),
@@ -189,6 +190,7 @@ impl prost_build::ServiceGenerator for ServiceGenerator {
189190
pub struct Builder {
190191
pub(crate) build_client: bool,
191192
pub(crate) build_server: bool,
193+
pub(crate) file_descriptor_set_path: Option<PathBuf>,
192194
pub(crate) extern_path: Vec<(String, String)>,
193195
pub(crate) field_attributes: Vec<(String, String)>,
194196
pub(crate) type_attributes: Vec<(String, String)>,
@@ -213,6 +215,13 @@ impl Builder {
213215
self
214216
}
215217

218+
/// Generate a file containing the encoded `prost_types::FileDescriptorSet` for protocol buffers
219+
/// modules. This is required for implementing gRPC Server Reflection.
220+
pub fn file_descriptor_set_path(mut self, path: impl AsRef<Path>) -> Self {
221+
self.file_descriptor_set_path = Some(path.as_ref().to_path_buf());
222+
self
223+
}
224+
216225
/// Enable the output to be formated by rustfmt.
217226
#[cfg(feature = "rustfmt")]
218227
pub fn format(mut self, run: bool) -> Self {
@@ -305,6 +314,9 @@ impl Builder {
305314
let format = self.format;
306315

307316
config.out_dir(out_dir.clone());
317+
if let Some(path) = self.file_descriptor_set_path.as_ref() {
318+
config.file_descriptor_set_path(path);
319+
}
308320
for (proto_path, rust_path) in self.extern_path.iter() {
309321
config.extern_path(proto_path, rust_path);
310322
}

tonic-reflection/Cargo.toml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "tonic-reflection"
3+
version = "0.1.0"
4+
authors = [
5+
"James Nugent <[email protected]>",
6+
"Samani G. Gikandi <[email protected]>"
7+
]
8+
edition = "2018"
9+
license = "MIT"
10+
repository = "https://github.com/hyperium/tonic"
11+
homepage = "https://github.com/hyperium/tonic"
12+
description = """
13+
Server Reflection module of `tonic` gRPC implementation.
14+
"""
15+
readme = "README.md"
16+
categories = ["network-programming", "asynchronous"]
17+
keywords = ["rpc", "grpc", "async", "reflection"]
18+
19+
[dependencies]
20+
bytes = "1.0"
21+
prost = "0.7"
22+
prost-types = "0.7"
23+
tokio = { version = "1.0", features = ["sync"] }
24+
tokio-stream = { version = "0.1", features = ["net"] }
25+
tonic = { version = "0.4", path = "../tonic", features = ["codegen", "prost"] }
26+
27+
[build-dependencies]
28+
tonic-build = { version = "0.4", path = "../tonic-build" }
29+
30+
[dev-dependencies]
31+
futures = "0.3"
32+
futures-util = "0.3"

tonic-reflection/build.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
4+
fn main() -> Result<(), Box<dyn std::error::Error>> {
5+
let reflection_descriptor =
6+
PathBuf::from(env::var("OUT_DIR").unwrap()).join("reflection_v1alpha1.bin");
7+
8+
tonic_build::configure()
9+
.file_descriptor_set_path(&reflection_descriptor)
10+
.build_server(true)
11+
.build_client(true) // Client is only used for tests
12+
.format(true)
13+
.compile(&["proto/reflection.proto"], &["proto/"])?;
14+
15+
Ok(())
16+
}
+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2016 gRPC authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Service exported by server reflection
16+
17+
syntax = "proto3";
18+
19+
package grpc.reflection.v1alpha;
20+
21+
service ServerReflection {
22+
// The reflection service is structured as a bidirectional stream, ensuring
23+
// all related requests go to a single server.
24+
rpc ServerReflectionInfo(stream ServerReflectionRequest)
25+
returns (stream ServerReflectionResponse);
26+
}
27+
28+
// The message sent by the client when calling ServerReflectionInfo method.
29+
message ServerReflectionRequest {
30+
string host = 1;
31+
// To use reflection service, the client should set one of the following
32+
// fields in message_request. The server distinguishes requests by their
33+
// defined field and then handles them using corresponding methods.
34+
oneof message_request {
35+
// Find a proto file by the file name.
36+
string file_by_filename = 3;
37+
38+
// Find the proto file that declares the given fully-qualified symbol name.
39+
// This field should be a fully-qualified symbol name
40+
// (e.g. <package>.<service>[.<method>] or <package>.<type>).
41+
string file_containing_symbol = 4;
42+
43+
// Find the proto file which defines an extension extending the given
44+
// message type with the given field number.
45+
ExtensionRequest file_containing_extension = 5;
46+
47+
// Finds the tag numbers used by all known extensions of extendee_type, and
48+
// appends them to ExtensionNumberResponse in an undefined order.
49+
// Its corresponding method is best-effort: it's not guaranteed that the
50+
// reflection service will implement this method, and it's not guaranteed
51+
// that this method will provide all extensions. Returns
52+
// StatusCode::UNIMPLEMENTED if it's not implemented.
53+
// This field should be a fully-qualified type name. The format is
54+
// <package>.<type>
55+
string all_extension_numbers_of_type = 6;
56+
57+
// List the full names of registered services. The content will not be
58+
// checked.
59+
string list_services = 7;
60+
}
61+
}
62+
63+
// The type name and extension number sent by the client when requesting
64+
// file_containing_extension.
65+
message ExtensionRequest {
66+
// Fully-qualified type name. The format should be <package>.<type>
67+
string containing_type = 1;
68+
int32 extension_number = 2;
69+
}
70+
71+
// The message sent by the server to answer ServerReflectionInfo method.
72+
message ServerReflectionResponse {
73+
string valid_host = 1;
74+
ServerReflectionRequest original_request = 2;
75+
// The server sets one of the following fields according to the
76+
// message_request in the request.
77+
oneof message_response {
78+
// This message is used to answer file_by_filename, file_containing_symbol,
79+
// file_containing_extension requests with transitive dependencies.
80+
// As the repeated label is not allowed in oneof fields, we use a
81+
// FileDescriptorResponse message to encapsulate the repeated fields.
82+
// The reflection service is allowed to avoid sending FileDescriptorProtos
83+
// that were previously sent in response to earlier requests in the stream.
84+
FileDescriptorResponse file_descriptor_response = 4;
85+
86+
// This message is used to answer all_extension_numbers_of_type requests.
87+
ExtensionNumberResponse all_extension_numbers_response = 5;
88+
89+
// This message is used to answer list_services requests.
90+
ListServiceResponse list_services_response = 6;
91+
92+
// This message is used when an error occurs.
93+
ErrorResponse error_response = 7;
94+
}
95+
}
96+
97+
// Serialized FileDescriptorProto messages sent by the server answering
98+
// a file_by_filename, file_containing_symbol, or file_containing_extension
99+
// request.
100+
message FileDescriptorResponse {
101+
// Serialized FileDescriptorProto messages. We avoid taking a dependency on
102+
// descriptor.proto, which uses proto2 only features, by making them opaque
103+
// bytes instead.
104+
repeated bytes file_descriptor_proto = 1;
105+
}
106+
107+
// A list of extension numbers sent by the server answering
108+
// all_extension_numbers_of_type request.
109+
message ExtensionNumberResponse {
110+
// Full name of the base type, including the package name. The format
111+
// is <package>.<type>
112+
string base_type_name = 1;
113+
repeated int32 extension_number = 2;
114+
}
115+
116+
// A list of ServiceResponse sent by the server answering list_services request.
117+
message ListServiceResponse {
118+
// The information of each service may be expanded in the future, so we use
119+
// ServiceResponse message to encapsulate it.
120+
repeated ServiceResponse service = 1;
121+
}
122+
123+
// The information of a single service used by ListServiceResponse to answer
124+
// list_services request.
125+
message ServiceResponse {
126+
// Full name of a registered service, including its package name. The format
127+
// is <package>.<service>
128+
string name = 1;
129+
}
130+
131+
// The error code and error message sent by the server when an error occurs.
132+
message ErrorResponse {
133+
// This field uses the error codes defined in grpc::StatusCode.
134+
int32 error_code = 1;
135+
string error_message = 2;
136+
}

tonic-reflection/src/lib.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! A `tonic` based gRPC Server Reflection implementation.
2+
3+
#![warn(
4+
missing_debug_implementations,
5+
missing_docs,
6+
rust_2018_idioms,
7+
unreachable_pub
8+
)]
9+
#![doc(
10+
html_logo_url = "https://github.com/hyperium/tonic/raw/master/.github/assets/tonic-docs.png"
11+
)]
12+
#![doc(html_root_url = "https://docs.rs/tonic-reflection/0.1.0")]
13+
#![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
14+
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
15+
#![cfg_attr(docsrs, feature(doc_cfg))]
16+
17+
pub(crate) mod proto {
18+
#![allow(unreachable_pub)]
19+
tonic::include_proto!("grpc.reflection.v1alpha");
20+
21+
pub(crate) const FILE_DESCRIPTOR_SET: &'static [u8] =
22+
tonic::include_file_descriptor_set!("reflection_v1alpha1");
23+
}
24+
25+
/// Implementation of the server component of gRPC Server Reflection.
26+
pub mod server;

0 commit comments

Comments
 (0)