Example #1
0
        /// <inheritdoc/>
        public async Task <CachedBuffer> GetAsync(string key)
        {
            IFileInfo fileInfo = this.fileProvider.GetFileInfo(this.ToFilePath(key));

            byte[] buffer;

            // Check to see if the file exists.
            if (!fileInfo.Exists)
            {
                return(default(CachedBuffer));
            }

            long length;

            using (Stream stream = fileInfo.CreateReadStream())
            {
                length = stream.Length;

                // Buffer is returned to the pool in the middleware
                buffer = BufferDataPool.Rent((int)length);
                await stream.ReadAsync(buffer, 0, (int)length);
            }

            return(new CachedBuffer(buffer, length));
        }
        private async Task SendResponse(ImageContext imageContext, string key, DateTimeOffset lastModified, byte[] buffer, int length)
        {
            imageContext.ComprehendRequestHeaders(lastModified, length);

            string contentType = FormatHelpers.GetContentType(this.options.Configuration, key);

            switch (imageContext.GetPreconditionState())
            {
            case ImageContext.PreconditionState.Unspecified:
            case ImageContext.PreconditionState.ShouldProcess:
                if (imageContext.IsHeadRequest())
                {
                    await imageContext.SendStatusAsync(ResponseConstants.Status200Ok, contentType);
                }

                this.logger.LogImageServed(imageContext.GetDisplayUrl(), key);
                if (buffer == null)
                {
                    // We're pulling the buffer from the cache. This should be pooled.
                    CachedBuffer cachedBuffer = await this.cache.GetAsync(key);

                    await imageContext.SendAsync(contentType, cachedBuffer.Buffer, cachedBuffer.Length);

                    BufferDataPool.Return(cachedBuffer.Buffer);
                }
                else
                {
                    await imageContext.SendAsync(contentType, buffer, length);
                }

                break;

            case ImageContext.PreconditionState.NotModified:
                this.logger.LogImageNotModified(imageContext.GetDisplayUrl());
                await imageContext.SendStatusAsync(ResponseConstants.Status304NotModified, contentType);

                break;

            case ImageContext.PreconditionState.PreconditionFailed:
                this.logger.LogImagePreconditionFailed(imageContext.GetDisplayUrl());
                await imageContext.SendStatusAsync(ResponseConstants.Status412PreconditionFailed, contentType);

                break;

            default:
                var exception = new NotImplementedException(imageContext.GetPreconditionState().ToString());
                Debug.Fail(exception.ToString());
                throw exception;
            }
        }
Example #3
0
        /// <inheritdoc/>
        public async Task <byte[]> ResolveImageAsync(HttpContext context, ILogger logger)
        {
            // Path has already been correctly parsed before here.
            IFileInfo fileInfo = this.fileProvider.GetFileInfo(context.Request.Path.Value);

            byte[] buffer;

            // Check to see if the file exists.
            if (!fileInfo.Exists)
            {
                return(null);
            }

            using (Stream stream = fileInfo.CreateReadStream())
            {
                // Buffer is returned to the pool in the middleware
                buffer = BufferDataPool.Rent((int)stream.Length);
                await stream.ReadAsync(buffer, 0, (int)stream.Length);
            }

            return(buffer);
        }
        /// <inheritdoc/>
        public async Task <byte[]> ResolveImageAsync(HttpContext context, ILogger logger)
        {
            // Path has already been correctly parsed before here.

            var file = await _mediaStore.MapFileAsync(context.Request.Path);

            byte[] buffer;

            // Check to see if the file exists.
            if (file == null)
            {
                return(null);
            }

            using (Stream stream = file.CreateReadStream())
            {
                // Buffer is returned to the pool in the middleware
                buffer = BufferDataPool.Rent((int)stream.Length);
                await stream.ReadAsync(buffer, 0, (int)stream.Length);
            }

            return(buffer);
        }
Example #5
0
        /// <inheritdoc/>
        public async Task <byte[]> ResolveImageAsync(HttpContext context, ILogger logger)
        {
            // Path has already been correctly parsed before here.

            var filePath = _mediaStore.MapPublicUrlToPath(context.Request.PathBase + context.Request.Path.Value);
            var file     = await _mediaStore.GetFileInfoAsync(filePath);

            // Check to see if the file exists.
            if (file == null)
            {
                return(null);
            }

            byte[] buffer;

            using (var stream = await _mediaStore.GetFileStreamAsync(filePath))
            {
                // Buffer is returned to the pool in the middleware
                buffer = BufferDataPool.Rent((int)stream.Length);
                await stream.ReadAsync(buffer, 0, (int)stream.Length);
            }

            return(buffer);
        }
        /// <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);
            }
        }
Example #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(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);
            }
        }