Skip to content

Commit

Permalink
feat: Implement gRPC Reflection Service (#340)
Browse files Browse the repository at this point in the history
Co-authored-by: Samani G. Gikandi <samani@gojulas.com>
  • Loading branch information
jen20 and sgg committed Feb 16, 2021
1 parent f49d4bd commit c54f247
Show file tree
Hide file tree
Showing 15 changed files with 1,524 additions and 1 deletion.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"tonic-build",
"tonic-health",
"tonic-types",
"tonic-reflection",

# Non-published crates
"examples",
Expand All @@ -18,3 +19,4 @@ members = [
"tests/extern_path/my_application",
"tests/integration_tests"
]

6 changes: 6 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ path = "src/hyper_warp/server.rs"
name = "health-server"
path = "src/health/server.rs"

[[bin]]
name = "reflection-server"
path = "src/reflection/server.rs"

[[bin]]
name = "autoreload-server"
path = "src/autoreload/server.rs"
Expand Down Expand Up @@ -173,6 +177,8 @@ http-body = "0.4"
pin-project = "1.0"
# Health example
tonic-health = { path = "../tonic-health" }
# Reflection example
tonic-reflection = { path = "../tonic-reflection" }
listenfd = "0.3"

[build-dependencies]
Expand Down
7 changes: 7 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ $ cargo run --bin tls-server
$ cargo run --bin health-server
```

## Server Reflection

### Server
```bash
$ cargo run --bin reflection-server
```

## Tower Middleware

### Server
Expand Down
10 changes: 9 additions & 1 deletion examples/build.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
use std::env;
use std::path::PathBuf;

fn main() {
tonic_build::configure()
.type_attribute("routeguide.Point", "#[derive(Hash)]")
.compile(&["proto/routeguide/route_guide.proto"], &["proto"])
.unwrap();

tonic_build::compile_protos("proto/helloworld/helloworld.proto").unwrap();
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
tonic_build::configure()
.file_descriptor_set_path(out_dir.join("helloworld_descriptor.bin"))
.compile(&["proto/helloworld/helloworld.proto"], &["proto"])
.unwrap();

tonic_build::compile_protos("proto/echo/echo.proto").unwrap();

tonic_build::configure()
Expand Down
46 changes: 46 additions & 0 deletions examples/src/reflection/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use tonic::transport::Server;
use tonic::{Request, Response, Status};

mod proto {
tonic::include_proto!("helloworld");

pub(crate) const FILE_DESCRIPTOR_SET: &'static [u8] =
tonic::include_file_descriptor_set!("helloworld_descriptor");
}

#[derive(Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl proto::greeter_server::Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<proto::HelloRequest>,
) -> Result<Response<proto::HelloReply>, Status> {
println!("Got a request from {:?}", request.remote_addr());

let reply = proto::HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};
Ok(Response::new(reply))
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let service = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(proto::FILE_DESCRIPTOR_SET)
.build()
.unwrap();

let addr = "[::1]:50052".parse().unwrap();
let greeter = MyGreeter::default();

Server::builder()
.add_service(service)
.add_service(proto::greeter_server::GreeterServer::new(greeter))
.serve(addr)
.await?;

Ok(())
}
12 changes: 12 additions & 0 deletions tonic-build/src/prost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub fn configure() -> Builder {
Builder {
build_client: true,
build_server: true,
file_descriptor_set_path: None,
out_dir: None,
extern_path: Vec::new(),
field_attributes: Vec::new(),
Expand Down Expand Up @@ -189,6 +190,7 @@ impl prost_build::ServiceGenerator for ServiceGenerator {
pub struct Builder {
pub(crate) build_client: bool,
pub(crate) build_server: bool,
pub(crate) file_descriptor_set_path: Option<PathBuf>,
pub(crate) extern_path: Vec<(String, String)>,
pub(crate) field_attributes: Vec<(String, String)>,
pub(crate) type_attributes: Vec<(String, String)>,
Expand All @@ -213,6 +215,13 @@ impl Builder {
self
}

/// Generate a file containing the encoded `prost_types::FileDescriptorSet` for protocol buffers
/// modules. This is required for implementing gRPC Server Reflection.
pub fn file_descriptor_set_path(mut self, path: impl AsRef<Path>) -> Self {
self.file_descriptor_set_path = Some(path.as_ref().to_path_buf());
self
}

/// Enable the output to be formated by rustfmt.
#[cfg(feature = "rustfmt")]
pub fn format(mut self, run: bool) -> Self {
Expand Down Expand Up @@ -305,6 +314,9 @@ impl Builder {
let format = self.format;

config.out_dir(out_dir.clone());
if let Some(path) = self.file_descriptor_set_path.as_ref() {
config.file_descriptor_set_path(path);
}
for (proto_path, rust_path) in self.extern_path.iter() {
config.extern_path(proto_path, rust_path);
}
Expand Down
32 changes: 32 additions & 0 deletions tonic-reflection/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "tonic-reflection"
version = "0.1.0"
authors = [
"James Nugent <james@jen20.com>",
"Samani G. Gikandi <samani@gojulas.com>"
]
edition = "2018"
license = "MIT"
repository = "https://github.com/hyperium/tonic"
homepage = "https://github.com/hyperium/tonic"
description = """
Server Reflection module of `tonic` gRPC implementation.
"""
readme = "README.md"
categories = ["network-programming", "asynchronous"]
keywords = ["rpc", "grpc", "async", "reflection"]

[dependencies]
bytes = "1.0"
prost = "0.7"
prost-types = "0.7"
tokio = { version = "1.0", features = ["sync"] }
tokio-stream = { version = "0.1", features = ["net"] }
tonic = { version = "0.4", path = "../tonic", features = ["codegen", "prost"] }

[build-dependencies]
tonic-build = { version = "0.4", path = "../tonic-build" }

[dev-dependencies]
futures = "0.3"
futures-util = "0.3"
16 changes: 16 additions & 0 deletions tonic-reflection/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::env;
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let reflection_descriptor =
PathBuf::from(env::var("OUT_DIR").unwrap()).join("reflection_v1alpha1.bin");

tonic_build::configure()
.file_descriptor_set_path(&reflection_descriptor)
.build_server(true)
.build_client(true) // Client is only used for tests
.format(true)
.compile(&["proto/reflection.proto"], &["proto/"])?;

Ok(())
}
136 changes: 136 additions & 0 deletions tonic-reflection/proto/reflection.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2016 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Service exported by server reflection

syntax = "proto3";

package grpc.reflection.v1alpha;

service ServerReflection {
// The reflection service is structured as a bidirectional stream, ensuring
// all related requests go to a single server.
rpc ServerReflectionInfo(stream ServerReflectionRequest)
returns (stream ServerReflectionResponse);
}

// The message sent by the client when calling ServerReflectionInfo method.
message ServerReflectionRequest {
string host = 1;
// To use reflection service, the client should set one of the following
// fields in message_request. The server distinguishes requests by their
// defined field and then handles them using corresponding methods.
oneof message_request {
// Find a proto file by the file name.
string file_by_filename = 3;

// Find the proto file that declares the given fully-qualified symbol name.
// This field should be a fully-qualified symbol name
// (e.g. <package>.<service>[.<method>] or <package>.<type>).
string file_containing_symbol = 4;

// Find the proto file which defines an extension extending the given
// message type with the given field number.
ExtensionRequest file_containing_extension = 5;

// Finds the tag numbers used by all known extensions of extendee_type, and
// appends them to ExtensionNumberResponse in an undefined order.
// Its corresponding method is best-effort: it's not guaranteed that the
// reflection service will implement this method, and it's not guaranteed
// that this method will provide all extensions. Returns
// StatusCode::UNIMPLEMENTED if it's not implemented.
// This field should be a fully-qualified type name. The format is
// <package>.<type>
string all_extension_numbers_of_type = 6;

// List the full names of registered services. The content will not be
// checked.
string list_services = 7;
}
}

// The type name and extension number sent by the client when requesting
// file_containing_extension.
message ExtensionRequest {
// Fully-qualified type name. The format should be <package>.<type>
string containing_type = 1;
int32 extension_number = 2;
}

// The message sent by the server to answer ServerReflectionInfo method.
message ServerReflectionResponse {
string valid_host = 1;
ServerReflectionRequest original_request = 2;
// The server sets one of the following fields according to the
// message_request in the request.
oneof message_response {
// This message is used to answer file_by_filename, file_containing_symbol,
// file_containing_extension requests with transitive dependencies.
// As the repeated label is not allowed in oneof fields, we use a
// FileDescriptorResponse message to encapsulate the repeated fields.
// The reflection service is allowed to avoid sending FileDescriptorProtos
// that were previously sent in response to earlier requests in the stream.
FileDescriptorResponse file_descriptor_response = 4;

// This message is used to answer all_extension_numbers_of_type requests.
ExtensionNumberResponse all_extension_numbers_response = 5;

// This message is used to answer list_services requests.
ListServiceResponse list_services_response = 6;

// This message is used when an error occurs.
ErrorResponse error_response = 7;
}
}

// Serialized FileDescriptorProto messages sent by the server answering
// a file_by_filename, file_containing_symbol, or file_containing_extension
// request.
message FileDescriptorResponse {
// Serialized FileDescriptorProto messages. We avoid taking a dependency on
// descriptor.proto, which uses proto2 only features, by making them opaque
// bytes instead.
repeated bytes file_descriptor_proto = 1;
}

// A list of extension numbers sent by the server answering
// all_extension_numbers_of_type request.
message ExtensionNumberResponse {
// Full name of the base type, including the package name. The format
// is <package>.<type>
string base_type_name = 1;
repeated int32 extension_number = 2;
}

// A list of ServiceResponse sent by the server answering list_services request.
message ListServiceResponse {
// The information of each service may be expanded in the future, so we use
// ServiceResponse message to encapsulate it.
repeated ServiceResponse service = 1;
}

// The information of a single service used by ListServiceResponse to answer
// list_services request.
message ServiceResponse {
// Full name of a registered service, including its package name. The format
// is <package>.<service>
string name = 1;
}

// The error code and error message sent by the server when an error occurs.
message ErrorResponse {
// This field uses the error codes defined in grpc::StatusCode.
int32 error_code = 1;
string error_message = 2;
}
26 changes: 26 additions & 0 deletions tonic-reflection/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//! A `tonic` based gRPC Server Reflection implementation.

#![warn(
missing_debug_implementations,
missing_docs,
rust_2018_idioms,
unreachable_pub
)]
#![doc(
html_logo_url = "https://github.com/hyperium/tonic/raw/master/.github/assets/tonic-docs.png"
)]
#![doc(html_root_url = "https://docs.rs/tonic-reflection/0.1.0")]
#![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
#![cfg_attr(docsrs, feature(doc_cfg))]

pub(crate) mod proto {
#![allow(unreachable_pub)]
tonic::include_proto!("grpc.reflection.v1alpha");

pub(crate) const FILE_DESCRIPTOR_SET: &'static [u8] =
tonic::include_file_descriptor_set!("reflection_v1alpha1");
}

/// Implementation of the server component of gRPC Server Reflection.
pub mod server;
Loading

0 comments on commit c54f247

Please sign in to comment.