예제 #1
0
        public void CachHashProducesIdenticalResults()
        {
            string input    = "http://testwebsite.com/image-12345.jpeg?width=400";
            string expected = CacheHash.Create(input, Configuration.Default);
            string actual   = CacheHash.Create(input, Configuration.Default);

            Assert.Equal(expected, actual);
        }
예제 #2
0
        public void CachHashLengthIsIdentical()
        {
            string input    = "http://testwebsite.com/image-12345.jpeg?width=400";
            string input2   = "http://testwebsite.com/image-12345.jpeg";
            int    expected = CacheHash.Create(input, Configuration.Default).Length;
            int    actual   = CacheHash.Create(input2, Configuration.Default).Length;

            Assert.Equal(expected, actual);
        }
예제 #3
0
        public void CacheHashEncodesExtensionCorrectly()
        {
            // Expected extension should match the default extension of the installed format
            string input    = "http://testwebsite.com/image-12345.jpeg?width=400";
            string expected = ".jpeg";
            string actual   = CacheHash.Create(input, Configuration.Default);

            Assert.Equal(expected, Path.GetExtension(actual));

            string input2    = "http://testwebsite.com/image-12345.jpeg?width=400&format=png";
            string expected2 = ".png";
            string actual2   = CacheHash.Create(input2, Configuration.Default);

            Assert.Equal(expected2, Path.GetExtension(actual2));
        }
예제 #4
0
 public string HashUsingSha256() => Sha256Hasher.Create(URL, 12);
        /// <inheritdoc />
        public override string GetCacheFilePath(string inPath, ProcessImageSettings settings)
        {
            string hash = CacheHash.Create(inPath);

            return(WrapFileName($"/{hash[0]}{hash[1]}/{hash[2]}{hash[3]}/{VirtualPathUtility.GetFileName(inPath)}", settings));
        }
 public string HashUsingSha256()
 {
     return(Sha256Hasher.Create(URL, 12));
 }
예제 #7
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?.Invoke(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;
            }

            // Create a cache key based on all the components of the requested url
            string uri = $"{context.Request.Host.ToString().ToLowerInvariant()}/{context.Request.PathBase.ToString().ToLowerInvariant()}/{context.Request.Path}{QueryString.Create(commands)}";
            string key = CacheHash.Create(uri, this.options.Configuration);

            // Prevent identical requests from running at the same time
            // This reduces the overheads of unnecessary processing plus avoids file locks
            bool processRequest = true;

            using (await this.asyncKeyLock.LockAsync(key))
            {
                // 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
                    processRequest = false;
                }

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

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

                    if (info.Equals(default(CachedInfo)))
                    {
                        // Cache has tried to resolve the source image and failed
                        // Log the error but let the pipeline handle the 404
                        this.logger.LogImageResolveFailed(imageContext.GetDisplayUrl());
                        processRequest = false;
                    }

                    if (processRequest)
                    {
                        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());
                                processRequest = false;
                            }

                            if (processRequest)
                            {
                                // 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);
                                    this.options.OnBeforeSave?.Invoke(image);
                                    image.Save(outStream);
                                }

                                // Allow for any further optimization of the image. Always reset the position just in case.
                                outStream.Position = 0;
                                this.options.OnProcessed?.Invoke(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)
                        {
                            // 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();

                            // Buffer should have been rented in IImageResolver
                            BufferDataPool.Return(inBuffer);
                            BufferDataPool.Return(outBuffer);
                        }
                    }
                }
            }

            if (!processRequest)
            {
                // Call the next delegate/middleware in the pipeline
                await this.next(context);
            }
        }
예제 #8
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);
            }
        }