本文接上一篇 浏览器网络请求 API:全面解析与高级封装(1)
五、网络请求监控与调试
5.1 网络请求监控器
javascript
class NetworkRequestMonitor {
constructor(options = {}) {
this.options = {
enabled: true,
logLevel: 'info', // 'debug', 'info', 'warn', 'error'
capturePerformance: true,
captureHeaders: true,
captureBody: false, // 注意:敏感数据!
maxEntries: 1000,
autoExport: false,
exportInterval: 60000, // 1分钟
...options
};
this.requests = new Map();
this.performanceEntries = [];
this.eventListeners = new Map();
this.stats = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
averageResponseTime: 0,
totalDataTransferred: 0
};
this.init();
}
init() {
if (!this.options.enabled) return;
// 监听所有Fetch请求
this.interceptFetch();
// 监听所有XMLHttpRequest请求
this.interceptXHR();
// 监听WebSocket连接
this.interceptWebSocket();
// 设置自动导出
if (this.options.autoExport) {
setInterval(() => {
this.exportData();
}, this.options.exportInterval);
}
}
interceptFetch() {
const originalFetch = window.fetch;
window.fetch = async (...args) => {
const [url, options = {}] = args;
const requestId = this.generateRequestId('fetch', url, options);
// 记录请求开始
const requestInfo = this.recordRequestStart(requestId, {
type: 'fetch',
url,
method: options.method || 'GET',
headers: options.headers || {},
body: options.body,
timestamp: Date.now()
});
try {
// 添加性能标记
if (this.options.capturePerformance) {
performance.mark(`fetch_start_${requestId}`);
}
// 执行原始fetch
const response = await originalFetch.apply(window, args);
// 克隆响应以读取数据
const clonedResponse = response.clone();
// 记录请求成功
await this.recordRequestSuccess(requestId, clonedResponse, requestInfo);
return response;
} catch (error) {
// 记录请求失败
this.recordRequestFailure(requestId, error, requestInfo);
throw error;
}
};
}
interceptXHR() {
const originalXHR = window.XMLHttpRequest;
const XHRMonitor = class extends originalXHR {
constructor() {
super();
this._monitorRequestId = null;
this._monitorStartTime = null;
this._monitorRequestInfo = null;
// 重写open方法
const originalOpen = this.open;
this.open = function(method, url, ...rest) {
this._monitorRequestId = NetworkRequestMonitor.prototype.generateRequestId(
'xhr', url, { method }
);
this._monitorStartTime = performance.now();
this._monitorRequestInfo = NetworkRequestMonitor.prototype.recordRequestStart(
this._monitorRequestId,
{
type: 'xhr',
url,
method,
timestamp: Date.now()
}
);
return originalOpen.apply(this, [method, url, ...rest]);
};
// 重写send方法
const originalSend = this.send;
this.send = function(body) {
if (body) {
this._monitorRequestInfo.body = body;
}
// 监听事件
this.addEventListener('load', function() {
NetworkRequestMonitor.prototype.recordXHRSuccess(
this._monitorRequestId,
this,
this._monitorRequestInfo
);
});
this.addEventListener('error', function() {
NetworkRequestMonitor.prototype.recordXHRFailure(
this._monitorRequestId,
new Error('XHR request failed'),
this._monitorRequestInfo
);
});
this.addEventListener('timeout', function() {
NetworkRequestMonitor.prototype.recordXHRFailure(
this._monitorRequestId,
new Error('XHR request timeout'),
this._monitorRequestInfo
);
});
return originalSend.apply(this, arguments);
};
}
};
window.XMLHttpRequest = XHRMonitor;
}
interceptWebSocket() {
const originalWebSocket = window.WebSocket;
const WebSocketMonitor = class extends originalWebSocket {
constructor(url, protocols) {
super(url, protocols);
const connectionId = NetworkRequestMonitor.prototype.generateRequestId(
'websocket', url, { protocols }
);
// 记录WebSocket连接
const connectionInfo = NetworkRequestMonitor.prototype.recordWebSocketConnection(
connectionId,
{
url,
protocols,
timestamp: Date.now()
}
);
// 监听WebSocket事件
this.addEventListener('open', (event) => {
NetworkRequestMonitor.prototype.recordWebSocketEvent(
connectionId,
'open',
event,
connectionInfo
);
});
this.addEventListener('message', (event) => {
NetworkRequestMonitor.prototype.recordWebSocketMessage(
connectionId,
event.data,
connectionInfo
);
});
this.addEventListener('error', (event) => {
NetworkRequestMonitor.prototype.recordWebSocketEvent(
connectionId,
'error',
event,
connectionInfo
);
});
this.addEventListener('close', (event) => {
NetworkRequestMonitor.prototype.recordWebSocketEvent(
connectionId,
'close',
event,
connectionInfo
);
});
// 重写send方法以监控发送的消息
const originalSend = this.send;
this.send = function(data) {
NetworkRequestMonitor.prototype.recordWebSocketSend(
connectionId,
data,
connectionInfo
);
return originalSend.apply(this, arguments);
};
}
};
window.WebSocket = WebSocketMonitor;
}
// 记录请求开始
recordRequestStart(requestId, info) {
const requestInfo = {
id: requestId,
...info,
startTime: Date.now(),
status: 'pending',
performanceMark: `request_start_${requestId}`
};
this.requests.set(requestId, requestInfo);
// 触发事件
this.emit('requestStart', requestInfo);
// 性能标记
if (this.options.capturePerformance) {
performance.mark(requestInfo.performanceMark);
}
return requestInfo;
}
// 记录请求成功
async recordRequestSuccess(requestId, response, requestInfo) {
const endTime = Date.now();
const duration = endTime - requestInfo.startTime;
// 读取响应数据
let responseBody = null;
let responseSize = 0;
if (this.options.captureBody) {
try {
if (response.headers.get('content-type')?.includes('application/json')) {
responseBody = await response.json();
} else if (response.headers.get('content-type')?.includes('text/')) {
responseBody = await response.text();
}
// 计算响应大小
if (responseBody) {
responseSize = new Blob([JSON.stringify(responseBody)]).size;
}
} catch (error) {
console.warn('Failed to read response body:', error);
}
}
// 更新请求信息
requestInfo.status = 'success';
requestInfo.endTime = endTime;
requestInfo.duration = duration;
requestInfo.response = {
status: response.status,
statusText: response.statusText,
headers: this.options.captureHeaders ?
Object.fromEntries(response.headers.entries()) : {},
body: responseBody,
size: responseSize
};
// 更新统计信息
this.updateStats({
successful: true,
duration,
size: responseSize
});
// 性能测量
if (this.options.capturePerformance) {
performance.mark(`request_end_${requestId}`);
performance.measure(
`request_${requestId}`,
requestInfo.performanceMark,
`request_end_${requestId}`
);
// 保存性能条目
const measures = performance.getEntriesByName(`request_${requestId}`);
if (measures.length > 0) {
this.performanceEntries.push(measures[0]);
}
}
// 触发事件
this.emit('requestSuccess', requestInfo);
// 清理旧条目
this.cleanupOldEntries();
}
// 记录XHR请求成功
recordXHRSuccess(requestId, xhr, requestInfo) {
const endTime = Date.now();
const duration = endTime - requestInfo.startTime;
let responseBody = null;
let responseSize = 0;
if (this.options.captureBody) {
try {
responseBody = xhr.responseText || xhr.response;
if (responseBody) {
responseSize = new Blob([responseBody]).size;
}
} catch (error) {
console.warn('Failed to read XHR response:', error);
}
}
// 更新请求信息
requestInfo.status = 'success';
requestInfo.endTime = endTime;
requestInfo.duration = duration;
requestInfo.response = {
status: xhr.status,
statusText: xhr.statusText,
headers: this.options.captureHeaders ?
this.parseXHRHeaders(xhr.getAllResponseHeaders()) : {},
body: responseBody,
size: responseSize
};
// 更新统计信息
this.updateStats({
successful: true,
duration,
size: responseSize
});
// 触发事件
this.emit('requestSuccess', requestInfo);
// 清理旧条目
this.cleanupOldEntries();
}
// 解析XHR响应头
parseXHRHeaders(headersString) {
const headers = {};
const lines = headersString.trim().split(/[\r\n]+/);
lines.forEach(line => {
const parts = line.split(': ');
const header = parts.shift();
const value = parts.join(': ');
headers[header] = value;
});
return headers;
}
// 记录请求失败
recordRequestFailure(requestId, error, requestInfo) {
const endTime = Date.now();
const duration = endTime - requestInfo.startTime;
requestInfo.status = 'error';
requestInfo.endTime = endTime;
requestInfo.duration = duration;
requestInfo.error = {
message: error.message,
name: error.name,
stack: error.stack
};
// 更新统计信息
this.updateStats({
successful: false,
duration
});
// 触发事件
this.emit('requestError', requestInfo);
// 清理旧条目
this.cleanupOldEntries();
}
// 记录XHR请求失败
recordXHRFailure(requestId, error, requestInfo) {
this.recordRequestFailure(requestId, error, requestInfo);
}
// 记录WebSocket连接
recordWebSocketConnection(connectionId, info) {
const connectionInfo = {
id: connectionId,
...info,
connectedAt: null,
disconnectedAt: null,
messagesSent: [],
messagesReceived: [],
status: 'connecting'
};
this.requests.set(connectionId, connectionInfo);
this.emit('websocketConnection', connectionInfo);
return connectionInfo;
}
// 记录WebSocket事件
recordWebSocketEvent(connectionId, eventType, event, connectionInfo) {
if (!connectionInfo) {
connectionInfo = this.requests.get(connectionId);
if (!connectionInfo) return;
}
const eventInfo = {
type: eventType,
timestamp: Date.now(),
event
};
if (eventType === 'open') {
connectionInfo.status = 'connected';
connectionInfo.connectedAt = Date.now();
} else if (eventType === 'close') {
connectionInfo.status = 'disconnected';
connectionInfo.disconnectedAt = Date.now();
connectionInfo.closeCode = event.code;
connectionInfo.closeReason = event.reason;
} else if (eventType === 'error') {
connectionInfo.lastError = event;
}
connectionInfo.events = connectionInfo.events || [];
connectionInfo.events.push(eventInfo);
this.emit('websocketEvent', { connectionId, eventType, eventInfo, connectionInfo });
}
// 记录WebSocket消息发送
recordWebSocketSend(connectionId, data, connectionInfo) {
if (!connectionInfo) {
connectionInfo = this.requests.get(connectionId);
if (!connectionInfo) return;
}
const messageInfo = {
type: 'sent',
data,
timestamp: Date.now(),
size: new Blob([data]).size
};
connectionInfo.messagesSent.push(messageInfo);
// 更新统计信息
this.updateStats({
size: messageInfo.size
});
this.emit('websocketMessage', { connectionId, messageInfo, connectionInfo });
}
// 记录WebSocket消息接收
recordWebSocketMessage(connectionId, data, connectionInfo) {
if (!connectionInfo) {
connectionInfo = this.requests.get(connectionId);
if (!connectionInfo) return;
}
const messageInfo = {
type: 'received',
data,
timestamp: Date.now(),
size: new Blob([data]).size
};
connectionInfo.messagesReceived.push(messageInfo);
// 更新统计信息
this.updateStats({
size: messageInfo.size
});
this.emit('websocketMessage', { connectionId, messageInfo, connectionInfo });
}
// 更新统计信息
updateStats({ successful, duration, size }) {
this.stats.totalRequests++;
if (successful !== undefined) {
if (successful) {
this.stats.successfulRequests++;
} else {
this.stats.failedRequests++;
}
}
if (duration !== undefined) {
const totalDuration = this.stats.averageResponseTime *
(this.stats.successfulRequests + this.stats.failedRequests - 1) + duration;
this.stats.averageResponseTime = totalDuration /
(this.stats.successfulRequests + this.stats.failedRequests);
}
if (size !== undefined) {
this.stats.totalDataTransferred += size;
}
}
// 生成请求ID
generateRequestId(type, url, options) {
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 9);
const hash = this.hashString(`${type}_${url}_${JSON.stringify(options)}`);
return `${type}_${hash}_${timestamp}_${random}`;
}
// 哈希函数
hashString(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash.toString(36);
}
// 清理旧条目
cleanupOldEntries() {
if (this.requests.size <= this.options.maxEntries) return;
// 转换为数组并排序
const entries = Array.from(this.requests.entries())
.sort((a, b) => a[1].startTime - b[1].startTime);
// 删除最旧的条目
const toRemove = entries.length - this.options.maxEntries;
for (let i = 0; i < toRemove; i++) {
this.requests.delete(entries[i][0]);
}
}
// 事件监听
on(event, listener) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, new Set());
}
this.eventListeners.get(event).add(listener);
}
off(event, listener) {
if (this.eventListeners.has(event)) {
this.eventListeners.get(event).delete(listener);
if (this.eventListeners.get(event).size === 0) {
this.eventListeners.delete(event);
}
}
}
// 触发事件
emit(event, data) {
if (this.eventListeners.has(event)) {
this.eventListeners.get(event).forEach(listener => {
try {
listener(data);
} catch (error) {
console.error(`Error in event listener for "${event}":`, error);
}
});
}
}
// 获取所有请求
getRequests() {
return Array.from(this.requests.values());
}
// 获取统计信息
getStats() {
return {
...this.stats,
successRate: this.stats.totalRequests > 0 ?
(this.stats.successfulRequests / this.stats.totalRequests) * 100 : 0,
failureRate: this.stats.totalRequests > 0 ?
(this.stats.failedRequests / this.stats.totalRequests) * 100 : 0,
activeRequests: Array.from(this.requests.values())
.filter(req => req.status === 'pending').length,
totalWebSocketConnections: Array.from(this.requests.values())
.filter(req => req.type === 'websocket').length
};
}
// 获取性能分析
getPerformanceAnalysis() {
if (this.performanceEntries.length === 0) {
return null;
}
const durations = this.performanceEntries.map(entry => entry.duration);
durations.sort((a, b) => a - b);
const avg = durations.reduce((a, b) => a + b, 0) / durations.length;
const p50 = durations[Math.floor(durations.length * 0.5)];
const p95 = durations[Math.floor(durations.length * 0.95)];
const p99 = durations[Math.floor(durations.length * 0.99)];
return {
count: durations.length,
average: avg,
min: durations[0],
max: durations[durations.length - 1],
p50,
p95,
p99,
distribution: this.calculateDistribution(durations)
};
}
// 计算分布
calculateDistribution(durations) {
const ranges = [
{ min: 0, max: 100, count: 0 },
{ min: 100, max: 500, count: 0 },
{ min: 500, max: 1000, count: 0 },
{ min: 1000, max: 5000, count: 0 },
{ min: 5000, max: Infinity, count: 0 }
];
durations.forEach(duration => {
for (const range of ranges) {
if (duration >= range.min && duration < range.max) {
range.count++;
break;
}
}
});
return ranges.map(range => ({
...range,
percentage: (range.count / durations.length) * 100
}));
}
// 导出数据
exportData(format = 'json') {
const data = {
timestamp: Date.now(),
stats: this.getStats(),
requests: this.getRequests(),
performance: this.getPerformanceAnalysis()
};
switch (format) {
case 'json':
return JSON.stringify(data, null, 2);
case 'csv':
return this.convertToCSV(data);
default:
return data;
}
}
// 转换为CSV
convertToCSV(data) {
const rows = [];
// 添加统计信息
rows.push('Category,Key,Value');
Object.entries(data.stats).forEach(([key, value]) => {
rows.push(`Stats,${key},${value}`);
});
// 添加请求信息
rows.push('\nRequests');
rows.push('ID,Type,Method,URL,Status,Duration(ms),Size(bytes)');
data.requests.forEach(request => {
const size = request.response?.size || 0;
rows.push(
`${request.id},${request.type},${request.method || 'N/A'},` +
`${request.url},${request.status},${request.duration || 0},${size}`
);
});
return rows.join('\n');
}
// 清除数据
clearData() {
this.requests.clear();
this.performanceEntries = [];
this.stats = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
averageResponseTime: 0,
totalDataTransferred: 0
};
}
}
// 使用示例
class NetworkMonitorExample {
static demonstrate() {
// 创建网络请求监控器
const monitor = new NetworkRequestMonitor({
enabled: true,
logLevel: 'info',
capturePerformance: true,
captureHeaders: true,
maxEntries: 500
});
// 监听请求事件
monitor.on('requestStart', (request) => {
console.log(`Request started: ${request.method} ${request.url}`);
});
monitor.on('requestSuccess', (request) => {
console.log(`Request completed: ${request.status} in ${request.duration}ms`);
});
monitor.on('requestError', (request) => {
console.error(`Request failed: ${request.error?.message}`);
});
// 执行一些网络请求
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(data => console.log('Fetch response:', data));
// 获取监控数据
setTimeout(() => {
const stats = monitor.getStats();
console.log('Network stats:', stats);
const performance = monitor.getPerformanceAnalysis();
console.log('Performance analysis:', performance);
// 导出数据
const exportData = monitor.exportData('json');
console.log('Exported data:', exportData);
}, 3000);
return monitor;
}
}
总结
本文全面探讨了浏览器网络请求相关的API和技术,涵盖:
1. 核心请求技术:
- XMLHttpRequest与现代Fetch API的对比
- Fetch API的高级特性和局限性解决方案
2. 增强型HTTP客户端:
- 完整的HttpClient实现,支持拦截器、缓存、重试
- 智能请求调度器,支持优先级、离线队列、并发控制
3. 请求取消与并发控制:
- 高级取消令牌机制
- 请求池和竞速请求实现
- 智能请求调度和重试策略
4. WebSocket高级封装:
- 完整的WebSocket客户端,支持重连、心跳、消息队列
- 可靠WebSocket实现,支持消息确认和重传
- 多连接管理和广播功能
5. 网络请求监控:
- 请求拦截和性能监控
- 详细的统计分析和性能报告
- 数据导出和调试支持
6. 实际应用场景:
- 大文件分片上传
- 流式响应处理
- 离线请求队列
- 请求合并和批处理
关键要点:
- 选择合适的请求技术: 根据需求选择Fetch、XHR或WebSocket
- 实现完整的错误处理: 网络错误、超时、重试、降级
- 优化用户体验: 请求取消、进度显示、离线支持
- 确保数据可靠性: 消息确认、重传机制、数据一致性
- 监控和分析: 性能监控、错误追踪、使用统计
最佳实践:
- 使用拦截器: 集中处理认证、错误、日志
- 实现智能重试: 指数退避、条件重试
- 管理请求生命周期: 取消、清理、资源释放
- 优化网络使用: 请求合并、缓存、优先级
- 确保安全性: HTTPS、认证、数据验证
通过这些技术和策略,开发者可以构建出强大、可靠、高性能的网络请求系统,为现代Web应用提供坚实的基础设施支持。