dlemstra / Magick.NET

The .NET library for ImageMagick
Apache License 2.0
3.47k stars 415 forks source link

"No decode delegate for this image (Log Report: Middleware with Parallel Image Upload has an issue with compressing the image)." #1724

Closed CodeByDani closed 1 month ago

CodeByDani commented 1 month ago

Magick.NET version

14.0.0

Environment (Operating system, version and so on)

windows 11

Description

I've been using a logging middleware to record errors along with user requests in the database. However, due to parallel image uploads and compression using Magick.NET, I encountered an error. What should I do? code :

upload parallel image :

`

public async Task<string> UploadFileAsync(IFormFile file, string folderPath, CancellationToken cancellationToken)
{
    ArgumentNullException.ThrowIfNull(file);

    try
    {
        using (var compressedStream = await _compressProviderService.CompressMedia(file.OpenReadStream(), cancellationToken))
        {
            var poa = new PutObjectArgs()
                .WithBucket(_config.Minio.BucketName)
                .WithContentType(file.ContentType)
                .WithObject($"{folderPath}/{file.FileName}")
                .WithStreamData(compressedStream)
                .WithObjectSize(compressedStream.Length);

            await _minioClient.PutObjectAsync(poa, cancellationToken).ConfigureAwait(false);
            var result =
                $"{_config.Minio.MinioEndpointExternal}/{_config.Minio.BucketName}/{folderPath}/{file.FileName}";
            return GetMediaUrl(result);
        }

    }
    catch (Exception ex)
    {
        _logger.CompileLog(ex, LogLevel.Error, $"upload file exception: {ex.Message}");
        throw;
    }
}

public async Task<List<string>> UploadFilesAsync(IList<IFormFile> files, string folderPath, CancellationToken cancellationToken)
{
    if (files == null || files.Count == 0)
    {
        throw new ArgumentNullException(nameof(files), "Files list is empty or null.");
    }

    var uploadedUrls = new List<string>();
    var options = new ParallelOptions()
    {
        MaxDegreeOfParallelism = 10
    };

    try
    {
        await Parallel.ForEachAsync(files, options, async (file, cancellationToken) =>
        {
            var url = await UploadFileAsync(file, folderPath, cancellationToken);
            uploadedUrls.Add(GetMediaUrl(url));
        });
        return uploadedUrls;
    }
    catch (Exception ex)
    {
        _logger.CompileLog(ex, LogLevel.Error, $"Upload files exception: {ex.Message}");
        throw;
    }
}

`

compress method :


public class CompressProviderService : ICompressProviderService
{
    public async Task<MemoryStream> CompressMedia(Stream stream, CancellationToken cancellationToken)
    {
        stream.Seek(0, SeekOrigin.Begin);
        using (var image = new MagickImage(stream))
        {
            image.Format = MagickFormat.Jpeg;
            image.Resize(1600, 900);
            image.Quality = 70;

            var compressedStream = new MemoryStream();

            await image.WriteAsync(compressedStream, cancellationToken);

            compressedStream.Position = 0;

            return compressedStream;

        }
    }

}

middleware :


using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common.Core.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using SharedKernel.Domain.Services;
using SharedKernel.Logger;

namespace Common.Core.Service;

public sealed class CustomExceptionMiddleware
{
    private readonly IConfiguration _configuration;
    private readonly IWebHostEnvironment _env;
    private readonly ILogger<CustomExceptionMiddleware> _logger;
    private readonly RequestDelegate _next;
    private readonly bool _useStringEnumConverter;

