Skip to content

Commit

Permalink
Server Reflection (#63)
Browse files Browse the repository at this point in the history
* Initial code for gRPC Reflection.

* Use FileDescriptorSet

* Add bindings for AspNetCore

* Make sure FileDescriptors are sorted correctly

* Updated to protobuf-net 3.0.17 for reflection.

* Revert changes after rebase

* Fixed a bug where reflection would crash if FileDescriptor for message would contain more than one message type.

- Enabled IServerReflection service to be exposed via Reflection also.
  • Loading branch information
bjorkstromm committed Jul 5, 2020
1 parent 84f7f11 commit edf6d80
Show file tree
Hide file tree
Showing 19 changed files with 1,075 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -19,4 +19,5 @@ filedata.bin
src/VBTest/*
src/Benchmark/DalSerializer.dll
~$*.pptx
.nupkgs/*
.nupkgs/*
.ionide/
1 change: 1 addition & 0 deletions examples/pb-net-grpc/Server_CS/Server_CS.csproj
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<ProjectReference Condition="'$(ExampleRefs)'=='local'" Include="..\..\..\src\protobuf-net.Grpc.AspNetCore\protobuf-net.Grpc.AspNetCore.csproj" />
<ProjectReference Condition="'$(ExampleRefs)'=='local'" Include="..\..\..\src\protobuf-net.Grpc.AspNetCore.Reflection\protobuf-net.Grpc.AspNetCore.Reflection.csproj" />
<PackageReference Condition="'$(ExampleRefs)'=='nuget'" Include="protobuf-net.Grpc.AspNetCore" Version="$(PBGRPCLibVersion)" />

<ProjectReference Include="..\Shared_CS\Shared_CS.csproj" />
Expand Down
2 changes: 2 additions & 0 deletions examples/pb-net-grpc/Server_CS/Startup.cs
Expand Up @@ -15,6 +15,7 @@ public void ConfigureServices(IServiceCollection services)
{
config.ResponseCompressionLevel = System.IO.Compression.CompressionLevel.Optimal;
});
services.AddCodeFirstGrpcReflection();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand All @@ -26,6 +27,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment _)
{
endpoints.MapGrpcService<MyCalculator>();
endpoints.MapGrpcService<MyTimeService>();
endpoints.MapCodeFirstGrpcReflectionService();
});
}
}
Expand Down
9 changes: 9 additions & 0 deletions nuget.config
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
<clear />
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
<add key="myget" value="https://www.myget.org/F/protobuf-net/api/v3/index.json " />
</packageSources>
</configuration>
27 changes: 27 additions & 0 deletions protobuf-net.Grpc.sln
Expand Up @@ -102,6 +102,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protobuf-net.Grpc.ClientFac
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protobuf-net.Grpc.Test.IntegrationUpLevel", "tests\protobuf-net.Grpc.Test.IntegrationUpLevel\protobuf-net.Grpc.Test.IntegrationUpLevel.csproj", "{B694ED60-93A4-4362-BBCF-4EA04B6F4660}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protobuf-net.Grpc.Reflection", "src\protobuf-net.Grpc.Reflection\protobuf-net.Grpc.Reflection.csproj", "{B9DAC732-68C2-41AA-96D0-79FEDF181711}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protobuf-net.Grpc.Reflection.Test", "tests\protobuf-net.Grpc.Reflection.Test\protobuf-net.Grpc.Reflection.Test.csproj", "{6F589BF3-221C-43AC-85A4-899599ABF6AC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "protobuf-net.Grpc.AspNetCore.Reflection", "src\protobuf-net.Grpc.AspNetCore.Reflection\protobuf-net.Grpc.AspNetCore.Reflection.csproj", "{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -265,6 +271,24 @@ Global
{B694ED60-93A4-4362-BBCF-4EA04B6F4660}.Release|Any CPU.Build.0 = Release|Any CPU
{B694ED60-93A4-4362-BBCF-4EA04B6F4660}.VS|Any CPU.ActiveCfg = Debug|Any CPU
{B694ED60-93A4-4362-BBCF-4EA04B6F4660}.VS|Any CPU.Build.0 = Debug|Any CPU
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.Release|Any CPU.Build.0 = Release|Any CPU
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.VS|Any CPU.ActiveCfg = Debug|Any CPU
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.VS|Any CPU.Build.0 = Debug|Any CPU
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.Release|Any CPU.Build.0 = Release|Any CPU
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.VS|Any CPU.ActiveCfg = Debug|Any CPU
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.VS|Any CPU.Build.0 = Debug|Any CPU
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.Release|Any CPU.Build.0 = Release|Any CPU
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.VS|Any CPU.ActiveCfg = Debug|Any CPU
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.VS|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -306,6 +330,9 @@ Global
{7AF5B934-AEE9-4FD1-928D-AAE0F7098A32} = {0A84599D-2CE9-416E-888F-24652EEAB0B3}
{4A7D8244-D6B2-4DD3-A0F3-1BF716FB1A0B} = {39491A90-84A2-4E13-B867-CFC3D4592084}
{B694ED60-93A4-4362-BBCF-4EA04B6F4660} = {0A84599D-2CE9-416E-888F-24652EEAB0B3}
{B9DAC732-68C2-41AA-96D0-79FEDF181711} = {3E0CF81A-BA7A-4AAB-B46D-5AC8E22B0644}
{6F589BF3-221C-43AC-85A4-899599ABF6AC} = {0A84599D-2CE9-416E-888F-24652EEAB0B3}
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E} = {39491A90-84A2-4E13-B867-CFC3D4592084}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BA14B07C-CA29-430D-A600-F37A050636D3}
Expand Down
136 changes: 136 additions & 0 deletions reflection.proto
@@ -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 the given message
// 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 set one of the following fields accroding 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 requst.
ExtensionNumberResponse all_extension_numbers_response = 5;

// This message is used to answer list_services request.
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;
}
@@ -0,0 +1,29 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using ProtoBuf.Grpc.Reflection;

namespace ProtoBuf.Grpc.Server
{
/// <summary>
/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add gRPC service endpoints.
/// </summary>
public static class EndpointRouteBuilderExtensions
{
/// <summary>
/// Maps incoming requests to the gRPC reflection service.
/// This service can be queried to discover the gRPC services on the server.
/// </summary>
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with the service.</returns>
public static IEndpointConventionBuilder MapCodeFirstGrpcReflectionService(this IEndpointRouteBuilder builder)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.MapGrpcService<ReflectionService>();
}
}
}
53 changes: 53 additions & 0 deletions src/protobuf-net.Grpc.AspNetCore.Reflection/ServicesExtensions.cs
@@ -0,0 +1,53 @@
using Grpc.AspNetCore.Server;
using Grpc.AspNetCore.Server.Model;
using Grpc.Core;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using ProtoBuf.Grpc.Configuration;
using ProtoBuf.Grpc.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ProtoBuf.Grpc.Server
{
/// <summary>
/// Provides extension methods to the IServiceCollection API
/// </summary>
public static class ServicesExtensions
{
/// <summary>
/// Adds gRPC reflection services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddCodeFirstGrpcReflection(this IServiceCollection services)
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}

// ReflectionService is designed to be a singleton
// Explicitly register creating it in DI using descriptors calculated from gRPC endpoints in the app
services.TryAddSingleton<ReflectionService>(serviceProvider =>
{
var binderConfiguration = serviceProvider.GetService<BinderConfiguration>();
var endpointDataSource = serviceProvider.GetRequiredService<EndpointDataSource>();
var grpcEndpointMetadata = endpointDataSource.Endpoints
.Select(ep => ep.Metadata.GetMetadata<GrpcMethodMetadata>())
.Where(m => m != null)
.ToList();
var serviceTypes = grpcEndpointMetadata.Select(m => m.ServiceType).Distinct().ToArray();
return new ReflectionService(binderConfiguration, serviceTypes);
});

return services;
}
}
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<RootNamespace>ProtoBuf.Grpc.Server.Reflection</RootNamespace>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\protobuf-net.Grpc.Reflection\protobuf-net.Grpc.Reflection.csproj" />
<PackageReference Include="Grpc.AspNetCore.Server" Version="$(GrpcDotNetVersion)" />
</ItemGroup>
</Project>

0 comments on commit edf6d80

Please sign in to comment.