예제 #1
0
 public FormattedImage Process(FormattedImage image, ILogger logger, IDictionary <string, string> commands)
 => image;
예제 #2
0
        private async Task ProcessRequestAsync(HttpContext context, bool processRequest, IImageResolver sourceImageResolver, ImageContext imageContext, IDictionary <string, string> commands)
        {
            // Create a cache key based on all the components of the requested url
            string uri = GetUri(context, commands);
            string key = this.cacheHash.Create(uri, this.options.CachedNameLength);

            ImageMetadata sourceImageMetadata = default;

            if (processRequest)
            {
                // Lock any reads when a write is being done for the same key to prevent potential file locks.
                using (await AsyncLock.ReaderLockAsync(key))
                {
                    // Check to see if the cache contains this image
                    sourceImageMetadata = await sourceImageResolver.GetMetaDataAsync();

                    IImageCacheResolver cachedImageResolver = await this.cache.GetAsync(key);

                    if (cachedImageResolver != null)
                    {
                        ImageCacheMetadata cachedImageMetadata = await cachedImageResolver.GetMetaDataAsync();

                        if (cachedImageMetadata != default)
                        {
                            // Has the cached image expired or has the source image been updated?
                            if (cachedImageMetadata.SourceLastWriteTimeUtc == sourceImageMetadata.LastWriteTimeUtc &&
                                cachedImageMetadata.CacheLastWriteTimeUtc > DateTimeOffset.Now.AddDays(-this.options.MaxCacheDays))
                            {
                                // We're pulling the image from the cache.
                                using (Stream cachedBuffer = await cachedImageResolver.OpenReadAsync())
                                {
                                    await this.SendResponseAsync(imageContext, key, cachedBuffer, cachedImageMetadata);
                                }

                                return;
                            }
                        }
                    }
                }

                // Not cached? Let's get it from the image resolver.
                ChunkedMemoryStream outStream = null;
                try
                {
                    if (processRequest)
                    {
                        // Enter a write lock which locks writing and any reads for the same request.
                        // This reduces the overheads of unnecessary processing plus avoids file locks.
                        using (await AsyncLock.WriterLockAsync(key))
                        {
                            // No allocations here for inStream since we are passing the raw input stream.
                            // outStream allocation depends on the memory allocator used.
                            ImageCacheMetadata cachedImageMetadata = default;
                            outStream = new ChunkedMemoryStream();
                            using (Stream inStream = await sourceImageResolver.OpenReadAsync())
                            {
                                IImageFormat format;

                                // No commands? We simply copy the stream across.
                                if (commands.Count == 0)
                                {
                                    format = Image.DetectFormat(this.options.Configuration, inStream);
                                    await inStream.CopyToAsync(outStream);
                                }
                                else
                                {
                                    using (var image = FormattedImage.Load(this.options.Configuration, inStream))
                                    {
                                        image.Process(this.logger, this.processors, commands);
                                        this.options.OnBeforeSave?.Invoke(image);
                                        image.Save(outStream);
                                        format = image.Format;
                                    }
                                }

                                // Check to see if the source metadata has a cachecontrol max-age value and use it to
                                // override the default max age from our options.
                                var maxAge = TimeSpan.FromDays(this.options.MaxBrowserCacheDays);
                                if (!sourceImageMetadata.CacheControlMaxAge.Equals(TimeSpan.MinValue))
                                {
                                    maxAge = sourceImageMetadata.CacheControlMaxAge;
                                }

                                cachedImageMetadata = new ImageCacheMetadata(
                                    sourceImageMetadata.LastWriteTimeUtc,
                                    DateTime.UtcNow,
                                    format.DefaultMimeType,
                                    maxAge);
                            }

                            // Allow for any further optimization of the image. Always reset the position just in case.
                            outStream.Position = 0;
                            string contentType = cachedImageMetadata.ContentType;
                            string extension   = this.formatUtilities.GetExtensionFromContentType(contentType);
                            this.options.OnProcessed?.Invoke(new ImageProcessingContext(context, outStream, commands, contentType, extension));
                            outStream.Position = 0;

                            // Save the image to the cache and send the response to the caller.
                            await this.cache.SetAsync(key, outStream, cachedImageMetadata);

                            await this.SendResponseAsync(imageContext, key, outStream, cachedImageMetadata);
                        }
                    }
                }
                catch (Exception ex)
                {
                    // Log the error internally then rethrow.
                    // We don't call next here, the pipeline will automatically handle it
                    this.logger.LogImageProcessingFailed(imageContext.GetDisplayUrl(), ex);
                    throw;
                }
                finally
                {
                    outStream?.Dispose();
                }
            }
        }
