Skip to content

Commit

Permalink
Trim regex from Grpc.AspNetCore (#2326)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesNK authored Nov 28, 2023
1 parent 2bbb977 commit 1625f89
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 129 deletions.
21 changes: 15 additions & 6 deletions src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ public static IGrpcServerBuilder AddGrpc(this IServiceCollection services)
{
ArgumentNullThrowHelper.ThrowIfNull(services);

services.AddRouting(options =>
{
// 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
// the regex engine when publishing trimmed or AOT apps. This change reduces Native AOT gRPC server app size by about 1 MB.
AddParameterPolicy<GrpcUnimplementedConstraint>(options, GrpcServerConstants.GrpcUnimplementedConstraintPrefix);
});
#if NET8_0_OR_GREATER
// Prefer AddRoutingCore when available.
// AddRoutingCore doesn't register a regex constraint and produces smaller result from trimming.
services.AddRoutingCore();
services.Configure<RouteOptions>(ConfigureRouting);
#else
services.AddRouting(ConfigureRouting);
#endif
services.AddOptions();
services.TryAddSingleton<GrpcMarkerService>();
services.TryAddSingleton(typeof(ServerCallHandlerFactory<>));
Expand All @@ -78,6 +80,13 @@ public static IGrpcServerBuilder AddGrpc(this IServiceCollection services)

return new GrpcServerBuilder(services);

static void ConfigureRouting(RouteOptions options)
{
// 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
// the regex engine when publishing trimmed or AOT apps. This change reduces Native AOT gRPC server app size by about 1 MB.
AddParameterPolicy<GrpcUnimplementedConstraint>(options, GrpcServerConstants.GrpcUnimplementedConstraintPrefix);
}

// This ensures the policy's constructors are preserved in .NET 6 with trimming. Remove when .NET 6 is no longer supported.
static void AddParameterPolicy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(RouteOptions options, string name)
where T : IParameterPolicy
Expand Down
3 changes: 3 additions & 0 deletions testassets/LinkerTestsClient/LinkerTestsClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<OutputType>Exe</OutputType>
<PublishTrimmed>true</PublishTrimmed>
<PublishAot>$(AppPublishAot)</PublishAot>

<IlcGenerateMstatFile>$(GenerateAotDiaganostics)</IlcGenerateMstatFile>
<IlcGenerateDgmlFile>$(GenerateAotDiaganostics)</IlcGenerateDgmlFile>
</PropertyGroup>

<ItemGroup>
Expand Down
101 changes: 47 additions & 54 deletions testassets/LinkerTestsClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,72 +22,65 @@
using Microsoft.Extensions.DependencyInjection;
using Unimplemented;

namespace Client;

// This app tests clients created directly from channel and clients created from factory.
// Because of the vagaries of trimming, there is a small chance that testing both in the same app could
// cause them to work when alone they might fail. Consider splitting into different client apps.
public class Program

try
{
static async Task<int> Main(string[] args)
if (args.Length != 1 || !int.TryParse(args[0], out var port))
{
try
{
if (args.Length != 1 || !int.TryParse(args[0], out var port))
{
throw new Exception("Port must be passed as an argument.");
}
throw new Exception("Port must be passed as an argument.");
}

var address = new Uri($"http://localhost:{port}");
var address = new Uri($"http://localhost:{port}");

// Basic channel
using var channel = GrpcChannel.ForAddress(address);
await CallGreeter(new Greeter.GreeterClient(channel));
await CallUnimplemented(new UnimplementedService.UnimplementedServiceClient(channel));
// Basic channel
using var channel = GrpcChannel.ForAddress(address);
await CallGreeter(new Greeter.GreeterClient(channel));
await CallUnimplemented(new UnimplementedService.UnimplementedServiceClient(channel));

// Client factory
var services = new ServiceCollection();
services.AddGrpcClient<Greeter.GreeterClient>(op =>
{
op.Address = address;
});
services.AddGrpcClient<UnimplementedService.UnimplementedServiceClient>(op =>
{
op.Address = address;
});
var serviceProvider = services.BuildServiceProvider();

await CallGreeter(serviceProvider.GetRequiredService<Greeter.GreeterClient>());
await CallUnimplemented(serviceProvider.GetRequiredService<UnimplementedService.UnimplementedServiceClient>());
// Client factory
var services = new ServiceCollection();
services.AddGrpcClient<Greeter.GreeterClient>(op =>
{
op.Address = address;
});
services.AddGrpcClient<UnimplementedService.UnimplementedServiceClient>(op =>
{
op.Address = address;
});
var serviceProvider = services.BuildServiceProvider();

Console.WriteLine("Shutting down");
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
return 1;
}
}
await CallGreeter(serviceProvider.GetRequiredService<Greeter.GreeterClient>());
await CallUnimplemented(serviceProvider.GetRequiredService<UnimplementedService.UnimplementedServiceClient>());

private static async Task CallGreeter(Greeter.GreeterClient client)
Console.WriteLine("Shutting down");
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
return 1;
}

static async Task CallGreeter(Greeter.GreeterClient client)
{
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
}

static async Task CallUnimplemented(UnimplementedService.UnimplementedServiceClient client)
{
var reply = client.DuplexData();

try
{
var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
await reply.ResponseStream.MoveNext();
throw new Exception("Expected error status.");
}

private static async Task CallUnimplemented(UnimplementedService.UnimplementedServiceClient client)
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unimplemented)
{
var reply = client.DuplexData();

try
{
await reply.ResponseStream.MoveNext();
throw new Exception("Expected error status.");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unimplemented)
{
Console.WriteLine("Unimplemented status correctly returned.");
}
Console.WriteLine("Unimplemented status correctly returned.");
}
}
3 changes: 3 additions & 0 deletions testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
-->
<TrimMode>full</TrimMode>
<TrimmerSingleWarn>false</TrimmerSingleWarn>

<IlcGenerateMstatFile>$(GenerateAotDiaganostics)</IlcGenerateMstatFile>
<IlcGenerateDgmlFile>$(GenerateAotDiaganostics)</IlcGenerateDgmlFile>
</PropertyGroup>

<ItemGroup>
Expand Down
42 changes: 17 additions & 25 deletions testassets/LinkerTestsWebsite/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,23 @@
#endregion

using Microsoft.AspNetCore.Server.Kestrel.Core;
using Server;

namespace Server;

public class Program
var builder = WebApplication.CreateSlimBuilder(args);
builder.Logging.SetMinimumLevel(LogLevel.Trace);
builder.WebHost.ConfigureKestrel(options =>
{
public static void Main(string[] args)
options.ListenAnyIP(0, listenOptions =>
{
CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureKestrel(options =>
{
options.ListenAnyIP(0, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
});
});
})
.ConfigureLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Trace);
});
}
listenOptions.Protocols = HttpProtocols.Http2;
});
});

builder.Services.AddGrpc();

var app = builder.Build();

app.MapGrpcService<GreeterService>();

app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started. Press Ctrl+C to shut down."));
app.Run();
44 changes: 0 additions & 44 deletions testassets/LinkerTestsWebsite/Startup.cs

This file was deleted.

0 comments on commit 1625f89

Please sign in to comment.