    public CustomExceptionMiddleware(RequestDelegate next, IWebHostEnvironment env,
        ILogger<CustomExceptionMiddleware> logger, IConfiguration configuration,
        bool useStringEnumConverter)
    {
        _next = next ?? throw new ArgumentNullException(nameof(next));
        _env = env;
        _logger = logger;
        _configuration = configuration;
        _useStringEnumConverter = useStringEnumConverter;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            context.Request.EnableBuffering();
            await _next(context).ConfigureAwait(false);
        }
        catch (CustomException e)
        {
            if (context.Response.HasStarted)
                throw;
            await HandleCustomExceptionAsync(context, e).ConfigureAwait(false);
        }
        catch (Exception exc)
        {
            if (context.Response.HasStarted)
            {
                _logger.CompileLog(exc, LogLevel.Error, exc.Message);
                throw;
            }
            await HandleExceptionAsync(context, exc).ConfigureAwait(false);
        }
    }

    private async Task HandleCustomExceptionAsync(HttpContext context, CustomException e)
    {
        var traceId = context.GenerateTraceId();
        var error = new ErrorDto(e.Error.Error.WithTraceIdentifier(traceId));
        SetResponse(context, e.Error.Error.HttpStatusCode);

        var errorHeaders = e.Error.Error.Headers;
        if (!string.IsNullOrEmpty(errorHeaders))
        {
            try
            {
                var headersList = errorHeaders.Split(';').Select(header => header.Split('=')).ToList();
                foreach (var headerPair in headersList)
                {
                    context.Response.Headers.Append(headerPair[0], headerPair[1]);
                }
            }
            catch
            {
                await HandleServerErrorAsync(context, traceId, $"invalid custom header: {errorHeaders}. correct header: f=10;h=9").ConfigureAwait(false);
                return;
            }
        }

        await WriteResponseAsync(context, error).ConfigureAwait(false);
    }

    private async Task HandleExceptionAsync(HttpContext context, Exception exc)
    {
        var traceId = context.GenerateTraceId();
        var message = "Internal Server Error";
        context.Response.StatusCode = 500;
        context.Response.ContentType = "application/json";
        context.Response.Headers.Append("exception", "Internal Server Error");

        var details = new List<ValidationError>();

        var isShowFullError = _configuration.GetValue("ExceptionShowFullError", false);
        var requestBody = await GetRequestBodyAsync(context.Request);

        var stringBuilder = new StringBuilder();
        stringBuilder.Append("Internal Server Error ").AppendFormat(CultureInfo.InvariantCulture, "#Message: {0}", exc.Message).AppendFormat(CultureInfo.InvariantCulture, ", #RequestBody: {0}", requestBody);
        var log = stringBuilder.ToString();
        if (_env.IsDevelopment() || isShowFullError)
        {
            message = $"#Message: {exc.Message}, #StackTrace: {exc.StackTrace}, #RequestBody: {requestBody}";
            var exception = exc.InnerException;

            while (exception != null)
            {
                details.Add(new ValidationError(
                    "ServerError",
                    $"#Message: {exception.Message} #StackTrace: {exception.StackTrace}, #RequestPath: {context.Request.Path}, #RequestBody: {requestBody}"
                ));

                exception = exception.InnerException;
            }
        }

        var jsonLog = log.ToJson();
        var errorDto = new ErrorDto(new ValidationError("ServerError", message, details, traceId));
        await WriteResponseAsync(context, errorDto).ConfigureAwait(false);
        _logger.CompileLog(exc, LogLevel.Error, jsonLog);
    }

    private static async Task<string> GetRequestBodyAsync(HttpRequest request)
    {
        string body;
        if (request.QueryString.HasValue)
        {
            var queryStringDict = QueryHelpers.ParseQuery(request.QueryString.Value);
            var queryParams = queryStringDict.SelectMany(x => x.Value, (col, value) => new KeyValuePair<string, string>(col.Key, value)).ToDictionary(x => x.Key, x => x.Value);
            body = queryParams.ToJson();
        }
        else if (request.HasFormContentType)
        {
            var formFields = request.Form.ToDictionary(x => x.Key, x => x.Value);
            body = formFields.ToJson();
            body = body.Replace("\"", ",", StringComparison.CurrentCulture);
            body = body.Replace(",,", " ", StringComparison.CurrentCulture);
            body = body.Replace(",", "", StringComparison.CurrentCulture);
        }
        else
        {
            using var stream = new StreamReader(request.Body);
            stream.BaseStream.Seek(0, SeekOrigin.Begin);
            body = await stream.ReadToEndAsync();
            body = body.Replace("\r\n", "", StringComparison.OrdinalIgnoreCase);
            request.Body.Position = 0;
        }

        if (body.Length > 512)
        {
            body = body[..512];
        }
        return body;
    }

    private static void SetResponse(HttpContext context, int httpStatusCode)
    {
        context.Response.StatusCode = httpStatusCode == 0 ? 400 : httpStatusCode;
        context.Response.ContentType = "application/json";
    }

    private async Task HandleServerErrorAsync(HttpContext context, string traceId, string errorMessage)
    {
        context.Response.StatusCode = 500;
        var errorDto = new ErrorDto(new ValidationError("ServerError", errorMessage, new List<ValidationError>(), traceId));
        await WriteResponseAsync(context, errorDto).ConfigureAwait(false);
    }

    private async Task WriteResponseAsync(HttpContext context, ErrorDto error)
    {
        var json = error.ToJson(_useStringEnumConverter ? Extensions.JsonSerializerOptions : Extensions.JsonSerializerOptionsWithoutEnumString);
        await context.Response.WriteAsync(json).ConfigureAwait(false);
    }
}

Steps to Reproduce

error :

