Nginx and ASP.NET Core: Running both, HTTP REST and gRPC services, at once
gRPC services are great - they’re fast and lightweight. However, for many use cases, REST WebAPI is also desirable. This post explains how to run them both at once.
What is gRPC?
Let’s start off by talking about what gRPC is. gRPC is a fast binary Remote Procedure Call protocol developed by Google. It’s really useful especially with service/microservice pattern, as it allows high speed communication between each of the components.
ASP.NET Core 3.0 added support for gRPC services through Grpc.AspNetCore package. Other flavours of .NET Core also support it, but in this blog post we focus on ASP.NET Core usage.
The main issue with gRPC is that it is not supported by all clients. Prime example that is important for web developers - Postman does not support gRPC, at least as of time of writing this post. Older browsers might also have trouble with it. If these things need to be supported, there are 2 choices - stick to REST API only, or enable support for both. For my Adafruit sensor service, I did want both. Here’s how I made sure it works.
How to make it all work
Build an ASP.NET Core service
The first step is to get the actual service code. As an example I am going to use parts of code of my AdafruitDHT service, which I might open source and describe at later time. Details of how the service functions is out of scope for this post, some parts were removed for brevity.
First let’s create a new ASP.NET Core WebAPI project. This is a rather simple step, so let’s jump into doing actual coding.
REST Controller code
First let’s create an API controller, that does whatever we need (in case of this example - reads AdafruitDHT sensor output) and returns data to the caller. I called it “CoreController”.
|
|
gRPC service code
Now let’s create a gRPC service .proto file. These files are used to describe the service, and Visual Studio will automatically generate a set of classes to use in C# code.
|
|
I also changed the .proto file properties a bit - I set value of gRPC Stub Classes to Server only
, as the service does not need client classes generated - this however is fully optional.
The gRPC service needs an actual C# service class too, so let’s create it next to our .proto file. The service class needs to inherit from a class AdafruitDHT.AdafruitDHTBase
that Visual Studio generated from the .proto file. If it doesn’t exist, rebuild the project to trigger class generation.
The actual service code is very similar to REST Controller code - we want to keep functionality the same, after all.
|
|
There are a few differences:
- We use
override
keyword for Read method. This is because we want to override the method created inside ofAdafruitDHT.AdafruitDHTBase
. - Instead of returning JSON object as a HTTP result, we return
AdafruitDhtGrpcResponse
- this type is also generated by Visual Studio from the .proto file. - Instead of returning a HTTP error status code, we throw a RpcException. This will let gRPC stack handle it correctly.
Enabling it all
Now we just need to enable it in Startup.cs. Add following lines in ConfigureServices
:
|
|
We also need to enable middlewares in Configure
method:
|
|
This should cover website code itself. Now we need to ensure that both work with Nginx.
Update Nginx
I personally use Nginx as reverse proxy. It’s highly configurable, lightweight, and multiplatform, which makes it perfect for use with services.
Nginx supports gRPC since version 1.13.10. As long as you have that version or higher, you’re good to go.
If your version is lower, you need to update it using your package manager. Here is an example of upgrading on debian-based Linux systems.
|
|
I personally use Raspberry Pis to host my personal use-stuff like personal services or Discord bots. Raspberry Pi is great for this.
However, I failed to find a way to update Nginx to required versions on Raspbian Stretch. Raspbian Buster was required for me to get a painless update.
If you use Raspbian Stretch and don’t want to do a full reinstall, you can upgrade to Buster using instructions found on PiMyLifeUp.com.
Upgrading may take a longer while - so go make a tea, eat a dinner, watch iZombie on Netflix or something. Just make sure to check on upgrade once in a while - there may be a few prompts for your action.
Configure Nginx
Now we need to configure Nginx server to proxy to your service. Web API part is simple. Open default site (or other site if you use multiple config files) in your favourite text editor - I personally use leafpad:
|
|
Add a new server snippet as follows:
|
|
All great and easy, right? Well, almost. This will work for HTTP REST WebAPI, but it will not work for gRPC, as it requires HTTP/2. Adding a http2
directive could help, if not for the second issue - Nginx does not allow using gRPC on the same port. Bummer.
Thankfully it’s rather easy to work around. We simply need to add a second server directive. Just add it right below the one we added just a moment ago, in the same file:
|
|
Now Nginx is properly configured. But there’s a bit more we need to do.
Configure Kestrel
If you set up TLS/SSL properly, default configuration should work just fine, and if you’re going to open the service to public internet, you absolutely should set it up. If that’s the case, feel free to skip this step.
However, if you want to use the service only locally, within LAN or your own VPN (like me), setting up TLS/SSL is more effort than it’s worth. But this means that Kestrel will reject connections, as without TLS, it needs to be set up to HTTP/2 only on that port.
Thankfully, it’s really easy to work around. Open up your appsettings.json
, and add following section:
|
|
This small configuration snippet will keep HTTP port support both HTTP/1 and HTTP/2, while enforcing HTTP/2 on gRPC port.
With this done, you should be good to go!
Test it!
Testing REST WebAPI endpoint
This step is easy. To test it, we publish the project, deploy it on our Raspberry Pi host, and run it:
|
|
Testing gRPC service
This step is rather more complicated. You could write your own client, and likely you’ll do it sooner or later - after all, creating a gRPC service would be pretty pointless without something that would actually use it. However, that’s a lot of work just for a test. The choice of testing tools is quite limited for now, and these that are available require reflection service to be added - but it’s a much easier approach to go with.
Enabling gRPC reflection
To use a gRPC testing tool, first install Grpc.AspNetCore.Server.Reflection package.
Once the package is installed, we need to make 2 small changes to our Startup.cs. First, add following line to ConfigureServices
method:
|
|
Next, in Configure
method, add a new endpoint for reflection service:
|
|
That’s all the code changes we need - build, deploy and run - make sure to run as Development environment so the Reflection Service endpoints are mapped:
|
|
Installing a testing tool
Microsoft lists a few tools to test gRPC with - you can use whichever you prefer, but I personally chose gRPCui.
The suggested way is to install is using Go Tool. Once you have Go installed, just run 2 commands to install gRPCui:
|
|
Performing the test (finally!)
Now it’s time to finally run the tool. You need to specify the address of the service and port. I connect to my Raspberry Pi over VPN, so my command looks as follows:
|
|
-plaintext
flag in your command.If everything is okay, your browser should open up a new website on localhost. There you can select service and method name, and then press Invoke
button. Once you do, you should get output displayed for you.
Summary
Setting up a service behind Nginx that supports both HTTP/1 REST WebAPI and gRPC service requires some effort, but as I explained in this post, it’s perfectly doable. Once you overcome the initial struggles, you can add more gRPC services to your ASP.NET Core project, and enjoy benefits of both - performance of gRPC where it’s supported, and availability of HTTP/1 REST WebAPI where it’s not!