19

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?

Laurence
  • 419
  • 1
  • 5
  • 12

5 Answers5

10

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.

Martin.Martinsson
  • 1,894
  • 21
  • 25
  • Unless I'm missing something, this will simply bind everything (controllers AND gRPC services) to ports 5000 and 5005. I need to bind my gRPC services to a specific port. – Laurence Oct 27 '20 at 15:36
  • @Lawrence: when you package everything into one assembly, maybe. I'm not sure. But doing this is a anti-pattern. When you package only your gRPC into on assembly, then it binds, of course, only your gRPC-services. – Martin.Martinsson Oct 27 '20 at 18:23
  • PS @Lawrence: putting ASP.NET-Core micro services and gRPC services into one assembly sounds really strange to me. Consider rethinking this. – Martin.Martinsson Oct 27 '20 at 18:33
  • we did consider creating separate assemblies. Our controller layer and gRPC services are thin layers on top of shared code, and separating them out would double our number of deployments which seems a high price to pay... Brando's answer above has worked out fine for us. – Laurence Oct 28 '20 at 12:54
  • @Martin.Martinsson, its not always an anti-pattern, for example the same service can be provided on grpc (HTTP2) and grpc-web (HTTP1) with reverse proxy and TLS termination, so the internal micro-services would not use HTTPS. Http1AndHttp2 does not work without TLS, however specifying separate ports for the two endpoints work that way. – Daniel Leiszen Feb 26 '23 at 21:12
8

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>();
   }
});
Martin Kosicky
  • 471
  • 4
  • 12
8

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"
      }
    }
L_J
  • 2,351
  • 10
  • 23
  • 28
  • 1
    `launchSettings.json` are not bound to VS/ VS Code but rather to using them on [local development machine](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-6.0#development-and-launchsettingsjson). – Guru Stron Jan 22 '22 at 23:34
5

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");
Brando Zhang
  • 22,586
  • 6
  • 37
  • 65
  • 1
    Thanks for your answer. Good tip on `*` for anyone else trying to achieve this! `0.0.0.0` is treated as `*` by Kestrel but not by RequireHost it would seem. – Laurence Sep 12 '20 at 17:40
1

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.

jeuxjeux20
  • 391
  • 1
  • 6
  • 20
  • Thanks for the alternative option - we've written an extension method that simplifies the `.RequireHost` repetition so it isn't too painful. From what I can see, calling `UseEndpoints` multiple times is not supported (https://github.com/dotnet/aspnetcore/issues/17750), so I think that could be a potential show-stopper for this method as well? – Laurence Mar 24 '21 at 15:46
  • That's odd, it worked for me. Although it's indeed a bit weird to have an entirely different request pipeline for gRPC and REST so I see why they don't recommend using it at all. – jeuxjeux20 Mar 24 '21 at 19:45