HTTP 请求方法与状态码

发布于:2025-06-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

前言:构建可靠前端应用的 HTTP 通信基础

在当今复杂的 Web 应用生态中,前端开发已远超简单的页面构建,转而成为与后端系统紧密交互的复杂体系。作为这一交互的核心机制,HTTP 协议承载着几乎所有的前后端数据交换,其设计理念直接影响着应用的健壮性、扩展性与性能表现。

深入理解 HTTP 请求方法与状态码并非仅是理论知识,而是构建高质量前端应用的基础技能。恰当的请求方法选择关系到 API 的语义清晰度与一致性;准确的状态码处理则直接影响用户体验与错误恢复能力。

一、HTTP 请求方法全解析

1.1 基础请求方法

GET 方法
// 基本 GET 请求
fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

// 带查询参数的 GET 请求
const params = new URLSearchParams({
  page: 1,
  limit: 10
});
fetch(`https://api.example.com/users?${params}`)
  .then(response => response.json());

特性分析

  • 安全性:是安全的,不改变服务器状态
  • 幂等性:具有幂等性,多次请求结果一致
  • 缓存:可被浏览器缓存
  • 使用场景:数据检索、查询操作
POST 方法
// JSON 数据提交
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Zhang San',
    email: 'zhangsan@example.com'
  })
})
.then(response => response.json())
.then(data => console.log(data));

// 表单数据提交
const formData = new FormData();
formData.append('name', 'Zhang San');
formData.append('email', 'zhangsan@example.com');

fetch('https://api.example.com/users', {
  method: 'POST',
  body: formData
})
.then(response => response.json());

特性分析

  • 安全性:非安全,会改变服务器状态
  • 幂等性:非幂等,多次请求可能产生多个资源
  • 缓存:通常不被缓存
  • 使用场景:创建资源、提交表单数据

1.2 进阶请求方法

PUT 方法
// 完整替换资源
fetch('https://api.example.com/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Li Si',
    email: 'lisi@example.com',
    role: 'admin'
  })
})
.then(response => response.json());

特性分析

  • 安全性:非安全,会改变服务器状态
  • 幂等性:具有幂等性,多次请求结果一致
  • 使用场景:替换现有资源
PATCH 方法
// 局部更新资源
fetch('https://api.example.com/users/123', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    email: 'new.lisi@example.com'
  })
})
.then(response => response.json());

特性分析

  • 安全性:非安全,会改变服务器状态
  • 幂等性:理论上非幂等,但实际实现常为幂等
  • 使用场景:部分更新资源
DELETE 方法
// 删除资源
fetch('https://api.example.com/users/123', {
  method: 'DELETE'
})
.then(response => {
  if (response.ok) {
    console.log('User deleted successfully');
  }
});

特性分析

  • 安全性:非安全,会改变服务器状态
  • 幂等性:具有幂等性,多次请求结果一致
  • 使用场景:删除资源

1.3 特殊请求方法

HEAD 方法
// 获取资源元数据
fetch('https://api.example.com/large-file.zip', {
  method: 'HEAD'
})
.then(response => {
  console.log('Content size:', response.headers.get('Content-Length'));
  console.log('Last modified:', response.headers.get('Last-Modified'));
});

特性分析

  • 安全性:是安全的,不改变服务器状态
  • 幂等性:具有幂等性
  • 使用场景:获取资源头信息而不传输资源内容
OPTIONS 方法
// 检查服务器支持的方法和CORS
fetch('https://api.example.com/users', {
  method: 'OPTIONS'
})
.then(response => {
  console.log('Allowed methods:', response.headers.get('Allow'));
  console.log('CORS headers:', response.headers.get('Access-Control-Allow-Methods'));
});

特性分析

  • 安全性:是安全的,不改变服务器状态
  • 幂等性:具有幂等性
  • 使用场景:CORS 预检请求、探测服务器支持的方法

二、HTTP 状态码详解与应用

2.1 成功状态码(2xx)

200 OK
fetch('https://api.example.com/users')
  .then(response => {
    if (response.status === 200) {
      return response.json();
    }
  })
  .then(data => console.log('数据获取成功:', data));

应用实践:最常见的成功响应,表示请求已成功处理。

201 Created
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({ name: 'Wang Wu' })
})
.then(response => {
  if (response.status === 201) {
    console.log('资源创建成功');
    console.log('新资源位置:', response.headers.get('Location'));
  }
});

应用实践:资源创建成功,常见于 POST 请求后。

204 No Content
fetch('https://api.example.com/users/123', {
  method: 'DELETE'
})
.then(response => {
  if (response.status === 204) {
    console.log('资源删除成功,无内容返回');
    // 在UI中更新删除状态
    document.getElementById('user-123').remove();
  }
});

应用实践:操作成功但无内容返回,常用于 DELETE 操作。

