浏览器网络请求 API:全面解析与高级封装(2)

本文接上一篇 浏览器网络请求 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. 实际应用场景:
  • 大文件分片上传
  • 流式响应处理
  • 离线请求队列
  • 请求合并和批处理
关键要点:
  1. 选择合适的请求技术: 根据需求选择Fetch、XHR或WebSocket
  2. 实现完整的错误处理: 网络错误、超时、重试、降级
  3. 优化用户体验: 请求取消、进度显示、离线支持
  4. 确保数据可靠性: 消息确认、重传机制、数据一致性
  5. 监控和分析: 性能监控、错误追踪、使用统计
最佳实践:
  1. 使用拦截器: 集中处理认证、错误、日志
  2. 实现智能重试: 指数退避、条件重试
  3. 管理请求生命周期: 取消、清理、资源释放
  4. 优化网络使用: 请求合并、缓存、优先级
  5. 确保安全性: HTTPS、认证、数据验证

通过这些技术和策略,开发者可以构建出强大、可靠、高性能的网络请求系统,为现代Web应用提供坚实的基础设施支持。

相关推荐
霍格沃兹测试学院-小舟畅学2 小时前
Cypress 入门与优势分析:前端自动化测试的新利器
前端
幼儿园技术家2 小时前
深入理解 CSR / SSR / SSG:前端三种渲染模式的本质与选型
前端
How_doyou_do2 小时前
常见的设计模式
前端·javascript·设计模式
3824278272 小时前
汇编:条件汇编、
前端·汇编·数据库
狗哥哥2 小时前
企业级 HTTP 客户端架构演进与设计
前端·架构
前端无涯2 小时前
react组件(4)---高阶使用及闭坑指南
前端·react.js
Gomiko2 小时前
JavaScript DOM 原生部分(五):事件绑定
开发语言·前端·javascript
出来吧皮卡丘2 小时前
A2UI:让 AI Agent 自主构建用户界面的新范式
前端·人工智能·aigc
Jeking2172 小时前
进阶流程图绘制工具 Unione Flow Editor-- 击破样式痛点:全维度自定义解决方案
前端·流程图·workflow·unione flow·flow editor·unione cloud