HarmonyOS —— Remote Communication Kit 自定义缓存拦截器实战笔记

HarmonyOS —— Remote Communication Kit 自定义缓存拦截器实战笔记

前面你已经搞定了:

  • HTTP 缓存基础能力
  • Session 间缓存共享
  • 各种过期策略

这一篇,就是 “我不想完全听 HTTP 协议的话,我要自己说了算” ——
自定义缓存拦截器(Custom Cache Interceptor)


一、功能概览 & 设备支持

鸿蒙开发者第四期活动

自定义缓存拦截器是干嘛的?

  • Remote Communication Kit 里已经有内置的 HTTP 缓存机制;

  • 但有些业务的需求是这样的:

    “我不完全想按 HTTP 标准缓存,我想:

    • 只缓存某些方法(比如 PUT 也要缓存)
    • 只按我自定义的 key 缓存
    • 甚至无视响应头,完全自己管缓存写入/读取”
  • 这时候就可以用 拦截器机制(rcp.Interceptor)+ ResponseCache 自己写缓存逻辑。

版本 & 设备支持:

  • 6.0.0(20) 开始支持自定义缓存拦截器;
  • 支持设备:Phone / 2in1 / Tablet / Wearable / TV

二、整体思路:拦截请求 → 查缓存 → 命中则返回,不命中则走网络再写入

自定义缓存拦截器的基本套路:

  1. intercept(context, next) 里:
    • 先根据 request 构造一个 缓存 keyResponseCacheKey);
    • 用这个 key 去 ResponseCache.get(key) 查一下;
  2. 如果命中缓存
    • 不再往下走 next.handle(即不真正发网络请求);
    • 直接用 rcp.createResponse(...) 包装缓存里的响应,返回给上层;
  3. 如果未命中
    • next.handle(context) 正常发网络请求;
    • 拿到 networkResponse 后,用 ResponseCache.set(key, createCachedResponse(...)) 写入缓存;
    • 最后把 networkResponse 返回。

官方示例就干了一件事:写了一个 “盲缓存拦截器(BlindCacheInterceptor)” —— 不考虑 HTTP 协议里的 Cache-Control 等头,完全按自己定义的逻辑缓存。


三、BlindCacheInterceptor 示例拆解

1. 拦截器类结构

import { rcp } from '@kit.RemoteCommunicationKit';

class BlindCacheInterceptor implements rcp.Interceptor {
  private readonly cache: rcp.ResponseCache;

  constructor(cache: rcp.ResponseCache) {
    this.cache = cache;
  }

  async intercept(context: rcp.RequestContext, next: rcp.RequestHandler): Promise<rcp.Response> {
    const key: rcp.ResponseCacheKey = {
      url: context.request.url,
      method: context.request.method,
    };

    // 1. 先查缓存
    const responseInCache = await this.cache.get(key);
    if (responseInCache) {
      // 直接用缓存里的数据构造 Response 返回
      return rcp.createResponse(context.request, responseInCache.response, new Date());
    }

    // 2. 未命中 → 走真实网络请求
    const networkResponse = await next.handle(context);

    // 3. 写入缓存(序列化为 CachedResponse)
    await this.cache.set(key, rcp.createCachedResponse(networkResponse));

    // 4. 把真实响应返回给上层
    return networkResponse;
  }
}

逐段说一下这个逻辑:

(1) 构造缓存 key

const key: rcp.ResponseCacheKey = {
  url: context.request.url,
  method: context.request.method,
};
  • Key 由两部分组成:
    • url:完整 URL(包含协议、host、path、query);
    • method:HTTP 方法(GET / POST / PUT …)。
  • 你也可以在以后 扩展自己的 key 策略(比如加上特定 header 值、tenantId 等),但示例里先用最基础的。

(2) 先查缓存:this.cache.get(key)

const responseInCache = await this.cache.get(key);
if (responseInCache) {
  return rcp.createResponse(context.request, responseInCache.response, new Date());
}
  • cache.get(key) 如果命中,就返回一个带 response 的结构;
  • rcp.createResponse(request, cached.response, new Date())
    • 用当前请求 + 缓存中的响应体,构造一个 rcp.Response
    • new Date() 相当于“这次响应的时间戳”,便于上层逻辑统一处理。

这一段的要点:一旦命中缓存,就不会调 next.handle(context),因此不会真正触发网络请求

(3) 未命中 → 发起网络请求并写入缓存

const networkResponse = await next.handle(context);
await this.cache.set(key, rcp.createCachedResponse(networkResponse));
return networkResponse;
  • next.handle(context) 是标准拦截器链的“往下传”方式:
    • 后面可能还有别的拦截器;
    • 最终会到实际的网络层;
  • rcp.createCachedResponse(networkResponse)
    • Response 包装成可缓存的结构(只保留适合存储的字段);
  • cache.set(key, cachedResponse)
    • 最终写入真正的缓存存储(内存 / 磁盘)。

这整个拦截器就是一个典型的 “Cache Aside” 模式

先查缓存 → 命中就直接返回
不命中 → 去源头拿数据 → 再写回缓存


