• ASP.NET Core ResponseCaching:基于 VaryByHeader 定制缓存 Key


    ASP.NET Core ResponseCaching 提供了缓存http响应内容的能力,通过它可以在本地内存中直接缓存http响应内容,这是速度最快的服务端缓存,省却了网络传输与生成响应内容的开销,是 Memcached 或 Redis 等分布式缓存的有效补充。欲更多了解 ResponseCaching ,推荐阅读园子里的一篇博文 谈谈ASP.NET Core中的ResponseCaching

    ResponseCaching 提供了 VaryByHeader 与 VaryByQueryKeys 这两种种方式配置缓存 Key ,我们在使用 VaryByHeader 时发现一个问题 —— 使用 VaryByHeader 时如果不做限制,会面临缓存被撑爆的风险。比如上面根据 Accept 请求头进行缓存,由于客户端可以任意修改请求头,如果有爬虫发出大量的请求,并且每个请求的 Accept 请求头不一样,ResponseCaching 会因此为每个请求生成缓存项,直至内存被耗尽。

    为了避免这个问题,我们需要对基于请求头生成缓存 key 的规则进行限制,但 ResponseCaching 没有提供对应的定制能力。

    我们开始想到的一个解决方法是基于适配器模式自己实现 IResponseCachingKeyProvider 接口,代码如下:

    public class CustomResponseCachingKeyProvider : IResponseCachingKeyProvider
    {
        private static readonly char KeyDelimiter = 'x1e';
        private ResponseCachingKeyProvider _responseCachingKeyProvider;
    
        public CustomResponseCachingKeyProvider(ResponseCachingKeyProvider responseCachingKeyProvider)
        {
            _responseCachingKeyProvider = responseCachingKeyProvider;
        }
    
        public string CreateBaseKey(ResponseCachingContext context)
        {
            return _responseCachingKeyProvider.CreateBaseKey(context);
        }
    
        public IEnumerable<string> CreateLookupVaryByKeys(ResponseCachingContext context)
        {
            return _responseCachingKeyProvider.CreateLookupVaryByKeys(context);
        }
    
        public string CreateStorageVaryByKey(ResponseCachingContext context)
        {
            var key = _responseCachingKeyProvider.CreateStorageVaryByKey(context);
            var accept = context.HttpContext.Request.GetTypedHeaders().Accept;
            if (accept.Any(x => x.MediaType == "application/json"))
            {   
                key += KeyDelimiter + "accept=application/json";
            }            
            else
            {
                key += KeyDelimiter + "accept=text/plain";
            }
    
            return key;
        }
    }

    然后在 Startup 的 ConfigureServices 方法中进行注册

    services.AddTransient<ResponseCachingKeyProvider>();
    services.AddSingleton<IResponseCachingKeyProvider, CustomResponseCachingKeyProvider>();
    services.AddResponseCaching();

    除了上面这两步之外,还要给 VaryByHeader 随便赋个值,因为 CreateStorageVaryByKey() 方法只有在 VaryByHeader 或 VaryByQueryKeys 有值的情况下才会被调用。

    [ResponseCache(Duration = 30, VaryByHeader = "_")]

    实现后觉得这不是一个优雅的解决方法。 

    后来尝试修改 ResponseCaching 的源代码,但 ResponseCaching 在设计时并没有考虑到这个场景,修改工作量比较大。

    再后来转念一想,不用这么麻烦,可以借助于已有的 VaryByHeader 机制,通过 middleware 根据客户端的请求头生成用于 VaryByHeader 的请求头,middleware 的代码如下

    app.Use(async (context, next) =>
    {
        var accept = context.Request.GetTypedHeaders().Accept;
        var mediaTypes = new string[] { "application/json", "text/html" };
        var mediaType = accept.Select(x => x.MediaType).FirstOrDefault(x => mediaTypes.Contains(x.Value));
        context.Request.Headers.Add("Accept-MediaType", mediaType == null ? "text/plain" : mediaType.Value);
        await next.Invoke();
    });

    ResponseCaching 属性的声明如下

    [ResponseCache(Duration = 60, VaryByHeader = "Accept-MediaType", VaryByQueryKeys = new string[] { "id" })]

    折腾了 1 天的问题换了个思路 10 分钟搞定。

    另外,使用 ResponseCaching 还需要注意一个地方,如果用到了 CORS ,还需要在 VaryByHeader 中添加 "Origin"

    [ResponseCache(Duration = 60, VaryByHeader = "Accept-MediaType,Origin", VaryByQueryKeys = new string[] { "id" })]
  • 相关阅读:
    vector的erase函数
    结构体定义容易混淆的地方
    JavaScript重点知识
    JS中预解析案例分析
    浏览器console控制台不显示编译错误/警告
    强烈推荐一款强大的公式编辑器软件AxMath
    DIV+CSS布局
    CSS-常见属性
    CSS-定义样式表
    CSS-使用CSS样式的方式
  • 原文地址:https://www.cnblogs.com/dudu/p/9177394.html
Copyright © 2020-2023  润新知