2.2 重定向状态码(3xx)

301 Moved Permanently
fetch('https://example.com/old-page')
  .then(response => {
    if (response.status === 301) {
      const newLocation = response.headers.get('Location');
      console.log('资源已永久移动到:', newLocation);
      return fetch(newLocation);
    }
  });

应用实践:资源已永久移动到新位置,客户端应更新书签。

304 Not Modified
// 带条件的 GET 请求
fetch('https://api.example.com/users', {
  headers: {
    'If-None-Match': '"e0023aa4f"',
    'If-Modified-Since': 'Wed, 21 Oct 2023 07:28:00 GMT'
  }
})
.then(response => {
  if (response.status === 304) {
    console.log('内容未变更,使用缓存版本');
    return getCachedData('users');
  } else {
    return response.json();
  }
});

应用实践:资源未修改,可使用客户端缓存,提高性能。

2.3 客户端错误状态码(4xx)

400 Bad Request
function submitUserData(userData) {
  fetch('https://api.example.com/users', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(userData)
  })
  .then(response => {
    if (response.status === 400) {
      return response.json().then(errors => {
        console.error('请求数据格式错误:', errors);
        displayValidationErrors(errors);
      });
    }
    return response.json();
  });
}

function displayValidationErrors(errors) {
  // 在表单中显示验证错误
  Object.keys(errors).forEach(field => {
    const element = document.getElementById(`${field}-error`);
    if (element) {
      element.textContent = errors[field];
      element.classList.add('visible');
    }
  });
}

应用实践:请求格式错误,前端应展示详细错误信息指导用户修正。

401 Unauthorized
fetch('https://api.example.com/protected-resource', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('token')}`
  }
})
.then(response => {
  if (response.status === 401) {
    console.log('身份验证失败');
    // 重定向到登录页
    window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname);
    return;
  }
  return response.json();
});

应用实践:未授权访问,需要用户重新登录。

403 Forbidden
fetch('https://api.example.com/admin/settings', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('token')}`
  }
})
.then(response => {
  if (response.status === 403) {
    console.log('权限不足');
    displayErrorMessage('您没有访问此资源的权限');
    return;
  }
  return response.json();
});

function displayErrorMessage(message) {
  const errorBox = document.createElement('div');
  errorBox.className = 'error-message';
  errorBox.textContent = message;
  document.body.appendChild(errorBox);
  setTimeout(() => errorBox.remove(), 5000);
}

应用实践:已认证但权限不足,展示友好错误信息。

404 Not Found
fetch('https://api.example.com/users/999')
  .then(response => {
    if (response.status === 404) {
      console.log('请求的资源不存在');
      showNotFoundPage();
      return;
    }
    return response.json();
  });

function showNotFoundPage() {
  document.getElementById('content').innerHTML = `
    <div class="not-found">
      <h2>资源未找到</h2>
      <p>您请求的内容不存在或已被移除</p>
      <a href="/">返回首页</a>
    </div>
  `;
}

应用实践:资源不存在,显示自定义 404 页面提高用户体验。

429 Too Many Requests
// 实现请求限流控制
class ThrottledAPI {
  constructor(baseUrl, maxRequestsPerMinute) {
    this.baseUrl = baseUrl;
    this.maxRequestsPerMinute = maxRequestsPerMinute;
    this.requestCount = 0;
    this.resetTime = Date.now() + 60000;
    this.queue = [];
  }

  async request(endpoint, options = {}) {
    if (this.requestCount >= this.maxRequestsPerMinute) {
      console.log('请求被节流,加入队列');
      return new Promise((resolve) => {
        this.queue.push(() => this.executeRequest(endpoint, options).then(resolve));
      });
    }
    
    return this.executeRequest(endpoint, options);
  }

  async executeRequest(endpoint, options) {
    this.requestCount++;
    
    if (Date.now() > this.resetTime) {
      this.requestCount = 1;
      this.resetTime = Date.now() + 60000;
      this.processQueue();
    }
    
    try {
      const response = await fetch(`${this.baseUrl}${endpoint}`, options);
      
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || 60;
        console.log(`请求频率过高,${retryAfter}秒后重试`);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        return this.executeRequest(endpoint, options);
      }
      
      return response;
    } catch (error) {
      console.error('请求出错:', error);
      throw error;
    }
  }
  
  processQueue() {
    while (this.queue.length > 0 && this.requestCount < this.maxRequestsPerMinute) {
      const request = this.queue.shift();
      request();
    }
  }
}

// 使用示例
const api = new ThrottledAPI('https://api.example.com', 60);
api.request('/messages').then(response => response.json());

应用实践:请求频率超限,前端实现智能重试和节流逻辑。

2.4 服务器错误状态码(5xx)

