Skip to content

Commit cfb839b

Browse files
authored
Refactor gRPC content-type constraint to be inline (#2065)
* Refactor gRPC content-type constraint to be inline * Fix tests * Clean up * Comment * Comment
1 parent c2274eb commit cfb839b

File tree

7 files changed

+76
-48
lines changed

7 files changed

+76
-48
lines changed

build/dependencies.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<MicrosoftAspNetCoreApp31PackageVersion>3.1.3</MicrosoftAspNetCoreApp31PackageVersion>
1313
<MicrosoftBuildLocatorPackageVersion>1.5.5</MicrosoftBuildLocatorPackageVersion>
1414
<MicrosoftBuildPackageVersion>16.9.0</MicrosoftBuildPackageVersion>
15-
<MicrosoftCodeAnalysisNetAnalyzersPackageVersion>7.0.0</MicrosoftCodeAnalysisNetAnalyzersPackageVersion>
15+
<MicrosoftCodeAnalysisNetAnalyzersPackageVersion>7.0.1-preview1.23061.1</MicrosoftCodeAnalysisNetAnalyzersPackageVersion>
1616
<MicrosoftCrankEventSourcesPackageVersion>0.2.0-alpha.21255.1</MicrosoftCrankEventSourcesPackageVersion>
1717
<MicrosoftExtensionsLoggingTestingPackageVersion>2.1.1</MicrosoftExtensionsLoggingTestingPackageVersion>
1818
<MicrosoftExtensionsPackageVersion>7.0.0</MicrosoftExtensionsPackageVersion>

global.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"sdk": {
3-
"version": "7.0.100"
3+
"version": "7.0.201"
44
}
55
}

src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -61,7 +61,16 @@ public static IGrpcServerBuilder AddGrpc(this IServiceCollection services)
6161
throw new ArgumentNullException(nameof(services));
6262
}
6363

64-
services.AddRouting();
64+
services.AddRouting(options =>
65+
{
66+
// Unimplemented constraint is added to the route as an inline constraint to avoid RoutePatternFactory.Parse overload that includes parameter policies. That overload infers strings as regex constraints, which brings in
67+
// the regex engine when publishing trimmed or AOT apps. This change reduces Native AOT gRPC server app size by about 1 MB.
68+
#if NET7_0_OR_GREATER
69+
options.SetParameterPolicy<GrpcUnimplementedConstraint>(GrpcServerConstants.GrpcUnimplementedConstraintPrefix);
70+
#else
71+
options.ConstraintMap[GrpcServerConstants.GrpcUnimplementedConstraintPrefix] = typeof(GrpcUnimplementedConstraint);
72+
#endif
73+
});
6574
services.AddOptions();
6675
services.TryAddSingleton<GrpcMarkerService>();
6776
services.TryAddSingleton(typeof(ServerCallHandlerFactory<>));

src/Grpc.AspNetCore.Server/Internal/GrpcServerConstants.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -25,4 +25,6 @@ internal static class GrpcServerConstants
2525

2626
internal const string ActivityStatusCodeTag = "grpc.status_code";
2727
internal const string ActivityMethodTag = "grpc.method";
28+
29+
internal const string GrpcUnimplementedConstraintPrefix = "grpcunimplemented";
2830
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
using Grpc.AspNetCore.Server.Internal;
20+
using Grpc.Shared;
21+
using Microsoft.AspNetCore.Http;
22+
using Microsoft.AspNetCore.Routing;
23+
24+
namespace Grpc.AspNetCore.Server.Model.Internal;
25+
26+
internal sealed class GrpcUnimplementedConstraint : IRouteConstraint
27+
{
28+
public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
29+
{
30+
if (httpContext == null)
31+
{
32+
return false;
33+
}
34+
35+
// Constraint needs to be valid when a CORS preflight request is received so that CORS middleware will run
36+
if (GrpcProtocolHelpers.IsCorsPreflightRequest(httpContext))
37+
{
38+
return true;
39+
}
40+
41+
if (!HttpMethods.IsPost(httpContext.Request.Method))
42+
{
43+
return false;
44+
}
45+
46+
return CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcContentType, httpContext.Request.ContentType) ||
47+
CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcWebContentType, httpContext.Request.ContentType) ||
48+
CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcWebTextContentType, httpContext.Request.ContentType);
49+
}
50+
51+
public GrpcUnimplementedConstraint()
52+
{
53+
}
54+
}

src/Grpc.AspNetCore.Server/Model/Internal/ServiceRouteBuilder.cs

