在常规的白盒测试中,请求的缓存,重放,也是个绕不过的话题。

那么如果要提供一个辅助回放的插件工具,应该如何入手开搞呢?

首先梳理思路:

  1. 拦截Ajax请求,判断是否有可用缓存结果
  2. 缓存请求响应结果,并设置过期机制
  3. 验证

举一个基于 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);

})();