예제 #3
0
        /// <summary>
        /// Performs operations upon the current request.
        /// </summary>
        /// <param name="context">The current HTTP request context</param>
        /// <returns>The <see cref="Task"/></returns>
        public async Task Invoke(HttpContext context)
        {
            IDictionary <string, string> commands = this.uriParser.ParseUriCommands(context);

            this.options.OnValidate(new ImageValidationContext(context, commands, CommandParser.Instance));

            if (!commands.Any() || !commands.Keys.Intersect(this.knownCommands).Any())
            {
                // Nothing to do. call the next delegate/middleware in the pipeline
                await this.next(context);

                return;
            }

            // Get the correct service for the request.
            IImageResolver resolver = this.resolvers.FirstOrDefault(r => r.Match(context));

            if (resolver == null || !await resolver.IsValidRequestAsync(context, this.logger))
            {
                // Nothing to do. call the next delegate/middleware in the pipeline
                await this.next(context);

                return;
            }

            string uri = context.Request.Path + QueryString.Create(commands);
            string key = CacheHash.Create(uri, this.options.Configuration);

            CachedInfo info = await this.cache.IsExpiredAsync(key, DateTime.UtcNow.AddDays(-this.options.MaxCacheDays));

            var imageContext = new ImageContext(context, this.options);

            if (!info.Expired)
            {
                // Image is a cached image. Return the correct response now.
                await this.SendResponse(imageContext, key, info.LastModifiedUtc, null, (int)info.Length);

                return;
            }

            // Not cached? Let's get it from the image resolver.
            byte[]       inBuffer  = null;
            byte[]       outBuffer = null;
            MemoryStream outStream = null;

            try
            {
                inBuffer = await resolver.ResolveImageAsync(context, this.logger);

                if (inBuffer == null || inBuffer.Length == 0)
                {
                    // Log the error but let the pipeline handle the 404
                    this.logger.LogImageResolveFailed(imageContext.GetDisplayUrl());
                    await this.next(context);

                    return;
                }

                // No allocations here for inStream since we are passing the buffer.
                // TODO: How to prevent the allocation in outStream? Passing a pooled buffer won't let stream grow if needed.
                outStream = new MemoryStream();
                using (var image = FormattedImage.Load(this.options.Configuration, inBuffer))
                {
                    image.Process(this.logger, this.processors, commands);
                    image.Save(outStream);
                }

                // Allow for any further optimization of the image. Always reset the position just in case.
                outStream.Position = 0;
                this.options.OnProcessed(new ImageProcessingContext(context, outStream, Path.GetExtension(key)));
                outStream.Position = 0;
                int outLength = (int)outStream.Length;

                // Copy the outstream to the pooled buffer.
                outBuffer = BufferDataPool.Rent(outLength);
                await outStream.ReadAsync(outBuffer, 0, outLength);

                DateTimeOffset cachedDate = await this.cache.SetAsync(key, outBuffer, outLength);

                await this.SendResponse(imageContext, key, cachedDate, outBuffer, outLength);
            }
            catch (Exception ex)
            {
                this.logger.LogImageProcessingFailed(imageContext.GetDisplayUrl(), ex);
            }
            finally
            {
                outStream?.Dispose();

                // Buffer should have been rented in IImageResolver
                BufferDataPool.Return(inBuffer);
                BufferDataPool.Return(outBuffer);
            }
        }
예제 #4
0
 /// <summary>
 /// Loops through the available processors and updates the image if any match.
 /// </summary>
 /// <param name="source">The image to resize.</param>
 /// <param name="logger">The type used for performing logging.</param>
 /// <param name="processors">The collection of available processors.</param>
 /// <param name="commands">The parsed collection of processing commands.</param>
 /// <param name="parser">The command parser use for parting commands.</param>
 /// <param name="culture">
 /// The <see cref="CultureInfo"/> to use as the current parsing culture.
 /// </param>
 /// <returns>The <see cref="FormattedImage"/>.</returns>
 public static FormattedImage Process(
     this FormattedImage source,
     ILogger logger,
     IReadOnlyList <(int Index, IImageWebProcessor Processor)> processors,