500 Internal Server Error
fetch('https://api.example.com/process-data')
  .then(response => {
    if (response.status === 500) {
      console.error('服务器内部错误');
      showErrorPage('服务器遇到问题,请稍后再试');
      // 上报错误到监控系统
      reportError({
        endpoint: 'process-data',
        status: 500,
        time: new Date().toISOString()
      });
      return;
    }
    return response.json();
  });

function reportError(errorData) {
  fetch('https://log.example.com/client-errors', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(errorData)
  });
}

应用实践:服务器内部错误,前端展示友好提示并上报监控。

503 Service Unavailable
let retryCount = 0;
const maxRetries = 3;

function fetchWithRetry(url, options = {}) {
  return fetch(url, options)
    .then(response => {
      if (response.status === 503) {
        const retryAfter = parseInt(response.headers.get('Retry-After') || '5', 10);
        
        if (retryCount < maxRetries) {
          retryCount++;
          console.log(`服务暂不可用,${retryAfter}秒后第${retryCount}次重试`);
          
          return new Promise(resolve => {
            setTimeout(() => {
              resolve(fetchWithRetry(url, options));
            }, retryAfter * 1000);
          });
        } else {
          showErrorPage('服务暂时不可用,请稍后再试');
          throw new Error('Maximum retry attempts reached');
        }
      }
      
      retryCount = 0;
      return response;
    });
}

// 使用示例
fetchWithRetry('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

应用实践:服务暂不可用,实现指数退避重试机制提高可靠性。

三、HTTP 请求设计最佳实践

3.1 RESTful API 设计与 HTTP 方法映射

// RESTful API 客户端实现
class RestClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }

  async request(endpoint, method = 'GET', data = null) {
    const url = `${this.baseUrl}${endpoint}`;
    const options = {
      method,
      headers: {
        'Accept': 'application/json'
      }
    };

    if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
      options.headers['Content-Type'] = 'application/json';
      options.body = JSON.stringify(data);
    }

    const response = await fetch(url, options);
    
    // 处理不同状态码
    if (response.status >= 200 && response.status < 300) {
      if (response.status === 204) return null; // No content
      return response.json();
    } else {
      const error = await response.json().catch(() => ({}));
      throw new RequestError(response.status, error.message || response.statusText, error);
    }
  }

  // RESTful 资源操作方法
  getAll(resource) {
    return this.request(`/${resource}`);
  }

  getOne(resource, id) {
    return this.request(`/${resource}/${id}`);
  }

  create(resource, data) {
    return this.request(`/${resource}`, 'POST', data);
  }

  update(resource, id, data) {
    return this.request(`/${resource}/${id}`, 'PUT', data);
  }

  patch(resource, id, data) {
    return this.request(`/${resource}/${id}`, 'PATCH', data);
  }

  delete(resource, id) {
    return this.request(`/${resource}/${id}`, 'DELETE');
  }
}

// 自定义错误类
class RequestError extends Error {
  constructor(status, message, data = {}) {
    super(message);
    this.name = 'RequestError';
    this.status = status;
    this.data = data;
  }
}

// 使用示例
const api = new RestClient('https://api.example.com/v1');

// 获取用户列表
api.getAll('users')
  .then(users => console.log(users))
  .catch(error => {
    if (error.status === 401) {
      // 处理未授权错误
    } else {
      // 处理其他错误
    }
  });

// 创建新用户
api.create('users', { name: 'Zhang San', email: 'zhangsan@example.com' })
  .then(newUser => console.log('Created:', newUser))
  .catch(error => console.error('Error creating user:', error));

最佳实践

  • 资源操作与 HTTP 方法一致性映射
  • 统一错误处理机制
  • 清晰的资源路径设计

3.2 条件请求与缓存控制

// 实现基于缓存控制的高效请求
class CacheAwareClient {
  constructor() {
    this.etags = new Map();
    this.lastModified = new Map();
  }

  async fetch(url, options = {}) {
    const headers = options.headers || {};
    
    // 添加条件请求头
    if (this.etags.has(url)) {
      headers['If-None-Match'] = this.etags.get(url);
    } else if (this.lastModified.has(url)) {
      headers['If-Modified-Since'] = this.lastModified.get(url);
    }
    
    const response = await fetch(url, { ...options, headers });
    
    // 更新缓存控制信息
    const etag = response.headers.get('ETag');
    if (etag) {
      this.etags.set(url, etag);
    }
    
    const lastMod = response.headers.get('Last-Modified');
    if (lastMod) {
      this.lastModified.set(url, lastMod);
    }
    
    // 处理 304 Not Modified
    if (response.status === 304) {
      console.log('资源未修改,使用缓存数据');
      return this.getCachedData(url);
    }
    
    // 缓存数据
    if (response.ok) {
      const data = await response.clone().json();
      this.cacheData(url, data);
      return data;
    }
    
    return response;
  }
  
