HttpCacheHeaders icon indicating copy to clipboard operation
HttpCacheHeaders copied to clipboard

ETag does not check whether is still valid

Open scholtz opened this issue 2 years ago • 0 comments

Hi, we had severe issue with caching running on multiple instances in k8s with this library.

According to https://simonhearne.com/2022/caching-header-best-practices/

This If-None-Match header is a message to the server that the client has a version of the asset in cache. The server can then check to see whether this is still a valid version of the asset - if so, we will receive an empty 304 response with another ETag which will match the original

The server can then check to see whether this is still a valid version.. With this caching library the method was not checking again anything.. If etag is found in the storage, the headers are served from the last run and is not executed.

Therefore we found a solution in setting our own storage which will give us option to set validity of the cache key. This solution is not ideal because if we set cache to 80000 seconds it will probably be refreshed within the day (unix % 80000) is in the cache key, but solves the primary issue.

public class TimedStoreKeyGenerator : Marvin.Cache.Headers.Interfaces.IStoreKeyGenerator
    {
        public static int Validity = 3;// seconds
        public static Func<IServiceProvider, Marvin.Cache.Headers.Interfaces.IStoreKeyGenerator> Instance = serviceProvider => new TimedStoreKeyGenerator();
        public System.Threading.Tasks.Task<Marvin.Cache.Headers.StoreKey> GenerateStoreKey(Marvin.Cache.Headers.Domain.StoreKeyContext context)
        {
            // generate a key to store the entity tag with in the entity tag store
            List<string> requestHeaderValues;

            // get the request headers to take into account (VaryBy) & take
            // their values        
            if (context.VaryByAll)
            {
                requestHeaderValues = context.HttpRequest
                        .Headers
                        .SelectMany(h => h.Value)
                        .ToList();
            }
            else
            {
                requestHeaderValues = context.HttpRequest
                        .Headers
                        .Where(x => context.Vary.Any(h =>
                            h.Equals(x.Key, StringComparison.CurrentCultureIgnoreCase)))
                        .SelectMany(h => h.Value)
                        .ToList();
            }

            // get the resource path
            var resourcePath = context.HttpRequest.Path.ToString();

            // get the query string
            var queryString = context.HttpRequest.QueryString.ToString();

            // generate each 3 seconds new cache key
            var time = (DateTimeOffset.Now.ToUnixTimeSeconds() % Validity).ToString();
            // combine these
            return Task.FromResult(new Marvin.Cache.Headers.StoreKey
            {
                { nameof(resourcePath), resourcePath },
                { nameof(queryString), queryString },
                { nameof(requestHeaderValues), string.Join("-", requestHeaderValues)},
                { "time", time }
            });
        }
    }

Initialized as

public static class StartupExtension
    {
        public static void AddCache(this IServiceCollection services, int duration)
        {
            TimedStoreKeyGenerator.Validity = duration;
            services.AddHttpCacheHeaders(
                (expirationModelOptions) =>
                {
                    expirationModelOptions.MaxAge = duration;
                },

                (validationModelOptions) =>
                {
                    validationModelOptions.MustRevalidate = true;
                    validationModelOptions.ProxyRevalidate = true;
                },
                storeKeyGeneratorFunc: TimedStoreKeyGenerator.Instance
                );
        }
    }

In startup .net 3,5

services.AddCache(3);

.net 6

builder.Services.AddCache(3600 * 20);

If @KevinDockx has any insight or to tell us the best practicies if we missed something please do so.

Thanks

scholtz avatar Sep 06 '22 06:09 scholtz