Skip to content

Commit edf6d80

Browse files
authored
Server Reflection (#63)
* 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.
1 parent 84f7f11 commit edf6d80

File tree

19 files changed

+1075
-1
lines changed

19 files changed

+1075
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ filedata.bin
1919
src/VBTest/*
2020
src/Benchmark/DalSerializer.dll
2121
~$*.pptx
22-
.nupkgs/*
22+
.nupkgs/*
23+
.ionide/

examples/pb-net-grpc/Server_CS/Server_CS.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

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

1415
<ProjectReference Include="..\Shared_CS\Shared_CS.csproj" />

examples/pb-net-grpc/Server_CS/Startup.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public void ConfigureServices(IServiceCollection services)
1515
{
1616
config.ResponseCompressionLevel = System.IO.Compression.CompressionLevel.Optimal;
1717
});
18+
services.AddCodeFirstGrpcReflection();
1819
}
1920

2021
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@@ -26,6 +27,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment _)
2627
{
2728
endpoints.MapGrpcService<MyCalculator>();
2829
endpoints.MapGrpcService<MyTimeService>();
30+
endpoints.MapCodeFirstGrpcReflectionService();
2931
});
3032
}
3133
}

nuget.config

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<packageSources>
4+
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
5+
<clear />
6+
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
7+
<add key="myget" value="https://www.myget.org/F/protobuf-net/api/v3/index.json " />
8+
</packageSources>
9+
</configuration>

protobuf-net.Grpc.sln

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "protobuf-net.Grpc.ClientFac
102102
EndProject
103103
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}"
104104
EndProject
105+
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}"
106+
EndProject
107+
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}"
108+
EndProject
109+
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}"
110+
EndProject
105111
Global
106112
GlobalSection(SolutionConfigurationPlatforms) = preSolution
107113
Debug|Any CPU = Debug|Any CPU
@@ -265,6 +271,24 @@ Global
265271
{B694ED60-93A4-4362-BBCF-4EA04B6F4660}.Release|Any CPU.Build.0 = Release|Any CPU
266272
{B694ED60-93A4-4362-BBCF-4EA04B6F4660}.VS|Any CPU.ActiveCfg = Debug|Any CPU
267273
{B694ED60-93A4-4362-BBCF-4EA04B6F4660}.VS|Any CPU.Build.0 = Debug|Any CPU
274+
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
275+
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.Debug|Any CPU.Build.0 = Debug|Any CPU
276+
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.Release|Any CPU.ActiveCfg = Release|Any CPU
277+
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.Release|Any CPU.Build.0 = Release|Any CPU
278+
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.VS|Any CPU.ActiveCfg = Debug|Any CPU
279+
{B9DAC732-68C2-41AA-96D0-79FEDF181711}.VS|Any CPU.Build.0 = Debug|Any CPU
280+
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
281+
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
282+
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
283+
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.Release|Any CPU.Build.0 = Release|Any CPU
284+
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.VS|Any CPU.ActiveCfg = Debug|Any CPU
285+
{6F589BF3-221C-43AC-85A4-899599ABF6AC}.VS|Any CPU.Build.0 = Debug|Any CPU
286+
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
287+
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
288+
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
289+
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.Release|Any CPU.Build.0 = Release|Any CPU
290+
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.VS|Any CPU.ActiveCfg = Debug|Any CPU
291+
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E}.VS|Any CPU.Build.0 = Debug|Any CPU
268292
EndGlobalSection
269293
GlobalSection(SolutionProperties) = preSolution
270294
HideSolutionNode = FALSE
@@ -306,6 +330,9 @@ Global
306330
{7AF5B934-AEE9-4FD1-928D-AAE0F7098A32} = {0A84599D-2CE9-416E-888F-24652EEAB0B3}
307331
{4A7D8244-D6B2-4DD3-A0F3-1BF716FB1A0B} = {39491A90-84A2-4E13-B867-CFC3D4592084}
308332
{B694ED60-93A4-4362-BBCF-4EA04B6F4660} = {0A84599D-2CE9-416E-888F-24652EEAB0B3}
333+
{B9DAC732-68C2-41AA-96D0-79FEDF181711} = {3E0CF81A-BA7A-4AAB-B46D-5AC8E22B0644}
334+
{6F589BF3-221C-43AC-85A4-899599ABF6AC} = {0A84599D-2CE9-416E-888F-24652EEAB0B3}
335+
{D884098C-35B2-4ACF-AFDF-8C0C01684A0E} = {39491A90-84A2-4E13-B867-CFC3D4592084}
309336
EndGlobalSection
310337
GlobalSection(ExtensibilityGlobals) = postSolution
311338
SolutionGuid = {BA14B07C-CA29-430D-A600-F37A050636D3}

reflection.proto

Lines changed: 136 additions & 0 deletions
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 the given message
48+
// type, and 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 set one of the following fields accroding to the message_request
76+
// 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. As
80+
// 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 requst.
87+
ExtensionNumberResponse all_extension_numbers_response = 5;
88+
89+
// This message is used to answer list_services request.
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+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Routing;
4+
using ProtoBuf.Grpc.Reflection;
5+
6+
namespace ProtoBuf.Grpc.Server
7+
{
8+
/// <summary>
9+
/// Provides extension methods for <see cref="IEndpointRouteBuilder"/> to add gRPC service endpoints.
10+
/// </summary>
11+
public static class EndpointRouteBuilderExtensions
12+
{
13+
/// <summary>
14+
/// Maps incoming requests to the gRPC reflection service.
15+
/// This service can be queried to discover the gRPC services on the server.
16+
/// </summary>
17+
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
18+
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with the service.</returns>
19+
public static IEndpointConventionBuilder MapCodeFirstGrpcReflectionService(this IEndpointRouteBuilder builder)
20+
{
21+
if (builder is null)
22+
{
23+
throw new ArgumentNullException(nameof(builder));
24+
}
25+
26+
return builder.MapGrpcService<ReflectionService>();
27+
}
28+
}
29+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using Grpc.AspNetCore.Server;
2+
using Grpc.AspNetCore.Server.Model;
3+
using Grpc.Core;
4+
using Microsoft.AspNetCore.Routing;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.DependencyInjection.Extensions;
7+
using Microsoft.Extensions.Logging;
8+
using ProtoBuf.Grpc.Configuration;
9+
using ProtoBuf.Grpc.Reflection;
10+
using System;
11+
using System.Collections.Generic;
12+
using System.Linq;
13+
14+
namespace ProtoBuf.Grpc.Server
15+
{
16+
/// <summary>
17+
/// Provides extension methods to the IServiceCollection API
18+
/// </summary>
19+
public static class ServicesExtensions
20+
{
21+
/// <summary>
22+
/// Adds gRPC reflection services to the specified <see cref="IServiceCollection" />.
23+
/// </summary>
24+
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
25+
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
26+
public static IServiceCollection AddCodeFirstGrpcReflection(this IServiceCollection services)
27+
{
28+
if (services is null)
29+
{
30+
throw new ArgumentNullException(nameof(services));
31+
}
32+
33+
// ReflectionService is designed to be a singleton
34+
// Explicitly register creating it in DI using descriptors calculated from gRPC endpoints in the app
35+
services.TryAddSingleton<ReflectionService>(serviceProvider =>
36+
{
37+
var binderConfiguration = serviceProvider.GetService<BinderConfiguration>();
38+
var endpointDataSource = serviceProvider.GetRequiredService<EndpointDataSource>();
39+
40+
var grpcEndpointMetadata = endpointDataSource.Endpoints
41+
.Select(ep => ep.Metadata.GetMetadata<GrpcMethodMetadata>())
42+
.Where(m => m != null)
43+
.ToList();
44+
45+
var serviceTypes = grpcEndpointMetadata.Select(m => m.ServiceType).Distinct().ToArray();
46+
47+
return new ReflectionService(binderConfiguration, serviceTypes);
48+
});
49+
50+
return services;
51+
}
52+
}
53+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.0</TargetFramework>
5+
<RootNamespace>ProtoBuf.Grpc.Server.Reflection</RootNamespace>
6+
<LangVersion>preview</LangVersion>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\protobuf-net.Grpc.Reflection\protobuf-net.Grpc.Reflection.csproj" />
11+
<PackageReference Include="Grpc.AspNetCore.Server" Version="$(GrpcDotNetVersion)" />
12+
</ItemGroup>
13+
</Project>

0 commit comments

Comments
 (0)