  cacheData(url, data) {
    localStorage.setItem(`cache_data_${url}`, JSON.stringify(data));
    localStorage.setItem(`cache_time_${url}`, Date.now().toString());
  }
  
  getCachedData(url) {
    try {
      return JSON.parse(localStorage.getItem(`cache_data_${url}`));
    } catch (e) {
      console.error('缓存数据解析错误', e);
      return null;
    }
  }
}

// 使用示例
const client = new CacheAwareClient();
client.fetch('https://api.example.com/data')
  .then(data => console.log(data));

最佳实践

  • 使用 ETag 和 If-None-Match 实现高效缓存控制
  • 配合 Last-Modified 和 If-Modified-Since 保证数据时效性
  • 客户端智能缓存管理减少不必要的网络传输

四、HTTP 请求安全与性能优化

4.1 跨域资源共享(CORS)与安全控制

// 前端 CORS 预检检测工具
function testCorsSupport(url, method = 'GET', headers = {}) {
  // 首先测试是否需要预检请求
  const needsPreflight = method !== 'GET' && 
                         method !== 'HEAD' && 
                         method !== 'POST' || 
                         Object.keys(headers).some(h => !['accept', 'accept-language', 'content-language', 'content-type'].includes(h.toLowerCase())) ||
                         (headers['content-type'] && !['application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain'].includes(headers['content-type'].toLowerCase()));

  console.log('该请求' + (needsPreflight ? '需要' : '不需要') + ' CORS 预检');

  if (needsPreflight) {
    // 发送预检请求
    return fetch(url, {
      method: 'OPTIONS',
      headers: {
        'Access-Control-Request-Method': method,
        'Access-Control-Request-Headers': Object.keys(headers).join(',')
      }
    })
    .then(response => {
      console.log('预检请求状态:', response.status);
      if (response.ok) {
        const allowedMethods = response.headers.get('Access-Control-Allow-Methods');
        const allowedHeaders = response.headers.get('Access-Control-Allow-Headers');
        const allowCredentials = response.headers.get('Access-Control-Allow-Credentials');
        
        console.log('允许的方法:', allowedMethods);
        console.log('允许的头信息:', allowedHeaders);
        console.log('允许凭证:', allowCredentials);
        
        // 检查请求方法是否被允许
        if (allowedMethods && !allowedMethods.split(',').map(m => m.trim()).includes(method)) {
          throw new Error(`方法 ${method} 不被允许`);
        }
        
        // 检查请求头是否被允许
        const headersArray = allowedHeaders ? allowedHeaders.split(',').map(h => h.trim().toLowerCase()) : [];
        const missingHeaders = Object.keys(headers).filter(h => !headersArray.includes(h.toLowerCase()));
        if (missingHeaders.length > 0) {
          throw new Error(`以下请求头不被允许: ${missingHeaders.join(', ')}`);
        }
        
        return true;
      } else {
        throw new Error(`预检请求失败: ${response.status}`);
      }
    });
  } else {
    console.log('不需要预检请求,直接发送主请求');
    return Promise.resolve(true);
  }
}

// 使用示例
testCorsSupport(
  'https://api.example.com/data', 
  'PUT', 
  { 'Content-Type': 'application/json', 'X-Custom-Header': 'value' }
)
.then(supported => {
  if (supported) {
    console.log('CORS 支持确认,可以安全发送请求');
  }
})
.catch(error => console.error('CORS 错误:', error.message));

4.2 性能优化与批量请求

