├── start.sh ├── .env ├── assets ├── GrpcJsonTranscoder.gif ├── overview_option1.png └── overview_option2.png ├── samples ├── OcelotGateway │ ├── Certs │ │ ├── client.pfx │ │ ├── server.pfx │ │ ├── Readme.md │ │ ├── client.crt │ │ ├── server.crt │ │ ├── ca.crt │ │ ├── client.key │ │ ├── server.key │ │ └── ca.key │ ├── appsettings.json │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ ├── Dockerfile │ ├── OcelotGateway.csproj │ ├── ocelot.Development.json │ ├── ocelot.json │ └── Program.cs ├── WeatherServer │ ├── WeatherServer.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Properties │ │ └── launchSettings.json │ ├── WeatherForecast.cs │ ├── Program.cs │ ├── Dockerfile │ ├── Startup.cs │ └── Controllers │ │ └── WeatherForecastController.cs ├── GreatGrpcServer │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Properties │ │ └── launchSettings.json │ ├── GreatGrpcServer.csproj │ ├── Program.cs │ ├── Services │ │ └── GreeterService.cs │ ├── Startup.cs │ └── Dockerfile ├── ProductGrpcServer │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Properties │ │ └── launchSettings.json │ ├── ProductGrpcServer.csproj │ ├── Program.cs │ ├── Startup.cs │ ├── Dockerfile │ └── Services │ │ └── ProductService.cs └── GrpcShared │ ├── greet.proto │ ├── GrpcShared.csproj │ └── product_catalog.proto ├── src ├── GrpcJsonTranscoder │ ├── GrpcJsonTranscoder.pfx │ ├── README.md │ ├── Internal │ │ ├── NameAndValue.cs │ │ ├── ObjectFactory.cs │ │ ├── Http │ │ │ ├── GrpcHttpContent.cs │ │ │ └── HttpContextExtensions.cs │ │ ├── Grpc │ │ │ ├── GrpcMethod.cs │ │ │ └── MethodDescriptorCaller.cs │ │ └── Middleware │ │ │ └── GrpcJsonTranscoderMiddleware.cs │ ├── GrpcMapperOptions.cs │ ├── ApplicationBuilderExtensions.cs │ ├── ServiceCollectionExtensions.cs │ ├── GrpcJsonTranscoder.csproj │ ├── Grpc │ │ └── GrpcAssemblyResolver.cs │ └── DownStreamContextExtensions.cs └── GrpcJsonTranscoder.FunctionalTests │ ├── Properties │ └── launchSettings.json │ ├── Dockerfile │ ├── appsettings.json │ ├── GreetServiceTest.cs │ └── GrpcJsonTranscoder.FunctionalTests.csproj ├── .dockerignore ├── GrpcJsonTranscoder.sln.DotSettings ├── NuGet.config ├── docker-compose-tests.yml ├── LICENSE ├── docker-compose.yml ├── GrpcJsonTranscoder.sln ├── README.md └── .gitignore /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ASPNETCORE_ENVIRONMENT=Production 2 | 3 | -------------------------------------------------------------------------------- /assets/GrpcJsonTranscoder.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thangchung/GrpcJsonTranscoder/HEAD/assets/GrpcJsonTranscoder.gif -------------------------------------------------------------------------------- /assets/overview_option1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thangchung/GrpcJsonTranscoder/HEAD/assets/overview_option1.png -------------------------------------------------------------------------------- /assets/overview_option2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thangchung/GrpcJsonTranscoder/HEAD/assets/overview_option2.png -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/client.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thangchung/GrpcJsonTranscoder/HEAD/samples/OcelotGateway/Certs/client.pfx -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/server.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thangchung/GrpcJsonTranscoder/HEAD/samples/OcelotGateway/Certs/server.pfx -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/GrpcJsonTranscoder.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thangchung/GrpcJsonTranscoder/HEAD/src/GrpcJsonTranscoder/GrpcJsonTranscoder.pfx -------------------------------------------------------------------------------- /samples/OcelotGateway/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/README.md: -------------------------------------------------------------------------------- 1 | We are temporary to move this library to this project for working and fixing bugs, and will move it into https://github.com/thangchung/GrpcJsonTranscoder soon. -------------------------------------------------------------------------------- /samples/WeatherServer/WeatherServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/WeatherServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/Internal/NameAndValue.cs: -------------------------------------------------------------------------------- 1 | namespace GrpcJsonTranscoder.Internal 2 | { 3 | public class NameAndValue 4 | { 5 | public string Name { get; set; } 6 | 7 | public string Value { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/GreatGrpcServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Grpc": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/ProductGrpcServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Grpc": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/WeatherServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder.FunctionalTests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "GrpcJsonTranscoder.IntegrationTests": { 4 | "commandName": "Project" 5 | }, 6 | "Docker": { 7 | "commandName": "Docker" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/Readme.md: -------------------------------------------------------------------------------- 1 | These certs are generated via openssl according to https://stackoverflow.com/questions/37714558/how-to-enable-server-side-ssl-for-grpc. The server.crt and server.key were combined into server.pfx. The password is 1111. These certs are not secure, do not use in production. -------------------------------------------------------------------------------- /samples/OcelotGateway/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "GrpcGateway": { 4 | "commandName": "Project", 5 | "applicationUrl": "http://localhost:5000/", 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /samples/GreatGrpcServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "Kestrel": { 10 | "EndpointDefaults": { 11 | "Protocols": "Http2" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/ProductGrpcServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "Kestrel": { 10 | "EndpointDefaults": { 11 | "Protocols": "Http2" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/WeatherServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "WeatherServer": { 4 | "commandName": "Project", 5 | "launchUrl": "weatherforecast", 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "applicationUrl": "http://localhost:5001" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /samples/GreatGrpcServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "GreatGrpcServer": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "applicationUrl": "http://localhost:5003", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.vs 6 | **/.vscode 7 | **/*.*proj.user 8 | **/azds.yaml 9 | **/charts 10 | **/bin 11 | **/obj 12 | **/Dockerfile 13 | **/Dockerfile.develop 14 | **/docker-compose.yml 15 | **/docker-compose.*.yml 16 | **/*.dbmdl 17 | **/*.jfm 18 | **/secrets.dev.yaml 19 | **/values.dev.yaml 20 | **/.toolstarget -------------------------------------------------------------------------------- /samples/ProductGrpcServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ProductCatalogGrpcServer": { 4 | "commandName": "Project", 5 | "launchBrowser": false, 6 | "applicationUrl": "http://localhost:5002;http://localhost:50022", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/WeatherServer/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WeatherServer 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/GreatGrpcServer/GreatGrpcServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/GrpcMapperOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace GrpcJsonTranscoder 4 | { 5 | public class GrpcMapperOptions 6 | { 7 | public List GrpcMappers { get; set; } 8 | 9 | public class GrpcLookupOption 10 | { 11 | public string GrpcMethod { get; set; } 12 | public string GrpcHost { get; set; } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using GrpcJsonTranscoder.Internal.Middleware; 2 | using Microsoft.AspNetCore.Builder; 3 | 4 | namespace GrpcJsonTranscoder 5 | { 6 | public static class ApplicationBuilderExtensions 7 | { 8 | public static IApplicationBuilder UseGrpcJsonTranscoder(this IApplicationBuilder app) 9 | { 10 | app.UseMiddleware(); 11 | 12 | return app; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /GrpcJsonTranscoder.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /samples/GrpcShared/greet.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "GrpcShared"; 4 | 5 | package Greet; 6 | 7 | // The greeting service definition. 8 | service Greeter { 9 | // Sends a greeting 10 | rpc SayHello (HelloRequest) returns (HelloReply) {} 11 | } 12 | 13 | // The request message containing the user's name. 14 | message HelloRequest { 15 | string name = 1; 16 | } 17 | 18 | // The response message containing the greetings. 19 | message HelloReply { 20 | string message = 1; 21 | } 22 | -------------------------------------------------------------------------------- /samples/ProductGrpcServer/ProductGrpcServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/GreatGrpcServer/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace GreatGrpcServer 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder.FunctionalTests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/runtime:2.2-stretch-slim AS base 2 | WORKDIR /app 3 | 4 | FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS build 5 | WORKDIR /src 6 | COPY ["src/GrpcJsonTranscoder.FunctionalTests/GrpcJsonTranscoder.FunctionalTests.csproj", "src/GrpcJsonTranscoder.FunctionalTests/"] 7 | RUN dotnet restore "src/GrpcJsonTranscoder.FunctionalTests/GrpcJsonTranscoder.FunctionalTests.csproj" 8 | COPY . . 9 | WORKDIR "/src/src/GrpcJsonTranscoder.FunctionalTests" 10 | RUN dotnet build "GrpcJsonTranscoder.FunctionalTests.csproj" -c Release -o /app 11 | 12 | FROM build as functional_tests 13 | -------------------------------------------------------------------------------- /samples/GrpcShared/GrpcShared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /samples/OcelotGateway/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "GrpcJsonTranscoder": { 3 | "GrpcMappers": [ 4 | { 5 | "GrpcMethod": "/Greet.Greeter/SayHello", 6 | "GrpcHost": "greetgrpc:80" 7 | }, 8 | { 9 | "GrpcMethod": "/ProductCatalog.Product/GetProducts", 10 | "GrpcHost": "cataloggrpc:80" 11 | }, 12 | { 13 | "GrpcMethod": "/ProductCatalog.Product/CreateProduct", 14 | "GrpcHost": "cataloggrpc:80" 15 | } 16 | ] 17 | }, 18 | "Logging": { 19 | "LogLevel": { 20 | "Default": "Debug", 21 | "System": "Information", 22 | "Microsoft": "Information" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder.FunctionalTests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "GrpcJsonTranscoder": { 3 | "GrpcMappers": [ 4 | { 5 | "GrpcMethod": "/Greet.Greeter/SayHello", 6 | "GrpcHost": "127.0.0.1:5003" 7 | }, 8 | { 9 | "GrpcMethod": "/ProductCatalog.Product/GetProducts", 10 | "GrpcHost": "127.0.0.1:5002" 11 | }, 12 | { 13 | "GrpcMethod": "/ProductCatalog.Product/CreateProduct", 14 | "GrpcHost": "127.0.0.1:5002" 15 | } 16 | ] 17 | }, 18 | "Logging": { 19 | "LogLevel": { 20 | "Default": "Information", 21 | "Microsoft": "Warning", 22 | "Microsoft.Hosting.Lifetime": "Information" 23 | } 24 | }, 25 | "AllowedHosts": "*" 26 | } 27 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder.FunctionalTests/GreetServiceTest.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace GrpcJsonTranscoder.IntegrationTests 6 | { 7 | public class GreetServiceTest 8 | { 9 | [Fact] 10 | public async Task CanCallToGreetEndPoint() 11 | { 12 | const string webApiBaseUrl = "http://aggregation_service:5001"; 13 | //const string webApiBaseUrl = "http://localhost:5001"; 14 | using (var client = new HttpClient()) 15 | { 16 | var result = await client.GetStringAsync($"{webApiBaseUrl}/weatherforecast"); 17 | Assert.NotNull(result); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/GreatGrpcServer/Services/GreeterService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Grpc.Core; 3 | using GrpcShared; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace GreatGrpcServer 7 | { 8 | public class GreeterService : Greeter.GreeterBase 9 | { 10 | private readonly ILogger _logger; 11 | public GreeterService(ILogger logger) 12 | { 13 | _logger = logger; 14 | } 15 | 16 | public override Task SayHello(HelloRequest request, ServerCallContext context) 17 | { 18 | return Task.FromResult(new HelloReply 19 | { 20 | Message = $"Hello {request.Name}" 21 | }); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/GrpcShared/product_catalog.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "GrpcShared"; 4 | 5 | package ProductCatalog; 6 | 7 | service Product { 8 | rpc GetProducts (GetProductsRequest) returns (GetProductsReply) {} 9 | rpc CreateProduct (CreateProductRequest) returns (CreateProductReply) {} 10 | } 11 | 12 | message GetProductsRequest { 13 | } 14 | 15 | message GetProductsReply { 16 | repeated ProductDto products = 1; 17 | } 18 | 19 | message CreateProductRequest { 20 | string name = 1; 21 | int32 quantity = 2; 22 | string description = 3; 23 | } 24 | 25 | message CreateProductReply { 26 | ProductDto product = 1; 27 | } 28 | 29 | message ProductDto { 30 | int32 id = 1; 31 | string name = 2; 32 | int32 quantity = 3; 33 | string description = 4; 34 | } 35 | -------------------------------------------------------------------------------- /samples/WeatherServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace WeatherServer 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/Internal/ObjectFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace GrpcJsonTranscoder.Internal 5 | { 6 | /// 7 | /// Ref at https://stackoverflow.com/questions/46500630/how-to-improve-performance-of-c-sharp-object-mapping-code 8 | /// 9 | /// 10 | public static class Factory 11 | where T : new() 12 | { 13 | private static readonly Func CreateInstanceFunc = 14 | Expression.Lambda>(Expression.New(typeof(T))).Compile(); 15 | 16 | #pragma warning disable CA1000 // Do not declare static members on generic types 17 | public static T CreateInstance() => CreateInstanceFunc(); 18 | #pragma warning restore CA1000 // Do not declare static members on generic types 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docker-compose-tests.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | functional_tests: 5 | image: functional_tests 6 | build: 7 | context: . 8 | dockerfile: ./src/GrpcJsonTranscoder.FunctionalTests/Dockerfile 9 | target: functional_tests 10 | networks: 11 | - mynetwork 12 | depends_on: 13 | - aggregation_service 14 | entrypoint: 15 | - dotnet 16 | - test 17 | - --logger 18 | - trx;LogFileName=/tests/functional-test-results.xml 19 | 20 | aggregation_service: 21 | image: aggregation_service 22 | build: 23 | context: . 24 | dockerfile: ./samples/AggregationRestApi/Dockerfile 25 | ports: 26 | - "5001:80" 27 | environment: 28 | - ASPNETCORE_ENVIRONMENT=Development 29 | - ASPNETCORE_URLS=http://*:5001 30 | networks: 31 | - mynetwork 32 | 33 | networks: 34 | mynetwork: 35 | name: mynetwork-network 36 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder.FunctionalTests/GrpcJsonTranscoder.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | false 6 | Linux 7 | ..\.. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using GrpcJsonTranscoder.Grpc; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | 6 | namespace GrpcJsonTranscoder 7 | { 8 | public static class ServiceCollectionExtensions 9 | { 10 | public static IServiceCollection AddGrpcJsonTranscoder(this IServiceCollection services, Func addGrpcAssembly) 11 | { 12 | //AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); 13 | 14 | using var scope = services.BuildServiceProvider().CreateScope(); 15 | var svcProvider = scope.ServiceProvider; 16 | var config = svcProvider.GetRequiredService(); 17 | services.Configure(config.GetSection("GrpcJsonTranscoder")); 18 | services.AddSingleton(resolver => addGrpcAssembly.Invoke()); 19 | return services; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/Internal/Http/GrpcHttpContent.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json; 6 | 7 | namespace GrpcJsonTranscoder.Internal.Http 8 | { 9 | public class GrpcHttpContent : HttpContent 10 | { 11 | private readonly string _result; 12 | 13 | public GrpcHttpContent(string result) 14 | { 15 | _result = result; 16 | } 17 | 18 | public GrpcHttpContent(object result) 19 | { 20 | _result = JsonConvert.SerializeObject(result); 21 | } 22 | 23 | protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) 24 | { 25 | var writer = new StreamWriter(stream); 26 | 27 | await writer.WriteAsync(_result); 28 | 29 | await writer.FlushAsync(); 30 | } 31 | 32 | protected override bool TryComputeLength(out long length) 33 | { 34 | length = _result.Length; 35 | 36 | return true; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Thang Chung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /samples/ProductGrpcServer/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | using System.Net; 4 | using Microsoft.AspNetCore.Server.Kestrel.Core; 5 | 6 | namespace ProductGrpcServer 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateHostBuilder(args).Build().Run(); 13 | } 14 | 15 | public static IHostBuilder CreateHostBuilder(string[] args) => 16 | Host.CreateDefaultBuilder(args) 17 | .ConfigureWebHostDefaults(webBuilder => 18 | { 19 | webBuilder.ConfigureKestrel((ctx, options) => 20 | { 21 | options.Limits.MinRequestBodyDataRate = null; 22 | options.Listen(IPAddress.Any, 5002); 23 | options.Listen(IPAddress.Any, 50022, listenOptions => 24 | { 25 | listenOptions.Protocols = HttpProtocols.Http2; 26 | }); 27 | }); 28 | 29 | webBuilder.UseStartup(); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/GreatGrpcServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace GreatGrpcServer 8 | { 9 | public class Startup 10 | { 11 | public void ConfigureServices(IServiceCollection services) 12 | { 13 | services.AddGrpc(); 14 | } 15 | 16 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 17 | { 18 | if (env.IsDevelopment()) 19 | { 20 | app.UseDeveloperExceptionPage(); 21 | } 22 | 23 | app.UseRouting(); 24 | 25 | app.UseEndpoints(endpoints => 26 | { 27 | endpoints.MapGrpcService(); 28 | 29 | endpoints.MapGet("/", async context => 30 | { 31 | await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); 32 | }); 33 | }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/WeatherServer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101-alpine3.10 AS builder 2 | 3 | ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc 4 | ENV GLIBC_VERSION=2.30-r0 5 | 6 | RUN set -ex && \ 7 | apk --update add libstdc++ curl ca-certificates && \ 8 | for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \ 9 | do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \ 10 | apk add --allow-untrusted /tmp/*.apk && \ 11 | rm -v /tmp/*.apk && \ 12 | /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib 13 | 14 | FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS builder 15 | WORKDIR /src 16 | COPY ["samples/WeatherServer/WeatherServer.csproj", "samples/WeatherServer/"] 17 | RUN dotnet restore "samples/WeatherServer/WeatherServer.csproj" 18 | COPY . . 19 | WORKDIR "/src/samples/WeatherServer" 20 | RUN dotnet build "WeatherServer.csproj" -c Release -o /app/build 21 | 22 | FROM builder AS publish 23 | RUN dotnet publish "WeatherServer.csproj" -c Release -o /app/publish 24 | 25 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.1-alpine3.10 26 | WORKDIR /app 27 | EXPOSE 80 28 | COPY --from=publish /app/publish . 29 | ENTRYPOINT ["dotnet", "WeatherServer.dll"] -------------------------------------------------------------------------------- /samples/GreatGrpcServer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101-alpine3.10 AS builder 2 | 3 | ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc 4 | ENV GLIBC_VERSION=2.30-r0 5 | 6 | RUN set -ex && \ 7 | apk --update add libstdc++ curl ca-certificates && \ 8 | for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \ 9 | do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \ 10 | apk add --allow-untrusted /tmp/*.apk && \ 11 | rm -v /tmp/*.apk && \ 12 | /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib 13 | 14 | WORKDIR /src 15 | COPY ["samples/GreatGrpcServer/GreatGrpcServer.csproj", "samples/GreatGrpcServer/"] 16 | COPY ["samples/GrpcShared/GrpcShared.csproj", "samples/GrpcShared/"] 17 | RUN dotnet restore "samples/GreatGrpcServer/GreatGrpcServer.csproj" 18 | COPY . . 19 | WORKDIR "/src/samples/GreatGrpcServer" 20 | RUN dotnet build "GreatGrpcServer.csproj" -c Release -o /app 21 | 22 | FROM builder AS publish 23 | RUN dotnet publish "GreatGrpcServer.csproj" -c Release -o /app 24 | 25 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.1-alpine3.10 26 | WORKDIR /app 27 | EXPOSE 80 28 | COPY --from=publish /app . 29 | ENTRYPOINT ["dotnet", "GreatGrpcServer.dll"] -------------------------------------------------------------------------------- /samples/ProductGrpcServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Routing; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using ProductGrpcServer.Services; 8 | 9 | namespace ProductGrpcServer 10 | { 11 | public class Startup 12 | { 13 | public void ConfigureServices(IServiceCollection services) 14 | { 15 | services.AddGrpc(); 16 | } 17 | 18 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 19 | { 20 | if (env.IsDevelopment()) 21 | { 22 | app.UseDeveloperExceptionPage(); 23 | } 24 | 25 | app.UseRouting(); 26 | 27 | app.UseEndpoints(endpoints => 28 | { 29 | endpoints.MapGrpcService(); 30 | 31 | endpoints.MapGet("/", async context => 32 | { 33 | await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); 34 | }); 35 | }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/ProductGrpcServer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101-alpine3.10 AS builder 2 | 3 | ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc 4 | ENV GLIBC_VERSION=2.30-r0 5 | 6 | RUN set -ex && \ 7 | apk --update add libstdc++ curl ca-certificates && \ 8 | for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \ 9 | do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \ 10 | apk add --allow-untrusted /tmp/*.apk && \ 11 | rm -v /tmp/*.apk && \ 12 | /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib 13 | 14 | FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build 15 | WORKDIR /src 16 | COPY ["samples/ProductGrpcServer/ProductGrpcServer.csproj", "samples/ProductGrpcServer/"] 17 | COPY ["samples/GrpcShared/GrpcShared.csproj", "samples/GrpcShared/"] 18 | RUN dotnet restore "samples/ProductGrpcServer/ProductGrpcServer.csproj" 19 | COPY . . 20 | WORKDIR "/src/samples/ProductGrpcServer" 21 | RUN dotnet build "ProductGrpcServer.csproj" -c Release -o /app 22 | 23 | FROM builder AS publish 24 | RUN dotnet publish "ProductGrpcServer.csproj" -c Release -o /app 25 | 26 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.1-alpine3.10 27 | WORKDIR /app 28 | EXPOSE 80 29 | COPY --from=publish /app . 30 | ENTRYPOINT ["dotnet", "ProductGrpcServer.dll"] -------------------------------------------------------------------------------- /samples/OcelotGateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101-alpine3.10 AS builder 2 | 3 | ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc 4 | ENV GLIBC_VERSION=2.30-r0 5 | 6 | RUN set -ex && \ 7 | apk --update add libstdc++ curl ca-certificates && \ 8 | for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \ 9 | do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \ 10 | apk add --allow-untrusted /tmp/*.apk && \ 11 | rm -v /tmp/*.apk && \ 12 | /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib 13 | 14 | WORKDIR /src 15 | COPY ["samples/OcelotGateway/OcelotGateway.csproj", "samples/OcelotGateway/"] 16 | COPY ["samples/GrpcShared/GrpcShared.csproj", "samples/GrpcShared/"] 17 | COPY ["src/GrpcJsonTranscoder/GrpcJsonTranscoder.csproj", "src/GrpcJsonTranscoder/"] 18 | RUN dotnet restore "samples/OcelotGateway/OcelotGateway.csproj" 19 | COPY . . 20 | WORKDIR "/src/samples/OcelotGateway" 21 | RUN dotnet build "OcelotGateway.csproj" -c Release -o /app 22 | 23 | FROM builder AS publish 24 | RUN dotnet publish "OcelotGateway.csproj" -c Release -o /app 25 | 26 | FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.1-alpine3.10 27 | WORKDIR /app 28 | EXPOSE 80 29 | COPY --from=publish /app . 30 | #todo: override configuration.json to ocelot.json 31 | ENTRYPOINT ["dotnet", "OcelotGateway.dll"] -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | ocelotgateway: 5 | image: ocelotgateway 6 | build: 7 | context: . 8 | dockerfile: ./samples/OcelotGateway/Dockerfile 9 | ports: 10 | - "5000:80" 11 | environment: 12 | - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT} 13 | depends_on: 14 | - weatherserver 15 | - productserver 16 | - greetserver 17 | networks: 18 | - mynetwork 19 | 20 | weatherserver: 21 | image: weatherserver 22 | build: 23 | context: . 24 | dockerfile: ./samples/WeatherServer/Dockerfile 25 | ports: 26 | - "5001:80" 27 | environment: 28 | - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT} 29 | networks: 30 | - mynetwork 31 | 32 | productserver: 33 | image: productserver 34 | build: 35 | context: . 36 | dockerfile: ./samples/ProductGrpcServer/Dockerfile 37 | ports: 38 | - "5002:5002" 39 | - "50022:50022" 40 | environment: 41 | - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT} 42 | networks: 43 | - mynetwork 44 | 45 | greetserver: 46 | image: greetserver 47 | build: 48 | context: . 49 | dockerfile: ./samples/GreatGrpcServer/Dockerfile 50 | ports: 51 | - "5003:80" 52 | environment: 53 | - ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT} 54 | networks: 55 | - mynetwork 56 | 57 | networks: 58 | mynetwork: 59 | name: mynetwork-network 60 | -------------------------------------------------------------------------------- /samples/WeatherServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Hosting; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace WeatherServer 14 | { 15 | public class Startup 16 | { 17 | public Startup(IConfiguration configuration) 18 | { 19 | Configuration = configuration; 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddControllers(); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | 38 | app.UseRouting(); 39 | 40 | app.UseAuthorization(); 41 | 42 | app.UseEndpoints(endpoints => 43 | { 44 | endpoints.MapControllers(); 45 | }); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /samples/WeatherServer/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace WeatherServer.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get() 28 | { 29 | var rng = new Random(); 30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | } 38 | 39 | [HttpGet("error")] 40 | public string GetError() 41 | { 42 | throw new Exception("Hey, I throw this"); 43 | return "Never touch this point..."; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/GrpcJsonTranscoder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 7 | true 8 | 0.5.0 9 | gRPC-JSON transcoder - This is a filter which allows a RESTful JSON API client to send requests to .NET web server over HTTP and get proxied to a gRPC service 10 | @ Thang Chung 11 | https://github.com/thangchung/GrpcJsonTranscoder 12 | https://github.com/thangchung/GrpcJsonTranscoder 13 | Thang Chung 14 | 15 | MIT 16 | git 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /samples/ProductGrpcServer/Services/ProductService.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | using GrpcShared; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace ProductGrpcServer.Services 9 | { 10 | public class ProductService : Product.ProductBase 11 | { 12 | private readonly ILogger _logger; 13 | public ProductService(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | public override async Task GetProducts(GetProductsRequest request, ServerCallContext context) 19 | { 20 | var products = new List(); 21 | 22 | for (int i = 1; i <= 5; i++) 23 | { 24 | products.Add(new ProductDto 25 | { 26 | Id = i, 27 | Name = $"product {i}", 28 | Quantity = new Random().Next(100), 29 | Description = $"description of product {i}" 30 | }); 31 | } 32 | 33 | var reply = new GetProductsReply(); 34 | reply.Products.AddRange(products); 35 | return await Task.FromResult(reply); 36 | } 37 | 38 | public override async Task CreateProduct(CreateProductRequest request, ServerCallContext context) 39 | { 40 | return await Task.FromResult(new CreateProductReply 41 | { 42 | Product = new ProductDto 43 | { 44 | Id = new Random().Next(1000), 45 | Name = $"{request.Name} created", 46 | Quantity = request.Quantity, 47 | Description = $"{request.Description} created" 48 | } 49 | }); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /samples/OcelotGateway/OcelotGateway.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | Always 22 | 23 | 24 | 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | PreserveNewest 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | PreserveNewest 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | PreserveNewest 46 | 47 | 48 | PreserveNewest 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/Internal/Grpc/GrpcMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using Google.Protobuf; 4 | using Google.Protobuf.Reflection; 5 | using Grpc.Core; 6 | 7 | namespace GrpcJsonTranscoder.Internal.Grpc 8 | { 9 | internal class GrpcMethod 10 | where TRequest : class, IMessage, new() 11 | where TResponse : class, IMessage, new() 12 | { 13 | private static readonly ConcurrentDictionary> _methods 14 | = new ConcurrentDictionary>(); 15 | 16 | public static Method GetMethod(MethodDescriptor methodDescriptor) 17 | { 18 | if (_methods.TryGetValue(methodDescriptor, out var method)) 19 | return method; 20 | 21 | var callingMethodType = 0; 22 | 23 | if (methodDescriptor.IsClientStreaming) 24 | callingMethodType = 1; 25 | 26 | if (methodDescriptor.IsServerStreaming) 27 | callingMethodType += 2; 28 | 29 | var methodType = (MethodType)Enum.ToObject(typeof(MethodType), callingMethodType); 30 | 31 | method = new Method( 32 | methodType, 33 | methodDescriptor.Service.FullName, 34 | methodDescriptor.Name, 35 | ArgsParser.Marshaller, 36 | ArgsParser.Marshaller); 37 | 38 | _methods.TryAdd(methodDescriptor, method); 39 | 40 | return method; 41 | } 42 | } 43 | 44 | internal static class ArgsParser where T : class, IMessage, new() 45 | { 46 | public static readonly MessageParser Parser = new MessageParser(Factory.CreateInstance); 47 | public static readonly Marshaller Marshaller = Marshallers.Create(MessageExtensions.ToByteArray, Parser.ParseFrom); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFPzCCAycCAQEwDQYJKoZIhvcNAQELBQAwYTELMAkGA1UEBhMCVVMxCzAJBgNV 3 | BAgMAldBMRAwDgYDVQQHDAdTZWF0dGxlMQswCQYDVQQKDAJNUzETMBEGA1UECwwK 4 | R1JQQ1NhbXBsZTERMA8GA1UEAwwITXlSb290Q0EwHhcNMTgxMTI5MjMxNzU2WhcN 5 | MTkxMTI5MjMxNzU2WjBqMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNV 6 | BAcMCUN1cGVydGlubzEUMBIGA1UECgwLWW91ckNvbXBhbnkxEDAOBgNVBAsMB1lv 7 | dXJBcHAxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIP 8 | ADCCAgoCggIBALoFTEuNh0ZsdUAJIzj0o52fa35LUUGLKS8teiK6uwDJ68jTE71f 9 | XR6W17AFGajtTkERIt+pEZICMvF9WmoF0pCH3sF8tRsDOHRnL72Nt70UZ+5g8q3E 10 | 8pfRfjAFYb2yFBlFpZrgboWWoOnblh0Q/HZdDrdhoIQNwISudYe8Ql9RrL5JXmfp 11 | kjWPeoc6okC4tj85zOBlgWrY791D9+zJEway4rVVcIDE7BBFLTYA83G1dPBw8+eD 12 | Bvu0Cpg5pZBaWEaDONKcQz56HwhRFaI5c/kfuY0BfTmhTj0mx+NnZfQJyw5/8V9V 13 | wBQSBp3qsusaSesGYSA9vYBrPqi09Fw2OGM1SofaYKv5vNXk099zk5OswpbztuYU 14 | ihIsRV0mDH1zRSNCfNJ5CG9ZE4IiRQbmxOYDCzY5ImuQ4Ft3fLxnCizd7fc+fo5d 15 | QIo2v5s65fZXyiA3vcNtYeJX56q8ttL6to56L5unqvvy3ZrFFAEO0ss1E8AmvY7A 16 | rGpDrVFbLiiVWkJw7nA5PuzpTIpsZjgG//QmeLFfOzam9TbXpj2cmk9SN18i/Vsr 17 | 74y/0x1MXprAW1ZEjEb/0I8ZdIoz9DciWJdDNt7K45dPTuAoM7O1SObqwISDOISV 18 | gaPPSKEpkN/xITMHaVErOxbp3KK0tMkHJZsEJ7JovQXpcOI2OqwUADPTAgMBAAEw 19 | DQYJKoZIhvcNAQELBQADggIBACNkRdyG4TRowEr58TfLDm08sVcJ5h5vmndHWG8X 20 | 0VAmtjhrBYfB8MJqbvxYaVdpkvoYgmsDBxBTGD5Cf71kNBKJJ7lQQQeejijCV7gI 21 | e30CWiP4jPnE3WjjI8bdyA4kncYxBWuV59ZfWhSo+OzL3BBZjDHdRTo8ZJeb6X3a 22 | 2cYXVCHTUPDK9wJZuj8aSMySRtQ5mzllxOCOuQdECeyTKRjsghJCk+pGjHDgy5YA 23 | qlGzBcWBcjMWQKsgFPOH9L4bdD0JmL55Io3XcxgwMCvUC6R8gfh3yyhMvQo68mUo 24 | 7oG6ho9WtVyjrF9VmVxxTEAAlrQ6Q+ff8sbLuyM+dn9CWd7gkftG/P71su+zzW6m 25 | ZwzvkZUcr+sYFgF9bKyNeYG0c4/6zolmY2A0ckxUwojnueGjktwzY4zeOX45O7we 26 | 7/iwCSBkNwEBOdVOP0MSEarb1FjCK1ILR02hpL9m3qCqOObvlKsRUjXwYElZ2l/I 27 | 4aIarnVqg8aOD4yPdXmwdDuw5lWc+Q3IKGmOejATJ4OqyWhtFXipL2Nslpo/BBUD 28 | GAZsItlC+FDLRxUCuTu8m/uUg6n9gx3cCs2+x2sHvOvGTxyAlNjkfUu5qNQd/yPw 29 | 4CR/puoE6z6xNHkNj1cFUznAqxIRo6iHbi43gES8UPeUreRsUs64XE4XbK3hgm1a 30 | gL0K 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFPzCCAycCAQEwDQYJKoZIhvcNAQELBQAwYTELMAkGA1UEBhMCVVMxCzAJBgNV 3 | BAgMAldBMRAwDgYDVQQHDAdTZWF0dGxlMQswCQYDVQQKDAJNUzETMBEGA1UECwwK 4 | R1JQQ1NhbXBsZTERMA8GA1UEAwwITXlSb290Q0EwHhcNMTgxMTI5MjMxNzIxWhcN 5 | MTkxMTI5MjMxNzIxWjBqMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNV 6 | BAcMCUN1cGVydGlubzEUMBIGA1UECgwLWW91ckNvbXBhbnkxEDAOBgNVBAsMB1lv 7 | dXJBcHAxEjAQBgNVBAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIP 8 | ADCCAgoCggIBAKxlfg9St+KVnoEKfIzXNFUBmt9rKNm2ZZD4A02ivEeGTzhcZEkN 9 | JcXZNjSKnx3okI5XQe6hit84S3VCHpRQJNaAvowz92sa2LI3uxzBoA5GltsC9FTL 10 | qWl8vDDnQeg5eotd/7zanUF96U6+oIc6OIa8V9If5O5uPDBGwjzmiKujjbZFmNJS 11 | N+ECa/r9fd5w9Zd/xDxo2KgpG1+3cpEyqoySi2ybmrEbPx5N17cOrAYMsOEbIHe1 12 | r9FlzWRHkH1JQuoQd50xXZ4LvkgEibnqzRiAZ+2K3RQyu6jDY6t7JFAE80CP4b+H 13 | y1YCj1sn9AeIVUwAdjZCD0oJ/Yd3DviMb8wn2QQyCDYfImZQRGieRkJyTXi2wrfv 14 | KnjqRDgJiQZ2M12u2j1GOjKwglYOsKyrrqg09/rZvb+WftwIRcCj3Yoh36BEWGA+ 15 | wdxPoddFXBtwcI3jmJkgelNlcqVjeY629Xz55Si/+rG2Up07Okd17p+e2M71fEjE 16 | IDf/dH8V6hi2cRqzY3aK1wIyxYeX4jBwiFW6mdIST0E+zOFRGyPrZZVnvOGrpW/N 17 | 24vdvhYhEJ8Jyv65LMd6G1I9i+T8q8s91qMXkci8YsJnvC/UHjyXJObSkzM4utK3 18 | 8gyqDYEbpmV8LyvCJTRxfb68CmkbDTo+JB3WvKakC+MqfMMNgJufqZaLAgMBAAEw 19 | DQYJKoZIhvcNAQELBQADggIBAJGWxSKbeiEdRmD7q8+U9huJRO2yxaAsmgrtPLqP 20 | 82c8ZCj2wnk8YzwwGFvUy49J46d1vxETYsgU9N206kwk4aZK745+wkwTHFmnh9vI 21 | DZLxzBBQZZ34C4ieZw2Qj4gcuQoEjBl9WGhtrTYq5C1KLRozQUgr8hJmf1Q1Z3K7 22 | 6i8EJhzZgKZ8WfUEXOjC/DYBBTDg7j3vIryD7k5iM7/nlXCoV9QwZszMAkTdO/Vx 23 | Gi9zobTDtmrvcaSAiKJuJyVrBfJM5Ghf1lKvV9cAJnVWiay5sft/26WN9HM8bBrn 24 | 2bxdnAnqcumPvSJOEgCnCUdoNq91eqPiAoakfiC+t0izRMVyETu/h7BPtmhG3UhI 25 | CRbBM8JE9OilLvMhhUC+jLdfwS61OTPCYWcN0eJUPwvkvV18KvEo2qo68WYbmo1E 26 | uy9TIN0DnQEJL0fYq6q/EI3VsTUZR3MxxhJqLdiAdpMtap5HZGSLy0EqJJ2Rs9ZW 27 | tbEn2V283Osp5SOqFLe9IdcFd9nb43SbOStF6c2Bv671aOqExbWC+XdmSKl2NqVP 28 | Kp6QU6AyDDGNywr42g5I82QS5DuivcHya2skTCySK0KzsnV5Ji566xK/cF+YUOCA 29 | L8c4c7SGrpfcotOYLNP36S9YhMDh7WtGgZQveIPfTPhHkl1YpxdOYBs1MA1G/EFv 30 | OhF4 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | 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 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /samples/OcelotGateway/ocelot.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ReRoutes": [ 3 | { 4 | "UpstreamPathTemplate": "/say/{name}", 5 | "UpstreamHttpMethod": [ "Get" ], 6 | "DownstreamPathTemplate": "/Greet.Greeter/SayHello", 7 | "DownstreamScheme": "http", 8 | "DownstreamHostAndPorts": [ 9 | { 10 | "Host": "localhost", 11 | "Port": 5003 12 | } 13 | ] 14 | }, 15 | { 16 | "UpstreamPathTemplate": "/products", 17 | "UpstreamHttpMethod": [ "Get" ], 18 | "DownstreamPathTemplate": "/ProductCatalog.Product/GetProducts", 19 | "DownstreamScheme": "http", 20 | "DownstreamHostAndPorts": [ 21 | { 22 | "Host": "localhost", 23 | "Port": 5002 24 | } 25 | ] 26 | }, 27 | { 28 | "UpstreamPathTemplate": "/products", 29 | "UpstreamHttpMethod": [ "Post" ], 30 | "DownstreamPathTemplate": "/ProductCatalog.Product/CreateProduct", 31 | "DownstreamScheme": "http", 32 | "DownstreamHostAndPorts": [ 33 | { 34 | "Host": "localhost", 35 | "Port": 5002 36 | }, 37 | { 38 | "Host": "localhost", 39 | "Port": 50022 40 | } 41 | ], 42 | "LoadBalancerOptions": { 43 | "Type": "RoundRobin" 44 | } 45 | }, 46 | { 47 | "DownstreamPathTemplate": "/todos", 48 | "DownstreamScheme": "http", 49 | "DownstreamHostAndPorts": [ 50 | { 51 | "Host": "jsonplaceholder.typicode.com", 52 | "Port": 80 53 | } 54 | ], 55 | "UpstreamPathTemplate": "/todos" 56 | }, 57 | { 58 | "UpstreamPathTemplate": "/weather", 59 | "UpstreamHttpMethod": [ "Get" ], 60 | "DownstreamPathTemplate": "/weatherforecast", 61 | "DownstreamScheme": "http", 62 | "DownstreamHostAndPorts": [ 63 | { 64 | "Host": "localhost", 65 | "Port": 5001 66 | } 67 | ] 68 | }, 69 | { 70 | "UpstreamPathTemplate": "/weatherwitherror", 71 | "UpstreamHttpMethod": [ "Get" ], 72 | "DownstreamPathTemplate": "/weatherforecast/error", 73 | "DownstreamScheme": "http", 74 | "DownstreamHostAndPorts": [ 75 | { 76 | "Host": "localhost", 77 | "Port": 5001 78 | } 79 | ] 80 | } 81 | ], 82 | "GlobalConfiguration": { 83 | } 84 | } -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/Internal/Middleware/GrpcJsonTranscoderMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Grpc.Core; 2 | using GrpcJsonTranscoder.Grpc; 3 | using GrpcJsonTranscoder.Internal.Grpc; 4 | using GrpcJsonTranscoder.Internal.Http; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Options; 7 | using Newtonsoft.Json; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace GrpcJsonTranscoder.Internal.Middleware 12 | { 13 | internal class GrpcJsonTranscoderMiddleware 14 | { 15 | private readonly RequestDelegate _next; 16 | 17 | public GrpcJsonTranscoderMiddleware(RequestDelegate next) 18 | { 19 | _next = next; 20 | } 21 | 22 | public async Task Invoke(HttpContext context, GrpcAssemblyResolver grpcAssemblyResolver, IOptions options) 23 | { 24 | if (!context.Request.Headers.Any(h => h.Key.ToLowerInvariant() == "content-type" && h.Value == "application/grpc")) await _next(context); 25 | else 26 | { 27 | var path = context.Request.Path.Value; 28 | 29 | var methodDescriptor = grpcAssemblyResolver.FindMethodDescriptor(path.Split('/').Last().ToUpperInvariant()); 30 | 31 | if (methodDescriptor == null) await _next(context); 32 | else 33 | { 34 | string requestData; 35 | 36 | if (context.Request.Method.ToLowerInvariant() == "get") 37 | { 38 | requestData = context.ParseGetJsonRequestOnAggregateService(); 39 | } 40 | else 41 | { 42 | requestData = await context.ParseOtherJsonRequestOnAggregateService(); 43 | } 44 | 45 | var grpcLookupTable = options.Value.GrpcMappers; 46 | var grpcClient = grpcLookupTable.FirstOrDefault(x => x.GrpcMethod == path).GrpcHost; //todo: should catch object to throw exception 47 | 48 | var channel = new Channel(grpcClient, ChannelCredentials.Insecure); 49 | var client = new MethodDescriptorCaller(channel); 50 | 51 | var requestObject = JsonConvert.DeserializeObject(requestData, methodDescriptor.InputType.ClrType); 52 | var result = await client.InvokeAsync(methodDescriptor, context.GetRequestHeaders(), requestObject); 53 | 54 | await context.Response.WriteAsync(JsonConvert.SerializeObject(result)); 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /samples/OcelotGateway/ocelot.json: -------------------------------------------------------------------------------- 1 | { 2 | "ReRoutes": [ 3 | { 4 | "UpstreamPathTemplate": "/say/{name}", 5 | "UpstreamHttpMethod": [ "Get" ], 6 | "DownstreamPathTemplate": "/Greet.Greeter/SayHello", 7 | "DownstreamScheme": "http", 8 | "DownstreamHostAndPorts": [ 9 | { 10 | "Host": "greetserver", 11 | "Port": 80 12 | } 13 | ] 14 | }, 15 | { 16 | "UpstreamPathTemplate": "/products", 17 | "UpstreamHttpMethod": [ "Get" ], 18 | "DownstreamPathTemplate": "/ProductCatalog.Product/GetProducts", 19 | "DownstreamScheme": "http", 20 | "DownstreamHostAndPorts": [ 21 | { 22 | "Host": "productserver", 23 | "Port": 80 24 | } 25 | ] 26 | }, 27 | { 28 | "UpstreamPathTemplate": "/products", 29 | "UpstreamHttpMethod": [ "Post" ], 30 | "DownstreamPathTemplate": "/ProductCatalog.Product/CreateProduct", 31 | "DownstreamScheme": "http", 32 | "DownstreamHostAndPorts": [ 33 | { 34 | "Host": "productserver", 35 | "Port": 80 36 | } 37 | ], 38 | "LoadBalancerOptions": { 39 | "Type": "LeastConnection" 40 | } 41 | }, 42 | { 43 | "DownstreamPathTemplate": "/todos", 44 | "DownstreamScheme": "http", 45 | "DownstreamHostAndPorts": [ 46 | { 47 | "Host": "jsonplaceholder.typicode.com", 48 | "Port": 80 49 | } 50 | ], 51 | "UpstreamPathTemplate": "/todos" 52 | }, 53 | { 54 | "UpstreamPathTemplate": "/weather", 55 | "UpstreamHttpMethod": [ "Get" ], 56 | "DownstreamPathTemplate": "/weatherforecast", 57 | "DownstreamScheme": "http", 58 | "DownstreamHostAndPorts": [ 59 | { 60 | "Host": "weatherserver", 61 | "Port": 80 62 | } 63 | ], 64 | 65 | "RateLimitOptions": { 66 | "ClientWhitelist": [], 67 | "EnableRateLimiting": true, 68 | "Period": "1s", 69 | "PeriodTimespan": 1, 70 | "Limit": 1 71 | } 72 | }, 73 | { 74 | "UpstreamPathTemplate": "/weatherwitherror", 75 | "UpstreamHttpMethod": [ "Get" ], 76 | "DownstreamPathTemplate": "/weatherforecast/error", 77 | "DownstreamScheme": "http", 78 | "DownstreamHostAndPorts": [ 79 | { 80 | "Host": "weatherserver", 81 | "Port": 80 82 | } 83 | ] 84 | } 85 | ], 86 | "GlobalConfiguration": { 87 | "RateLimitOptions": { 88 | "DisableRateLimitHeaders": false, 89 | "QuotaExceededMessage": "Hey, you request too many times, slow down!!!", 90 | "HttpStatusCode": 999 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /samples/OcelotGateway/Program.cs: -------------------------------------------------------------------------------- 1 | using GrpcJsonTranscoder; 2 | using GrpcJsonTranscoder.Grpc; 3 | using GrpcShared; 4 | using Microsoft.AspNetCore; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Ocelot.DependencyInjection; 9 | using Ocelot.Middleware; 10 | using System.Threading.Tasks; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace OcelotGateway 14 | { 15 | public class Program 16 | { 17 | public static async Task Main(string[] args) 18 | { 19 | await BuildWebHost(args).RunAsync(); 20 | } 21 | 22 | public static IWebHost BuildWebHost(string[] args) => 23 | WebHost.CreateDefaultBuilder(args) 24 | .UseKestrel() 25 | .ConfigureAppConfiguration((hostingContext, config) => 26 | { 27 | config 28 | .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) 29 | .AddJsonFile("appsettings.json", true, true) 30 | .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) 31 | .AddJsonFile("ocelot.json") 32 | .AddJsonFile($"ocelot.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) 33 | .AddEnvironmentVariables(); 34 | }) 35 | .ConfigureServices(services => 36 | { 37 | services.AddGrpcJsonTranscoder(() => 38 | new GrpcAssemblyResolver().ConfigGrpcAssembly( 39 | services.BuildServiceProvider().GetService>(), 40 | typeof(Greeter.GreeterClient).Assembly)); 41 | services.AddOcelot(); 42 | services.AddHttpContextAccessor(); 43 | }) 44 | .Configure(app => 45 | { 46 | var configuration = new OcelotPipelineConfiguration 47 | { 48 | PreQueryStringBuilderMiddleware = async (ctx, next) => 49 | { 50 | // https://stackoverflow.com/questions/54960613/how-to-create-callcredentials-from-sslcredentials-and-token-string 51 | await ctx.HandleGrpcRequestAsync(next/*, new SslCredentials(File.ReadAllText("Certs/server.crt"))*/); 52 | } 53 | }; 54 | 55 | app.UseOcelot(configuration).Wait(); 56 | }) 57 | .Build(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/Grpc/GrpcAssemblyResolver.cs: -------------------------------------------------------------------------------- 1 | using Google.Protobuf.Reflection; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace GrpcJsonTranscoder.Grpc 9 | { 10 | public class GrpcAssemblyResolver 11 | { 12 | private ILogger _logger; 13 | private readonly IList _assemblies = new List(); 14 | private ConcurrentDictionary _methodDescriptorDic; 15 | 16 | public GrpcAssemblyResolver ConfigGrpcAssembly(ILogger logger, params Assembly[] assemblies) 17 | { 18 | _logger = logger; 19 | 20 | if (assemblies != null) 21 | { 22 | foreach (var assembly in assemblies) 23 | { 24 | _assemblies.Add(assembly); 25 | } 26 | }; 27 | 28 | _methodDescriptorDic = GetMethodDescriptors(_assemblies.ToArray()); 29 | 30 | return this; 31 | } 32 | 33 | public MethodDescriptor FindMethodDescriptor(string methodName) 34 | { 35 | _logger.LogInformation($"Finding method #{methodName} in the assembly resolver."); 36 | 37 | if (!_methodDescriptorDic.TryGetValue(methodName, out var methodDescriptor)) 38 | { 39 | throw new System.Exception($"Could not find out method #{methodName} in the assemblies you provided."); 40 | } 41 | 42 | return methodDescriptor; 43 | } 44 | 45 | private ConcurrentDictionary GetMethodDescriptors(params Assembly[] assemblies) 46 | { 47 | var methodDescriptorDic = new ConcurrentDictionary(); 48 | var types = assemblies.SelectMany(a => a.GetTypes()); 49 | var fileTypes = types.Where(type => type.Name.EndsWith("Reflection")); 50 | 51 | foreach (var type in fileTypes) 52 | { 53 | const BindingFlags flags = BindingFlags.Static | BindingFlags.Public; 54 | var property = type.GetProperties(flags).FirstOrDefault(t => t.Name == "Descriptor"); 55 | 56 | if (property is null) 57 | continue; 58 | if (!(property.GetValue(null) is FileDescriptor fileDescriptor)) 59 | continue; 60 | 61 | foreach (var svr in fileDescriptor.Services) 62 | { 63 | var srvName = svr.FullName.ToUpper(); 64 | _logger.LogInformation($"Add service name #{srvName} into the assembly resolver."); 65 | 66 | foreach (var method in svr.Methods) 67 | { 68 | _logger.LogInformation($"Add method name #{method.Name.ToUpper()} into the assembly resolver."); 69 | methodDescriptorDic.TryAdd(method.Name.ToUpper(), method); 70 | } 71 | } 72 | } 73 | 74 | return methodDescriptorDic; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /samples/OcelotGateway/Certs/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,09834CF7C507C60B 4 | 5 | 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 54 | -----END RSA PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/Internal/Http/HttpContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Http; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | 11 | namespace GrpcJsonTranscoder.Internal.Http 12 | { 13 | internal static class HttpContextExtensions 14 | { 15 | public static JObject ParseRequestData(this HttpContext context, IDictionary upstreamHeaders = null) 16 | { 17 | var o = new JObject(); 18 | 19 | if (upstreamHeaders != null && upstreamHeaders.ContainsKey("x-grpc-route-data")) 20 | { 21 | var nameValues = JsonConvert.DeserializeObject>(upstreamHeaders["x-grpc-route-data"]); // work with ocelot 22 | foreach (var nameValue in nameValues) 23 | { 24 | o.Add(nameValue.Name.Replace("{", "").Replace("}", ""), nameValue.Value); 25 | } 26 | } 27 | 28 | if (upstreamHeaders != null && upstreamHeaders.ContainsKey("x-grpc-body-data")) 29 | { 30 | var json = upstreamHeaders["x-grpc-body-data"]; 31 | if (json != string.Empty) 32 | { 33 | o = MergeJsonObjects(o, JsonConvert.DeserializeObject(json)); 34 | } 35 | } 36 | 37 | // query string 38 | foreach (var q in context.Request.Query) 39 | { 40 | o.Add(q.Key, q.Value.ToString()); 41 | } 42 | 43 | return o; 44 | } 45 | 46 | public static string ParseGetJsonRequestOnAggregateService(this HttpContext context) 47 | { 48 | var o = new JObject(); 49 | 50 | if (context.Request.Headers.ContainsKey("x-grpc-route-data")) 51 | { 52 | // route data 53 | var nameValues = JsonConvert.DeserializeObject>(context.Request.Headers["x-grpc-route-data"]); // work with ocelot 54 | foreach (var nameValue in nameValues) 55 | { 56 | o.Add(nameValue.Name.Replace("{", "").Replace("}", ""), nameValue.Value); 57 | } 58 | } 59 | 60 | // query string 61 | foreach (var q in context.Request.Query) 62 | { 63 | o.Add(q.Key, q.Value.ToString()); 64 | } 65 | 66 | return JsonConvert.SerializeObject(o); 67 | } 68 | 69 | public static async Task ParseOtherJsonRequestOnAggregateService(this HttpContext context) 70 | { 71 | // ref at https://stackoverflow.com/questions/43403941/how-to-read-asp-net-core-response-body 72 | var encoding = context.Request.GetTypedHeaders().ContentType?.Encoding ?? Encoding.UTF8; 73 | 74 | var stream = new StreamReader(context.Request.Body, encoding); 75 | 76 | var json = await stream.ReadToEndAsync(); 77 | 78 | return json == string.Empty ? "{}" : json; 79 | } 80 | 81 | public static IDictionary GetRequestHeaders(this HttpContext context) 82 | { 83 | var headers = new Dictionary(); 84 | 85 | foreach (var key in context.Request.Headers.Keys) 86 | { 87 | if (key.StartsWith(":")) 88 | { 89 | continue; 90 | } 91 | 92 | if (key.StartsWith("grpc-", StringComparison.OrdinalIgnoreCase)) 93 | { 94 | continue; 95 | } 96 | 97 | if (key.ToLowerInvariant() != "content-type" && key.ToLowerInvariant() != "authorization") continue; 98 | 99 | //todo: investigate it more 100 | var value = context.Request.Headers[key]; 101 | headers.Add(key, value.FirstOrDefault()); 102 | } 103 | 104 | if (!string.IsNullOrEmpty(context.TraceIdentifier)) 105 | { 106 | headers.Add("X-Correlation-ID", context.TraceIdentifier); 107 | } 108 | 109 | return headers; 110 | } 111 | 112 | private static JObject MergeJsonObjects(params JObject[] objects) 113 | { 114 | var json = new JObject(); 115 | 116 | foreach (var jsonObject in objects) 117 | { 118 | foreach (var (key, value) in jsonObject) 119 | { 120 | if (!json.ContainsKey(key)) 121 | { 122 | json.Add(key, value); 123 | } 124 | } 125 | } 126 | 127 | return json; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/DownStreamContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using Grpc.Core; 8 | using Grpc.Core.Interceptors; 9 | using GrpcJsonTranscoder.Grpc; 10 | using GrpcJsonTranscoder.Internal.Grpc; 11 | using GrpcJsonTranscoder.Internal.Http; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Newtonsoft.Json; 14 | using Newtonsoft.Json.Serialization; 15 | using Ocelot.LoadBalancer.LoadBalancers; 16 | using Ocelot.Logging; 17 | using Ocelot.Middleware; 18 | using Ocelot.Responses; 19 | 20 | namespace GrpcJsonTranscoder 21 | { 22 | public static class DownStreamContextExtensions 23 | { 24 | public static async Task HandleGrpcRequestAsync(this DownstreamContext context, Func next, IEnumerable interceptors = null, SslCredentials secureCredentials = null) 25 | { 26 | // ignore if the request is not a gRPC content type 27 | if (!context.HttpContext.Request.Headers.Any(h => h.Key.ToLowerInvariant() == "content-type" && h.Value == "application/grpc")) 28 | { 29 | await next.Invoke(); 30 | } 31 | else 32 | { 33 | var methodPath = context.DownstreamReRoute.DownstreamPathTemplate.Value; 34 | var grpcAssemblyResolver = context.HttpContext.RequestServices.GetService(); 35 | var methodDescriptor = grpcAssemblyResolver.FindMethodDescriptor(methodPath.Split('/').Last().ToUpperInvariant()); 36 | 37 | if (methodDescriptor == null) 38 | { 39 | await next.Invoke(); 40 | } 41 | else 42 | { 43 | var logger = context.HttpContext.RequestServices.GetService().CreateLogger(); 44 | var upstreamHeaders = new Dictionary 45 | { 46 | { "x-grpc-route-data", JsonConvert.SerializeObject(context.TemplatePlaceholderNameAndValues.Select(x => new {x.Name, x.Value})) }, 47 | { "x-grpc-body-data", await context.DownstreamRequest.Content.ReadAsStringAsync() } 48 | }; 49 | 50 | logger.LogInformation($"Upstream request method is {context.HttpContext.Request.Method}"); 51 | logger.LogInformation($"Upstream header data for x-grpc-route-data is {upstreamHeaders["x-grpc-route-data"]}"); 52 | logger.LogInformation($"Upstream header data for x-grpc-body-data is {upstreamHeaders["x-grpc-body-data"]}"); 53 | var requestObject = context.HttpContext.ParseRequestData(upstreamHeaders); 54 | var requestJsonData = JsonConvert.SerializeObject(requestObject); 55 | logger.LogInformation($"Request object data is {requestJsonData}"); 56 | 57 | var loadBalancerFactory = context.HttpContext.RequestServices.GetService(); 58 | var loadBalancerResponse = await loadBalancerFactory.Get(context.DownstreamReRoute, context.Configuration.ServiceProviderConfiguration); 59 | var serviceHostPort = await loadBalancerResponse.Data.Lease(context); 60 | 61 | var downstreamHost = $"{serviceHostPort.Data.DownstreamHost}:{serviceHostPort.Data.DownstreamPort}"; 62 | logger.LogInformation($"Downstream IP Address is {downstreamHost}"); 63 | 64 | var channel = new Channel(downstreamHost, secureCredentials ?? ChannelCredentials.Insecure); 65 | 66 | MethodDescriptorCaller client; 67 | if (interceptors != null && interceptors.Any()) 68 | { 69 | CallInvoker callInvoker = null; 70 | foreach (var interceptor in interceptors) 71 | { 72 | callInvoker = channel.Intercept(interceptor); 73 | } 74 | client = new MethodDescriptorCaller(callInvoker); 75 | } 76 | else 77 | { 78 | client = new MethodDescriptorCaller(channel); 79 | } 80 | 81 | var concreteObject = JsonConvert.DeserializeObject(requestJsonData, methodDescriptor.InputType.ClrType); 82 | var result = await client.InvokeAsync(methodDescriptor, context.HttpContext.GetRequestHeaders(), concreteObject); 83 | logger.LogDebug($"gRPC response called with {JsonConvert.SerializeObject(result)}"); 84 | 85 | var jsonSerializer = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; 86 | var response = new OkResponse(new GrpcHttpContent(JsonConvert.SerializeObject(result, jsonSerializer))); 87 | 88 | var httpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK) 89 | { 90 | Content = response.Data 91 | }; 92 | 93 | context.HttpContext.Response.ContentType = "application/json"; 94 | context.DownstreamResponse = new DownstreamResponse(httpResponseMessage); 95 | } 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /GrpcJsonTranscoder.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29123.89 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GrpcJsonTranscoder", "src\GrpcJsonTranscoder\GrpcJsonTranscoder.csproj", "{B762BD06-72DA-4A30-BB48-B9FDA5DCF1B8}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{6A19AF1E-0584-4F63-B232-911248299643}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "services", "services", "{F8EBC152-2354-4C34-A9BD-F3E281C705A8}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__", "__", "{896E81EA-7D8B-4554-A3E0-558D01AF7F68}" 13 | ProjectSection(SolutionItems) = preProject 14 | .dockerignore = .dockerignore 15 | .gitignore = .gitignore 16 | docker-compose-tests.yml = docker-compose-tests.yml 17 | docker-compose.yml = docker-compose.yml 18 | LICENSE = LICENSE 19 | NuGet.config = NuGet.config 20 | README.md = README.md 21 | start.sh = start.sh 22 | EndProjectSection 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GrpcShared", "samples\GrpcShared\GrpcShared.csproj", "{C0008786-D13F-4A96-A0EF-C1E8EA1E5F0E}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotGateway", "samples\OcelotGateway\OcelotGateway.csproj", "{58A8392E-6145-4F8E-888A-1716889D0CF2}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GrpcJsonTranscoder.FunctionalTests", "src\GrpcJsonTranscoder.FunctionalTests\GrpcJsonTranscoder.FunctionalTests.csproj", "{108CC360-6D5A-49D1-AA33-7A4DAC140DB2}" 29 | EndProject 30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WeatherServer", "samples\WeatherServer\WeatherServer.csproj", "{2D5D84E0-88CF-48C8-90B2-6F085D6AC019}" 31 | EndProject 32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProductGrpcServer", "samples\ProductGrpcServer\ProductGrpcServer.csproj", "{06269C04-990A-4221-8FCD-E1BAF6928A7D}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreatGrpcServer", "samples\GreatGrpcServer\GreatGrpcServer.csproj", "{67183319-329D-455E-9ABD-01AD64E49358}" 35 | EndProject 36 | Global 37 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 38 | Debug|Any CPU = Debug|Any CPU 39 | Release|Any CPU = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 42 | {B762BD06-72DA-4A30-BB48-B9FDA5DCF1B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {B762BD06-72DA-4A30-BB48-B9FDA5DCF1B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {B762BD06-72DA-4A30-BB48-B9FDA5DCF1B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {B762BD06-72DA-4A30-BB48-B9FDA5DCF1B8}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {C0008786-D13F-4A96-A0EF-C1E8EA1E5F0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {C0008786-D13F-4A96-A0EF-C1E8EA1E5F0E}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {C0008786-D13F-4A96-A0EF-C1E8EA1E5F0E}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {C0008786-D13F-4A96-A0EF-C1E8EA1E5F0E}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {58A8392E-6145-4F8E-888A-1716889D0CF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {58A8392E-6145-4F8E-888A-1716889D0CF2}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {58A8392E-6145-4F8E-888A-1716889D0CF2}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {58A8392E-6145-4F8E-888A-1716889D0CF2}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {108CC360-6D5A-49D1-AA33-7A4DAC140DB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {108CC360-6D5A-49D1-AA33-7A4DAC140DB2}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {108CC360-6D5A-49D1-AA33-7A4DAC140DB2}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {108CC360-6D5A-49D1-AA33-7A4DAC140DB2}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {2D5D84E0-88CF-48C8-90B2-6F085D6AC019}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {2D5D84E0-88CF-48C8-90B2-6F085D6AC019}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {2D5D84E0-88CF-48C8-90B2-6F085D6AC019}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {2D5D84E0-88CF-48C8-90B2-6F085D6AC019}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {06269C04-990A-4221-8FCD-E1BAF6928A7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {06269C04-990A-4221-8FCD-E1BAF6928A7D}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {06269C04-990A-4221-8FCD-E1BAF6928A7D}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {06269C04-990A-4221-8FCD-E1BAF6928A7D}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {67183319-329D-455E-9ABD-01AD64E49358}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {67183319-329D-455E-9ABD-01AD64E49358}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {67183319-329D-455E-9ABD-01AD64E49358}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {67183319-329D-455E-9ABD-01AD64E49358}.Release|Any CPU.Build.0 = Release|Any CPU 70 | EndGlobalSection 71 | GlobalSection(SolutionProperties) = preSolution 72 | HideSolutionNode = FALSE 73 | EndGlobalSection 74 | GlobalSection(NestedProjects) = preSolution 75 | {F8EBC152-2354-4C34-A9BD-F3E281C705A8} = {6A19AF1E-0584-4F63-B232-911248299643} 76 | {C0008786-D13F-4A96-A0EF-C1E8EA1E5F0E} = {6A19AF1E-0584-4F63-B232-911248299643} 77 | {58A8392E-6145-4F8E-888A-1716889D0CF2} = {6A19AF1E-0584-4F63-B232-911248299643} 78 | {2D5D84E0-88CF-48C8-90B2-6F085D6AC019} = {F8EBC152-2354-4C34-A9BD-F3E281C705A8} 79 | {06269C04-990A-4221-8FCD-E1BAF6928A7D} = {F8EBC152-2354-4C34-A9BD-F3E281C705A8} 80 | {67183319-329D-455E-9ABD-01AD64E49358} = {F8EBC152-2354-4C34-A9BD-F3E281C705A8} 81 | EndGlobalSection 82 | GlobalSection(ExtensibilityGlobals) = postSolution 83 | SolutionGuid = {1C466B36-D9C6-4F70-8F17-F43201BEBB3B} 84 | EndGlobalSection 85 | EndGlobal 86 | -------------------------------------------------------------------------------- /src/GrpcJsonTranscoder/Internal/Grpc/MethodDescriptorCaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Google.Protobuf; 7 | using Google.Protobuf.Reflection; 8 | using Grpc.Core; 9 | 10 | namespace GrpcJsonTranscoder.Internal.Grpc 11 | { 12 | internal class MethodDescriptorCaller : ClientBase 13 | { 14 | public MethodDescriptorCaller() 15 | { 16 | } 17 | 18 | public MethodDescriptorCaller(CallInvoker callInvoker) : base(callInvoker) { } 19 | 20 | public MethodDescriptorCaller(ChannelBase channel) : base(channel) { } 21 | 22 | protected MethodDescriptorCaller(ClientBaseConfiguration configuration) : base(configuration) { } 23 | 24 | protected override MethodDescriptorCaller NewInstance(ClientBaseConfiguration configuration) 25 | { 26 | return new MethodDescriptorCaller(configuration); 27 | } 28 | 29 | public Task InvokeAsync(MethodDescriptor method, IDictionary headers, object requestObject) 30 | { 31 | object requests; 32 | 33 | if (requestObject != null && typeof(IEnumerable<>).MakeGenericType(method.InputType.ClrType).IsInstanceOfType(requestObject)) 34 | { 35 | requests = requestObject; 36 | } 37 | else 38 | { 39 | var arrayInstance = Array.CreateInstance(method.InputType.ClrType, 1); 40 | arrayInstance.SetValue(requestObject, 0); 41 | requests = arrayInstance; 42 | } 43 | 44 | var callGrpcAsyncCoreMethod = typeof(MethodDescriptorCaller).GetMethod("CallGrpcAsyncCore", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); 45 | 46 | var task = (Task)callGrpcAsyncCoreMethod?.MakeGenericMethod(new Type[] { method.InputType.ClrType, method.OutputType.ClrType }).Invoke(this, new [] { method, headers, requests }); 47 | 48 | return task; 49 | } 50 | 51 | [DebuggerStepThrough] 52 | private Task CallGrpcAsyncCore(MethodDescriptor method, IDictionary headers, IEnumerable requests) 53 | where TRequest : class, IMessage, new() 54 | where TResponse : class, IMessage, new() 55 | { 56 | var option = CreateCallOptions(headers); 57 | var rpc = GrpcMethod.GetMethod(method); 58 | switch (rpc.Type) 59 | { 60 | case MethodType.Unary: 61 | var taskUnary = AsyncUnaryCall(CallInvoker, rpc, option, requests.FirstOrDefault()); 62 | return Task.FromResult(taskUnary.Result); 63 | 64 | case MethodType.ClientStreaming: 65 | var taskClientStreaming = AsyncClientStreamingCall(CallInvoker, rpc, option, requests); 66 | return Task.FromResult(taskClientStreaming.Result); 67 | 68 | case MethodType.ServerStreaming: 69 | var taskServerStreaming = AsyncServerStreamingCall(CallInvoker, rpc, option, requests.FirstOrDefault()); 70 | return Task.FromResult(taskServerStreaming.Result); 71 | 72 | case MethodType.DuplexStreaming: 73 | var taskDuplexStreaming = AsyncDuplexStreamingCall(CallInvoker, rpc, option, requests); 74 | return Task.FromResult(taskDuplexStreaming.Result); 75 | 76 | default: 77 | throw new NotSupportedException($"MethodType '{rpc.Type}' is not supported."); 78 | } 79 | } 80 | 81 | private static CallOptions CreateCallOptions(IDictionary headers) 82 | { 83 | var meta = new Metadata(); 84 | 85 | foreach (var (key, value) in headers) 86 | { 87 | meta.Add(key, value); 88 | } 89 | 90 | var option = new CallOptions(meta); 91 | 92 | return option; 93 | } 94 | 95 | private static Task AsyncUnaryCall(CallInvoker invoker, Method method, CallOptions option, TRequest request) where TRequest : class where TResponse : class 96 | { 97 | return invoker.AsyncUnaryCall(method, null, option, request).ResponseAsync; 98 | } 99 | 100 | private static async Task AsyncClientStreamingCall(CallInvoker invoker, Method method, CallOptions option, IEnumerable requests) where TRequest : class where TResponse : class 101 | { 102 | using var call = invoker.AsyncClientStreamingCall(method, null, option); 103 | if (requests != null) 104 | { 105 | foreach (var request in requests) 106 | { 107 | await call.RequestStream.WriteAsync(request).ConfigureAwait(false); 108 | } 109 | } 110 | 111 | await call.RequestStream.CompleteAsync().ConfigureAwait(false); 112 | 113 | return call.ResponseAsync.Result; 114 | } 115 | 116 | private static async Task> AsyncServerStreamingCall(CallInvoker invoker, Method method, CallOptions option, TRequest request) where TRequest : class where TResponse : class 117 | { 118 | using var call = invoker.AsyncServerStreamingCall(method, null, option, request); 119 | var responses = new List(); 120 | 121 | while (await call.ResponseStream.MoveNext().ConfigureAwait(false)) 122 | { 123 | responses.Add(call.ResponseStream.Current); 124 | } 125 | 126 | return responses; 127 | } 128 | 129 | private static async Task> AsyncDuplexStreamingCall(CallInvoker invoker, Method method, CallOptions option, IEnumerable requests) where TRequest : class where TResponse : class 130 | { 131 | using var call = invoker.AsyncDuplexStreamingCall(method, null, option); 132 | if (requests != null) 133 | { 134 | foreach (var request in requests) 135 | { 136 | await call.RequestStream.WriteAsync(request).ConfigureAwait(false); 137 | } 138 | } 139 | 140 | await call.RequestStream.CompleteAsync().ConfigureAwait(false); 141 | 142 | var responses = new List(); 143 | 144 | while (await call.ResponseStream.MoveNext().ConfigureAwait(false)) 145 | { 146 | responses.Add(call.ResponseStream.Current); 147 | } 148 | 149 | return responses; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grpc-Json Transcoder project 2 | 3 | [![Price](https://img.shields.io/badge/price-FREE-0098f7.svg)](https://github.com/thangchung/GrpcJsonTranscoder/blob/master/LICENSE) 4 | [![version](https://img.shields.io/nuget/v/GrpcJsonTranscoder.svg?label=version)](https://www.nuget.org/packages?q=GrpcJsonTranscoder) 5 | 6 | This is a filter that allows a RESTful JSON API client (Ocelot Gateway) to send requests to .NET Web API (Aggregator Service) over HTTP and get proxied to a gRPC service (on the downstream). 7 | 8 | This project is inspired by [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) which is totally for golang, [grpc-dynamic-gateway](https://github.com/konsumer/grpc-dynamic-gateway) is for nodejs. And especially, [Envoy gRPC-JSON transcoder](https://www.envoyproxy.io/docs/envoy/latest/configuration/http_filters/grpc_json_transcoder_filter) is the best of transcoding in this area, but it is only on the infrastructure level. You also can use it just like my project used at [coolstore-microservices](https://github.com/vietnam-devs/coolstore-microservices/blob/master/deploys/dockers/envoy-proxy/envoy.yaml). We might use the approach from Microsoft at [GrpcHttpApi](https://github.com/aspnet/AspLabs/blob/master/src/GrpcHttpApi/README.md), but it is not publish in the official release by Microsoft in the meantime. 9 | 10 | gRPC parser borrows the idea from [Ocelot.GrpcHttpGateway](https://github.com/BuiltCloud/Ocelot.GrpcHttpGateway) code-based. 11 | 12 | ## Give a Star! 13 | 14 | If you liked [`GrpcJsonTranscoder`](https://github.com/thangchung/GrpcJsonTranscoder) project or if it helped you, please give a star :star: for this repository. That will not only help strengthen our .NET community but also improve cloud-native apps development skills for .NET developers in around the world. Thank you very much :+1: 15 | 16 | Check out my [blog](https://medium.com/@thangchung) or say hi on [Twitter](https://twitter.com/thangchung)! 17 | 18 | ## How to run it! 19 | 20 | ![](assets/GrpcJsonTranscoder.gif) 21 | 22 | 23 | ```bash 24 | $ docker-compose up 25 | ``` 26 | 27 | or 28 | 29 | ```bash 30 | $ bash 31 | $ start.sh # I haven't done it yet :p 32 | ``` 33 | 34 | > In the mean time, we open up the visual studio, run multiple projects included OcelotGateway, AggregationRestApi, ProductCatalogGrpcServer and GreatGrpcServer 35 | 36 | - OcelotGateway (.NET Core 3.1): 37 | - REST: http://localhost:5000 38 | - WeatherServer (.NET Core 3.1): 39 | - REST: http://localhost:5001 40 | - ProductCatalogGrpcServer (.NET Core 3.1): 41 | - REST: http://localhost:5002 42 | - gRPC: http://localhost:50022 43 | - GreatGrpcServer (.NET Core 3.1): 44 | - gRPC: http://localhost:5003 45 | 46 | Test it as below: 47 | 48 | ```bash 49 | # gRPC 50 | $ curl -X GET -H 'content-type: application/grpc' -k http://localhost:5000/say/Bob 51 | $ {"Message":"Hello Bob"} 52 | ``` 53 | 54 | ```bash 55 | # gRPC 56 | $ curl -X GET -H 'content-type: application/grpc' -k http://localhost:5000/products 57 | $ {"Products":[{"Id":1,"Name":"product 1","Quantity":52,"Description":"description of product 1"},...]} 58 | ``` 59 | 60 | ```bash 61 | # gRPC 62 | $ curl -X POST -H 'content-type: application/grpc' -d '{ "name": "product 1", "quantity": 1, "description": "this is product 1" }' -k http://localhost:5000/products 63 | $ {"Product":{"Id":915,"Name":"product 1 created","Quantity":1,"Description":"this is product 1 created"}} 64 | ``` 65 | 66 | ```bash 67 | # REST Api 68 | $ curl -X GET -H 'content-type: application/json' -k http://localhost:5000/weather 69 | $ [{"date":"2019-08-17T18:34:41.1090164+07:00","temperatureC":-6,"temperatureF":22,"summary":"Sweltering"},{"date":"2019-08-18T18:34:41.1090371+07:00","temperatureC":27,"temperatureF":80,"summary":"Hot"},{"date":"2019-08-19T18:34:41.1090499+07:00","temperatureC":33,"temperatureF":91,"summary":"Balmy"},{"date":"2019-08-20T18:34:41.1090617+07:00","temperatureC":-14,"temperatureF":7,"summary":"Chilly"},{"date":"2019-08-21T18:34:41.1090743+07:00","temperatureC":22,"temperatureF":71,"summary":"Hot"}] 70 | ``` 71 | 72 | **Notes**: 73 | - REST method: `content-type: application/json` is for REST method on the downstream services. 74 | - gRPC method: `content-type: application/grpc` is really important if you call to `gRPC endpoint` on the downstream services. 75 | 76 | ## How to understand it! 77 | 78 | The project aims to .NET community and its ecosystem which leverage the power of [Ocelot Gateway](https://github.com/ThreeMammals/Ocelot) which is very powerful in the gateway components were used by various of companies and sample source code when we try to adopt the microservices architecture project. 79 | 80 | ![](assets/overview_option1.png) 81 | 82 | That's quite simple with only a few steps to make it work :) 83 | Create the .NET Core project with Ocelot in place, then put the configuration as below 84 | 85 | ```json 86 | { 87 | "ReRoutes": [ 88 | { 89 | "UpstreamPathTemplate": "/say/{name}", 90 | "UpstreamHttpMethod": [ "Get" ], 91 | "DownstreamPathTemplate": "/Greet.Greeter/SayHello", 92 | "DownstreamScheme": "http", 93 | "DownstreamHostAndPorts": [ 94 | { 95 | "Host": "127.0.0.1", 96 | "Port": 5003 97 | } 98 | ] 99 | }, 100 | { 101 | "UpstreamPathTemplate": "/products", 102 | "UpstreamHttpMethod": [ "Get" ], 103 | "DownstreamPathTemplate": "/ProductCatalog.Product/GetProducts", 104 | "DownstreamScheme": "http", 105 | "DownstreamHostAndPorts": [ 106 | { 107 | "Host": "127.0.0.1", 108 | "Port": 5002 109 | } 110 | ] 111 | }, 112 | { 113 | "UpstreamPathTemplate": "/products", 114 | "UpstreamHttpMethod": [ "Post" ], 115 | "DownstreamPathTemplate": "/ProductCatalog.Product/CreateProduct", 116 | "DownstreamScheme": "http", 117 | "DownstreamHostAndPorts": [ 118 | { 119 | "Host": "127.0.0.1", 120 | "Port": 5002 121 | } 122 | ] 123 | }, 124 | { 125 | "UpstreamPathTemplate": "/weather", 126 | "UpstreamHttpMethod": [ "Get" ], 127 | "DownstreamPathTemplate": "/weatherforecast", 128 | "DownstreamScheme": "http", 129 | "DownstreamHostAndPorts": [ 130 | { 131 | "Host": "localhost", 132 | "Port": 5001 133 | } 134 | ] 135 | } 136 | ], 137 | "GlobalConfiguration": { 138 | } 139 | } 140 | ``` 141 | 142 | Then in code `Program.cs`, you only put a few line 143 | 144 | ```csharp 145 | var configuration = new OcelotPipelineConfiguration 146 | { 147 | PreQueryStringBuilderMiddleware = async (ctx, next) => 148 | { 149 | await ctx.HandleGrpcRequestAsync(next); 150 | } 151 | }; 152 | 153 | app.UseOcelot(configuration).Wait(); 154 | ``` 155 | 156 | ### **Don't believe what I said. Try it!** 157 | 158 | > We haven't tested it with stream and full-duplex transport protocols yet. So we feel free to contribute by the .NET community. 159 | 160 | ## Contributing 161 | 162 | 1. Fork it! 163 | 2. Create your feature branch: `git checkout -b my-new-feature` 164 | 3. Commit your changes: `git commit -am 'Add some feature'` 165 | 4. Push to the branch: `git push origin my-new-feature` 166 | 5. Submit a pull request :p -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | #**/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | #*.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | --------------------------------------------------------------------------------