From fe0fd36e0fd8bdf2fddd4245bce4ef0f8c9d6774 Mon Sep 17 00:00:00 2001 From: Erik Pique Date: Mon, 19 Aug 2019 09:24:58 +0200 Subject: [PATCH] add grpc basket --- docker-compose.override.yml | 6 + .../Controllers/BasketController.cs | 7 +- .../aggregator/Controllers/OrderController.cs | 2 +- .../Mobile.Bff.Shopping/aggregator/Dockerfile | 4 +- .../Filters/AuthorizeCheckOperationFilter.cs | 19 +-- ...ttpClientAuthorizationDelegatingHandler.cs | 13 +- .../Mobile.Shopping.HttpAggregator.csproj | 39 ++---- .../Mobile.Bff.Shopping/aggregator/Program.cs | 41 +++++++ .../aggregator/Services/BasketService.cs | 113 ++++++++++++++++-- .../aggregator/Services/CatalogService.cs | 31 +++-- .../aggregator/Services/IBasketService.cs | 2 +- .../Mobile.Bff.Shopping/aggregator/Startup.cs | 79 ++++++------ .../aggregator/appsettings.json | 5 + .../aggregator/appsettings.localhost.json | 15 +++ .../Basket/Basket.API/Basket.API.csproj | 9 ++ .../Basket/Basket.API/Grpc/BasketService.cs | 100 ++++++++++++++++ .../Repositories/RedisBasketRepository.cs | 2 +- src/Services/Basket/Basket.API/Program.cs | 27 ++++- .../Basket/Basket.API/Proto/basket.proto | 38 ++++++ src/Services/Basket/Basket.API/Startup.cs | 42 +++++-- .../Basket/Basket.API/appsettings.json | 5 + src/_build/dependencies.props | 1 + 22 files changed, 476 insertions(+), 124 deletions(-) create mode 100644 src/Services/Basket/Basket.API/Grpc/BasketService.cs create mode 100644 src/Services/Basket/Basket.API/Proto/basket.proto diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 60f2efb45..97379a4b0 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -219,6 +219,7 @@ services: - MarketingUrlHC=http://marketing.api/hc - PaymentUrlHC=http://payment.api/hc - LocationUrlHC=http://locations.api/hc + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 ports: - "5200:80" volumes: @@ -235,6 +236,7 @@ services: - MarketingUrlHC=http://marketing.api/hc - PaymentUrlHC=http://payment.api/hc - LocationUrlHC=http://locations.api/hc + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 ports: - "5201:80" volumes: @@ -251,6 +253,7 @@ services: - MarketingUrlHC=http://marketing.api/hc - PaymentUrlHC=http://payment.api/hc - LocationUrlHC=http://locations.api/hc + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 ports: - "5202:80" volumes: @@ -267,6 +270,7 @@ services: - MarketingUrlHC=http://marketing.api/hc - PaymentUrlHC=http://payment.api/hc - LocationUrlHC=http://locations.api/hc + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 ports: - "5203:80" volumes: @@ -286,6 +290,7 @@ services: - MarketingUrlHC=http://marketing.api/hc - PaymentUrlHC=http://payment.api/hc - LocationUrlHC=http://locations.api/hc + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 ports: - "5120:80" @@ -303,6 +308,7 @@ services: - MarketingUrlHC=http://marketing.api/hc - PaymentUrlHC=http://payment.api/hc - LocationUrlHC=http://locations.api/hc + - IdentityUrlExternal=http://${ESHOP_EXTERNAL_DNS_NAME_OR_IP}:5105 ports: - "5121:80" diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/BasketController.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/BasketController.cs index a2c0d1fea..65b07239a 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/BasketController.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/BasketController.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services; using System; -using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; @@ -36,7 +35,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers } // Retrieve the current basket - var basket = await _basket.GetByIdAsync(data.BuyerId) ?? new BasketData(data.BuyerId); + var basket = await _basket.GetById(data.BuyerId) ?? new BasketData(data.BuyerId); var catalogItems = await _catalog.GetCatalogItemsAsync(data.Items.Select(x => x.ProductId)); @@ -76,7 +75,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers } // Retrieve the current basket - var currentBasket = await _basket.GetByIdAsync(data.BasketId); + var currentBasket = await _basket.GetById(data.BasketId); if (currentBasket == null) { return BadRequest($"Basket with id {data.BasketId} not found."); @@ -118,7 +117,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers //item.PictureUri = // Step 2: Get current basket status - var currentBasket = (await _basket.GetByIdAsync(data.BasketId)) ?? new BasketData(data.BasketId); + var currentBasket = (await _basket.GetById(data.BasketId)) ?? new BasketData(data.BasketId); // Step 3: Merge current status with new product currentBasket.Items.Add(new BasketDataItem() { diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/OrderController.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/OrderController.cs index a4b33c6cb..ac359b4b5 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/OrderController.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Controllers/OrderController.cs @@ -32,7 +32,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Controllers return BadRequest("Need a valid basketid"); } // Get the basket data and build a order draft based on it - var basket = await _basketService.GetByIdAsync(basketId); + var basket = await _basketService.GetById(basketId); if (basket == null) { diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile index 9b03eccbd..3d89c9b27 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Dockerfile @@ -1,8 +1,8 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base +FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build +FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build WORKDIR /src COPY scripts scripts/ diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs index 21997360b..3b8298bfe 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Filters/AuthorizeCheckOperationFilter.cs @@ -1,7 +1,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters { using Microsoft.AspNetCore.Authorization; - using Swashbuckle.AspNetCore.Swagger; + using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using System.Collections.Generic; using System.Linq; @@ -10,7 +10,7 @@ { public class AuthorizeCheckOperationFilter : IOperationFilter { - public void Apply(Operation operation, OperationFilterContext context) + public void Apply(OpenApiOperation operation, OperationFilterContext context) { // Check for authorize attribute var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType().Any() || @@ -18,14 +18,19 @@ if (!hasAuthorize) return; - operation.Responses.TryAdd("401", new Response { Description = "Unauthorized" }); - operation.Responses.TryAdd("403", new Response { Description = "Forbidden" }); + operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" }); + operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" }); - operation.Security = new List>> + var oAuthScheme = new OpenApiSecurityScheme { - new Dictionary> + Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" } + }; + + operation.Security = new List + { + new OpenApiSecurityRequirement { - { "oauth2", new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" } } + [ oAuthScheme ] = new [] { "Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator" } } }; } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs index 967a8c826..4562139df 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Infrastructure/HttpClientAuthorizationDelegatingHandler.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; @@ -8,18 +9,22 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure { - public class HttpClientAuthorizationDelegatingHandler - : DelegatingHandler + public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccesor; + private readonly ILogger _logger; - public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor) + public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor, ILogger logger) { _httpContextAccesor = httpContextAccesor; + _logger = logger; } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + request.Version = new System.Version(2, 0); + request.Method = HttpMethod.Get; + var authorizationHeader = _httpContextAccesor.HttpContext .Request.Headers["Authorization"]; @@ -35,6 +40,8 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastruct request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } + _logger.LogInformation("@@@@@@@@@@@@@@@@@ {@request}", request); + return await base.SendAsync(request, cancellationToken); } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj index 57efd4a9f..ca8768a66 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Mobile.Shopping.HttpAggregator.csproj @@ -1,4 +1,4 @@ - - - - - netcoreapp2.2 - Mobile.Shopping.HttpAggregator - Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator - ..\..\..\docker-compose.dcproj - $(LangVersion) - - + - - - - - - - - - - - - - - - - - + + diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs index 5d1840192..0dc91cbc0 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Program.cs @@ -1,13 +1,21 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; using Serilog; +using System.IO; +using System.Net; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator { public class Program { + private static IConfiguration _configuration; + public static void Main(string[] args) { + _configuration = GetConfiguration(); + BuildWebHost(args).Run(); } @@ -24,6 +32,20 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator ReloadOnChange = false }); }) + .ConfigureKestrel(options => + { + var ports = GetDefinedPorts(_configuration); + + options.Listen(IPAddress.Any, ports.httpPort, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); + + options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + }); + }) .UseStartup() .UseSerilog((builderContext, config) => { @@ -33,5 +55,24 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator .WriteTo.Console(); }) .Build(); + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + var config = builder.Build(); + + return builder.Build(); + } + + private static (int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) + { + var grpcPort = config.GetValue("GRPC_PORT", 5001); + var port = config.GetValue("PORT", 80); + return (port, grpcPort); + } } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs index 5186fe361..657e3cd54 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/BasketService.cs @@ -1,41 +1,130 @@ -using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; +using grpc; +using Grpc.Net.Client; +using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Newtonsoft.Json; +using System; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using static grpc.Basket; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services { public class BasketService : IBasketService { - private readonly HttpClient _httpClient; - private readonly ILogger _logger; private readonly UrlsConfig _urls; + private readonly ILogger _logger; - public BasketService(HttpClient httpClient, ILogger logger, IOptions config) + public BasketService(HttpClient httpClient, IOptions config, ILogger logger) { _httpClient = httpClient; - _logger = logger; _urls = config.Value; + _logger = logger; } - public async Task GetByIdAsync(string id) + public async Task GetById(string id) { - var data = await _httpClient.GetStringAsync(_urls.Basket + UrlsConfig.BasketOperations.GetItemById(id)); + _logger.LogInformation("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GetById @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + + _logger.LogInformation("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Http2UnencryptedSupport disable @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + + _httpClient.BaseAddress = new Uri("http://localhost:5001"); + + _logger.LogInformation("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ {_httpClient.BaseAddress} @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@", _httpClient.BaseAddress); + + var client = GrpcClient.Create(_httpClient); + + _logger.LogInformation("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ client create @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + + var response = await client.GetBasketByIdAsync(new BasketRequest { Id = id }); + + _logger.LogInformation("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ call grpc server @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); + + _logger.LogInformation("############## DATA: {@a}", response.Buyerid); + + //if (streaming.IsCompleted) + //{ + // _logger.LogInformation("############## DATA: {@a}", streaming.GetResult()); + //} + //var streaming = client.GetBasketById(new BasketRequest { Id = id }); - var basket = !string.IsNullOrEmpty(data) ? JsonConvert.DeserializeObject(data) : null; - return basket; + //var status = streaming.GetStatus(); + + //if (status.StatusCode == Grpc.Core.StatusCode.OK) + //{ + // return null; + //} + + return null; + //return MapToBasketData(response.ResponseStream); } public async Task UpdateAsync(BasketData currentBasket) { - var basketContent = new StringContent(JsonConvert.SerializeObject(currentBasket), System.Text.Encoding.UTF8, "application/json"); + _httpClient.BaseAddress = new Uri(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket()); + + var client = GrpcClient.Create(_httpClient); + var request = MapToCustomerBasketRequest(currentBasket); + + await client.UpdateBasketAsync(request); + } + + private BasketData MapToBasketData(CustomerBasketResponse customerBasketRequest) + { + if (customerBasketRequest == null) + { + return null; + } + + var map = new BasketData + { + BuyerId = customerBasketRequest.Buyerid + }; + + customerBasketRequest.Items.ToList().ForEach(item => map.Items.Add(new BasketDataItem + { + Id = item.Id, + OldUnitPrice = (decimal)item.Oldunitprice, + PictureUrl = item.Pictureurl, + ProductId = item.Productid, + ProductName = item.Productname, + Quantity = item.Quantity, + UnitPrice = (decimal)item.Unitprice + })); + + return map; + } + + private CustomerBasketRequest MapToCustomerBasketRequest(BasketData basketData) + { + if (basketData == null) + { + return null; + } + + var map = new CustomerBasketRequest + { + Buyerid = basketData.BuyerId + }; + + basketData.Items.ToList().ForEach(item => map.Items.Add(new BasketItemResponse + { + Id = item.Id, + Oldunitprice = (double)item.OldUnitPrice, + Pictureurl = item.PictureUrl, + Productid = item.ProductId, + Productname = item.ProductName, + Quantity = item.Quantity, + Unitprice = (double)item.UnitPrice + })); - var data = await _httpClient.PostAsync(_urls.Basket + UrlsConfig.BasketOperations.UpdateBasket(), basketContent); + return map; } } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs index 69bca2b39..fbcc8349b 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/CatalogService.cs @@ -1,33 +1,37 @@ -using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; +using CatalogApi; +using Grpc.Net.Client; +using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Models; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; +using static CatalogApi.Catalog; namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services { public class CatalogService : ICatalogService { private readonly HttpClient _httpClient; - private readonly ILogger _logger; private readonly UrlsConfig _urls; - public CatalogService(HttpClient httpClient, ILogger logger, IOptions config) + public CatalogService(HttpClient httpClient, IOptions config) { _httpClient = httpClient; - _logger = logger; _urls = config.Value; } public async Task GetCatalogItemAsync(int id) { - var stringContent = await _httpClient.GetStringAsync(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id)); - var catalogItem = JsonConvert.DeserializeObject(stringContent); + _httpClient.BaseAddress = new Uri(_urls.Catalog + UrlsConfig.CatalogOperations.GetItemById(id)); - return catalogItem; + var client = GrpcClient.Create(_httpClient); + var request = new CatalogItemRequest { Id = id }; + var response = await client.GetItemByIdAsync(request); + + return MapToCatalogItemResponse(response); } public async Task> GetCatalogItemsAsync(IEnumerable ids) @@ -37,5 +41,16 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services return catalogItems; } + + private CatalogItem MapToCatalogItemResponse(CatalogItemResponse catalogItemResponse) + { + return new CatalogItem + { + Id = catalogItemResponse.Id, + Name = catalogItemResponse.Name, + PictureUri = catalogItemResponse.PictureUri, + Price = (decimal)catalogItemResponse.Price + }; + } } } diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs index ad49e1adb..d7a390c75 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Services/IBasketService.cs @@ -5,7 +5,7 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Services { public interface IBasketService { - Task GetByIdAsync(string id); + Task GetById(string id); Task UpdateAsync(BasketData currentBasket); diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs index c60cb787a..2a9d0b341 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/Startup.cs @@ -5,7 +5,6 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Config; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Filters.Basket.API.Infrastructure.Filters; using Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator.Infrastructure; @@ -14,9 +13,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; using Polly; using Polly.Extensions.Http; -using Swashbuckle.AspNetCore.Swagger; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; @@ -63,19 +62,6 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator app.UsePathBase(pathBase); } - app.UseHealthChecks("/hc", new HealthCheckOptions() - { - Predicate = _ => true, - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse - }); - - app.UseHealthChecks("/liveness", new HealthCheckOptions - { - Predicate = r => r.Name.Contains("self") - }); - - app.UseCors("CorsPolicy"); - if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -86,15 +72,31 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator app.UseHsts(); } - app.UseAuthentication(); + app.UseCors("CorsPolicy"); app.UseHttpsRedirection(); - app.UseMvc(); + app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + endpoints.MapControllers(); + endpoints.MapHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse + }); + endpoints.MapHealthChecks("/liveness", new HealthCheckOptions + { + Predicate = r => r.Name.Contains("self") + }); + }); app.UseSwagger().UseSwaggerUI(c => { c.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Purchase BFF V1"); - c.OAuthClientId("Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregatorwaggerui"); + c.OAuthClientId("mobileshoppingaggswaggerui"); c.OAuthClientSecret(string.Empty); c.OAuthRealm(string.Empty); c.OAuthAppName("Purchase BFF Swagger UI"); @@ -109,29 +111,32 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator services.AddOptions(); services.Configure(configuration.GetSection("urls")); - services.AddMvc() - .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + services.AddControllers() + .AddNewtonsoftJson(); services.AddSwaggerGen(options => { options.DescribeAllEnumsAsStrings(); - options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info + options.SwaggerDoc("v1", new OpenApiInfo { Title = "Shopping Aggregator for Mobile Clients", Version = "v1", - Description = "Shopping Aggregator for Mobile Clients", - TermsOfService = "Terms Of Service" + Description = "Shopping Aggregator for Mobile Clients" }); - - options.AddSecurityDefinition("oauth2", new OAuth2Scheme + options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { - Type = "oauth2", - Flow = "implicit", - AuthorizationUrl = $"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize", - TokenUrl = $"{configuration.GetValue("IdentityUrlExternal")}/connect/token", - Scopes = new Dictionary() + Type = SecuritySchemeType.OAuth2, + Flows = new OpenApiOAuthFlows() { - { "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" } + Implicit = new OpenApiOAuthFlow() + { + AuthorizationUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/authorize"), + TokenUrl = new Uri($"{configuration.GetValue("IdentityUrlExternal")}/connect/token"), + Scopes = new Dictionary() + { + { "mobileshoppingagg", "Shopping Aggregator for Mobile Clients" } + } + } } }); @@ -152,7 +157,8 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator } public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration) { - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub"); + var identityUrl = configuration.GetValue("urls:identity"); services.AddAuthentication(options => @@ -166,15 +172,6 @@ namespace Microsoft.eShopOnContainers.Mobile.Shopping.HttpAggregator options.Authority = identityUrl; options.RequireHttpsMetadata = false; options.Audience = "mobileshoppingagg"; - options.Events = new JwtBearerEvents() - { - OnAuthenticationFailed = async ctx => - { - }, - OnTokenValidated = async ctx => - { - } - }; }); return services; diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json index 26bb0ac7a..95b8bad4c 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.json @@ -1,4 +1,9 @@ { + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, "Logging": { "IncludeScopes": false, "Debug": { diff --git a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json index 57b5e894d..91bbf5f87 100644 --- a/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json +++ b/src/ApiGateways/Mobile.Bff.Shopping/aggregator/appsettings.localhost.json @@ -4,5 +4,20 @@ "catalog": "http://localhost:55101", "orders": "http://localhost:55102", "identity": "http://localhost:55105" + }, + "IdentityUrlExternal": "http://localhost:5105", + "IdentityUrl": "http://localhost:5105", + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Debug" + } + }, + "Console": { + "LogLevel": { + "Default": "Debug" + } + } } } diff --git a/src/Services/Basket/Basket.API/Basket.API.csproj b/src/Services/Basket/Basket.API/Basket.API.csproj index 5281f9107..78cd14b2a 100644 --- a/src/Services/Basket/Basket.API/Basket.API.csproj +++ b/src/Services/Basket/Basket.API/Basket.API.csproj @@ -21,6 +21,9 @@ + + + @@ -39,6 +42,12 @@ + + + + + + diff --git a/src/Services/Basket/Basket.API/Grpc/BasketService.cs b/src/Services/Basket/Basket.API/Grpc/BasketService.cs new file mode 100644 index 000000000..f6bd42ce7 --- /dev/null +++ b/src/Services/Basket/Basket.API/Grpc/BasketService.cs @@ -0,0 +1,100 @@ +using Grpc.Core; +using Microsoft.eShopOnContainers.Services.Basket.API.Model; +using Microsoft.Extensions.Logging; +using System.Linq; +using System.Threading.Tasks; + +namespace grpc +{ + public class BasketService : Basket.BasketBase + { + private readonly IBasketRepository _repository; + private readonly ILogger _logger; + + public BasketService(IBasketRepository repository, ILogger logger) + { + _repository = repository; + _logger = logger; + } + + public override async Task GetBasketById(BasketRequest request, ServerCallContext context) + { + _logger.LogInformation($"Begin grpc call from method {context.Method} for basket id {request.Id}"); + + var data = await _repository.GetBasketAsync(request.Id); + + if (data != null) + { + context.Status = new Status(StatusCode.OK, $"Basket with id {request.Id} do exist"); + + return MapToCustomerBasketResponse(data); + } + else + { + context.Status = new Status(StatusCode.NotFound, $"Basket with id {request.Id} do not exist"); + } + + return null; + } + + public override async Task UpdateBasket(CustomerBasketRequest request, ServerCallContext context) + { + _logger.LogInformation($"Begin grpc call BasketService.UpdateBasketAsync for buyer id {request.Buyerid}"); + + var customerBasket = MapToCustomerBasket(request); + + var response = await _repository.UpdateBasketAsync(customerBasket); + + if (response != null) + { + return MapToCustomerBasketResponse(response); + } + + context.Status = new Status(StatusCode.NotFound, $"Basket with buyer id {request.Buyerid} do not exist"); + + return null; + } + + private CustomerBasketResponse MapToCustomerBasketResponse(CustomerBasket customerBasket) + { + var response = new CustomerBasketResponse + { + Buyerid = customerBasket.BuyerId + }; + + customerBasket.Items.ForEach(item => response.Items.Add(new BasketItemResponse + { + Id = item.Id, + Oldunitprice = (double)item.OldUnitPrice, + Pictureurl = item.PictureUrl, + Productid = item.ProductId, + Productname = item.ProductName, + Quantity = item.Quantity, + Unitprice = (double)item.UnitPrice + })); + + return response; + } + + private CustomerBasket MapToCustomerBasket(CustomerBasketRequest customerBasketRequest) + { + var response = new CustomerBasket + { + BuyerId = customerBasketRequest.Buyerid + }; + + customerBasketRequest.Items.ToList().ForEach(item => response.Items.Add(new BasketItem + { + Id = item.Id, + OldUnitPrice = (decimal)item.Oldunitprice, + PictureUrl = item.Pictureurl, + ProductId = item.Productid, + ProductName = item.Productname, + Quantity = item.Quantity, + UnitPrice = (decimal)item.Unitprice + })); + + return response; + } + } +} diff --git a/src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs b/src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs index 6653f5ed1..93adcc023 100644 --- a/src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs +++ b/src/Services/Basket/Basket.API/Infrastructure/Repositories/RedisBasketRepository.cs @@ -28,7 +28,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Infrastructure.Reposit public IEnumerable GetUsers() { - var server = GetServer(); + var server = GetServer(); var data = server.Keys(); return data?.Select(k => k.ToString()); diff --git a/src/Services/Basket/Basket.API/Program.cs b/src/Services/Basket/Basket.API/Program.cs index 40cc0eebc..caf3d581b 100644 --- a/src/Services/Basket/Basket.API/Program.cs +++ b/src/Services/Basket/Basket.API/Program.cs @@ -2,11 +2,12 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; using Serilog; using System; using System.IO; +using System.Net; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -45,8 +46,21 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API private static IWebHost BuildWebHost(IConfiguration configuration, string[] args) => WebHost.CreateDefaultBuilder(args) .CaptureStartupErrors(false) - .UseFailing(options => - options.ConfigPath = "/Failing") + .ConfigureKestrel(options => + { + var ports = GetDefinedPorts(configuration); + + options.Listen(IPAddress.Any, ports.httpPort, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + }); + + options.Listen(IPAddress.Any, ports.grpcPort, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + }); + }) + .UseFailing(options => options.ConfigPath = "/Failing") .UseStartup() .UseApplicationInsights() .UseContentRoot(Directory.GetCurrentDirectory()) @@ -88,5 +102,12 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API return builder.Build(); } + + private static (int httpPort, int grpcPort) GetDefinedPorts(IConfiguration config) + { + var grpcPort = config.GetValue("GRPC_PORT", 5001); + var port = config.GetValue("PORT", 80); + return (port, grpcPort); + } } } diff --git a/src/Services/Basket/Basket.API/Proto/basket.proto b/src/Services/Basket/Basket.API/Proto/basket.proto new file mode 100644 index 000000000..9b69effad --- /dev/null +++ b/src/Services/Basket/Basket.API/Proto/basket.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; + +option csharp_namespace = "grpc"; + +package BasketApi; + +service Basket { + rpc GetBasketById(BasketRequest) returns (CustomerBasketResponse) { + option (google.api.http) = { + get: "/GetBasketById" + }; + } + rpc UpdateBasket(CustomerBasketRequest) returns (CustomerBasketResponse) {} +} + +message BasketRequest { + string id = 1; +} + +message CustomerBasketRequest { + string buyerid = 1; + repeated BasketItemResponse items = 2; +} + +message CustomerBasketResponse { + string buyerid = 1; + repeated BasketItemResponse items = 2; +} + +message BasketItemResponse { + string id = 1; + string productid = 2; + string productname = 3; + double unitprice = 4; + double oldunitprice = 5; + int32 quantity = 6; + string pictureurl = 7; +} diff --git a/src/Services/Basket/Basket.API/Startup.cs b/src/Services/Basket/Basket.API/Startup.cs index 2533f31e5..0176ea936 100644 --- a/src/Services/Basket/Basket.API/Startup.cs +++ b/src/Services/Basket/Basket.API/Startup.cs @@ -4,6 +4,7 @@ using Basket.API.Infrastructure.Filters; using Basket.API.Infrastructure.Middlewares; using Basket.API.IntegrationEvents.EventHandling; using Basket.API.IntegrationEvents.Events; +using grpc; using HealthChecks.UI.Client; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; @@ -32,6 +33,7 @@ using StackExchange.Redis; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; +using System.IO; namespace Microsoft.eShopOnContainers.Services.Basket.API { @@ -47,6 +49,11 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API // This method gets called by the runtime. Use this method to add services to the container. public virtual IServiceProvider ConfigureServices(IServiceCollection services) { + services.AddGrpc(options => + { + options.EnableDetailedErrors = true; + }); + RegisterAppInsights(services); services.AddControllers(options => @@ -84,10 +91,9 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API } } }); - + options.OperationFilter(); }); - ConfigureAuthService(services); @@ -192,6 +198,14 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API app.UsePathBase(pathBase); } + app.UseSwagger() + .UseSwaggerUI(setup => + { + setup.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Basket.API V1"); + setup.OAuthClientId("basketswaggerui"); + setup.OAuthAppName("Basket Swagger UI"); + }); + app.UseRouting(); ConfigureAuth(app); @@ -202,6 +216,21 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API { endpoints.MapDefaultControllerRoute(); endpoints.MapControllers(); + endpoints.MapGet("/_proto/", async ctx => + { + ctx.Response.ContentType = "text/plain"; + using var fs = new FileStream(Path.Combine(env.ContentRootPath, "Proto", "basket.proto"), FileMode.Open, FileAccess.Read); + using var sr = new StreamReader(fs); + while (!sr.EndOfStream) + { + var line = await sr.ReadLineAsync(); + if (line != "/* >>" || line != "<< */") + { + await ctx.Response.WriteAsync(line); + } + } + }); + endpoints.MapGrpcService(); endpoints.MapHealthChecks("/hc", new HealthCheckOptions() { Predicate = _ => true, @@ -212,15 +241,6 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API Predicate = r => r.Name.Contains("self") }); }); - - app.UseSwagger() - .UseSwaggerUI(setup => - { - setup.SwaggerEndpoint($"{ (!string.IsNullOrEmpty(pathBase) ? pathBase : string.Empty) }/swagger/v1/swagger.json", "Basket.API V1"); - setup.OAuthClientId("basketswaggerui"); - setup.OAuthAppName("Basket Swagger UI"); - }); - ConfigureEventBus(app); } diff --git a/src/Services/Basket/Basket.API/appsettings.json b/src/Services/Basket/Basket.API/appsettings.json index 37d5b08d6..a5b723116 100644 --- a/src/Services/Basket/Basket.API/appsettings.json +++ b/src/Services/Basket/Basket.API/appsettings.json @@ -11,6 +11,11 @@ } } }, + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + }, "SubscriptionClientName": "Basket", "ApplicationInsights": { "InstrumentationKey": "" diff --git a/src/_build/dependencies.props b/src/_build/dependencies.props index 44651cb53..e7941d51b 100644 --- a/src/_build/dependencies.props +++ b/src/_build/dependencies.props @@ -19,6 +19,7 @@ 0.1.22-pre1 3.9.0-rc1 1.22.0 + 0.1.22-pre2 3.0.0-preview7.19362.4 1.7.9