// HTTP/2 多路复用请求优化
class OptimizedHttpClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.pendingRequests = new Map();
    this.requestQueue = [];
    this.batchTimerId = null;
    this.batchDelay = 50; // 50ms 批处理延迟
  }
  
  async request(endpoint, options = {}) {
    const key = this.getRequestKey(endpoint, options);
    
    // 如果完全相同的请求正在处理中,复用 Promise
    if (this.pendingRequests.has(key)) {
      return this.pendingRequests.get(key);
    }
    
    // 创建新请求 Promise
    const requestPromise = new Promise((resolve, reject) => {
      this.requestQueue.push({ endpoint, options, resolve, reject });
      
      // 设置批处理定时器
      if (!this.batchTimerId) {
        this.batchTimerId = setTimeout(() => this.processBatch(), this.batchDelay);
      }
    });
    
    // 存储进行中的请求
    this.pendingRequests.set(key, requestPromise);
    
    // 请求完成后清理映射
    requestPromise.finally(() => {
      this.pendingRequests.delete(key);
    });
    
    return requestPromise;
  }
  
  async processBatch() {
    this.batchTimerId = null;
    
    if (this.requestQueue.length === 0) return;
    
    // 提取当前批次的请求
    const batch = this.requestQueue.splice(0, this.requestQueue.length);
    
    // 按资源类型分组请求
    const groupedRequests = this.groupRequestsByResource(batch);
    
    // 处理每个资源组
    for (const [resource, requests] of groupedRequests.entries()) {
      // 如果资源组中只有一个请求,直接发送
      if (requests.length === 1) {
        const req = requests[0];
        this.processSingleRequest(req.endpoint, req.options, req.resolve, req.reject);
        continue;
      }
      
      // 尝试合并同类请求(例如,多个 GET 请求)
      if (this.canBatchRequests(requests)) {
        await this.sendBatchRequest(resource, requests);
      } else {
        // 无法批处理的请求单独发送
        for (const req of requests) {
          this.processSingleRequest(req.endpoint, req.options, req.resolve, req.reject);
        }
      }
    }
  }
  
  getRequestKey(endpoint, options) {
    return `${options.method || 'GET'}-${endpoint}-${JSON.stringify(options.body || {})}`;
  }
  
  groupRequestsByResource(requests) {
    const groups = new Map();
    
    for (const req of requests) {
      // 提取资源类型(例如,/users, /products 等)
      const resource = req.endpoint.split('/')[1];
      if (!groups.has(resource)) {
        groups.set(resource, []);
      }
      groups.get(resource).push(req);
    }
    
    return groups;
  }
  
  canBatchRequests(requests) {
    // 检查请求是否可以批处理(例如,都是 GET 或都是同类型请求)
    const method = requests[0].options.method || 'GET';
    return requests.every(req => (req.options.method || 'GET') === method);
  }
  
  async sendBatchRequest(resource, requests) {
    try {
      // 构建批处理请求
      const ids = requests
        .map(req => req.endpoint.split('/').pop())
        .filter(id => !isNaN(id));
      
      // 如果是批量获取多个资源
      if (ids.length === requests.length) {
        const batchUrl = `${this.baseUrl}/${resource}?ids=${ids.join(',')}`;
        const response = await fetch(batchUrl);
        
        if (!response.ok) throw new Error(`Batch request failed: ${response.status}`);
        
        const data = await response.json();
        
        // 将批量响应分发回各个原始请求
        for (let i = 0; i < requests.length; i++) {
          const req = requests[i];
          const id = ids[i];
          const itemData = Array.isArray(data) ? 
            data.find(item => item.id == id) : 
            data[id];
          
          if (itemData) {
            req.resolve(itemData);
          } else {
            req.reject(new Error(`Resource ${id} not found in batch response`));
          }
        }
      } else {
        // 其他批处理场景
        for (const req of requests) {
          this.processSingleRequest(req.endpoint, req.options, req.resolve, req.reject);
        }
      }
    } catch (error) {
      // 批处理失败,将错误传递给所有请求
      for (const req of requests) {
        req.reject(error);
      }
    }
  }
  
  async processSingleRequest(endpoint, options, resolve, reject) {
    try {
      const response = await fetch(`${this.baseUrl}${endpoint}`, options);
      
      if (!response.ok) {
        throw new Error(`Request failed with status ${response.status}`);
      }
      
      const data = await response.json();
      resolve(data);
    } catch (error) {
      reject(error);
    }
  }
}

// 使用示例
const client = new OptimizedHttpClient('https://api.example.com');

// 这些请求会被智能批处理
client.request('/users/1').then(data => console.log(data));
client.request('/users/2').then(data => console.log(data));
client.request('/users/3').then(data => console.log(data));

五、HTTP 状态码在前端路由中的应用

5.1 基于 HTTP 状态码的路由决策

// 状态码感知的前端路由器
class StatusAwareRouter {
  constructor() {
    this.routes = [];
    this.globalErrorHandlers = {
      401: null,
      403: null,
      404: null,
      500: null
    };
  }

  // 添加路径与组件的映射
  addRoute(path, component) {
    this.routes.push({ path, component });
    return this;
  }

  // 设置特定状态码的全局处理组件
  setErrorHandler(statusCode, component) {
    if (this.globalErrorHandlers.hasOwnProperty(statusCode)) {
      this.globalErrorHandlers[statusCode] = component;
    }
    return this;
  }

