在常规的白盒测试中,请求的缓存,重放,也是个绕不过的话题。
那么如果要提供一个辅助回放的插件工具,应该如何入手开搞呢?
首先梳理思路:
- 拦截Ajax请求,判断是否有可用缓存结果
- 缓存请求响应结果,并设置过期机制
- 验证
举一个基于 Jquery 实现的例子。
由于插件的外挂机制,首先拦截 script load 事件。
在 Jquery 初始化后,拦截 $.ajax 或者通过 $.ajaxPrefilter进行相关操作。
下面是基于 $.ajax 拦截的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
| ;(function () { 'use strict';
class ServiceCache { /** * @typedef {Object} CacheConfig * @property {Storage} storage 缓存存储对象,默认为 sessionStorage * @property {string} key 缓存键名 * @property {number} expire 缓存过期时间(毫秒) */
/** * 初始化缓存配置 * @param {CacheConfig} config 缓存配置参数 */ constructor(config) { const defaultConfig = { storage: sessionStorage, key: 'cache-ajax', expire: 3 * 60 * 1000 }; this.config = Object.assign(defaultConfig, config); this.cache = {}; this.recover(); return this; }
/** * 节流函数,用于限制函数的执行频率 * @param {Function} func 需要节流的函数 * @param {number} delay 节流延迟时间(毫秒) * @returns {Function} 节流后的函数 */ throttle(func, delay) { let timeoutId; return function (...args) { if (!timeoutId) { timeoutId = setTimeout(() => { func.apply(this, args); clearTimeout(timeoutId); timeoutId = null; }, delay); } }; }
/** * 保存缓存到存储对象 */ save = this.throttle(() => { this.config.storage.setItem(this.config.key, JSON.stringify(this.cache)); }, 100);
/** * 从存储对象中恢复缓存 */ recover() { this.cache = JSON.parse(this.config.storage.getItem(this.config.key) || '{}'); }
/** * 设置缓存项 * @param {string} key 缓存键名 * @param {*} value 缓存数据 * @returns {ServiceCache} 当前 ServiceCache 实例 */ set(key, value) { this.cache[key] = { data: value, timestamp: Date.now() }; this.save(); return this; }
/** * 获取缓存项 * @param {string} key 缓存键名 * @returns {*} 缓存数据,若缓存无效则返回 null */ get(key) { const cacheData = this.cache[key]; const isCacheValid = this.isCacheValid(cacheData); if (!isCacheValid) { this.remove(key); return null; } else { return cacheData.data; } }
/** * 移除指定缓存项,若不传入 key 则移除所有缓存项 * @param {string} [key] 缓存键名 * @returns {ServiceCache} 当前 ServiceCache 实例 */ remove(key) { if (key) { delete this.cache[key]; } else { this.cache = {}; } this.save(); return this; }
/** * 清空所有缓存项 * @returns {ServiceCache} 当前 ServiceCache 实例 */ clear() { for (const key in this.cache) { this.get(key); } return this; }
/** * 验证缓存项是否有效 * @param {Object} cachedData 缓存项数据 * @returns {boolean} 缓存项是否有效 */ isCacheValid(cachedData) { const currentTime = Date.now(); return currentTime - cachedData.timestamp <= this.config.expire; } }
function initJqueryAjaxCache() { // 用于存储缓存的对象 const cache = JSON.parse(sessionStorage.getItem('cache-ajax') || '{}')
// 保存原始的$.ajax函数 const originalAjax = $.ajax;
// 重写$.ajax函数 $.ajax = function (options) { const key = generateCacheKey(options.url, options.type, options.data); const cachedData = cache[key]; if (cachedData && isCacheValid(cachedData)) { // 如果存在缓存且未过期,直接使用缓存内容 console.warn(`Using cached response for ${key}`); const deferred = $.Deferred(); deferred.resolve(cachedData.response); return deferred.promise(); } else { // 如果不存在缓存或者缓存已过期,使用原始的$.ajax函数发送请求 return originalAjax(options).done(function (data) { // 请求成功时缓存响应 cache[key] = { response: data, timestamp: new Date().getTime(), }; console.warn(`Cached response for ${key}`); sessionStorage.setItem('cache-ajax', JSON.stringify(cache)) }); } };
// 生成缓存键的函数 function generateCacheKey(url, type, data) { return `${url.replace(/t=\d+/, '')}_${type}_${JSON.stringify(data)}`; }
// 判断缓存是否有效的函数 function isCacheValid(cachedData) { const currentTime = new Date().getTime(); return currentTime - cachedData.timestamp <= 3 * 60 * 1000; // 缓存有效期为3分钟 }
// 清空缓存的函数 function clearCache() { for (const key in cache) { delete cache[key]; } console.warn('Cache cleared'); }
// 打印缓存内容的函数 function printCache() { for (const key in cache) { console.warn(`Cache Key: ${key}`); console.warn(`Cached Response Data: ${cache[key].response}`); } }
window.printCache = printCache; }
document.addEventListener("load", function (event) { var target = event.target; if (target.tagName === "SCRIPT" && target.src.indexOf('jquery.min.js') !== -1) { // jq.js加载完成,执行alert() console.warn("jq.js已加载完成"); initJqueryAjaxCache() } }, true);
})();
|