From 1e764bac317b73eefd886ad160fb32378425b1d2 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 27 Nov 2023 17:03:55 +0000 Subject: [PATCH] Serve product images from WebApp, so CatalogApi doesn't have to be publicly reachable (#74) * Serve product images from WebApp, so CatalogApi doesn't have to be publicly reachable * Use Yarp to reverse-proxy the images * Retrieve HybridApp images from mobile BFF (proxied to catalog API) (#96) --------- Co-authored-by: Eilon Lipton --- src/Catalog.API/Apis/CatalogApi.cs | 4 ++- .../Components/Pages/Item/ItemPage.razor | 3 +- src/HybridApp/MauiProgram.cs | 11 ++++--- .../Services/ProductImageUrlProvider.cs | 9 ++++++ .../Extensions/Extensions.cs | 5 +-- src/Mobile.Bff.Shopping/Program.cs | 27 +++++++++++++++- src/Mobile.Bff.Shopping/appsettings.json | 31 ------------------- .../Components/Pages/Cart/CartPage.razor | 3 +- .../Components/Pages/Item/ItemPage.razor | 3 +- src/WebApp/Extensions/Extensions.cs | 3 ++ src/WebApp/Program.cs | 2 ++ src/WebApp/Services/BasketItem.cs | 1 - src/WebApp/Services/BasketState.cs | 1 - .../Services/ProductImageUrlProvider.cs | 9 ++++++ src/WebApp/WebApp.csproj | 1 + .../Catalog/CatalogListItem.razor | 3 +- .../Services/IProductImageUrlProvider.cs | 11 +++++++ 17 files changed, 80 insertions(+), 47 deletions(-) create mode 100644 src/HybridApp/Services/ProductImageUrlProvider.cs create mode 100644 src/WebApp/Services/ProductImageUrlProvider.cs create mode 100644 src/WebAppComponents/Services/IProductImageUrlProvider.cs diff --git a/src/Catalog.API/Apis/CatalogApi.cs b/src/Catalog.API/Apis/CatalogApi.cs index 08fee7a8..35114a9c 100644 --- a/src/Catalog.API/Apis/CatalogApi.cs +++ b/src/Catalog.API/Apis/CatalogApi.cs @@ -117,8 +117,9 @@ public static async Task> GetItemPictu string imageFileExtension = Path.GetExtension(item.PictureFileName); string mimetype = GetImageMimeTypeFromImageFileExtension(imageFileExtension); + DateTime lastModified = File.GetLastWriteTimeUtc(path); - return TypedResults.PhysicalFile(path, mimetype); + return TypedResults.PhysicalFile(path, mimetype, lastModified: lastModified); } public static async Task, RedirectToRouteHttpResult, Ok>>> GetItemsBySemanticRelevance( @@ -319,6 +320,7 @@ private static List ChangeUriPlaceholder(CatalogOptions options, Li ".wmf" => "image/wmf", ".jp2" => "image/jp2", ".svg" => "image/svg+xml", + ".webp" => "image/webp", _ => "application/octet-stream", }; diff --git a/src/HybridApp/Components/Pages/Item/ItemPage.razor b/src/HybridApp/Components/Pages/Item/ItemPage.razor index e04e87b7..f9ff3daf 100644 --- a/src/HybridApp/Components/Pages/Item/ItemPage.razor +++ b/src/HybridApp/Components/Pages/Item/ItemPage.razor @@ -3,6 +3,7 @@ @inject CatalogService CatalogService @* @inject BasketState BasketState *@ @inject NavigationManager Nav +@inject IProductImageUrlProvider ProductImages @if (item is not null) { @@ -11,7 +12,7 @@ @item.CatalogBrand?.Brand
- +

@item.Description

diff --git a/src/HybridApp/MauiProgram.cs b/src/HybridApp/MauiProgram.cs index 8f2178f5..c17e37d9 100644 --- a/src/HybridApp/MauiProgram.cs +++ b/src/HybridApp/MauiProgram.cs @@ -1,12 +1,14 @@ -using eShop.WebAppComponents.Services; +using eShop.HybridApp.Services; +using eShop.WebAppComponents.Services; using Microsoft.Extensions.Logging; namespace eShop.HybridApp; public static class MauiProgram { - // NOTE: Must have a trailing slash to ensure the full BaseAddress URL is used to resolve relative URLs - private static string MobileBffBaseUrl = "http://localhost:61632/catalog-api/"; + // NOTE: Must have a trailing slash on base URLs to ensure the full BaseAddress URL is used to resolve relative URLs + private static string MobileBffHost = "http://localhost:61632"; + internal static string MobileBffCatalogBaseUrl = $"{MobileBffHost}/catalog-api/"; public static MauiApp CreateMauiApp() { @@ -25,7 +27,8 @@ public static MauiApp CreateMauiApp() builder.Logging.AddDebug(); #endif - builder.Services.AddHttpClient(o => o.BaseAddress = new(MobileBffBaseUrl)); + builder.Services.AddHttpClient(o => o.BaseAddress = new(MobileBffCatalogBaseUrl)); + builder.Services.AddSingleton(); return builder.Build(); } diff --git a/src/HybridApp/Services/ProductImageUrlProvider.cs b/src/HybridApp/Services/ProductImageUrlProvider.cs new file mode 100644 index 00000000..17f4bf2c --- /dev/null +++ b/src/HybridApp/Services/ProductImageUrlProvider.cs @@ -0,0 +1,9 @@ +using eShop.WebAppComponents.Services; + +namespace eShop.HybridApp.Services; + +public class ProductImageUrlProvider : IProductImageUrlProvider +{ + public string GetProductImageUrl(int productId) + => $"{MauiProgram.MobileBffCatalogBaseUrl}api/v1/catalog/items/{productId}/pic"; +} diff --git a/src/Mobile.Bff.Shopping/Extensions/Extensions.cs b/src/Mobile.Bff.Shopping/Extensions/Extensions.cs index 0711bef1..9b9d1684 100644 --- a/src/Mobile.Bff.Shopping/Extensions/Extensions.cs +++ b/src/Mobile.Bff.Shopping/Extensions/Extensions.cs @@ -2,10 +2,7 @@ { public static void AddApplicationServices(this IHostApplicationBuilder builder) { - builder.Services - .AddReverseProxy() - .LoadFromConfig(builder.Configuration.GetRequiredSection("ReverseProxy")) - .AddServiceDiscoveryDestinationResolver(); + builder.Services.AddHttpForwarderWithServiceDiscovery(); builder.Services.AddHealthChecks() .AddUrlGroup(new Uri("http://catalog-api/health"), name: "catalogapi-check") diff --git a/src/Mobile.Bff.Shopping/Program.cs b/src/Mobile.Bff.Shopping/Program.cs index bde9bcac..a540cd4a 100644 --- a/src/Mobile.Bff.Shopping/Program.cs +++ b/src/Mobile.Bff.Shopping/Program.cs @@ -8,6 +8,31 @@ app.UseHttpsRedirection(); app.MapDefaultEndpoints(); -app.MapReverseProxy(); + +const string CatalogApiPrefix = "/api/v1/catalog/"; +var forwardedCatalogApis = new[] +{ + CatalogApiPrefix + "items", + CatalogApiPrefix + "items/by", + CatalogApiPrefix + "items/{id}", + CatalogApiPrefix + "items/by/{name}", + + CatalogApiPrefix + "items/withsemanticrelevance/{text}", + + CatalogApiPrefix + "items/type/{typeId}/brand/{brandId?}", + CatalogApiPrefix + "items/type/all/brand/{brandId?}", + CatalogApiPrefix + "catalogTypes", + CatalogApiPrefix + "catalogBrands", + + CatalogApiPrefix + "items/{id}/pic", +}; + +foreach (var forwardedUrl in forwardedCatalogApis) +{ + var mapFromPattern = "/catalog-api" + forwardedUrl; + var mapToTargetPath = forwardedUrl; + + app.MapForwarder(mapFromPattern, "http://catalog-api", mapToTargetPath); +} await app.RunAsync(); diff --git a/src/Mobile.Bff.Shopping/appsettings.json b/src/Mobile.Bff.Shopping/appsettings.json index e8ac9264..a168074d 100644 --- a/src/Mobile.Bff.Shopping/appsettings.json +++ b/src/Mobile.Bff.Shopping/appsettings.json @@ -5,36 +5,5 @@ "Microsoft.AspNetCore": "Warning", "System.Net.Http": "Warning" } - }, - "ReverseProxy": { - "Routes": { - "c-short": { - "ClusterId": "catalog", - "Match": { - "Path": "c/{**catch-all}" - }, - "Transforms": [ - { "PathRemovePrefix": "/c" } - ] - }, - "c-long": { - "ClusterId": "catalog", - "Match": { - "Path": "catalog-api/{**catch-all}" - }, - "Transforms": [ - { "PathRemovePrefix": "/catalog-api" } - ] - } - }, - "Clusters": { - "catalog": { - "Destinations": { - "destination0": { - "Address": "http://catalog-api" - } - } - } - } } } diff --git a/src/WebApp/Components/Pages/Cart/CartPage.razor b/src/WebApp/Components/Pages/Cart/CartPage.razor index 45f3238b..51e88256 100644 --- a/src/WebApp/Components/Pages/Cart/CartPage.razor +++ b/src/WebApp/Components/Pages/Cart/CartPage.razor @@ -1,6 +1,7 @@ @page "/cart" @inject NavigationManager Nav @inject BasketState Basket +@inject IProductImageUrlProvider ProductImages @attribute [StreamRendering] @attribute [Authorize] @@ -31,7 +32,7 @@ var quantity = CurrentOrPendingQuantity(item.ProductId, item.Quantity);

- @item.ProductName + @item.ProductName

@item.ProductName

$@item.UnitPrice.ToString("0.00")

diff --git a/src/WebApp/Components/Pages/Item/ItemPage.razor b/src/WebApp/Components/Pages/Item/ItemPage.razor index 49a514e4..975de245 100644 --- a/src/WebApp/Components/Pages/Item/ItemPage.razor +++ b/src/WebApp/Components/Pages/Item/ItemPage.razor @@ -3,6 +3,7 @@ @inject CatalogService CatalogService @inject BasketState BasketState @inject NavigationManager Nav +@inject IProductImageUrlProvider ProductImages @if (item is not null) { @@ -11,7 +12,7 @@ @item.CatalogBrand?.Brand
- @item.Name + @item.Name

@item.Description

diff --git a/src/WebApp/Extensions/Extensions.cs b/src/WebApp/Extensions/Extensions.cs index f1c950b9..bfb33c41 100644 --- a/src/WebApp/Extensions/Extensions.cs +++ b/src/WebApp/Extensions/Extensions.cs @@ -14,11 +14,14 @@ public static void AddApplicationServices(this IHostApplicationBuilder builder) builder.AddRabbitMqEventBus("EventBus") .AddEventBusSubscriptions(); + builder.Services.AddHttpForwarderWithServiceDiscovery(); + // Application services builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); // HTTP and GRPC client registrations builder.Services.AddGrpcClient(o => o.Address = new("http://basket-api")) diff --git a/src/WebApp/Program.cs b/src/WebApp/Program.cs index 2da79a47..e0aa9e32 100644 --- a/src/WebApp/Program.cs +++ b/src/WebApp/Program.cs @@ -26,4 +26,6 @@ app.MapRazorComponents().AddInteractiveServerRenderMode(); +app.MapForwarder("/product-images/{id}", "http://catalog-api", "/api/v1/catalog/items/{id}/pic"); + app.Run(); diff --git a/src/WebApp/Services/BasketItem.cs b/src/WebApp/Services/BasketItem.cs index 2018d886..056e44f7 100644 --- a/src/WebApp/Services/BasketItem.cs +++ b/src/WebApp/Services/BasketItem.cs @@ -8,5 +8,4 @@ public class BasketItem public decimal UnitPrice { get; set; } public decimal OldUnitPrice { get; set; } public int Quantity { get; set; } - public required string PictureUrl { get; set; } } diff --git a/src/WebApp/Services/BasketState.cs b/src/WebApp/Services/BasketState.cs index 6cc11337..1ab3ba7d 100644 --- a/src/WebApp/Services/BasketState.cs +++ b/src/WebApp/Services/BasketState.cs @@ -137,7 +137,6 @@ async Task> FetchCoreAsync() { Id = Guid.NewGuid().ToString(), // TODO: this value is meaningless, use ProductId instead. ProductId = catalogItem.Id, - PictureUrl = catalogItem.PictureUri, ProductName = catalogItem.Name, UnitPrice = catalogItem.Price, Quantity = item.Quantity, diff --git a/src/WebApp/Services/ProductImageUrlProvider.cs b/src/WebApp/Services/ProductImageUrlProvider.cs new file mode 100644 index 00000000..e71bf9cb --- /dev/null +++ b/src/WebApp/Services/ProductImageUrlProvider.cs @@ -0,0 +1,9 @@ +using eShop.WebAppComponents.Services; + +namespace eShop.WebApp.Services; + +public class ProductImageUrlProvider : IProductImageUrlProvider +{ + public string GetProductImageUrl(int productId) + => $"product-images/{productId}"; +} diff --git a/src/WebApp/WebApp.csproj b/src/WebApp/WebApp.csproj index 3caff563..2eebefaa 100644 --- a/src/WebApp/WebApp.csproj +++ b/src/WebApp/WebApp.csproj @@ -18,6 +18,7 @@ + diff --git a/src/WebAppComponents/Catalog/CatalogListItem.razor b/src/WebAppComponents/Catalog/CatalogListItem.razor index d8eef9e3..e072d01e 100644 --- a/src/WebAppComponents/Catalog/CatalogListItem.razor +++ b/src/WebAppComponents/Catalog/CatalogListItem.razor @@ -1,9 +1,10 @@ @using eShop.WebAppComponents.Item +@inject IProductImageUrlProvider ProductImages