  // 基于 API 响应状态渲染合适的组件
  async resolveRoute(path, apiEndpoint) {
    // 查找匹配的路由
    const route = this.routes.find(r => r.path === path);
    
    if (!route) {
      return this.globalErrorHandlers[404] || (() => {
        return `<div class="error-page">
          <h1>404 - Page Not Found</h1>
          <p>The requested page "${path}" does not exist.</p>
        </div>`;
      });
    }
    
    // 如果提供了 API 端点,检查权限和数据状态
    if (apiEndpoint) {
      try {
        const response = await fetch(apiEndpoint, {
          credentials: 'include' // 包含认证信息
        });
        
        // 处理常见的错误状态码
        switch (response.status) {
          case 401: // 未认证
            return this.globalErrorHandlers[401] || (() => {
              // 保存当前路径以便登录后重定向回来
              localStorage.setItem('redirectAfterLogin', path);
              // 重定向到登录页
              window.location.href = '/login';
              return null;
            });
            
          case 403: // 权限不足
            return this.globalErrorHandlers[403] || (() => {
              return `<div class="error-page">
                <h1>403 - Forbidden</h1>
                <p>You don't have permission to access this page.</p>
              </div>`;
            });
            
          case 404: // API 资源不存在
            return this.globalErrorHandlers[404] || (() => {
              return `<div class="error-page">
                <h1>404 - Resource Not Found</h1>
                <p>The requested resource does not exist.</p>
              </div>`;
            });
            
          case 500: // 服务器错误
          case 502:
          case 503:
          case 504:
            return this.globalErrorHandlers[500] || (() => {
              return `<div class="error-page">
                <h1>Server Error</h1>
                <p>Something went wrong. Please try again later.</p>
                <button onclick="window.location.reload()">Retry</button>
              </div>`;
            });
        }
        
        // 正常状态,返回路由组件并传入数据
        if (response.ok) {
          const data = await response.json();
          // 闭包捕获 data,使路由组件可以使用 API 数据
          return () => route.component(data);
        }
      } catch (error) {
        console.error('Error fetching API data:', error);
        // 网络错误或其他异常
        return () => `<div class="error-page">
          <h1>Connection Error</h1>
          <p>Failed to connect to the server. Please check your internet connection.</p>
          <button onclick="window.location.reload()">Retry</button>
        </div>`;
      }
    }
    
    // 无需 API 检查或数据,直接返回路由组件
    return route.component;
  }

  // 监听浏览器历史变化并渲染视图
  listen(rootElement) {
    const renderCurrentRoute = async () => {
      const path = window.location.pathname;
      const apiPath = window.location.pathname.startsWith('/dashboard') 
                     ? `/api${path}` 
                     : null;
      
      const component = await this.resolveRoute(path, apiPath);
      if (component) {
        rootElement.innerHTML = typeof component === 'function' 
                              ? component() 
                              : component;
      }
    };
    
    // 初始渲染
    renderCurrentRoute();
    
    // 监听历史变化
    window.addEventListener('popstate', renderCurrentRoute);
    
    // 拦截链接点击
    document.addEventListener('click', (e) => {
      if (e.target.tagName === 'A' && 
          e.target.href.startsWith(window.location.origin) && 
          !e.target.hasAttribute('external')) {
        e.preventDefault();
        const url = new URL(e.target.href);
        window.history.pushState({}, '', url.pathname);
        renderCurrentRoute();
      }
    });
    
    return {
      navigate: (path) => {
        window.history.pushState({}, '', path);
        renderCurrentRoute();
      }
    };
  }
}

// 使用示例
document.addEventListener('DOMContentLoaded', () => {
  const router = new StatusAwareRouter();
  
  // 定义路由
  router
    .addRoute('/', () => '<h1>Home</h1>')
    .addRoute('/dashboard', (data) => `
      <h1>Dashboard</h1>
      <p>Welcome, ${data.user.name}!</p>
      <ul>
        ${data.items.map(item => `<li>${item.title}</li>`).join('')}
      </ul>
    `)
    .addRoute('/settings', () => '<h1>Settings</h1>')
    
    // 设置错误处理
    .setErrorHandler(401, () => {
      localStorage.setItem('redirectAfterLogin', window.location.pathname);
      return '<h1>Please Login</h1><p>You need to login to view this page.</p>';
    })
    .setErrorHandler(403, () => '<h1>Access Denied</h1><p>You don\'t have permission to view this content.</p>')
    .setErrorHandler(404, () => '<h1>Page Not Found</h1><p>The requested page does not exist.</p>');
  
  // 启动路由
  const app = router.listen(document.getElementById('app'));
  
  // 导航示例
  document.getElementById('nav-home').addEventListener('click', () => {
    app.navigate('/');
  });
});

六、HTTP 深度分析与前沿技术

6.1 HTTP/2 与 HTTP/3 技术实践

// HTTP 版本特性检测与优化
class HttpVersionOptimizer {
  constructor() {
    this.supportInfo = {
      http2: undefined,
      http3: undefined
    };
    this.testResults = [];
  }

  // 检测 HTTP/2 支持
  async detectHttp2Support() {
    try {
      const startTime = performance.now();
      const response = await fetch('https://example.com', {
        cache: 'no-store'
      });
      const endTime = performance.now();
      
      const httpVersion = response.headers.get('x-http-version') || 
                         response.headers.get('x-used-protocol') ||
                         'unknown';
                         
      const isHttp2 = httpVersion.includes('2') || httpVersion.includes('h2');
      
      this.testResults.push({
        type: 'http2',
        success: isHttp2,
        responseTime: endTime - startTime
      });
      
      this.supportInfo.http2 = isHttp2;
      return isHttp2;
    } catch (error) {
      console.error('Error detecting HTTP/2 support:', error);
      this.supportInfo.http2 = false;
      return false;
    }
  }

