From fcb939fa10c1fa0af593327abba2b03be9e3de9d Mon Sep 17 00:00:00 2001 From: Sven Vinkemeier Date: Sun, 19 Nov 2017 21:00:26 +0100 Subject: [PATCH 01/11] Added NuGetPack.ps1 --- .../HiP-ThumbnailService.Sdk.csproj | 1 - NuGetPack.ps1 | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 NuGetPack.ps1 diff --git a/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj b/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj index 4c301eb..0a9a414 100644 --- a/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj +++ b/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj @@ -9,7 +9,6 @@ True - diff --git a/NuGetPack.ps1 b/NuGetPack.ps1 new file mode 100644 index 0000000..72b4c14 --- /dev/null +++ b/NuGetPack.ps1 @@ -0,0 +1,10 @@ +$csproj = (ls *.Sdk\*.csproj).FullName +Switch ("$env:Build_SourceBranchName") +{ + "master" { dotnet pack "$csproj" -o . } + "develop" { dotnet pack "$csproj" -o . --version-suffix "develop" } + default { exit } +} +$nupkg = (ls *.Sdk\*.nupkg).FullName +dotnet --% nuget push "$nupkg" -k %MyGetKey% -s %NuGetFeed% +$LASTEXITCODE = 0 \ No newline at end of file From ad3bbfe87fa5325e1da521fa05d9abb7b878c3db Mon Sep 17 00:00:00 2001 From: Sven Erik Vinkemeier Date: Sat, 2 Dec 2017 17:26:55 +0100 Subject: [PATCH 02/11] Fixed script (#10) --- NuGetPack.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGetPack.ps1 b/NuGetPack.ps1 index 72b4c14..971166d 100644 --- a/NuGetPack.ps1 +++ b/NuGetPack.ps1 @@ -6,5 +6,5 @@ Switch ("$env:Build_SourceBranchName") default { exit } } $nupkg = (ls *.Sdk\*.nupkg).FullName -dotnet --% nuget push "$nupkg" -k %MyGetKey% -s %NuGetFeed% +dotnet nuget push "$nupkg" -k "$env:MyGetKey" -s "$env:NuGetFeed" $LASTEXITCODE = 0 \ No newline at end of file From f5850d8d9c4d957c02ed28ce8f674bc71151375a Mon Sep 17 00:00:00 2001 From: Sven Erik Vinkemeier Date: Mon, 18 Dec 2017 11:20:17 +0100 Subject: [PATCH 03/11] SDK & documentation improvements (#11) * Improved documentation and README, minor code improvements, updated WebserviceLib, added ThumbnailConfig and an injectable ThumbnailService to SDK, bumped SDK version to v1.1.0 * Finished ThumbnailService * Added cache invalidation method * Fixed compiler errors * Improved validation of config * Fix * Fixed: Handling HttpContext == null * Fixed Resharper warnings * Fixed Resharper warning --- .../HiP-ThumbnailService.Sdk.csproj | 7 +- HiP-ThumbnailService.Sdk/ThumbnailConfig.cs | 21 +++++ HiP-ThumbnailService.Sdk/ThumbnailService.cs | 91 +++++++++++++++++++ .../Arguments/CreationArgs.cs | 67 ++++---------- HiP-ThumbnailService/Arguments/CropMode.cs | 2 +- .../Controllers/ThumbnailsController.cs | 70 +++++++------- .../HiP-ThumbnailService.csproj | 8 +- HiP-ThumbnailService/Startup.cs | 43 ++------- HiP-ThumbnailService/Utility/AuthConfig.cs | 8 -- HiP-ThumbnailService/Utility/CorsConfig.cs | 16 ---- .../Utility/EndpointConfig.cs | 7 -- .../Utility/ThumbnailConfig.cs | 15 ++- .../appsettings.Development.json.example | 22 ----- HiP-ThumbnailService/appsettings.json | 72 ++++++++------- README.md | 29 +++++- 15 files changed, 256 insertions(+), 222 deletions(-) create mode 100644 HiP-ThumbnailService.Sdk/ThumbnailConfig.cs create mode 100644 HiP-ThumbnailService.Sdk/ThumbnailService.cs delete mode 100644 HiP-ThumbnailService/Utility/AuthConfig.cs delete mode 100644 HiP-ThumbnailService/Utility/CorsConfig.cs delete mode 100644 HiP-ThumbnailService/Utility/EndpointConfig.cs delete mode 100644 HiP-ThumbnailService/appsettings.Development.json.example diff --git a/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj b/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj index 0a9a414..a759882 100644 --- a/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj +++ b/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj @@ -4,14 +4,17 @@ netstandard2.0 PaderbornUniversity.SILab.Hip.ThumbnailService 1701;1702;1705;1591 - 1.0.0 + 1.1.2 $(Version)-$(VersionSuffix) True + + + - + diff --git a/HiP-ThumbnailService.Sdk/ThumbnailConfig.cs b/HiP-ThumbnailService.Sdk/ThumbnailConfig.cs new file mode 100644 index 0000000..598bb6b --- /dev/null +++ b/HiP-ThumbnailService.Sdk/ThumbnailConfig.cs @@ -0,0 +1,21 @@ +namespace PaderbornUniversity.SILab.Hip.ThumbnailService +{ + /// + /// Configuration properties for clients using the thumbnail service. + /// + public sealed class ThumbnailConfig + { + /// + /// URL pointing to a running instance of the thumbnail service. + /// Example: "https://docker-hip.cs.upb.de/develop/thumbnailservice" + /// + public string ThumbnailServiceHost { get; set; } + + /// + /// Relative URL pattern for generating thumbnail URLs. Should contain one or more placeholders + /// "{0}", "{1}" etc. that are replaced with the ID(s) of the requested image at runtime. + /// Example: "datastore/api/Media/{0}/File" + /// + public string ThumbnailUrlPattern { get; set; } + } +} diff --git a/HiP-ThumbnailService.Sdk/ThumbnailService.cs b/HiP-ThumbnailService.Sdk/ThumbnailService.cs new file mode 100644 index 0000000..5c1b46a --- /dev/null +++ b/HiP-ThumbnailService.Sdk/ThumbnailService.cs @@ -0,0 +1,91 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Threading.Tasks; + +namespace PaderbornUniversity.SILab.Hip.ThumbnailService +{ + /// + /// A service that can be used with ASP.NET Core dependency injection. + /// Usage: In ConfigureServices(): + /// + /// services.Configure<ThumbnailConfig>(Configuration.GetSection("Thumbnails")); + /// services.AddSingleton<ThumbnailService>(); + /// + /// + public class ThumbnailService + { + private readonly ThumbnailConfig _config; + private readonly ILogger _logger; + private readonly IHttpContextAccessor _httpContextAccessor; + + public ThumbnailsClient ThumbnailsClient => new ThumbnailsClient(_config.ThumbnailServiceHost) + { + Authorization = _httpContextAccessor.HttpContext?.Request.Headers["Authorization"] + }; + + public ThumbnailService(IOptions config, ILogger logger, + IHttpContextAccessor httpContextAccessor) + { + _config = config.Value; + _logger = logger; + _httpContextAccessor = httpContextAccessor; + + if (string.IsNullOrWhiteSpace(config.Value.ThumbnailServiceHost)) + logger.LogWarning($"{nameof(ThumbnailConfig.ThumbnailServiceHost)} is not configured correctly!"); + + if (string.IsNullOrWhiteSpace(config.Value.ThumbnailUrlPattern)) + logger.LogWarning($"{nameof(ThumbnailConfig.ThumbnailUrlPattern)} is not configured correctly!"); + } + + /// + /// Constructs an absolute URL that, when accessed, returns an image from the thumbnail service. + /// + /// Example: Given + /// ThumbnailServiceHost = "https://docker-hip.cs.upb.de/develop/thumbnailservice" and + /// ThumbnailUrlPattern = "datastore/api/Media/{0}/File", + /// => GetThumbnailUrl(42) = "https://docker-hip.cs.upb.de/develop/thumbnailservice?Url=datastore/api/Media/42/File" + /// + /// Arguments replacing the placeholders in + public string GetThumbnailUrl(params object[] args) => + $"{_config.ThumbnailServiceHost}/api/Thumbnails?Url={GetThumbnailUrlArgument(args)}"; + + /// + /// Constructs the relative URL that is used to request thumbnails. + /// + /// Arguments replacing the placeholders in + public string GetThumbnailUrlArgument(params object[] args) => + string.Format(_config.ThumbnailUrlPattern ?? "", args); + + /// + /// Tries to delete all cached thumbnails of an image in the thumbnail service. + /// Exceptions are catched and logged as warning. + /// + /// Arguments replacing the placeholders in + public async Task TryClearThumbnailCacheAsync(params object[] args) + { + if (string.IsNullOrWhiteSpace(_config.ThumbnailUrlPattern) || + string.IsNullOrWhiteSpace(_config.ThumbnailServiceHost)) + { + return false; + } + + var urlArgument = GetThumbnailUrlArgument(args); + + try + { + await ThumbnailsClient.DeleteAsync(urlArgument); + return true; + } + catch (Exception e) + { + _logger.LogWarning(e, + $"Request to clear thumbnail cache failed for relative URL '{urlArgument}'; " + + $"thumbnail service might return outdated images."); + + return false; + } + } + } +} diff --git a/HiP-ThumbnailService/Arguments/CreationArgs.cs b/HiP-ThumbnailService/Arguments/CreationArgs.cs index edcc632..39a4a3f 100644 --- a/HiP-ThumbnailService/Arguments/CreationArgs.cs +++ b/HiP-ThumbnailService/Arguments/CreationArgs.cs @@ -1,71 +1,36 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using Newtonsoft.Json; namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Arguments { public class CreationArgs { + /// + /// URL from where the thumbnail service can retrieve the original image. + /// This URL must be relative to 'HostUrl' configured in the thumbnail service. + /// Example: "datastore/Media/42/File" + /// (with 'HostUrl' configured as "https://docker-hip.cs.upb.de/develop/" for example) + /// [Required] public string Url { get; set; } + /// + /// One of the preconfigured size options, e.g. "small". If null or empty, the image is + /// returned in its original size without cropping or resizing being applied. + /// public string Size { get; set; } /// - /// Specifies crop mode that is used. If no mode is specified is used + /// Image cropping mode that is used. Defaults to . /// - [JsonIgnore] - public CropMode Mode - { -#pragma warning disable 0618 - get => InternalMode ?? CropMode.FillSquare; - set => InternalMode = value; -#pragma warning restore 0618 - } - - /// Property 'InternalMode' is listed by NSwag, but not by Swashbuckle (due to 'internal'). - /// Property 'Mode' is listed by Swashbuckle, but not by NSwag (due to [JsonIgnore]). - /// - /// We need a NULLABLE status parameter for NSwag because: - /// 1) The status parameter shouldn't be required (clients shouldn't need to pass it, it defaults to FillSquare) - /// 2) Since status is not required, the NSwag-generated C# client has "CropMode? mode = null" in the - /// method signature, however if it weren't nullable here, the client would throw an exception if 'mode == null'. - /// This is weird: The method signature states that status can be null, but passing null throws an exception. - /// - /// Why don't we make Mode nullable in general? We don't want the rest of the codebase to have to distinguish - /// between 'Mode == null' and 'Mode == FillSquare'. - [JsonProperty("mode")] [DefaultValue(CropMode.FillSquare)] - [Obsolete("For internal use only. Use 'Mode' instead.")] - internal CropMode? InternalMode { get; set; } + public CropMode Mode { get; set; } = CropMode.FillSquare; /// - /// Specifies image format that the resulting thumbnail has. If no format is specified is used + /// The desired image format of the resulting thumbnail. + /// Defaults to . /// - [JsonIgnore] - public RequestedImageFormat Format - { -#pragma warning disable 0618 - get => InternalFormat ?? RequestedImageFormat.Jpeg; - set => InternalFormat = value; -#pragma warning restore 0618 - } - - /// Property 'InternalFormat' is listed by NSwag, but not by Swashbuckle (due to 'internal'). - /// Property 'Format' is listed by Swashbuckle, but not by NSwag (due to [JsonIgnore]). - /// - /// We need a NULLABLE status parameter for NSwag because: - /// 1) The status parameter shouldn't be required (clients shouldn't need to pass it, it defaults to Jpeg) - /// 2) Since status is not required, the NSwag-generated C# client has "RequestedImageFormat? format = null" in the - /// method signature, however if it weren't nullable here, the client would throw an exception if 'format == null'. - /// This is weird: The method signature states that status can be null, but passing null throws an exception. - /// - /// Why don't we make Format nullable in general? We don't want the rest of the codebase to have to distinguish - /// between 'Format == null' and 'Format == Jpeg'. - [JsonProperty("format")] [DefaultValue(RequestedImageFormat.Jpeg)] - [Obsolete("For internal use only. Use 'Format' instead.")] - internal RequestedImageFormat? InternalFormat { get; set; } + public RequestedImageFormat Format { get; set; } = RequestedImageFormat.Jpeg; } } diff --git a/HiP-ThumbnailService/Arguments/CropMode.cs b/HiP-ThumbnailService/Arguments/CropMode.cs index 6867950..f5c27f6 100644 --- a/HiP-ThumbnailService/Arguments/CropMode.cs +++ b/HiP-ThumbnailService/Arguments/CropMode.cs @@ -2,7 +2,7 @@ { /// /// Describes the different modes that can be used for cropping. - /// crops at the center of the image resulting in a square + /// crops at the center of the image resulting in a square. /// keeps the aspect ratio while cropping the longer side to the specified value. /// public enum CropMode diff --git a/HiP-ThumbnailService/Controllers/ThumbnailsController.cs b/HiP-ThumbnailService/Controllers/ThumbnailsController.cs index 7e6a228..44594bb 100644 --- a/HiP-ThumbnailService/Controllers/ThumbnailsController.cs +++ b/HiP-ThumbnailService/Controllers/ThumbnailsController.cs @@ -1,12 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using PaderbornUniversity.SILab.Hip.ThumbnailService.Arguments; @@ -14,6 +6,14 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; using SixLabors.Primitives; +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Controllers { @@ -23,9 +23,7 @@ public class ThumbnailsController : Controller { private readonly ThumbnailConfig _thumbnailConfig; - /// - /// This dictionary is used for synchronizaing requests for the same id - /// + // This dictionary is used for synchronizing requests for the same id private static readonly ConcurrentDictionary LockDictionary = new ConcurrentDictionary(); public ThumbnailsController(IOptions thumbnailConfig) @@ -33,6 +31,10 @@ public ThumbnailsController(IOptions thumbnailConfig) _thumbnailConfig = thumbnailConfig.Value; } + /// + /// Clears the thumbnail cache for the specified URL. + /// + /// URL relative to 'HostUrl' configured in the thumbnail service. [ProducesResponseType(400)] [ProducesResponseType(204)] [HttpDelete] @@ -41,16 +43,14 @@ public async Task Delete(string url) if (!ModelState.IsValid) return BadRequest(ModelState); - var encodedId = Convert.ToBase64String(Encoding.UTF8.GetBytes(url)); var folderPath = Path.Combine(_thumbnailConfig.Path, encodedId); - var semaphore = LockDictionary.GetOrAdd(encodedId, new SemaphoreSlim(1)); await semaphore.WaitAsync(); try { - //delete the folder + // delete the folder if (Directory.Exists(folderPath)) Directory.Delete(folderPath, true); return NoContent(); @@ -68,22 +68,20 @@ public async Task Delete(string url) /// [ProducesResponseType(404)] [ProducesResponseType(400)] - [ProducesResponseType(typeof(FileStream),200)] + [ProducesResponseType(typeof(FileStream), 200)] [HttpGet] public async Task Get([FromQuery]CreationArgs args) { + if (!string.IsNullOrEmpty(args.Size) && !_thumbnailConfig.SupportedSizes.ContainsKey(args.Size)) + ModelState.AddModelError(nameof(args.Size), "Invalid size. Must be one of the following: " + + string.Join(", ", _thumbnailConfig.SupportedSizes)); + if (!ModelState.IsValid) return BadRequest(ModelState); - if (string.IsNullOrEmpty(args.Url)) - return BadRequest(new { Message = "An url for image access must be provided" }); - var encodedId = Convert.ToBase64String(Encoding.UTF8.GetBytes(args.Url)); var folderPath = Path.Combine(_thumbnailConfig.Path, encodedId); - if (!string.IsNullOrEmpty(args.Size) && !_thumbnailConfig.SupportedSizes.ContainsKey(args.Size)) - return BadRequest(new { Message = "Invalid size" }); - var semaphore = LockDictionary.GetOrAdd(encodedId, new SemaphoreSlim(1)); await semaphore.WaitAsync(); try @@ -95,16 +93,15 @@ public async Task Get([FromQuery]CreationArgs args) { var client = new HttpClient(); client.DefaultRequestHeaders.Add("Authorization", Request.Headers["Authorization"].ToString()); - var hostUrl = _thumbnailConfig.HostUrl; - using (var stream = await client.GetStreamAsync(hostUrl + args.Url)) - { - //Create Directory if it doesn't exist + using (var stream = await client.GetStreamAsync(_thumbnailConfig.HostUrl + args.Url)) + { + // Create directory if it doesn't exist Directory.CreateDirectory(folderPath); if (string.IsNullOrEmpty(args.Size)) { - //The original image should be returned if the size is empty + // The original image should be returned if the size is empty SaveImage(stream, filePath); } else @@ -113,7 +110,6 @@ public async Task Get([FromQuery]CreationArgs args) GenerateThumbnail(args.Mode, stream, filePath, value); } } - } return File(new FileStream(filePath, FileMode.Open), requestedImageFormat.DefaultMimeType, @@ -132,19 +128,19 @@ public async Task Get([FromQuery]CreationArgs args) { semaphore.Release(); } - } /// - /// Generates a new thumbnail which replaces the old thumbnail + /// Generates a new thumbnail which replaces the old thumbnail. /// /// Crop mode /// File stream /// Path to thumbnail - /// Size value - private static void GenerateThumbnail(CropMode mode, Stream fileStream, string thumbnailPath, int value) + /// Size value + private static void GenerateThumbnail(CropMode mode, Stream fileStream, string thumbnailPath, int size) { - ResizeMode resizeMode = ResizeMode.Crop; + var resizeMode = ResizeMode.Crop; + switch (mode) { case CropMode.FillSquare: @@ -156,8 +152,7 @@ private static void GenerateThumbnail(CropMode mode, Stream fileStream, string t } SaveImage(fileStream, thumbnailPath, - c => c.Resize(new ResizeOptions { Mode = resizeMode, Size = new Size(value) })); - + c => c.Resize(new ResizeOptions { Mode = resizeMode, Size = new Size(size) })); } private static void SaveImage(Stream fileStream, string filePath, Action> operation = null) @@ -171,8 +166,7 @@ private static void SaveImage(Stream fileStream, string filePath, Action string.IsNullOrEmpty(size) ? "originalImage" : $"{size}({mode})"; - + private static string GetFileName(string size, CropMode mode) => + string.IsNullOrEmpty(size) ? "originalImage" : $"{size}({mode})"; } - } diff --git a/HiP-ThumbnailService/HiP-ThumbnailService.csproj b/HiP-ThumbnailService/HiP-ThumbnailService.csproj index b6abf40..db23b4d 100644 --- a/HiP-ThumbnailService/HiP-ThumbnailService.csproj +++ b/HiP-ThumbnailService/HiP-ThumbnailService.csproj @@ -13,10 +13,10 @@ - - - - + + + + diff --git a/HiP-ThumbnailService/Startup.cs b/HiP-ThumbnailService/Startup.cs index 22a9935..650efbd 100644 --- a/HiP-ThumbnailService/Startup.cs +++ b/HiP-ThumbnailService/Startup.cs @@ -4,18 +4,14 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using NSwag.AspNetCore; using PaderbornUniversity.SILab.Hip.ThumbnailService.Utility; using PaderbornUniversity.SILab.Hip.Webservice; -using Swashbuckle.AspNetCore.Swagger; -using System.IO; namespace PaderbornUniversity.SILab.Hip.ThumbnailService { public class Startup { - private const string Name = "HiP Thumbnail Service"; - private const string Version = "v1"; - public Startup(IConfiguration configuration) { Configuration = configuration; @@ -26,18 +22,8 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - // Register the Swagger generator - services.AddSwaggerGen(c => - { - // Define a Swagger document - c.SwaggerDoc("v1", new Info { Title = Name, Version = Version }); - c.OperationFilter(); - c.DescribeAllEnumsAsStrings(); - c.IncludeXmlComments(Path.ChangeExtension(typeof(Startup).Assembly.Location, ".xml")); - }); - - services.Configure(Configuration.GetSection("EndpointConfig")) - .Configure(Configuration.GetSection("ThumbnailConfig")) + services + .Configure(Configuration.GetSection("Thumbnails")) .Configure(Configuration.GetSection("Auth")) .Configure(Configuration); @@ -65,13 +51,15 @@ public void ConfigureServices(IServiceCollection services) } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions endpointConfig, IOptions corsConfig) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions corsConfig) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + app.UseRequestSchemeFixer(); + // Use CORS (important: must be before app.UseMvc()) app.UseCors(builder => { @@ -85,24 +73,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions app.UseAuthentication(); app.UseMvc(); - - // Swagger / Swashbuckle configuration: - // Enable middleware to serve generated Swagger as a JSON endpoint - app.UseSwagger(c => - { - c.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Host = httpReq.Host.Value); - }); - - // Configure SwaggerUI endpoint - app.UseSwaggerUI(c => - { - var swaggerJsonUrl = string.IsNullOrEmpty(endpointConfig.Value.SwaggerEndpoint) - ? $"/swagger/{Version}/swagger.json" - : endpointConfig.Value.SwaggerEndpoint; - - c.SwaggerEndpoint(swaggerJsonUrl, $"{Name} {Version}"); - }); - + app.UseSwaggerUiHip(); } } } diff --git a/HiP-ThumbnailService/Utility/AuthConfig.cs b/HiP-ThumbnailService/Utility/AuthConfig.cs deleted file mode 100644 index b9b6dde..0000000 --- a/HiP-ThumbnailService/Utility/AuthConfig.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Utility -{ - public class AuthConfig - { - public string Audience { get; set; } - public string Authority { get; set; } - } -} diff --git a/HiP-ThumbnailService/Utility/CorsConfig.cs b/HiP-ThumbnailService/Utility/CorsConfig.cs deleted file mode 100644 index 6465e1b..0000000 --- a/HiP-ThumbnailService/Utility/CorsConfig.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; - -namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Utility -{ - public class CorsConfig - { - public Dictionary Cors { get; set; } - } - public class CorsEnvironmentConfig - { - public string[] Origins { get; set; } - public string[] Headers { get; set; } - public string[] Methods { get; set; } - public string[] ExposedHeaders { get; set; } - } -} diff --git a/HiP-ThumbnailService/Utility/EndpointConfig.cs b/HiP-ThumbnailService/Utility/EndpointConfig.cs deleted file mode 100644 index 9ab0aff..0000000 --- a/HiP-ThumbnailService/Utility/EndpointConfig.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Utility -{ - public class EndpointConfig - { - public string SwaggerEndpoint { get; set; } - } -} diff --git a/HiP-ThumbnailService/Utility/ThumbnailConfig.cs b/HiP-ThumbnailService/Utility/ThumbnailConfig.cs index b5cd337..b19ff25 100644 --- a/HiP-ThumbnailService/Utility/ThumbnailConfig.cs +++ b/HiP-ThumbnailService/Utility/ThumbnailConfig.cs @@ -5,18 +5,23 @@ namespace PaderbornUniversity.SILab.Hip.ThumbnailService.Utility public class ThumbnailConfig { /// - /// Path to thumbnail images + /// Path where thumbnail images are stored. + /// Default value: "Thumbnails" /// - public string Path { get; set; } + public string Path { get; set; } = "Thumbnails"; /// - /// Url which describes where the service is hosted. Should end with a slash + /// URL which is a common prefix of all services using the thumbnail service. + /// Should end with a slash. This prefix is to be omitted when requesting + /// thumbnails from the service. + /// Example: "https://docker-hip.cs.upb.de/develop/" /// public string HostUrl { get; set; } /// - /// Dictionary for the supported sizes + /// The image sizes options that can be chosen when requesting thumbnails. + /// Example: { "small": 50, "medium": 100, "large": 250 } /// - public Dictionary SupportedSizes { get; set; } + public Dictionary SupportedSizes { get; set; } } } diff --git a/HiP-ThumbnailService/appsettings.Development.json.example b/HiP-ThumbnailService/appsettings.Development.json.example deleted file mode 100644 index 8d9a606..0000000 --- a/HiP-ThumbnailService/appsettings.Development.json.example +++ /dev/null @@ -1,22 +0,0 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - }, - - - "ThumbnailConfig": { - "SupportedSizes": { - "small": 50, - "medium": 100, - "large": 250 - }, - "HostUrl": "https://docker-hip.cs.upb.de/develop/", - "Path": "Thumbnails" - } - -} diff --git a/HiP-ThumbnailService/appsettings.json b/HiP-ThumbnailService/appsettings.json index 01a36f6..3178933 100644 --- a/HiP-ThumbnailService/appsettings.json +++ b/HiP-ThumbnailService/appsettings.json @@ -1,37 +1,47 @@ { - "Port": 5000, - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } + "Port": 5000, + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } }, - "Console": { - "LogLevel": { - "Default": "Warning" - } - } - }, - //CORS settings for Multiple environments the application is currently running in [Development, Staging, Production] - "CORS": { - "Development": { - "Origins": [ "https://docker-hip.cs.uni-paderborn.de", "https://docker-hip.cs.upb.de", "http://localhost:3000", "http://localhost:8080" ], - "Headers": [ "*" ], - "Methods": [ "*" ], - "ExposedHeaders": [ "Content-Disposition" ] + //CORS settings for Multiple environments the application is currently running in [Development, Staging, Production] + "CORS": { + "Development": { + "Origins": [ "https://docker-hip.cs.uni-paderborn.de", "https://docker-hip.cs.upb.de", "http://localhost:3000", "http://localhost:8080" ], + "Headers": [ "*" ], + "Methods": [ "*" ], + "ExposedHeaders": [ "Content-Disposition" ] + }, + "Production": { + "Origins": [ "https://docker-hip.cs.uni-paderborn.de", "https://docker-hip.cs.upb.de" ], + "Headers": [ "*" ], + "Methods": [ "GET", "PUT", "POST", "DELETE" ], + "ExposedHeaders": [ "Content-Disposition" ] + } }, - "Production": { - "Origins": [ "https://docker-hip.cs.uni-paderborn.de", "https://docker-hip.cs.upb.de" ], - "Headers": [ "*" ], - "Methods": [ "GET", "PUT", "POST", "DELETE" ], - "ExposedHeaders": [ "Content-Disposition" ] - } - }, - "Auth": { - "Audience": "https://hip.cs.upb.de/API", - "Authority": "https://hip.eu.auth0.com/" - } + "Thumbnails": { + "SupportedSizes": { + "small": 50, + "medium": 100, + "large": 250 + }, + "HostUrl": null, + "Path": "Thumbnails" + }, + + "Auth": { + "Audience": "https://hip.cs.upb.de/API", + "Authority": "https://hip.eu.auth0.com/" + } } diff --git a/README.md b/README.md index 82a180d..a534e3b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,29 @@ # HiP-ThumbnailService -Microservice (REST API) for generating thumbnails +Microservice (REST API) for generating thumbnails. + +## Development Environment Setup +* Clone the repository +* Configure the app: + Create a file `appsettings.Development.json` in the same folder as `HiP-ThumbnailService.csproj` with the following content, replacing "YOUR URL PREFIX" with a valid URL according to the documentation for [ThumbnailConfig.HostUrl](https://github.com/HiP-App/HiP-ThumbnailService/blob/develop/HiP-ThumbnailService/Utility/ThumbnailConfig.cs): + ``` + { + "Thumbnails": { + "HostUrl": "YOUR URL PREFIX" + } + } + ``` +* Launch the app + * via Visual Studio: Open the solution (*.sln) and run the app (F5) + * via Terminal: Execute `dotnet run` from the project folder containing `HiP-ThumbnailService.csproj` + +The app is preconfigured to run on dev machines with minimal manual configuration. See [appsettings.json](https://github.com/HiP-App/HiP-ThumbnailService/blob/develop/HiP-ThumbnailService/appsettings.json) for a list of configuration fields and their default values. + +## Core Concepts +When requesting thumbnails via `GET /api/Thumbnails?Url=...`, the thumbnail service retrieves the original image from another server or service, resizes and crops the image, and then returns it. The URL to the original image is constructed from two parts: + +1. **The "HostUrl" configured in the thumbnail service** + (example: "https://docker-hip.cs.upb.de/develop/") +1. **The relative URL given by the client via `?Url=`** + (example: "datastore/Media/42/File") + +Thumbnails of different sizes and formats are cached. When the original image changes, the service owning the image needs to clear the thumbnail cache for that image by calling `DELETE /api/Thumbnails?Url=...`, otherwise the thumbnail service will continue to return the outdated, cached thumbnails. \ No newline at end of file From 78869fbb15cb0a2b6f6ed742e0075f2851d922fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mich=C3=A9l=20Burkhardt?= Date: Thu, 11 Jan 2018 01:01:36 +0100 Subject: [PATCH 04/11] Upgraded WebServiceLib version (#12) --- HiP-ThumbnailService/HiP-ThumbnailService.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HiP-ThumbnailService/HiP-ThumbnailService.csproj b/HiP-ThumbnailService/HiP-ThumbnailService.csproj index db23b4d..fed9190 100644 --- a/HiP-ThumbnailService/HiP-ThumbnailService.csproj +++ b/HiP-ThumbnailService/HiP-ThumbnailService.csproj @@ -13,7 +13,7 @@ - + From c2aba4db2f3868c5933cfc420031e0ada4d4b9ed Mon Sep 17 00:00:00 2001 From: Zain ulabidin Date: Sun, 11 Feb 2018 00:33:10 +0100 Subject: [PATCH 05/11] Logging Integration --- .../HiP-ThumbnailService.csproj | 2 +- HiP-ThumbnailService/Startup.cs | 59 ++++++++----- HiP-ThumbnailService/appsettings.json | 83 ++++++++++--------- 3 files changed, 85 insertions(+), 59 deletions(-) diff --git a/HiP-ThumbnailService/HiP-ThumbnailService.csproj b/HiP-ThumbnailService/HiP-ThumbnailService.csproj index fed9190..1ffbb70 100644 --- a/HiP-ThumbnailService/HiP-ThumbnailService.csproj +++ b/HiP-ThumbnailService/HiP-ThumbnailService.csproj @@ -13,7 +13,7 @@ - + diff --git a/HiP-ThumbnailService/Startup.cs b/HiP-ThumbnailService/Startup.cs index 650efbd..683ba1c 100644 --- a/HiP-ThumbnailService/Startup.cs +++ b/HiP-ThumbnailService/Startup.cs @@ -1,12 +1,15 @@ -using Microsoft.AspNetCore.Authentication.JwtBearer; +using System; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NSwag.AspNetCore; using PaderbornUniversity.SILab.Hip.ThumbnailService.Utility; using PaderbornUniversity.SILab.Hip.Webservice; +using PaderbornUniversity.SILab.Hip.Webservice.Logging; namespace PaderbornUniversity.SILab.Hip.ThumbnailService { @@ -25,7 +28,8 @@ public void ConfigureServices(IServiceCollection services) services .Configure(Configuration.GetSection("Thumbnails")) .Configure(Configuration.GetSection("Auth")) - .Configure(Configuration); + .Configure(Configuration) + .Configure(Configuration.GetSection("HiPLoggerConfig")); var serviceProvider = services.BuildServiceProvider(); // allows us to actually get the configured services var authConfig = serviceProvider.GetService>(); @@ -51,29 +55,46 @@ public void ConfigureServices(IServiceCollection services) } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions corsConfig) + public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, + IOptions corsConfig, IOptions loggingConfig) { - if (env.IsDevelopment()) + loggerFactory.AddConsole(Configuration.GetSection("Logging")) + .AddDebug() + .AddHipLogger(loggingConfig.Value); + + var logger = loggerFactory.CreateLogger("ApplicationStartup"); + try { - app.UseDeveloperExceptionPage(); - } + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRequestSchemeFixer(); - app.UseRequestSchemeFixer(); + // Use CORS (important: must be before app.UseMvc()) + app.UseCors(builder => + { + var corsEnvConf = corsConfig.Value.Cors[env.EnvironmentName]; + builder + .WithOrigins(corsEnvConf.Origins) + .WithMethods(corsEnvConf.Methods) + .WithHeaders(corsEnvConf.Headers) + .WithExposedHeaders(corsEnvConf.ExposedHeaders); + }); - // Use CORS (important: must be before app.UseMvc()) - app.UseCors(builder => + app.UseAuthentication(); + app.UseMvc(); + app.UseSwaggerUiHip(); + + logger.LogInformation("ThumbnailService started successfully"); + } + catch (Exception e) { - var corsEnvConf = corsConfig.Value.Cors[env.EnvironmentName]; - builder - .WithOrigins(corsEnvConf.Origins) - .WithMethods(corsEnvConf.Methods) - .WithHeaders(corsEnvConf.Headers) - .WithExposedHeaders(corsEnvConf.ExposedHeaders); - }); + logger.LogCritical($"ThumbnailService Startup Failed:{e.Message}"); + throw; + } - app.UseAuthentication(); - app.UseMvc(); - app.UseSwaggerUiHip(); } } } diff --git a/HiP-ThumbnailService/appsettings.json b/HiP-ThumbnailService/appsettings.json index 3178933..844b473 100644 --- a/HiP-ThumbnailService/appsettings.json +++ b/HiP-ThumbnailService/appsettings.json @@ -1,47 +1,52 @@ { - "Port": 5000, - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "LogLevel": { - "Default": "Warning" - } - } + "Port": 5000, + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + }, - //CORS settings for Multiple environments the application is currently running in [Development, Staging, Production] - "CORS": { - "Development": { - "Origins": [ "https://docker-hip.cs.uni-paderborn.de", "https://docker-hip.cs.upb.de", "http://localhost:3000", "http://localhost:8080" ], - "Headers": [ "*" ], - "Methods": [ "*" ], - "ExposedHeaders": [ "Content-Disposition" ] - }, - "Production": { - "Origins": [ "https://docker-hip.cs.uni-paderborn.de", "https://docker-hip.cs.upb.de" ], - "Headers": [ "*" ], - "Methods": [ "GET", "PUT", "POST", "DELETE" ], - "ExposedHeaders": [ "Content-Disposition" ] - } + //CORS settings for Multiple environments the application is currently running in [Development, Staging, Production] + "CORS": { + "Development": { + "Origins": [ "https://docker-hip.cs.uni-paderborn.de", "https://docker-hip.cs.upb.de", "http://localhost:3000", "http://localhost:8080" ], + "Headers": [ "*" ], + "Methods": [ "*" ], + "ExposedHeaders": [ "Content-Disposition" ] }, + "Production": { + "Origins": [ "https://docker-hip.cs.uni-paderborn.de", "https://docker-hip.cs.upb.de" ], + "Headers": [ "*" ], + "Methods": [ "GET", "PUT", "POST", "DELETE" ], + "ExposedHeaders": [ "Content-Disposition" ] + } + }, - "Thumbnails": { - "SupportedSizes": { - "small": 50, - "medium": 100, - "large": 250 - }, - "HostUrl": null, - "Path": "Thumbnails" + "Thumbnails": { + "SupportedSizes": { + "small": 50, + "medium": 100, + "large": 250 }, + "HostUrl": null, + "Path": "Thumbnails" + }, - "Auth": { - "Audience": "https://hip.cs.upb.de/API", - "Authority": "https://hip.eu.auth0.com/" - } + "Auth": { + "Audience": "https://hip.cs.upb.de/API", + "Authority": "https://hip.eu.auth0.com/" + }, + "HiPLoggerConfig": { + "LogSource": "HiP-thumbnailService-localhost", + "Host": "127.0.0.1", + "Port": 12201 + } } From ea635d7eb78cc3e889903c4ad9923b69ba41ea04 Mon Sep 17 00:00:00 2001 From: Patrick Thiele <32452794+thielep@users.noreply.github.com> Date: Tue, 15 May 2018 18:02:36 +0200 Subject: [PATCH 06/11] Changed a comment (#14) * Changed a comment * Remove unnecessary using --- HiP-ThumbnailService/Controllers/ThumbnailsController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HiP-ThumbnailService/Controllers/ThumbnailsController.cs b/HiP-ThumbnailService/Controllers/ThumbnailsController.cs index 44594bb..13957e3 100644 --- a/HiP-ThumbnailService/Controllers/ThumbnailsController.cs +++ b/HiP-ThumbnailService/Controllers/ThumbnailsController.cs @@ -50,7 +50,7 @@ public async Task Delete(string url) await semaphore.WaitAsync(); try { - // delete the folder + // Delete the folder if (Directory.Exists(folderPath)) Directory.Delete(folderPath, true); return NoContent(); From ad7ef95b3695408f0cf1dc83819d01a81b90e6c6 Mon Sep 17 00:00:00 2001 From: Patrick Thiele <32452794+thielep@users.noreply.github.com> Date: Sat, 16 Jun 2018 18:10:08 +0200 Subject: [PATCH 07/11] Removed distinction between develop and master branch from publishing scripts (#15) --- NuGetPack.ps1 | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/NuGetPack.ps1 b/NuGetPack.ps1 index 971166d..dc9cd2a 100644 --- a/NuGetPack.ps1 +++ b/NuGetPack.ps1 @@ -1,10 +1,7 @@ $csproj = (ls *.Sdk\*.csproj).FullName -Switch ("$env:Build_SourceBranchName") -{ - "master" { dotnet pack "$csproj" -o . } - "develop" { dotnet pack "$csproj" -o . --version-suffix "develop" } - default { exit } -} + +dotnet pack "$csproj" -o . + $nupkg = (ls *.Sdk\*.nupkg).FullName dotnet nuget push "$nupkg" -k "$env:MyGetKey" -s "$env:NuGetFeed" $LASTEXITCODE = 0 \ No newline at end of file From dc80060cca16d639640b69d9d8e3db39f2b5a6f8 Mon Sep 17 00:00:00 2001 From: Dany Wehbe Date: Mon, 25 Jun 2018 15:13:20 +0200 Subject: [PATCH 08/11] Migrating to ASP.NET Core 2.1 (#16) * Migrating to ASP.NET Core 2.1 * updated a library to work properly with the new Dockerfile --- .../HiP-ThumbnailService.Sdk.csproj | 10 +++++----- HiP-ThumbnailService/HiP-ThumbnailService.csproj | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj b/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj index a759882..c3774cc 100644 --- a/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj +++ b/HiP-ThumbnailService.Sdk/HiP-ThumbnailService.Sdk.csproj @@ -4,17 +4,17 @@ netstandard2.0 PaderbornUniversity.SILab.Hip.ThumbnailService 1701;1702;1705;1591 - 1.1.2 + 1.2.0 $(Version)-$(VersionSuffix) True - - - + + + - + diff --git a/HiP-ThumbnailService/HiP-ThumbnailService.csproj b/HiP-ThumbnailService/HiP-ThumbnailService.csproj index 1ffbb70..8961572 100644 --- a/HiP-ThumbnailService/HiP-ThumbnailService.csproj +++ b/HiP-ThumbnailService/HiP-ThumbnailService.csproj @@ -2,7 +2,7 @@ true - netcoreapp2.0 + netcoreapp2.1 PaderbornUniversity.SILab.Hip.ThumbnailService 1701;1702;1705;1591 true @@ -13,9 +13,9 @@ - - - + + + @@ -26,7 +26,7 @@ - - + + From 419eced622fc61bd18945da859c54017b7e17636 Mon Sep 17 00:00:00 2001 From: Patrick Thiele <32452794+thielep@users.noreply.github.com> Date: Mon, 2 Jul 2018 14:00:28 +0200 Subject: [PATCH 09/11] Upgraded WebserviceLib (#17) * Migrating to ASP.NET Core 2.1 * updated a library to work properly with the new Dockerfile * Upgraded WebserviceLib version --- HiP-ThumbnailService/HiP-ThumbnailService.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HiP-ThumbnailService/HiP-ThumbnailService.csproj b/HiP-ThumbnailService/HiP-ThumbnailService.csproj index 8961572..8b9aea9 100644 --- a/HiP-ThumbnailService/HiP-ThumbnailService.csproj +++ b/HiP-ThumbnailService/HiP-ThumbnailService.csproj @@ -13,7 +13,7 @@ - + From 2d709adda4ddcba25c93f7c73da2fc93788d92e0 Mon Sep 17 00:00:00 2001 From: Patrick Thiele <32452794+thielep@users.noreply.github.com> Date: Tue, 3 Jul 2018 16:48:23 +0200 Subject: [PATCH 10/11] Updated version of SixLabors.ImageSharp library and added necessary usings (#18) --- HiP-ThumbnailService/Controllers/ThumbnailsController.cs | 2 ++ HiP-ThumbnailService/HiP-ThumbnailService.csproj | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/HiP-ThumbnailService/Controllers/ThumbnailsController.cs b/HiP-ThumbnailService/Controllers/ThumbnailsController.cs index 13957e3..d5fb005 100644 --- a/HiP-ThumbnailService/Controllers/ThumbnailsController.cs +++ b/HiP-ThumbnailService/Controllers/ThumbnailsController.cs @@ -4,7 +4,9 @@ using PaderbornUniversity.SILab.Hip.ThumbnailService.Arguments; using PaderbornUniversity.SILab.Hip.ThumbnailService.Utility; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Transforms; using SixLabors.Primitives; using System; using System.Collections.Concurrent; diff --git a/HiP-ThumbnailService/HiP-ThumbnailService.csproj b/HiP-ThumbnailService/HiP-ThumbnailService.csproj index 8b9aea9..fda25ae 100644 --- a/HiP-ThumbnailService/HiP-ThumbnailService.csproj +++ b/HiP-ThumbnailService/HiP-ThumbnailService.csproj @@ -16,7 +16,7 @@ - + From 111328cd4a2e21eeacf68f7e221cbecf2c8cc261 Mon Sep 17 00:00:00 2001 From: Patrick Thiele <32452794+thielep@users.noreply.github.com> Date: Wed, 18 Jul 2018 15:58:27 +0200 Subject: [PATCH 11/11] Updated NuGetPack.ps1, s.t. it only packs NuGet packages (#20) --- NuGetPack.ps1 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NuGetPack.ps1 b/NuGetPack.ps1 index dc9cd2a..1db276a 100644 --- a/NuGetPack.ps1 +++ b/NuGetPack.ps1 @@ -1,7 +1,6 @@ $csproj = (ls *.Sdk\*.csproj).FullName -dotnet pack "$csproj" -o . +dotnet pack "$csproj" -o . +dotnet pack "$csproj" -o . --version-suffix "develop" -$nupkg = (ls *.Sdk\*.nupkg).FullName -dotnet nuget push "$nupkg" -k "$env:MyGetKey" -s "$env:NuGetFeed" $LASTEXITCODE = 0 \ No newline at end of file