From f83a5862081ada2feab05934dfa40bffe532be31 Mon Sep 17 00:00:00 2001 From: Proinfocus Date: Thu, 30 Jun 2022 16:51:29 +0530 Subject: [PATCH 1/4] Add Simple API Gateway project --- Simple/ApiGateway/Extension.cs | 18 ++++++++ Simple/ApiGateway/Middleware.cs | 60 +++++++++++++++++++++++++++ Simple/ApiGateway/Route.cs | 9 ++++ Simple/Program.cs | 6 +++ Simple/Properties/launchSettings.json | 31 ++++++++++++++ Simple/SimpleApiGateway.csproj | 9 ++++ Simple/appsettings.Development.json | 8 ++++ Simple/appsettings.json | 30 ++++++++++++++ 8 files changed, 171 insertions(+) create mode 100644 Simple/ApiGateway/Extension.cs create mode 100644 Simple/ApiGateway/Middleware.cs create mode 100644 Simple/ApiGateway/Route.cs create mode 100644 Simple/Program.cs create mode 100644 Simple/Properties/launchSettings.json create mode 100644 Simple/SimpleApiGateway.csproj create mode 100644 Simple/appsettings.Development.json create mode 100644 Simple/appsettings.json diff --git a/Simple/ApiGateway/Extension.cs b/Simple/ApiGateway/Extension.cs new file mode 100644 index 0000000..c622b87 --- /dev/null +++ b/Simple/ApiGateway/Extension.cs @@ -0,0 +1,18 @@ +namespace Simple.ApiGateway; + +public static class Extension +{ + public static void AddApiGateway(this WebApplicationBuilder builder) + { + var result = builder.Services.FirstOrDefault(a => a.ServiceType == typeof(IHttpClientFactory)); + if (result is null) builder.Services.AddHttpClient(); + + builder.Services.Configure>(builder.Configuration.GetSection("SimpleApiGatewayRoute")); + builder.Services.AddSingleton(); + } + + public static void UseApiGateway(this WebApplication app) + { + app.UseMiddleware(); + } +} \ No newline at end of file diff --git a/Simple/ApiGateway/Middleware.cs b/Simple/ApiGateway/Middleware.cs new file mode 100644 index 0000000..f191420 --- /dev/null +++ b/Simple/ApiGateway/Middleware.cs @@ -0,0 +1,60 @@ +using System.Text; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.Options; + +namespace Simple.ApiGateway; + +public class Middleware : IMiddleware +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly List apiRoutes; + + public Middleware(IHttpClientFactory httpClientFactory, IOptionsMonitor> options) + { + _httpClientFactory = httpClientFactory; + apiRoutes = options.CurrentValue; + } + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + var urlSegments = context.Request.Path.ToString().Split('/'); + var urlPath = string.Join('/', urlSegments[1..]); + if (urlPath == "/") urlPath = string.Empty; + var routeFound = apiRoutes.FirstOrDefault(a => urlPath.ToLower().StartsWith(a.RouteName)); + if (routeFound is not null) + { + context.Request.Scheme = routeFound.Scheme; + context.Request.Host = new HostString(routeFound.Host, routeFound.Port); + context.Request.Path = $"/{urlPath}"; + await ExecuteAsync(context); + } + else + { + await context.Response.WriteAsync("Welcome to API Gateway!"); + } + } + + private async Task ExecuteAsync(HttpContext context) + { + var response = await SendRequest(context.Request); + if (response is not null) + { + context.Response.StatusCode = (int)response.StatusCode; + if (response.IsSuccessStatusCode) + await context.Response.WriteAsync(await response.Content.ReadAsStringAsync()); + } + } + + private async Task SendRequest(HttpRequest request) + { + using var client = _httpClientFactory.CreateClient(); + string requestContent; + using (Stream receiveStream = request.Body) + using (StreamReader readStream = new(receiveStream, Encoding.UTF8)) + requestContent = await readStream.ReadToEndAsync(); + + using var newRequest = new HttpRequestMessage(new HttpMethod(request.Method), request.GetDisplayUrl()); + newRequest.Content = new StringContent(requestContent, Encoding.UTF8, request.ContentType); + return await client.SendAsync(newRequest); + } +} \ No newline at end of file diff --git a/Simple/ApiGateway/Route.cs b/Simple/ApiGateway/Route.cs new file mode 100644 index 0000000..0fecbc3 --- /dev/null +++ b/Simple/ApiGateway/Route.cs @@ -0,0 +1,9 @@ +namespace Simple.ApiGateway; + +public class Route +{ + public string RouteName { get; set; } = null!; + public string Scheme { get; set; } = "https"; + public string Host { get; set; } = "localhost"; + public int Port { get; set; } = 5000; +} \ No newline at end of file diff --git a/Simple/Program.cs b/Simple/Program.cs new file mode 100644 index 0000000..b83ada2 --- /dev/null +++ b/Simple/Program.cs @@ -0,0 +1,6 @@ +using Simple.ApiGateway; +var builder = WebApplication.CreateBuilder(args); +builder.AddApiGateway(); +var app = builder.Build(); +app.UseApiGateway(); +app.Run(); \ No newline at end of file diff --git a/Simple/Properties/launchSettings.json b/Simple/Properties/launchSettings.json new file mode 100644 index 0000000..02e4175 --- /dev/null +++ b/Simple/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:45044", + "sslPort": 44390 + } + }, + "profiles": { + "SimpleApiGateway": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7084;http://localhost:5219", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Simple/SimpleApiGateway.csproj b/Simple/SimpleApiGateway.csproj new file mode 100644 index 0000000..c78c9c7 --- /dev/null +++ b/Simple/SimpleApiGateway.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/Simple/appsettings.Development.json b/Simple/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Simple/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Simple/appsettings.json b/Simple/appsettings.json new file mode 100644 index 0000000..e9cea0a --- /dev/null +++ b/Simple/appsettings.json @@ -0,0 +1,30 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "SimpleApiGatewayRoute": [ + { + "routeName": "api/customer", + "scheme": "http", + "host": "localhost", + "port": 18002 + }, + { + "routeName": "api/product", + "scheme": "http", + "host": "localhost", + "port": 18004 + } + , + { + "routeName": "api/order", + "scheme": "http", + "host": "localhost", + "port": 18006 + } + ] +} \ No newline at end of file From e5e3580a3e39fe0316277386028522d6bbb253ee Mon Sep 17 00:00:00 2001 From: Proinfocus Date: Thu, 30 Jun 2022 17:08:54 +0530 Subject: [PATCH 2/4] Handled exception for API endpoint not available. --- Simple/ApiGateway/Middleware.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Simple/ApiGateway/Middleware.cs b/Simple/ApiGateway/Middleware.cs index f191420..89542cc 100644 --- a/Simple/ApiGateway/Middleware.cs +++ b/Simple/ApiGateway/Middleware.cs @@ -1,3 +1,4 @@ +using System; using System.Text; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Options; @@ -36,12 +37,20 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) private async Task ExecuteAsync(HttpContext context) { - var response = await SendRequest(context.Request); - if (response is not null) + try { - context.Response.StatusCode = (int)response.StatusCode; - if (response.IsSuccessStatusCode) - await context.Response.WriteAsync(await response.Content.ReadAsStringAsync()); + var response = await SendRequest(context.Request); + if (response is not null) + { + context.Response.StatusCode = (int)response.StatusCode; + if (response.IsSuccessStatusCode) + await context.Response.WriteAsync(await response.Content.ReadAsStringAsync()); + } + } + catch (Exception ex) + { + context.Response.StatusCode = 500; + System.Console.WriteLine(ex.Message); } } From e42967c573b823bfb305a3b7554e441117b46b4b Mon Sep 17 00:00:00 2001 From: Proinfocus Date: Sat, 2 Jul 2022 13:23:02 +0530 Subject: [PATCH 3/4] Updated the solution to use SimpleAPIGateway instead of Ocelot APIGateway and fixed a bug in ContentType to ignore 'chartset=utf-8' part. --- BlazorServerWebUI/Program.cs | 7 ++----- DemoMicroserviceSolution.sln | 7 +++++++ Simple/ApiGateway/Extension.cs | 2 +- Simple/ApiGateway/Middleware.cs | 22 +++++++++++++++------- Simple/Dockerfile | 21 +++++++++++++++++++++ Simple/appsettings.json | 18 +++++++++--------- docker-compose.override.yml | 9 +++++++-- docker-compose.yml | 28 ++++++++++++++++++++-------- 8 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 Simple/Dockerfile diff --git a/BlazorServerWebUI/Program.cs b/BlazorServerWebUI/Program.cs index c1174c7..3e320d6 100644 --- a/BlazorServerWebUI/Program.cs +++ b/BlazorServerWebUI/Program.cs @@ -1,13 +1,10 @@ -using BlazorServerWebUI.Data; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; - var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("http://apigateway") }); +// builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("http://apigateway") }); +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("http://simpleapigateway") }); var app = builder.Build(); diff --git a/DemoMicroserviceSolution.sln b/DemoMicroserviceSolution.sln index 51a0318..d87071e 100644 --- a/DemoMicroserviceSolution.sln +++ b/DemoMicroserviceSolution.sln @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{BF2E0098-3F2F- EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorServerWebUI", "BlazorServerWebUI\BlazorServerWebUI.csproj", "{BDDC7CB5-D948-42D3-92AD-CDC8B4DD65BB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleApiGateway", "Simple\SimpleApiGateway.csproj", "{122CC5B6-09E4-4F0C-8A32-2157142D3C45}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,10 @@ Global {BDDC7CB5-D948-42D3-92AD-CDC8B4DD65BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {BDDC7CB5-D948-42D3-92AD-CDC8B4DD65BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {BDDC7CB5-D948-42D3-92AD-CDC8B4DD65BB}.Release|Any CPU.Build.0 = Release|Any CPU + {122CC5B6-09E4-4F0C-8A32-2157142D3C45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {122CC5B6-09E4-4F0C-8A32-2157142D3C45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {122CC5B6-09E4-4F0C-8A32-2157142D3C45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {122CC5B6-09E4-4F0C-8A32-2157142D3C45}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -61,6 +67,7 @@ Global {C73F9EA0-BD0D-4B4D-9F34-5BA91F055F75} = {2B947001-C3C6-47D2-BFC9-D68A7D72AAAC} {3BD63280-F9EB-4978-AE5A-3324BFD7C143} = {EE2C0224-8662-49A2-9F66-ED47B9BFBB2D} {BDDC7CB5-D948-42D3-92AD-CDC8B4DD65BB} = {BF2E0098-3F2F-43FD-80D4-108EE255EC68} + {122CC5B6-09E4-4F0C-8A32-2157142D3C45} = {EE2C0224-8662-49A2-9F66-ED47B9BFBB2D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5DE9B6CB-0BEA-4BFB-8CD6-9928D54BB4DA} diff --git a/Simple/ApiGateway/Extension.cs b/Simple/ApiGateway/Extension.cs index c622b87..1b8e773 100644 --- a/Simple/ApiGateway/Extension.cs +++ b/Simple/ApiGateway/Extension.cs @@ -6,7 +6,7 @@ public static void AddApiGateway(this WebApplicationBuilder builder) { var result = builder.Services.FirstOrDefault(a => a.ServiceType == typeof(IHttpClientFactory)); if (result is null) builder.Services.AddHttpClient(); - + builder.Services.Configure>(builder.Configuration.GetSection("SimpleApiGatewayRoute")); builder.Services.AddSingleton(); } diff --git a/Simple/ApiGateway/Middleware.cs b/Simple/ApiGateway/Middleware.cs index 89542cc..a5f6821 100644 --- a/Simple/ApiGateway/Middleware.cs +++ b/Simple/ApiGateway/Middleware.cs @@ -1,4 +1,3 @@ -using System; using System.Text; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Options; @@ -8,12 +7,12 @@ namespace Simple.ApiGateway; public class Middleware : IMiddleware { private readonly IHttpClientFactory _httpClientFactory; - private readonly List apiRoutes; + private readonly List _apiRoutes; public Middleware(IHttpClientFactory httpClientFactory, IOptionsMonitor> options) { _httpClientFactory = httpClientFactory; - apiRoutes = options.CurrentValue; + _apiRoutes = options.CurrentValue; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) @@ -21,11 +20,14 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) var urlSegments = context.Request.Path.ToString().Split('/'); var urlPath = string.Join('/', urlSegments[1..]); if (urlPath == "/") urlPath = string.Empty; - var routeFound = apiRoutes.FirstOrDefault(a => urlPath.ToLower().StartsWith(a.RouteName)); + var routeFound = _apiRoutes.FirstOrDefault(a => urlPath.ToLower().StartsWith(a.RouteName.ToLower())); if (routeFound is not null) { context.Request.Scheme = routeFound.Scheme; - context.Request.Host = new HostString(routeFound.Host, routeFound.Port); + if (routeFound.Port == 80 || routeFound.Port == 143) + context.Request.Host = new HostString(routeFound.Host); + else + context.Request.Host = new HostString(routeFound.Host, routeFound.Port); context.Request.Path = $"/{urlPath}"; await ExecuteAsync(context); } @@ -50,7 +52,7 @@ private async Task ExecuteAsync(HttpContext context) catch (Exception ex) { context.Response.StatusCode = 500; - System.Console.WriteLine(ex.Message); + Console.WriteLine(ex.Message); } } @@ -63,7 +65,13 @@ private async Task SendRequest(HttpRequest request) requestContent = await readStream.ReadToEndAsync(); using var newRequest = new HttpRequestMessage(new HttpMethod(request.Method), request.GetDisplayUrl()); - newRequest.Content = new StringContent(requestContent, Encoding.UTF8, request.ContentType); + + string mediaType = null!; + + if (!string.IsNullOrEmpty(request.ContentType)) + mediaType = request.ContentType.Replace("; charset=utf-8", ""); + + newRequest.Content = new StringContent(requestContent, Encoding.UTF8, mediaType); return await client.SendAsync(newRequest); } } \ No newline at end of file diff --git a/Simple/Dockerfile b/Simple/Dockerfile new file mode 100644 index 0000000..aa81732 --- /dev/null +++ b/Simple/Dockerfile @@ -0,0 +1,21 @@ +#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /src +COPY ["Simple/SimpleApiGateway.csproj", "Simple/"] +RUN dotnet restore "Simple/SimpleApiGateway.csproj" +COPY . . +WORKDIR "/src/Simple" +RUN dotnet build "SimpleApiGateway.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "SimpleApiGateway.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "SimpleApiGateway.dll"] \ No newline at end of file diff --git a/Simple/appsettings.json b/Simple/appsettings.json index e9cea0a..3f15b52 100644 --- a/Simple/appsettings.json +++ b/Simple/appsettings.json @@ -8,23 +8,23 @@ "AllowedHosts": "*", "SimpleApiGatewayRoute": [ { - "routeName": "api/customer", + "routeName": "api/Customer", "scheme": "http", - "host": "localhost", - "port": 18002 + "host": "customerwebapi", + "port": 80 }, { - "routeName": "api/product", + "routeName": "api/Product", "scheme": "http", - "host": "localhost", - "port": 18004 + "host": "productwebapi", + "port": 80 } , { - "routeName": "api/order", + "routeName": "api/Order", "scheme": "http", - "host": "localhost", - "port": 18006 + "host": "orderwebapi", + "port": 80 } ] } \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 188e332..13ecc05 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -21,13 +21,18 @@ services: - "80" - apigateway: + # apigateway: + # environment: + # - ASPNETCORE_ENVIRONMENT=Development + # ports: + # - "80" + + simpleapigateway: environment: - ASPNETCORE_ENVIRONMENT=Development ports: - "80" - blazorserverwebui: environment: - ASPNETCORE_ENVIRONMENT=Development diff --git a/docker-compose.yml b/docker-compose.yml index 5e087f3..4062487 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: customerwebapi: container_name: customer-api - image: ${DOCKER_REGISTRY-}customerwebapi + image: customerwebapi build: context: . dockerfile: CustomerWebApi/Dockerfile @@ -41,7 +41,7 @@ services: productwebapi: container_name: product-api - image: ${DOCKER_REGISTRY-}productwebapi + image: productwebapi build: context: . dockerfile: ProductWebApi/Dockerfile @@ -62,7 +62,7 @@ services: orderwebapi: container_name: order-api - image: ${DOCKER_REGISTRY-}orderwebapi + image: orderwebapi build: context: . dockerfile: OrderWebApi/Dockerfile @@ -72,12 +72,24 @@ services: - DB_HOST=orderdb - DB_NAME=dms_order - apigateway: - container_name: api-gateway - image: ${DOCKER_REGISTRY-}apigateway + # apigateway: + # container_name: api-gateway + # image: apigateway + # build: + # context: . + # dockerfile: ApiGateway/Dockerfile + # ports: + # - 8001:80 + # networks: + # - backend + # - frontend + + simpleapigateway: + container_name: simple-api-gateway + image: simpleapigateway build: context: . - dockerfile: ApiGateway/Dockerfile + dockerfile: Simple/Dockerfile ports: - 8001:80 networks: @@ -86,7 +98,7 @@ services: blazorserverwebui: container_name: blazor-web-ui - image: ${DOCKER_REGISTRY-}blazorserverwebui + image: blazorserverwebui build: context: . dockerfile: BlazorServerWebUI/Dockerfile From 0e4bb50c91a3400ce64fd3d4c1a605947ac24a4c Mon Sep 17 00:00:00 2001 From: Proinfocus Date: Sat, 2 Jul 2022 13:24:21 +0530 Subject: [PATCH 4/4] Updated gitignore to ignore .vscode folder --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9491a2f..988c4b1 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,5 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd +/.vscode