四、如何把自定义缓存拦截器接入 Session?

1. 创建 ResponseCache 实例

const responseCache = new rcp.ResponseCache({
  persistent: {
    kind: 'file-system',
    pathToFolder: "/path/dir" // HTTP 缓存记录文件路径(沙箱目录)
  }
});
  • 路径和前面 HTTP 缓存章节的一样;
  • 完全可以和 Session 间缓存共享那节配合起来使用。

2. 在 Session 中挂上拦截器

const session: rcp.Session = rcp.createSession({
  interceptors: [new BlindCacheInterceptor(responseCache)]
});
  • 和之前的 RequestUrlChangeInterceptor / ResponseHeaderRemoveInterceptor 一样,缓存拦截器也是普通拦截器的一种
  • interceptors 数组的顺序依然决定:
    • 请求时:执行顺序;
    • 响应时:逆序回来的顺序。

五、请求流程:第一次命中网络,第二次命中缓存

1. 第一次请求:写缓存

const responseA = await session.put('https://www.example.com');
console.info(`Request succeeded, message is ${JSON.stringify(responseA)}`);

let cacheState = await responseCache.getState();
console.info(`The current number of cache entries is: ${cacheState.count}`);

几点小细节:

  • 示例里故意用的是 PUT,而不是 GET
    • 正常 HTTP 缓存更多针对 GET;
    • 这里证明:自定义缓存拦截器可以缓存你想缓存的任何方法(但业务上要自己保证一致性)。
  • getState().count
    • 查看当前缓存条目数;
    • 第一次成功后,理论上应为 1

2. 第二次请求:命中拦截器的缓存逻辑

const responseB = await session.put('https://www.example.com'); 
console.info(`Request succeeded, message is ${JSON.stringify(responseB)}`);

cacheState = await responseCache.getState();
console.info(`The current cache hit count is: ${cacheState.hitCount}`);
  • 因为 key = { url, method } 一样;
  • 拦截器会先 cache.get(key),直接返回缓存;
  • 此时不会真正往网络发请求;
  • hitCount 应当为 1

六、你可以怎么「魔改」这个缓存拦截器?

目前的 BlindCacheInterceptor 是最简单版本,它特点很明显:

  • 忽略 HTTP 缓存头(Cache-Control, ETag, Expires 等);
  • 忽略响应状态码;
  • 只认 “同 URL + 同 Method”。

你在写博客 / 做题 / 实战时可以延展出一堆变体:

1. 只缓存 GET / 特定状态码

if (context.request.method !== 'GET') {
  return next.handle(context);
}

const networkResponse = await next.handle(context);
if (networkResponse.statusCode === 200) {
  await this.cache.set(key, rcp.createCachedResponse(networkResponse));
}
return networkResponse;

2. 按自定义 key 缓存(比如加上某个 Header)

const tenantId = context.request.headers?.['X-Tenant-Id'] ?? 'default';
const key: rcp.ResponseCacheKey = {
  url: context.request.url,
  method: context.request.method,
  // 有些实现允许扩展字段,或者你在 path/query 里 encode
  // 这里就当成你设计的“逻辑 key”
};

3. 与 Session 间缓存共享结合

  • 拦截器里使用的 responseCache 本身就可以:
    • 用“同一个实例”共享缓存;
    • 或者用“同一路径 pathToFolder”共享磁盘缓存;
  • 这样多个 Session 都能利用这套自定义的缓存规则。

4. 结合 TracingConfiguration 做“缓存命中率监控”

  • intercept 里打印:
    • 命中/未命中日志;
    • 利用 Tracing 采样网络耗时,对比“命中缓存”和“走网络”的差异;
  • 可直接作为“缓存优化前后”的数据对比基础。

七、考点速记版(给你之后写题 / 复习用)

  • 功能:自定义缓存拦截器(基于 rcp.Interceptor + ResponseCache

  • 版本:从 6.0.0(20) 开始支持

  • 设备:Phone / 2in1 / Tablet / Wearable / TV

  • 关键类 / 接口:

    • rcp.Interceptor
    • rcp.RequestContext
    • rcp.RequestHandler
    • rcp.ResponseCache
    • rcp.ResponseCacheKey
    • rcp.createCachedResponse(response)
    • rcp.createResponse(request, cached.response, date)
  • 典型流程:

    async intercept(context, next) {
      const key = { url: context.request.url, method: context.request.method };
    
      // 1. 先查缓存
      const cached = await cache.get(key);
      if (cached) {
        return rcp.createResponse(context.request, cached.response, new Date());
      }
    
      // 2. 未命中 → 发请求
      const resp = await next.handle(context);
    
      // 3. 写缓存
      await cache.set(key, rcp.createCachedResponse(resp));
    
      return resp;
    }
    
  • Session 接入方式:

    const responseCache = new rcp.ResponseCache({ ... });
    const session = rcp.createSession({
      interceptors: [new BlindCacheInterceptor(responseCache)]
    });
    
posted @ 2025-12-13 20:23  遇到困难睡大觉哈哈  阅读(3)  评论(0)    收藏  举报