Bind gRPC services to specific port in aspnetcore

Viewed 9961

Using aspnetcore 3.1 and the Grpc.AspNetCore nuget package, I have managed to get gRPC services running successfully alongside standard asp.net controllers as described in this tutorial.

However I would like to bind the gRPC services to a specific port (e.g. 5001), preferably through configuration instead of code if possible. This is because I would like to limit how my gRPC services are exposed.

The closest I have come has been using RequireHost when mapping the endpoints:

// Startup.cs
public void Configure(IApplicationBuilder app)
{
    // ...
    
    app.useEndpoints(endpoints => 
    {
        endpoints.MapGrpcService<MyService>()
            .RequireHost("0.0.0.0:5001");
    }); 
}

This seems to do what I want but I can't find any documentation about it, and it requires configuration in code per service. Perhaps there is a better way?

5 Answers

This works (server side) with Kestrel:

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
    webBuilder.ConfigureKestrel(options =>
    {
       options.Listen(IPAddress.Loopback, 5000);
       options.Listen(IPAddress.Loopback, 5005, configure => configure.UseHttps());
    });
    webBuilder.UseStartup<Startup>();
});

client side:

 var httpHandler = new HttpClientHandler
 {
     ServerCertificateCustomValidationCallback =
     HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
 };  

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
                
using var channel = GrpcChannel.ForAddress("https://localhost:5005", new GrpcChannelOptions { HttpHandler = httpHandler } );
            
var client = new Greeter.GreeterClient(channel);

Note:

 var httpHandler = new HttpClientHandler
 {
     ServerCertificateCustomValidationCallback =
     HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
 };  

when you have a self-signed certificate without a trust chain (mostly when developing).

AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);

Is for support of http.

You need to configure the middleware

app.UseRouting();
app.MapWhen(context => {
   return context.Connection.LocalPort == 1000
}, newApp => {
   newApp.UseRouting();
   newApp.UseEndpoints(endpoints =>
   {
        endpoints.MapGrpcService<Service1>();
   }
});
app.MapWhen(context => {
   return context.Connection.LocalPort == 2000
}, newApp => {
   newApp.UseRouting();
   newApp.UseEndpoints(endpoints =>
   {
        endpoints.MapGrpcService<Service2>();
   }
});

As far as I know, there is no other way to set a specific port for the GRPC service.

The grpc service is also running on the asp.net core kestrel server, the server will listen the port not the service.

If your asp.net core application just has GRPC service, you could just set the kestrel server's listen port to 5001.

If you have multiple service like MVC web api or else, RequireHost is the best workaround to allow only specific port access the grpc service.

If you want to prompt the routing system for GRPC service to require the specified port, you could use below port:

 routes.MapGrpcService<MyService>().RequireHost("*:5001");

In the ASP.NET Core 6.0 ports can be changed in the Properties > launchSettings.json file. But this file is considered only if you run the server from the Visual Studio or VS Code.

I was trying to run the server directly using the .exe file for testing. The server was running with the default ports: "http://localhost:5000;https://localhost:5001".

Finally, I changed it from the appsettings.json for the .exe file:

  "AllowedHosts": "*",
  "Kestrel": {
    "Endpoints": {
      "Http": {
        "Url": "https://localhost:7005",
        "Protocols": "Http1AndHttp2"
      },
      "gRPC": {
        "Url": "http://localhost:5005",
        "Protocols": "Http2"
      }
    }

You can try to use the UseWhen method to use the MapGrpcService endpoints only when the request uses the port you defined.

var grpcPort = 5001;
app.UseWhen(context => context.Connection.LocalPort == grpcPort,
    builder =>
    {
        builder.UseRouting();
        builder.UseEndpoints(endpoints =>
        {
            endpoints.MapGrpcService<MyService>();
        });
    });

This has the benefit of not repeating .RequireHost("*:5001"); for every single service, although repeating UseRouting twice may induce weird behaviour: for instance, authentication may not be working unless you put in builder.UseAuthentication() after builder.UseRouting().

However, this behaviour be useful if you want to have a distinct request pipeline for REST and gRPC.

Related