+4-41
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -123,7 +123,7 @@ internal static void CreateUnimplementedEndpoints(
123123
if (!serverCallHandlerFactory.IgnoreUnknownServices && serviceMethodsRegistry.Methods.Count == 0)
124124
{
125125
// Only one unimplemented service endpoint is needed for the application
126-
endpointConventionBuilders.Add(CreateUnimplementedEndpoint(endpointRouteBuilder, "{unimplementedService}/{unimplementedMethod}", "Unimplemented service", serverCallHandlerFactory.CreateUnimplementedService()));
126+
endpointConventionBuilders.Add(CreateUnimplementedEndpoint(endpointRouteBuilder, $"{{unimplementedService}}/{{unimplementedMethod:{GrpcServerConstants.GrpcUnimplementedConstraintPrefix}}}", "Unimplemented service", serverCallHandlerFactory.CreateUnimplementedService()));
127127
}
128128

129129
// Return UNIMPLEMENTED status for missing method:
@@ -142,19 +142,14 @@ internal static void CreateUnimplementedEndpoints(
142142
continue;
143143
}
144144

145-
endpointConventionBuilders.Add(CreateUnimplementedEndpoint(endpointRouteBuilder, serviceName + "/{unimplementedMethod}", $"Unimplemented method for {serviceName}", serverCallHandlerFactory.CreateUnimplementedMethod()));
145+
endpointConventionBuilders.Add(CreateUnimplementedEndpoint(endpointRouteBuilder, $"{serviceName}/{{unimplementedMethod:{GrpcServerConstants.GrpcUnimplementedConstraintPrefix}}}", $"Unimplemented method for {serviceName}", serverCallHandlerFactory.CreateUnimplementedMethod()));
146146
}
147147
}
148148
}
149149

150150
private static IEndpointConventionBuilder CreateUnimplementedEndpoint(IEndpointRouteBuilder endpointRouteBuilder, string pattern, string displayName, RequestDelegate requestDelegate)
151151
{
152-
var routePattern = RoutePatternFactory.Parse(
153-
pattern,
154-
defaults: null,
155-
parameterPolicies: new RouteValueDictionary { ["contentType"] = GrpcUnimplementedConstraint.Instance });
156-
157-
var endpointBuilder = endpointRouteBuilder.Map(routePattern, requestDelegate);
152+
var endpointBuilder = endpointRouteBuilder.Map(pattern, requestDelegate);
158153

159154
endpointBuilder.Add(ep =>
160155
{
@@ -165,38 +160,6 @@ private static IEndpointConventionBuilder CreateUnimplementedEndpoint(IEndpointR
165160

166161
return endpointBuilder;
167162
}
168-
169-
private class GrpcUnimplementedConstraint : IRouteConstraint
170-
{
171-
public static readonly GrpcUnimplementedConstraint Instance = new GrpcUnimplementedConstraint();
172-
173-
public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
174-
{
175-
if (httpContext == null)
176-
{
177-
return false;
178-
}
179-
180-
// Constraint needs to be valid when a CORS preflight request is received so that CORS middleware will run
181-
if (GrpcProtocolHelpers.IsCorsPreflightRequest(httpContext))
182-
{
183-
return true;
184-
}
185-
186-
if (!HttpMethods.IsPost(httpContext.Request.Method))
187-
{
188-
return false;
189-
}
190-
191-
return CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcContentType, httpContext.Request.ContentType) ||
192-
CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcWebContentType, httpContext.Request.ContentType) ||
193-
CommonGrpcProtocolHelpers.IsContentType(GrpcProtocolConstants.GrpcWebTextContentType, httpContext.Request.ContentType);
194-
}
195-
196-
private GrpcUnimplementedConstraint()
197-
{
198-
}
199-
}
200163
}
201164

202165
internal static class ServiceRouteBuilderLog

test/Grpc.AspNetCore.Server.Tests/GrpcEndpointRouteBuilderExtensionsTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,11 @@ public void MapGrpcService_ConventionBuilder_AddsMetadata()
226226
Assert.NotNull(routeEndpoint2.Metadata.GetMetadata<CustomMetadata>());
227227

228228
var routeEndpoint3 = (RouteEndpoint)endpoints[2];
229-
Assert.AreEqual("{unimplementedService}/{unimplementedMethod}", routeEndpoint3.RoutePattern.RawText);
229+
Assert.AreEqual("{unimplementedService}/{unimplementedMethod:grpcunimplemented}", routeEndpoint3.RoutePattern.RawText);
230230
Assert.NotNull(routeEndpoint3.Metadata.GetMetadata<CustomMetadata>());
231231

232232
var routeEndpoint4 = (RouteEndpoint)endpoints[3];
233-
Assert.AreEqual("greet.Greeter/{unimplementedMethod}", routeEndpoint4.RoutePattern.RawText);
233+
Assert.AreEqual("greet.Greeter/{unimplementedMethod:grpcunimplemented}", routeEndpoint4.RoutePattern.RawText);
234234
Assert.NotNull(routeEndpoint4.Metadata.GetMetadata<CustomMetadata>());
235235
}
236236

0 commit comments

Comments
 (0)