  // 检测 HTTP/3 支持
  async detectHttp3Support() {
    try {
      const startTime = performance.now();
      const response = await fetch('https://cloudflare-http3.com', {
        cache: 'no-store'
      });
      const endTime = performance.now();
      
      const httpVersion = response.headers.get('alt-svc') || 'unknown';
      const isHttp3 = httpVersion.includes('h3') || httpVersion.includes('quic');
      
      this.testResults.push({
        type: 'http3',
        success: isHttp3,
        responseTime: endTime - startTime
      });
      
      this.supportInfo.http3 = isHttp3;
      return isHttp3;
    } catch (error) {
      console.error('Error detecting HTTP/3 support:', error);
      this.supportInfo.http3 = false;
      return false;
    }
  }

  // 基于 HTTP 版本优化连接管理
  optimizeConnections() {
    const result = {
      maxConnections: 6, // HTTP/1.1 默认值
      resourceHints: []
    };
    
    if (this.supportInfo.http2) {
      // HTTP/2 允许多路复用,减少连接数
      result.maxConnections = 1; // 为单域名优化为单连接
      result.resourceHints.push({
        type: 'preconnect',
        hint: 'Reduce connection count, HTTP/2 uses multiplexing'
      });
    } else {
      // 为 HTTP/1.1 分片资源到多个域名
      result.resourceHints.push({
        type: 'sharding',
        hint: 'Distribute resources across multiple domains for parallel downloads'
      });
    }
    
    if (this.supportInfo.http3) {
      // 利用 HTTP/3 的 0-RTT 恢复
      result.resourceHints.push({
        type: '0-rtt',
        hint: 'Enable 0-RTT session resumption for repeat visitors'
      });
    }
    
    return result;
  }

  // 为特定资源应用优化
  optimizeResourceLoading(resources) {
    // 获取优化配置
    const config = this.optimizeConnections();
    const optimizedResources = [];
    
    for (const resource of resources) {
      const optimized = { ...resource };
      
      // 应用资源类型特定优化
      switch (resource.type) {
        case 'image':
          if (this.supportInfo.http2) {
            // 对于 HTTP/2,使用服务器推送
            optimized.hints = ['use-server-push'];
          } else {
            // 对于 HTTP/1.1,应用域名分片
            optimized.url = this.applySharding(resource.url);
          }
          break;
          
        case 'script':
          optimized.attributes = ['defer']; // 默认延迟加载脚本
          if (resource.critical) {
            optimized.hints = ['preload']; // 关键脚本预加载
          }
          break;
          
        case 'style':
          if (resource.critical) {
            optimized.hints = ['preload', 'critical']; // 关键样式内联
          }
          break;
      }
      
      optimizedResources.push(optimized);
    }
    
    return optimizedResources;
  }
  
  applySharding(url) {
    // 简单的域名分片实现
    const urlObj = new URL(url);
    const domain = urlObj.hostname;
    const shard = Math.floor(Math.random() * 4) + 1; // 1-4 之间的随机分片
    
    if (!domain.startsWith('shard')) {
      urlObj.hostname = `shard${shard}.${domain}`;
    }
    
    return urlObj.toString();
  }
  
  // 生成优化报告
  generateReport() {
    return {
      supportInfo: this.supportInfo,
      testResults: this.testResults,
      recommendations: [
        {
          id: 'connection-strategy',
          title: 'Connection Management Strategy',
          description: this.supportInfo.http2 
            ? 'Use a single connection for HTTP/2 enabled domains to leverage multiplexing'
            : 'Use domain sharding for HTTP/1.1 connections to increase parallel downloads',
          importance: 'high'
        },
        {
          id: 'resource-prioritization',
          title: 'Resource Prioritization',
          description: this.supportInfo.http2 
            ? 'Utilize HTTP/2 stream priorities to load critical resources first'
            : 'Properly sequence your resource loading to prioritize critical path rendering',
          importance: 'high'
        },
        {
          id: 'protocol-upgrade',
          title: 'Protocol Upgrade Options',
          description: !this.supportInfo.http2 
            ? 'Consider upgrading your server to support HTTP/2 for better performance'
            : !this.supportInfo.http3 
              ? 'Consider enabling HTTP/3 for improved performance on lossy networks'
              : 'Your server is using the latest HTTP protocol version',
          importance: !this.supportInfo.http2 ? 'high' : !this.supportInfo.http3 ? 'medium' : 'low'
        }
      ]
    };
  }
}