{
  "error": {
    "code": "ServerError",
    "message": "#Message: no decode delegate for this image format `' @ error/blob.c/CustomStreamToImage/828, #StackTrace:   
   at ImageMagick.NativeInstance.CheckException(IntPtr exception, IntPtr result) in /_/src/Magick.NET/Native/NativeInstance.cs:line 57\r\n  
   at ImageMagick.MagickImage.NativeMagickImage.ReadStream(IMagickSettings`1 settings, ReadWriteStreamDelegate reader, SeekStreamDelegate seeker, TellStreamDelegate teller, Void* data) in /_/src/Magick.NET/Native/MagickImage.cs:line 7100\r\n  
   at ImageMagick.MagickImage.NativeMagickImage.ReadStream(IMagickSettings`1 settings, ReadWriteStreamDelegate reader, SeekStreamDelegate seeker, TellStreamDelegate teller) in /_/src/Magick.NET/MagickImage.cs:line 7708\r\n  
   at ImageMagick.MagickImage.Read(Stream stream, IMagickReadSettings`1 readSettings, Boolean ping) in /_/src/Magick.NET/MagickImage.cs:line 7627\r\n  
   at ImageMagick.MagickImage.Read(Stream stream, IMagickReadSettings`1 readSettings) in /_/src/Magick.NET/MagickImage.cs:line 4907\r\n  
   at ImageMagick.MagickImage.Read(Stream stream, MagickFormat format) in /_/src/Magick.NET/MagickImage.cs:line 4898\r\n  
   at ImageMagick.MagickImage.Read(Stream stream) in /_/src/Magick.NET/MagickImage.cs:line 4889\r\n  
   at ImageMagick.MagickImage..ctor(Stream stream) in /_/src/Magick.NET/MagickImage.cs:line 171\r\n  
   at GoldenVitrin.Service.Services.CompressProviderService.CompressMedia(Stream stream, CancellationToken cancellationToken) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.Service\\Services\\CompressProviderService.cs:line 13\r\n  
   at GoldenVitrin.Service.Services.MinioService.UploadFileAsync(IFormFile file, String folderPath, CancellationToken cancellationToken) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.Service\\Services\\MinioService.cs:line 109\r\n  
   at GoldenVitrin.Service.Services.MinioService.<>c__DisplayClass10_0.<<UploadFilesAsync>b__0>d.MoveNext() in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.Service\\Services\\MinioService.cs:line 149\r\n
   --- End of stack trace from previous location ---\r\n  
   at System.Threading.Tasks.Parallel.<>c__53`1.<<ForEachAsync>b__53_0>d.MoveNext()\r\n
   --- End of stack trace from previous location ---\r\n  
   at GoldenVitrin.Service.Services.MinioService.UploadFilesAsync(IList`1 files, String folderPath, CancellationToken cancellationToken) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.Service\\Services\\MinioService.cs:line 147\r\n  
   at GoldenVitrin.Service.Services.MediaService.SaveMultiMediaAsync(IList`1 inputFile, String webRootPath, String uploadFolder, CancellationToken cancellationToken) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.Service\\Services\\MediaService.cs:line 31\r\n  
   at GoldenVitrin.Service.CommandHandlers.Product.Vendor.CreateProductCommandHandlerV3.UploadedImage(UploadedImageProduct request) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.Service\\CommandHandlers\\Product\\Vendor\\CreateProductCommandHandlerV3.cs:line 132\r\n  
   at GoldenVitrin.Service.CommandHandlers.Product.Vendor.CreateProductCommandHandlerV3.HandleCore(CreateProductCommandV3Request request, CancellationToken cancellationToken) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.Service\\CommandHandlers\\Product\\Vendor\\CreateProductCommandHandlerV3.cs:line 50\r\n  
   at GoldenVitrin.Service.Behaviours.Vendor.CheckVariationBehaviour`2.HandleCore(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.Service\\Behaviours\\Vendor\\CheckVariationBehaviour.cs:line 29\r\n  
   at SharedKernel.Common.Behavior.EventsBehaviour`2.HandleCore(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\BuildingBlocks\\GoldenVitrin.SharedKernel\\Common\\Behavior\\EventsBehaviour.cs:line 29\r\n  
   at SharedKernel.Common.Behavior.EventsBehaviour`2.HandleCore(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\BuildingBlocks\\GoldenVitrin.SharedKernel\\Common\\Behavior\\EventsBehaviour.cs:line 36\r\n  
   at SharedKernel.Common.Behavior.ValidationBehaviour`2.HandleCore(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\BuildingBlocks\\GoldenVitrin.SharedKernel\\Common\\Behavior\\ValidationBehaviour.cs:line 56\r\n  
   at GoldenVitrin.API.Controllers.Vendor.v2.VendorController.CreateProductAsync(ProductCommandV3 createCommand, CancellationToken cancellationToken) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\GoldenVitrin.API\\Controllers\\Vendor\\v2\\VendorController.cs:line 154\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)\r\n  
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)\r\n  
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\r\n  
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)\r\n  
   at Common.Core.Service.CustomExceptionMiddleware.InvokeAsync(HttpContext context) in C:\\Users\\Lucifer\\source\\repos\\GoldenVitrinBackEnd\\src\\BuildingBlocks\\GoldenVitrin.Common.Core\\Service\\CustomExceptionMiddleware.cs:line 45, #RequestBody: {discount:[0.1] vendorId:[1] taxAmount:[10000] isPublished:[true] price:[1200] name:[string] categoryId:[2147483647] availability:[All] description:[string]}",
    "target": null,
    "details": [],
    "displayMode": "Toast",
    "trackingNumber": null,
    "traceIdentifier": "d95d9295-5652-44c2-92ea-a366d9d573f8"
  }
}

Formatted by https://st.elmah.io

response_1727267569026.json

dlemstra commented 1 month ago

The message no decode delegate for this image format means that the image that you are trying to read is not supported by this library.