├── 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