// 使用示例
async function optimizeWebsite() {
  const optimizer = new HttpVersionOptimizer();
  
  // 检测 HTTP 版本支持
  await Promise.all([
    optimizer.detectHttp2Support(),
    optimizer.detectHttp3Support()
  ]);
  
  console.log('HTTP Protocol Support:', optimizer.supportInfo);
  
  // 优化示例资源集
  const resources = [
    { type: 'image', url: 'https://example.com/hero.jpg', critical: true },
    { type: 'script', url: 'https://example.com/app.js', critical: true },
    { type: 'script', url: 'https://example.com/analytics.js', critical: false },
    { type: 'style', url: 'https://example.com/styles.css', critical: true }
  ];
  
  const optimizedResources = optimizer.optimizeResourceLoading(resources);
  console.log('Optimized Resources:', optimizedResources);
  
  // 生成优化报告
  const report = optimizer.generateReport();
  console.log('Optimization Report:', report);
  
  return report;
}

// 运行优化
optimizeWebsite().then(report => {
  // 在 UI 中展示优化建议
  displayOptimizationSuggestions(report);
});

前端调试 HTTP 的技巧

现代前端开发中,掌握 HTTP 请求的调试技巧至关重要。以下是一些实用技术和工具:

1. 浏览器开发者工具

// 使用控制台 API 监控网络请求
// 在开发环境中插入此脚本

// 监听所有 Fetch 请求
const originalFetch = window.fetch;
window.fetch = async function(...args) {
  const url = args[0];
  const options = args[1] || {};
  
  console.group(`🌐 Fetch Request: ${options.method || 'GET'} ${url}`);
  console.log('Request Options:', options);
  console.time('Response Time');
  
  try {
    const response = await originalFetch.apply(this, args);
    console.timeEnd('Response Time');
    
    // 克隆响应以便检查内容(因为 body 只能读取一次)
    const clonedResponse = response.clone();
    
    // 尝试解析不同类型的响应
    let responseData;
    const contentType = response.headers.get('content-type') || '';
    
    if (contentType.includes('application/json')) {
      responseData = await clonedResponse.json().catch(e => 'Cannot parse JSON');
    } else if (contentType.includes('text/')) {
      responseData = await clonedResponse.text().catch(e => 'Cannot parse text');
    } else {
      responseData = `Binary data: ${contentType}`;
    }
    
    console.log('Response Status:', response.status, response.statusText);
    console.log('Response Headers:', Object.fromEntries([...response.headers.entries()]));
    console.log('Response Data:', responseData);
    console.groupEnd();
    
    return response;
  } catch (error) {
    console.timeEnd('Response Time');
    console.error('Request Failed:', error);
    console.groupEnd();
    throw error;
  }
};

// 监听 XMLHttpRequest
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.open = function(method, url) {
  this._url = url;
  this._method = method;
  this._requestTime = Date.now();
  return originalXHROpen.apply(this, arguments);
};

XMLHttpRequest.prototype.send = function(body) {
  console.group(`🌐 XHR Request: ${this._method} ${this._url}`);
  console.log('Request Payload:', body);
  
  this.addEventListener('load', function() {
    console.log('Response Status:', this.status);
    console.log('Response Time:', Date.now() - this._requestTime, 'ms');
    
    try {
      const contentType = this.getResponseHeader('Content-Type') || '';
      if (contentType.includes('application/json')) {
        console.log('Response:', JSON.parse(this.responseText));
      } else {
        console.log('Response:', this.responseText);
      }
    } catch (e) {
      console.log('Response: Unable to parse');
    }
    
    console.groupEnd();
  });
  
  this.addEventListener('error', function() {
    console.error('Request failed');
    console.log('Response Time:', Date.now() - this._requestTime, 'ms');
    console.groupEnd();
  });
  
  return originalXHRSend.apply(this, arguments);
};

结语:HTTP 协议的未来

HTTP 协议作为 Web 应用的核心通信机制,其深入理解对前端开发者至关重要。通过本文的系统解析,我们详细探讨了 HTTP 请求方法的语义特性、状态码的应用场景及实践策略。

掌握 GET、POST、PUT、PATCH、DELETE 等方法的幂等性与安全性特征,能够帮助我们设计出符合 RESTful 原则的 API 交互模式。合理处理各类状态码则是构建健壮前端应用的关键,尤其是在错误处理、认证流程和缓存优化方面。

随着 HTTP/2 和 HTTP/3 的广泛应用,多路复用、服务器推送等先进特性正在改变前端性能优化的策略方向。前端开发者应关注协议升级带来的机遇,同时采用基于状态码的智能重试、优雅降级等模式提升应用可靠性。

参考资源

  1. MDN Web Docs: HTTP 请求方法
  2. RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
  3. Web.dev: HTTP/2 简介
  4. MDN Web Docs: HTTP 响应状态码
  5. IETF: HTTP/3 规范
  6. Google Developers: 网络可靠性指南
  7. OWASP: REST 安全备忘单

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻


网站公告

今日签到

点亮在社区的每一天
去签到