17

Exposing proto files in a gRPC service over a frameworkless and lightweight API

 3 years ago
source link: https://anthonygiretti.com/2020/07/06/exposing-proto-files-in-a-grpc-service-over-a-frameworkless-and-lightweight-api/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
asp-net-core-exposing-proto-logo.png?fit=750%2C386&ssl=1

Exposing proto files in a gRPC service over a frameworkless and lightweight API

2020-07-06 by anthonygiretti

Introduction

A few days ago I introduced the notion of nano service in ASP.NET Core or rather how to implement a REST API without any particular framework (https://anthonygiretti.com/2020/06/29/nano-services-with-asp-net-core-or-how-to-build-a-light-api/). Since then, I have challenged myself regularly on the possibilities of relevant applications of this idea. Well, I found a very interesting use case: exposing the protobuffs of a gRPC service without using a framework and having to manage collisions with the gRPC framework of ASP.NET Core. This article will show you how to do it in the simplest way possible.

Implementing a ProtoService

Well, in order to make a clearer code, I have implemented a service (then unit testable) instead of writing the whole implementation in the Startup.cs file. The way I implement it (fetching files in the constructor) it’s because I will use Singleton lifetime of ASP.NET Core DI system and let it manage the my service instance, I could have implemented myself a thread safe constructor. Implementation of the ProtoService:

using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting;

namespace DemoGrpc.Web.Services { public class ProtoService { private readonly string _baseDirectory; private readonly Dictionary<string, IEnumerable<string>> _protosByVersion;

public ProtoService(IWebHostEnvironment webHost) { _baseDirectory = webHost.ContentRootPath; _protosByVersion = Get(_baseDirectory); }

public async Task<string> GetAllAsync() { using (var stream = new MemoryStream()) { await JsonSerializer.SerializeAsync(stream, _protosByVersion); stream.Position = 0; using var reader = new StreamReader(stream); return await reader.ReadToEndAsync(); }

}

public string Get(int version, string protoName) { var filePath = $"{_baseDirectory}\\protos\\v{version}\\{protoName}"; var exist = File.Exists(filePath);

return exist ? filePath : null; }

private Dictionary<string, IEnumerable<string>> Get(string baseDirectory) =>

Directory.GetDirectories($"{baseDirectory}\\protos") .Select(x => new { version = x, protos = Directory.GetFiles(x).Select(Path.GetFileName)}) .ToDictionary(o => Path.GetRelativePath("protos", o.version), o => o.protos); } }

Once done we can instantiate the service in the Starup.cs file and expose endpoints.

Exposing endpoints

I’m expising here 2 endpoints:

  • GET /protos that provides a json collection of protobuf files grouped version with the WriteAsync extension.
  • GET /protos/v{version:int}/{protoName} that provides the protobuf file in text format with the SendFileAsync extension.

After route parameters conversion to their right type, the Startup file looks like this:

using DemoGrpc.Web.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using CountryGrpcServiceV1 = DemoGrpc.Web.Services.V1.CountryGrpcService; using System.Net;

namespace DemoGrpc.Web { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services) { services.AddGrpc();

services.AddSingleton<ProtoService>(); }

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); }

app.UseHttpsRedirection();

app.UseRouting();

app.UseEndpoints(endpoints => { var protoService = endpoints.ServiceProvider.GetRequiredService<ProtoService>();

endpoints.MapGrpcService<CountryGrpcServiceV1>();

endpoints.MapGet("/protos", async context => { await context.Response.WriteAsync(await protoService.GetAllAsync()); });

endpoints.MapGet("/protos/v{version:int}/{protoName}", async context => { var version = int.Parse((string)context.Request.RouteValues["version"]); var protoName = (string)context.Request.RouteValues["protoName"];

var filePath = protoService.Get(version, protoName);

if (filePath != null) { await context.Response.SendFileAsync(filePath); } else { context.Response.StatusCode = (int) HttpStatusCode.NotFound; } }); }); } } }

Demo:

proto-url-demo-get.png?resize=640%2C330&ssl=1
proto-url-demo-get-proto.png?resize=640%2C731&ssl=1

Like this:

Loading...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK