gRPC API Server for .NET on Raspberry Pi
Setting up a gRPC API Server on an edge device such as a Raspberry Pi can be useful to offload compute responsibilities elsewhere, maintaining a small footprint computationally and power-wise. When scaled up, the gRPC framework compared to using REST, gives us advantages of native HTTP/2 support with more efficiency and performance improvements. These improvements come in the form of smaller message sizes and persistent connections and streams.
In this short blog post, I experiment with setting up a gRPC Server on a Raspberry Pi 4 with .NET 8, and sending requests to it from a Windows client using an https connection. Let's see what this looks like.
Setup gRPC Server Code for .NET
Using a ASP.NET Web API Project template, we can use the following server code.
Alternatively you can configure the kestrel server from the appsettings file as follows:
The service definition and definition of available gRPC methods is done through the use of .proto files like the following. In this blog, I will be demonstrating a single gRPC endpoint for simplicity and brevity but multiple services can be defined:
The concrete implementation of an available can be the following:
Fish Class:
public class Fish
{
public string FishName { get; set; }
public string BionomialName { get; set; }
public string WaterType { get; set; }
public string Diet { get; set; }
public string AverageLifespanYears { get; set; }
public string IsEndangered { get; set; }
}
Server Project package references:
Setup gRPC client code for .NET
The Windows client must also have the certificate that we used on the server as a one of the trusted certificates within the list of Trusted Root Certificate Authorities. Doing this will mean that our Windows client does not face a Root Certificate validation error when carrying out certificate validation. There is a quick, great Windows guide on doing this here within the Accepted answer.
The Windows Client console app example here will set up a perpetual text input for the user that uses the input as the body of the gRPC request. When the server (raspberry Pi 4) responds, the Windows client has to carry out checks on the validity of the server certificate in order for the https handshake to complete successfully (or this can be completely ignored too). The checks include key checks, such as making sure the request that the client makes will access the server through the value defined in the certificate's Common Name definition or secondarily the Subject Alternative Name. We are then able to check for validation errors:
using Grpc.Net.Client;
using GrpcGreeter;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
namespace ConsoleApp
{
public class Program
{
public async static Task Main(string[] args)
{
var fish = new Aqua();
await fish.QueryFish();
Console.ReadKey();
}
public string Endangered(bool isEndangered)
{
return isEndangered ? "endagered" : "not endangered";
}
public class Aqua
{
public bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}
public async Task<string> QueryFish()
{
try
{
Func<object,X509Certificate,X509Chain,SslPolicyErrors, bool>
checkCertificateValidation = ValidateServerCertificate;
var httpHandler = new HttpClientHandler();
//if using a certificate on the server, check for any errors
//through a validation process
httpHandler.ServerCertificateCustomValidationCallback =
checkCertificateValidation;
// to skip certificate validation for dev purposes, use this instead
// httpHandler.ServerCertificateCustomValidationCallback =
// HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
//use the RPI4's local IP address and port.
//The name/IP used here must be the CN or SAN value in your certificate
var channel = GrpcChannel.ForAddress("https://192.168.1.27:5001",
new GrpcChannelOptions { HttpHandler = httpHandler });
var client = new Greeter.GreeterClient(channel);
while (true)
{
Console.Write("Send Message: ");
string text = Console.ReadLine();
var reply =
await client.InspectAquariumAsync(new FishRequest
{
Name = text
});
if (text.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
Console.WriteLine($"Reply: A {reply.BionomialName} is naturally" +
$" a habitant of {reply.WaterType}," +
$" and feeds on {reply.Diet} with an average lifespan of" +
$" {reply.AverageLifespanYears} years." +
$" It is currently {Endangered(reply.IsEndangered)}");
}
}
catch (Exception e)
{
Console.WriteLine("went wrong" + e.Message);
}
return "done";
}
}
}
}
Client Project Package References:
Before running the client code, we must make sure that our client can trust the Certificate it sees from our server by adding the Certificate Authority to the Windows Trust Store, since the Windows client has never interacted with any services using the custom certificate issued by the custom Certificate Authority. I added my very roughly made certificate authority (using this handy tool https://certificatetools.com/ which should give you a .pfx) in my trusted Root CAs that will be trusted until 2025. (For quick Instructions to do this in Windows follow this quick guide)
Publish Server App and run the code
Publish the Web Server Code and deploy to the Raspberry Pi 4 (I simply used a web publish in Visual Studio and copying the folder to the RPI4), ending up with a folder on your Raspberry Pi with the published web app files alongside your ready .pfx file.
With the .NET Framework already installed on the Raspberry Pi 4, from the root of your deployed Web app folder, run:
dotnet [YourWebAppName.dll]
The server should start running without any issues:
On the Windows machine, run the console client code and you can now communicate with the server running from the Raspberry Pi 4 using gRPC!!. As you can see below we can send a message to server with the simple name of the fish we want and we get some details back from the gRPC Server. Neat:
Using Postman and it's new support for making gRPC requests (compared to standard HTTP requests), we can do the same with good results: