コード例 #1
0
        public async Task Invoke(
            HttpContext context,
            IMediaService mediaService,
            IFolderService folderService,
            IPermissionService permissionService,
            IWorkContext workContext,
            MediaSettings mediaSettings,
            MediaHelper mediaHelper,
            Lazy <IEnumerable <IMediaHandler> > mediaHandlers,
            ILogger <MediaMiddleware> logger)
        {
            var mediaFileId = context.GetRouteValueAs <int>("id");
            var path        = context.GetRouteValueAs <string>("path");

            if (context.Request.Method != HttpMethods.Get && context.Request.Method != HttpMethods.Head)
            {
                await NotFound(null);

                return;
            }

            var method = context.Request.Method;

            MediaFileInfo mediaFile = null;
            MediaPathData pathData  = null;

            if (mediaFileId == 0)
            {
                // This is most likely a request for a default placeholder image
                pathData = new MediaPathData(path);
            }
            else if (!mediaHelper.TokenizePath(path, false, out pathData))
            {
                // Missing or malformed Uri: get file metadata from DB by id, but only when current user has media manage rights
                if (!(await permissionService.AuthorizeAsync(Permissions.Media.Update)))
                {
                    await NotFound(null);

                    return;
                }

                mediaFile = await mediaService.GetFileByIdAsync(mediaFileId, MediaLoadFlags.AsNoTracking);

                if (mediaFile == null || mediaFile.FolderId == null || mediaFile.Deleted)
                {
                    await NotFound(mediaFile?.MimeType);

                    return;
                }

                pathData = new MediaPathData(folderService.GetNodeById(mediaFile.FolderId.Value), mediaFile.Name)
                {
                    Extension = mediaFile.Extension,
                    MimeType  = mediaFile.MimeType
                };
            }

            var q = await CreateImageQuery(context, pathData.MimeType, pathData.Extension);

            // Security: check allowed thumnail sizes and return 404 if disallowed.
            var thumbMaxWidth    = q.MaxWidth;
            var thumbMaxHeight   = q.MaxHeight;
            var thumbSizeAllowed = IsThumbnailSizeAllowed(thumbMaxWidth) && (thumbMaxHeight == thumbMaxWidth || IsThumbnailSizeAllowed(thumbMaxHeight));

            if (!thumbSizeAllowed)
            {
                await NotFound(pathData.MimeType);

                return;
            }

            // Create the handler context
            var handlerContext = new MediaHandlerContext
            {
                HttpContext       = context,
                CurrentCustomer   = workContext.CurrentCustomer,
                PermissionService = permissionService,
                MediaFileId       = mediaFileId,
                RawPath           = path,
                MediaService      = mediaService,
                PathData          = pathData,
                ImageQuery        = q
            };

            handlerContext.SetSourceFile(mediaFile);

            var handlers = mediaHandlers.Value.OrderBy(x => x.Order).ToArray();

            // Run every registered media handler to obtain a thumbnail for the requested media file
            IMediaHandler currentHandler;

            for (var i = 0; i < handlers.Length; i++)
            {
                currentHandler = handlers[i];

                // Execute handler
                await currentHandler.ExecuteAsync(handlerContext);

                if (handlerContext.Exception != null)
                {
                    var isThumbExtractFail = handlerContext.Exception is ExtractThumbnailException;
                    var statusCode         = isThumbExtractFail ? StatusCodes.Status204NoContent : StatusCodes.Status500InternalServerError;
                    var statusMessage      = isThumbExtractFail ? handlerContext.Exception.InnerException?.Message.EmptyNull() : handlerContext.Exception.Message;

                    await SendStatus(statusCode, statusMessage);

                    return;
                }

                if (handlerContext.Executed || handlerContext.ResultFile != null)
                {
                    // Get out if the handler produced a result file or has been executed in any way
                    break;
                }
            }

            try
            {
                var responseFile = handlerContext.ResultFile ?? await handlerContext.GetSourceFileAsync();

                if (responseFile == null || !responseFile.Exists)
                {
                    await NotFound(pathData.MimeType);

                    return;
                }

                if (string.Equals(responseFile.Extension, "." + pathData.Extension, StringComparison.CurrentCultureIgnoreCase))
                {
                    pathData.MimeType = MimeTypes.MapNameToMimeType(responseFile.Extension);
                }

                // Create FileStreamResult object
                var fileResult = CreateFileResult(responseFile, pathData);

                // Cache control
                ApplyResponseCaching(context, mediaSettings);

                // INFO: Although we are outside of the MVC pipeline we gonna use ActionContext anyway, because "FileStreamResult"
                // does everything we need (ByteRange, ETag etc.), so wo we gonna use it instead of reinventing the wheel.
                // A look at the MVC source code reveals that HttpContext is the only property that gets accessed, therefore we can omit
                // all the other stuff like ActionDescriptor or ModelState (which we cannot access or create from a middleware anyway).
                await fileResult.ExecuteResultAsync(new ActionContext { HttpContext = context, RouteData = context.GetRouteData() });
            }
            finally
            {
                var imageProcessor = context.RequestServices.GetRequiredService <IImageProcessor>();
                logger.Debug("ImageProcessor TOTAL: {0} ms.", imageProcessor.TotalProcessingTimeMs);
            }

            #region Functions

            bool IsThumbnailSizeAllowed(int?size)
            {
                return(size.GetValueOrDefault() == 0 ||
                       mediaSettings.IsAllowedThumbnailSize(size.Value) ||
                       permissionService.Authorize(Permissions.Media.Update, workContext.CurrentCustomer));
            }

            async Task NotFound(string mime)
            {
                context.Response.ContentType = mime.NullEmpty() ?? "text/html";
                context.Response.StatusCode  = 404;
                await context.Response.WriteAsync("404: Not Found");
            }

            async Task SendStatus(int code, string message)
            {
                context.Response.StatusCode = code;
                await context.Response.WriteAsync(message);
            }

            #endregion
        }
コード例 #2
0
        public async Task ExecuteAsync(MediaHandlerContext context)
        {
            if (!IsProcessable(context))
            {
                return;
            }

            var query    = context.ImageQuery;
            var pathData = context.PathData;

            var cachedImage = await ImageCache.GetAsync(context.MediaFileId, pathData, query);

            if (!pathData.Extension.EqualsNoCase(cachedImage.Extension))
            {
                // The query requests another format.
                // Adjust extension and mime type fo proper ETag creation.
                pathData.Extension = cachedImage.Extension;
                pathData.MimeType  = cachedImage.MimeType;
            }

            var exists = cachedImage.Exists;

            if (exists && cachedImage.FileSize == 0)
            {
                // Empty file means: thumb extraction failed before and will most likely fail again.
                // Don't bother proceeding.
                context.Exception = ExceptionFactory.ExtractThumbnail(cachedImage.FileName);
                context.Executed  = true;
                return;
            }

            if (!exists)
            {
                // Lock concurrent requests to same resource
                using (await AsyncLock.KeyedAsync("ImageHandlerBase.Execute." + cachedImage.Path))
                {
                    await ImageCache.RefreshInfoAsync(cachedImage);

                    // File could have been processed by another request in the meantime, check again.
                    if (!cachedImage.Exists)
                    {
                        // Call inner function
                        var sourceFile = await context.GetSourceFileAsync();

                        if (sourceFile == null || sourceFile.Length == 0)
                        {
                            context.Executed = true;
                            return;
                        }

                        var inputStream = await sourceFile.OpenReadAsync();

                        if (inputStream == null)
                        {
                            context.Exception = ExceptionFactory.ExtractThumbnail(sourceFile.SubPath, T("Admin.Media.Exception.NullInputStream"));
                            context.Executed  = true;
                            return;
                        }

                        try
                        {
                            await ProcessImageAsync(context, cachedImage, inputStream);
                        }
                        catch (Exception ex)
                        {
                            Logger.Error(ex);

                            if (ex is ExtractThumbnailException)
                            {
                                // Thumbnail extraction failed and we must assume that it always will fail.
                                // Therefore we create an empty file to prevent repetitive processing.
                                using (var memStream = new MemoryStream())
                                {
                                    await ImageCache.PutAsync(cachedImage, memStream);
                                }
                            }

                            context.Exception = ex;
                            context.Executed  = true;
                            return;
                        }
                        finally
                        {
                            if (inputStream != null)
                            {
                                inputStream.Dispose();
                            }
                        }

                        if (context.ResultImage != null)
                        {
                            await ImageCache.PutAsync(cachedImage, context.ResultImage);

                            context.ResultFile = cachedImage.File;
                        }

                        context.Executed = true;
                        return;
                    }
                }
            }

            // Cached image existed already
            context.ResultFile = cachedImage.File;
            context.Executed   = true;
        }