pin_drop当前位置:知识文库 ❯ 图文
后端通信:HTTP状态码 - 从入门到实践详解
教程简介
HTTP状态码是服务器对客户端请求的响应状态标识,由三位数字组成,是前后端通信中判断请求成功与否、定位问题的关键依据。理解HTTP状态码的语义,能够帮助开发者快速判断请求处理结果、正确处理错误场景、设计规范的API响应。
本教程将全面讲解HTTP状态码的分类体系、各类状态码的含义与使用场景、自定义状态码处理、错误页面设计,以及前后端如何基于状态码实现健壮的错误处理机制。
核心概念
1. 状态码分类
HTTP状态码分为5大类,由第一位数字决定:
2. 1xx 信息性状态码
1xx状态码表示请求已被接收,继续处理。
100 Continue 的工作流程:
客户端发送带有Expect: 100-continue头部的请求时,服务器先返回100,客户端再发送请求体:
代码示例
客户端 → 服务器:
POST /upload HTTP/1.1
Expect: 100-continue
Content-Length: 10485760
服务器 → 客户端:
HTTP/1.1 100 Continue
客户端 → 服务器:
(发送请求体数据)
服务器 → 客户端:
HTTP/1.1 201 Created101 Switching Protocols 在WebSocket升级时使用:
代码示例
客户端 → 服务器:
GET /ws HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
服务器 → 客户端:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=3. 2xx 成功状态码
2xx状态码表示请求已成功被服务器接收、理解并处理。
常用2xx状态码详解:
200 OK:最通用的成功状态码,适用于大多数成功响应
201 Created:资源创建成功,响应应包含Location头部指向新资源
204 No Content:操作成功但不需要返回内容,常用于DELETE和PUT
206 Partial Content:配合Range头部实现断点续传
4. 3xx 重定向状态码
3xx状态码表示需要客户端采取进一步操作才能完成请求。
重要区别:
301 vs 302:301是永久重定向,浏览器会缓存;302是临时的,每次都请求原URL
302 vs 307:302可能将POST变为GET(历史原因),307保证保持原方法
301 vs 308:308是301的改进版,保证保持原HTTP方法
304 Not Modified:虽然属于3xx,但不是重定向,而是缓存相关:
代码示例
客户端 → 服务器:
GET /api/data HTTP/1.1
If-None-Match: "abc123"
服务器 → 客户端:
HTTP/1.1 304 Not Modified
ETag: "abc123"
(无响应体)5. 4xx 客户端错误状态码
4xx状态码表示客户端发送的请求有错误。
401 vs 403 的区别:
401 Unauthorized:客户端未提供有效的身份认证凭据。响应应包含
WWW-Authenticate头部403 Forbidden:客户端已认证,但没有权限访问该资源。即使重新认证也无法获得权限
400 vs 422 的区别:
400 Bad Request:请求格式本身有问题(语法错误、缺少必要参数)
422 Unprocessable Entity:请求格式正确,但数据语义有问题(验证失败)
6. 5xx 服务器错误状态码
5xx状态码表示服务器在处理请求时发生了错误。
502 vs 504 的区别:
502 Bad Gateway:网关/代理收到了上游服务器的无效响应
504 Gateway Timeout:网关/代理等待上游服务器响应超时
语法与用法
前端基于状态码的处理模式
代码示例
async function apiRequest(url, options = {}) {
const response = await fetch(url, options);
// 根据状态码分类处理
if (response.status >= 200 && response.status < 300) {
// 2xx 成功
if (response.status === 204) return null;
return await response.json();
}
if (response.status === 401) {
// 未认证,跳转登录
window.location.href = '/login';
throw new Error('请先登录');
}
if (response.status === 403) {
throw new Error('权限不足');
}
if (response.status === 404) {
throw new Error('请求的资源不存在');
}
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new Error(`请求过于频繁,请${retryAfter}秒后重试`);
}
if (response.status >= 500) {
throw new Error('服务器错误,请稍后重试');
}
throw new Error(`请求失败: ${response.status}`);
}代码示例
示例1:HTTP状态码速查工具
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTTP状态码速查工具</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #f0f2f5;
padding: 20px;
color: #333;
}
.container { max-width: 1000px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.search-box {
width: 100%;
padding: 14px 20px;
border: 2px solid #dadce0;
border-radius: 12px;
font-size: 16px;
margin-bottom: 20px;
}
.search-box:focus {
outline: none;
border-color: #1a73e8;
box-shadow: 0 0 0 3px rgba(26,115,232,0.15);
}
.category-section { margin-bottom: 24px; }
.category-header {
padding: 12px 20px;
border-radius: 10px;
font-weight: 700;
font-size: 16px;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 10px;
}
.cat-1xx { background: #e3f2fd; color: #1565c0; }
.cat-2xx { background: #e8f5e9; color: #2e7d32; }
.cat-3xx { background: #fff3e0; color: #ef6c00; }
.cat-4xx { background: #fce4ec; color: #c62828; }
.cat-5xx { background: #ffebee; color: #b71c1c; }
.code-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 10px;
}
.code-card {
background: #fff;
border-radius: 10px;
padding: 16px;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
border-left: 4px solid transparent;
}
.code-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.code-card.type-1xx { border-left-color: #1565c0; }
.code-card.type-2xx { border-left-color: #2e7d32; }
.code-card.type-3xx { border-left-color: #ef6c00; }
.code-card.type-4xx { border-left-color: #c62828; }
.code-card.type-5xx { border-left-color: #b71c1c; }
.code-number {
font-size: 22px;
font-weight: 800;
margin-bottom: 4px;
}
.code-name {
font-size: 13px;
color: #666;
}
.detail-modal {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.detail-modal.show { display: flex; }
.detail-content {
background: #fff;
border-radius: 16px;
padding: 32px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.detail-code {
font-size: 48px;
font-weight: 800;
margin-bottom: 8px;
}
.detail-name {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
color: #555;
}
.detail-desc {
font-size: 15px;
line-height: 1.8;
color: #555;
margin-bottom: 16px;
}
.detail-section {
margin-bottom: 16px;
}
.detail-section h4 {
font-size: 14px;
color: #888;
margin-bottom: 8px;
}
.detail-code-block {
background: #1e1e1e;
color: #d4d4d4;
padding: 14px;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 13px;
line-height: 1.7;
white-space: pre-wrap;
}
.close-btn {
position: absolute;
top: 16px;
right: 16px;
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: #f0f0f0;
font-size: 18px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>HTTP状态码速查工具</h1>
<input type="text" class="search-box" id="searchBox" placeholder="搜索状态码或名称,如:404、Not Found、未找到...">
<div id="statusCodes"></div>
</div>
<div class="detail-modal" id="detailModal">
<div class="detail-content" style="position:relative;">
<button class="close-btn" onclick="closeDetail()">×</button>
<div id="detailBody"></div>
</div>
</div>
<script>
const statusCodes = {
'1xx': {
name: '信息性状态码',
codes: [
{ code: 100, name: 'Continue', desc: '服务器已收到请求的起始部分,客户端应继续发送剩余部分。通常用于大文件上传场景,客户端先发送Expect: 100-continue头部,服务器确认后再发送请求体。', example: '客户端:POST /upload\\nExpect: 100-continue\\n\\n服务器:HTTP/1.1 100 Continue\\n\\n客户端:(发送请求体)' },
{ code: 101, name: 'Switching Protocols', desc: '服务器同意切换协议。WebSocket连接建立时,服务器返回101状态码表示协议升级成功。', example: '客户端:GET /ws HTTP/1.1\\nUpgrade: websocket\\nConnection: Upgrade\\n\\n服务器:HTTP/1.1 101 Switching Protocols\\nUpgrade: websocket' },
{ code: 103, name: 'Early Hints', desc: '在最终响应之前,服务器可以先发送一些资源提示,让浏览器预加载关键资源。', example: '服务器:HTTP/1.1 103 Early Hints\\nLink: </style.css>; rel=preload; as=style\\n\\n(稍后发送最终响应)' }
]
},
'2xx': {
name: '成功状态码',
codes: [
{ code: 200, name: 'OK', desc: '请求成功。GET请求返回资源,POST请求返回创建结果,PUT请求返回更新结果。是最常用的成功状态码。', example: 'HTTP/1.1 200 OK\\nContent-Type: application/json\\n\\n{"id":1,"name":"张三"}' },
{ code: 201, name: 'Created', desc: '资源创建成功。POST请求创建新资源后返回,响应应包含Location头部指向新资源的URI。', example: 'HTTP/1.1 201 Created\\nLocation: /api/users/5\\n\\n{"id":5,"name":"新用户"}' },
{ code: 202, name: 'Accepted', desc: '请求已接受但尚未处理完成。用于异步操作场景,客户端需要通过其他方式查询处理结果。', example: 'HTTP/1.1 202 Accepted\\n\\n{"taskId":"abc123","status":"processing"}' },
{ code: 204, name: 'No Content', desc: '请求成功但无返回内容。常用于DELETE请求和不需要返回数据的PUT/PATCH请求。', example: 'HTTP/1.1 204 No Content\\n(无响应体)' },
{ code: 206, name: 'Partial Content', desc: '返回部分内容。配合Range请求头使用,实现断点续传和分块下载。', example: 'HTTP/1.1 206 Partial Content\\nContent-Range: bytes 0-1023/10240\\n\\n(部分数据)' }
]
},
'3xx': {
name: '重定向状态码',
codes: [
{ code: 301, name: 'Moved Permanently', desc: '资源已永久移动到新URL。浏览器会缓存此重定向,SEO权重会转移到新URL。注意:301重定向可能将POST请求变为GET。', example: 'HTTP/1.1 301 Moved Permanently\\nLocation: https://new-domain.com/page' },
{ code: 302, name: 'Found', desc: '资源临时移动到新URL。浏览器不会缓存此重定向。历史原因,302可能将POST变为GET。', example: 'HTTP/1.1 302 Found\\nLocation: /maintenance.html' },
{ code: 304, name: 'Not Modified', desc: '资源未修改,使用缓存版本。不是重定向,而是缓存协商的结果。服务器不返回响应体。', example: 'HTTP/1.1 304 Not Modified\\nETag: "abc123"\\n(无响应体)' },
{ code: 307, name: 'Temporary Redirect', desc: '临时重定向,保证保持原HTTP方法。POST请求重定向后仍然是POST。', example: 'HTTP/1.1 307 Temporary Redirect\\nLocation: /new-endpoint' },
{ code: 308, name: 'Permanent Redirect', desc: '永久重定向,保证保持原HTTP方法。是301的改进版,POST重定向后仍然是POST。', example: 'HTTP/1.1 308 Permanent Redirect\\nLocation: https://new-api.example.com/users' }
]
},
'4xx': {
name: '客户端错误状态码',
codes: [
{ code: 400, name: 'Bad Request', desc: '请求语法错误或参数无效。客户端应修改请求后重试。常用于参数验证失败的场景。', example: 'HTTP/1.1 400 Bad Request\\n\\n{"error":"参数验证失败","details":{"email":"邮箱格式不正确"}}' },
{ code: 401, name: 'Unauthorized', desc: '未提供有效的身份认证凭据。响应应包含WWW-Authenticate头部。与403不同,401表示"未认证"。', example: 'HTTP/1.1 401 Unauthorized\\nWWW-Authenticate: Bearer realm="api"\\n\\n{"error":"Token已过期"}' },
{ code: 403, name: 'Forbidden', desc: '已认证但没有权限访问该资源。即使重新认证也无法获得权限。与401不同,403表示"已认证但无权限"。', example: 'HTTP/1.1 403 Forbidden\\n\\n{"error":"您没有权限访问此资源"}' },
{ code: 404, name: 'Not Found', desc: '请求的资源不存在。可能是URL错误或资源已被删除。是最常见的客户端错误码。', example: 'HTTP/1.1 404 Not Found\\n\\n{"error":"用户ID 999不存在"}' },
{ code: 405, name: 'Method Not Allowed', desc: '请求的HTTP方法不被允许。响应应包含Allow头部,列出该资源支持的方法。', example: 'HTTP/1.1 405 Method Not Allowed\\nAllow: GET, POST\\n\\n{"error":"不支持DELETE方法"}' },
{ code: 409, name: 'Conflict', desc: '请求与服务器当前状态冲突。常用于重复创建、版本冲突等场景。', example: 'HTTP/1.1 409 Conflict\\n\\n{"error":"用户名已存在","conflictField":"username"}' },
{ code: 422, name: 'Unprocessable Entity', desc: '请求格式正确但语义错误,无法处理。常用于数据验证失败。与400不同,422表示语法正确但数据不合法。', example: 'HTTP/1.1 422 Unprocessable Entity\\n\\n{"error":"数据验证失败","errors":{"age":"年龄必须大于0"}}' },
{ code: 429, name: 'Too Many Requests', desc: '请求频率超过限制。响应应包含Retry-After头部,告知客户端何时可以重试。', example: 'HTTP/1.1 429 Too Many Requests\\nRetry-After: 60\\n\\n{"error":"请求过于频繁,请60秒后重试"}' }
]
},
'5xx': {
name: '服务器错误状态码',
codes: [
{ code: 500, name: 'Internal Server Error', desc: '服务器内部错误。是最通用的服务器错误状态码,表示服务器遇到了未预期的错误。', example: 'HTTP/1.1 500 Internal Server Error\\n\\n{"error":"服务器内部错误,请稍后重试"}' },
{ code: 502, name: 'Bad Gateway', desc: '网关或代理服务器从上游服务器收到了无效响应。通常表示后端服务崩溃或返回了错误。', example: 'HTTP/1.1 502 Bad Gateway\\n\\n{"error":"上游服务不可用"}' },
{ code: 503, name: 'Service Unavailable', desc: '服务器暂时无法处理请求。通常是因为服务器过载或正在维护。响应应包含Retry-After头部。', example: 'HTTP/1.1 503 Service Unavailable\\nRetry-After: 300\\n\\n{"error":"服务维护中,预计5分钟后恢复"}' },
{ code: 504, name: 'Gateway Timeout', desc: '网关或代理服务器等待上游服务器响应超时。通常表示后端服务响应过慢。', example: 'HTTP/1.1 504 Gateway Timeout\\n\\n{"error":"上游服务响应超时"}' }
]
}
};
function renderCodes(filter = '') {
const container = document.getElementById('statusCodes');
container.innerHTML = '';
const lowerFilter = filter.toLowerCase();
for (const [category, data] of Object.entries(statusCodes)) {
const filteredCodes = data.codes.filter(c =>
!filter ||
c.code.toString().includes(filter) ||
c.name.toLowerCase().includes(lowerFilter) ||
c.desc.includes(filter)
);
if (filteredCodes.length === 0) continue;
container.innerHTML += `
<div class="category-section">
<div class="category-header cat-${category}">
<span>${category}</span>
<span>${data.name}</span>
</div>
<div class="code-grid">
${filteredCodes.map(c => `
<div class="code-card type-${category}" onclick="showDetail(${c.code})">
<div class="code-number">${c.code}</div>
<div class="code-name">${c.name}</div>
</div>
`).join('')}
</div>
</div>
`;
}
}
function showDetail(code) {
for (const [category, data] of Object.entries(statusCodes)) {
const found = data.codes.find(c => c.code === code);
if (found) {
const colorClass = `type-${category}`;
document.getElementById('detailBody').innerHTML = `
<div class="detail-code ${colorClass}" style="color:${
category === '1xx' ? '#1565c0' :
category === '2xx' ? '#2e7d32' :
category === '3xx' ? '#ef6c00' :
category === '4xx' ? '#c62828' : '#b71c1c'
}">${found.code}</div>
<div class="detail-name">${found.name}</div>
<div class="detail-desc">${found.desc}</div>
<div class="detail-section">
<h4>示例</h4>
<div class="detail-code-block">${found.example}</div>
</div>
`;
document.getElementById('detailModal').classList.add('show');
break;
}
}
}
function closeDetail() {
document.getElementById('detailModal').classList.remove('show');
}
document.getElementById('searchBox').addEventListener('input', (e) => {
renderCodes(e.target.value);
});
document.getElementById('detailModal').addEventListener('click', (e) => {
if (e.target === e.currentTarget) closeDetail();
});
renderCodes();
</script>
</body>
</html>示例2:API响应状态码处理器
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API响应状态码处理器</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #f0f2f5;
padding: 20px;
color: #333;
}
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card {
background: #fff;
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.card h2 {
font-size: 18px;
color: #1a73e8;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #e8eaed;
}
.scenario-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
margin-bottom: 20px;
}
.scenario-btn {
padding: 14px;
border: 2px solid #e8eaed;
border-radius: 10px;
background: #fff;
cursor: pointer;
text-align: center;
transition: all 0.2s;
}
.scenario-btn:hover {
border-color: #1a73e8;
transform: translateY(-2px);
}
.scenario-btn.active {
border-color: #1a73e8;
background: #e3f2fd;
}
.scenario-code {
font-size: 24px;
font-weight: 800;
margin-bottom: 4px;
}
.scenario-name { font-size: 13px; color: #666; }
.response-area {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.response-box {
padding: 16px;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 13px;
line-height: 1.7;
}
.raw-response { background: #1e1e1e; color: #d4d4d4; }
.processed-response { background: #f8f9fa; }
.handler-code {
background: #1e1e1e;
color: #d4d4d4;
padding: 16px;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 13px;
line-height: 1.7;
margin-top: 16px;
white-space: pre-wrap;
}
.status-indicator {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-weight: 700;
font-size: 14px;
margin-bottom: 12px;
}
.status-success { background: #e8f5e9; color: #2e7d32; }
.status-redirect { background: #fff3e0; color: #ef6c00; }
.status-client-error { background: #fce4ec; color: #c62828; }
.status-server-error { background: #ffebee; color: #b71c1c; }
.action-list {
list-style: none;
margin-top: 12px;
}
.action-list li {
padding: 8px 12px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
}
.action-icon {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
flex-shrink: 0;
}
.action-check { background: #e8f5e9; color: #2e7d32; }
.action-warn { background: #fff3e0; color: #ef6c00; }
.action-error { background: #fce4ec; color: #c62828; }
</style>
</head>
<body>
<div class="container">
<h1>API响应状态码处理器</h1>
<div class="card">
<h2>选择响应场景</h2>
<div class="scenario-grid">
<div class="scenario-btn" onclick="showScenario(200)">
<div class="scenario-code" style="color:#2e7d32">200</div>
<div class="scenario-name">GET 成功</div>
</div>
<div class="scenario-btn" onclick="showScenario(201)">
<div class="scenario-code" style="color:#2e7d32">201</div>
<div class="scenario-name">POST 创建成功</div>
</div>
<div class="scenario-btn" onclick="showScenario(304)">
<div class="scenario-code" style="color:#ef6c00">304</div>
<div class="scenario-name">缓存命中</div>
</div>
<div class="scenario-btn" onclick="showScenario(400)">
<div class="scenario-code" style="color:#c62828">400</div>
<div class="scenario-name">参数错误</div>
</div>
<div class="scenario-btn" onclick="showScenario(401)">
<div class="scenario-code" style="color:#c62828">401</div>
<div class="scenario-name">未认证</div>
</div>
<div class="scenario-btn" onclick="showScenario(403)">
<div class="scenario-code" style="color:#c62828">403</div>
<div class="scenario-name">无权限</div>
</div>
<div class="scenario-btn" onclick="showScenario(404)">
<div class="scenario-code" style="color:#c62828">404</div>
<div class="scenario-name">资源不存在</div>
</div>
<div class="scenario-btn" onclick="showScenario(429)">
<div class="scenario-code" style="color:#c62828">429</div>
<div class="scenario-name">请求过多</div>
</div>
<div class="scenario-btn" onclick="showScenario(500)">
<div class="scenario-code" style="color:#b71c1c">500</div>
<div class="scenario-name">服务器错误</div>
</div>
<div class="scenario-btn" onclick="showScenario(503)">
<div class="scenario-code" style="color:#b71c1c">503</div>
<div class="scenario-name">服务不可用</div>
</div>
</div>
</div>
<div id="resultArea"></div>
</div>
<script>
const scenarios = {
200: {
raw: 'HTTP/1.1 200 OK\nContent-Type: application/json\nCache-Control: max-age=3600\n\n{"id":1,"name":"张三","email":"zhangsan@example.com"}',
statusClass: 'status-success',
statusText: '请求成功',
actions: [
{ type: 'check', text: '解析JSON响应体' },
{ type: 'check', text: '更新UI显示数据' },
{ type: 'check', text: '根据Cache-Control缓存响应' }
],
handler: `async function handleResponse(response) {\n const data = await response.json();\n // 更新UI\n updateUI(data);\n // 缓存数据\n if (response.headers.get('Cache-Control')) {\n cacheData(response.url, data);\n }\n return data;\n}`
},
201: {
raw: 'HTTP/1.1 201 Created\nLocation: /api/users/5\nContent-Type: application/json\n\n{"id":5,"name":"新用户","email":"new@example.com"}',
statusClass: 'status-success',
statusText: '资源创建成功',
actions: [
{ type: 'check', text: '解析JSON获取新资源数据' },
{ type: 'check', text: '从Location头部获取新资源URI' },
{ type: 'check', text: '更新列表或跳转到详情页' },
{ type: 'check', text: '显示创建成功提示' }
],
handler: `async function handleCreated(response) {\n const data = await response.json();\n const location = response.headers.get('Location');\n // 显示成功提示\n showToast('创建成功');\n // 跳转或更新列表\n if (location) navigateTo(location);\n return data;\n}`
},
304: {
raw: 'HTTP/1.1 304 Not Modified\nETag: "abc123"\nCache-Control: max-age=3600\n\n(无响应体)',
statusClass: 'status-redirect',
statusText: '缓存命中,使用本地缓存',
actions: [
{ type: 'check', text: '不解析响应体(无内容)' },
{ type: 'check', text: '使用浏览器缓存的数据' },
{ type: 'check', text: '更新ETag/Last-Modified缓存标识' }
],
handler: `async function handleNotModified(response) {\n // 304无响应体,使用缓存数据\n const cachedData = getFromCache(response.url);\n // 更新缓存标识\n const etag = response.headers.get('ETag');\n updateCacheHeaders(response.url, { etag });\n return cachedData;\n}`
},
400: {
raw: 'HTTP/1.1 400 Bad Request\nContent-Type: application/json\n\n{"error":"参数验证失败","details":{"email":"邮箱格式不正确","age":"年龄必须大于0"}}',
statusClass: 'status-client-error',
statusText: '请求参数错误',
actions: [
{ type: 'error', text: '解析错误详情' },
{ type: 'error', text: '在表单对应字段显示错误信息' },
{ type: 'warn', text: '不自动重试,需用户修正后重新提交' }
],
handler: `async function handleBadRequest(response) {\n const error = await response.json();\n // 在表单字段旁显示错误\n if (error.details) {\n Object.entries(error.details).forEach(([field, msg]) => {\n showFieldError(field, msg);\n });\n }\n throw new Error(error.error);\n}`
},
401: {
raw: 'HTTP/1.1 401 Unauthorized\nWWW-Authenticate: Bearer realm="api"\nContent-Type: application/json\n\n{"error":"Token已过期","code":"TOKEN_EXPIRED"}',
statusClass: 'status-client-error',
statusText: '未认证,需要登录',
actions: [
{ type: 'error', text: '清除本地存储的Token' },
{ type: 'error', text: '尝试使用Refresh Token刷新' },
{ type: 'warn', text: '刷新失败则跳转到登录页' }
],
handler: `async function handleUnauthorized(response) {\n const error = await response.json();\n // 尝试刷新Token\n const refreshed = await tryRefreshToken();\n if (refreshed) {\n // 重试原请求\n return retryOriginalRequest();\n }\n // 跳转登录\n clearAuth();\n redirectToLogin();\n}`
},
403: {
raw: 'HTTP/1.1 403 Forbidden\nContent-Type: application/json\n\n{"error":"您没有权限执行此操作","requiredRole":"admin"}',
statusClass: 'status-client-error',
statusText: '权限不足',
actions: [
{ type: 'error', text: '显示权限不足提示' },
{ type: 'warn', text: '不自动重试,重新登录也无法解决' },
{ type: 'warn', text: '可提供申请权限的入口' }
],
handler: `async function handleForbidden(response) {\n const error = await response.json();\n // 显示权限不足提示\n showForbiddenMessage(error.error);\n // 不重试,即使重新认证也无法获得权限\n throw new Error('权限不足');\n}`
},
404: {
raw: 'HTTP/1.1 404 Not Found\nContent-Type: application/json\n\n{"error":"请求的资源不存在","resource":"User","id":999}',
statusClass: 'status-client-error',
statusText: '资源不存在',
actions: [
{ type: 'error', text: '显示"未找到"页面或提示' },
{ type: 'warn', text: '不自动重试' },
{ type: 'warn', text: '提供返回首页或列表的链接' }
],
handler: `async function handleNotFound(response) {\n const error = await response.json();\n // 显示404页面或提示\n showNotFoundMessage(error.error);\n // 提供导航\n showNavigationOptions({\n back: true,\n home: true\n });\n}`
},
429: {
raw: 'HTTP/1.1 429 Too Many Requests\nRetry-After: 60\nContent-Type: application/json\n\n{"error":"请求过于频繁","limit":"100次/分钟","retryAfter":60}',
statusClass: 'status-client-error',
statusText: '请求频率超限',
actions: [
{ type: 'warn', text: '读取Retry-After头部' },
{ type: 'warn', text: '等待指定时间后自动重试' },
{ type: 'warn', text: '显示倒计时提示用户' }
],
handler: `async function handleTooManyRequests(response) {\n const retryAfter = parseInt(\n response.headers.get('Retry-After') || '60'\n );\n // 显示倒计时\n showCountdown(retryAfter);\n // 延迟后重试\n await sleep(retryAfter * 1000);\n return retryOriginalRequest();\n}`
},
500: {
raw: 'HTTP/1.1 500 Internal Server Error\nContent-Type: application/json\n\n{"error":"服务器内部错误","requestId":"req-abc123"}',
statusClass: 'status-server-error',
statusText: '服务器内部错误',
actions: [
{ type: 'error', text: '显示友好的错误提示' },
{ type: 'warn', text: '可自动重试1-2次' },
{ type: 'warn', text: '记录requestId用于问题排查' },
{ type: 'warn', text: '上报错误到监控系统' }
],
handler: `async function handleServerError(response) {\n const error = await response.json().catch(() => ({}));\n // 上报错误\n reportError({\n url: response.url,\n status: 500,\n requestId: error.requestId\n });\n // 显示友好提示\n showErrorMessage('服务器开小差了,请稍后重试');\n throw new Error('服务器错误');\n}`
},
503: {
raw: 'HTTP/1.1 503 Service Unavailable\nRetry-After: 300\nContent-Type: application/json\n\n{"error":"服务维护中","estimatedRecovery":"2026-04-22T12:00:00Z"}',
statusClass: 'status-server-error',
statusText: '服务暂时不可用',
actions: [
{ type: 'error', text: '显示维护通知页面' },
{ type: 'warn', text: '根据Retry-After延迟重试' },
{ type: 'warn', text: '显示预计恢复时间' }
],
handler: `async function handleServiceUnavailable(response) {\n const error = await response.json();\n const retryAfter = response.headers.get('Retry-After');\n // 显示维护页面\n showMaintenancePage({\n message: error.error,\n estimatedRecovery: error.estimatedRecovery\n });\n // 后台定时重试\n scheduleRetry(parseInt(retryAfter) * 1000);\n}`
}
};
function showScenario(code) {
document.querySelectorAll('.scenario-btn').forEach(b => b.classList.remove('active'));
event.currentTarget.classList.add('active');
const s = scenarios[code];
document.getElementById('resultArea').innerHTML = `
<div class="card">
<h2>响应处理方案</h2>
<span class="status-indicator ${s.statusClass}">${code} ${s.statusText}</span>
<div class="response-area">
<div>
<h4 style="margin-bottom:8px;color:#555;">原始响应</h4>
<div class="response-box raw-response">${s.raw}</div>
</div>
<div>
<h4 style="margin-bottom:8px;color:#555;">前端处理步骤</h4>
<ul class="action-list">
${s.actions.map(a => `
<li>
<span class="action-icon action-${a.type}">${a.type === 'check' ? '' : a.type === 'warn' ? '!' : ''}</span>
${a.text}
</li>
`).join('')}
</ul>
</div>
</div>
<h4 style="margin-top:20px;margin-bottom:8px;color:#555;">推荐处理代码</h4>
<div class="handler-code">${s.handler}</div>
</div>
`;
}
</script>
</body>
</html>示例3:自定义错误页面设计
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义错误页面设计</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #f0f2f5;
padding: 20px;
color: #333;
}
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.tabs {
display: flex;
gap: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.tab-btn {
padding: 10px 20px;
border: 2px solid #dadce0;
border-radius: 8px;
background: #fff;
cursor: pointer;
font-weight: 600;
transition: all 0.2s;
}
.tab-btn.active { background: #1a73e8; color: #fff; border-color: #1a73e8; }
.error-page {
display: none;
min-height: 400px;
border-radius: 16px;
overflow: hidden;
}
.error-page.active { display: block; }
.ep-404 {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.ep-404 .error-code {
font-size: 120px;
font-weight: 900;
line-height: 1;
opacity: 0.3;
margin-bottom: -30px;
}
.ep-404 h2 { font-size: 28px; margin-bottom: 12px; }
.ep-404 p { font-size: 16px; opacity: 0.8; margin-bottom: 24px; max-width: 400px; }
.ep-404 .search-box {
display: flex;
gap: 8px;
margin-bottom: 20px;
}
.ep-404 input {
padding: 12px 20px;
border: none;
border-radius: 8px;
font-size: 15px;
width: 280px;
}
.ep-404 button {
padding: 12px 24px;
border: none;
border-radius: 8px;
background: #fff;
color: #764ba2;
font-weight: 700;
cursor: pointer;
}
.ep-404 .links { display: flex; gap: 16px; }
.ep-404 .links a {
color: #fff;
text-decoration: underline;
opacity: 0.8;
}
.ep-500 {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.ep-500 .icon { font-size: 80px; margin-bottom: 20px; }
.ep-500 h2 { font-size: 28px; margin-bottom: 12px; }
.ep-500 p { font-size: 16px; opacity: 0.8; margin-bottom: 24px; max-width: 400px; }
.ep-500 .retry-btn {
padding: 14px 32px;
border: 2px solid #fff;
border-radius: 8px;
background: transparent;
color: #fff;
font-weight: 700;
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
}
.ep-500 .retry-btn:hover { background: #fff; color: #f5576c; }
.ep-403 {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
color: #333;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.ep-403 .lock-icon { font-size: 80px; margin-bottom: 20px; }
.ep-403 h2 { font-size: 28px; margin-bottom: 12px; }
.ep-403 p { font-size: 16px; opacity: 0.7; margin-bottom: 24px; max-width: 400px; }
.ep-403 .contact-btn {
padding: 14px 32px;
border: none;
border-radius: 8px;
background: #333;
color: #fff;
font-weight: 700;
font-size: 16px;
cursor: pointer;
}
.ep-maintenance {
background: linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%);
color: #333;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
}
.ep-maintenance .tool-icon { font-size: 80px; margin-bottom: 20px; }
.ep-maintenance h2 { font-size: 28px; margin-bottom: 12px; }
.ep-maintenance p { font-size: 16px; opacity: 0.7; margin-bottom: 8px; max-width: 400px; }
.ep-maintenance .countdown {
font-size: 32px;
font-weight: 800;
margin: 16px 0;
color: #6a1b9a;
}
.ep-maintenance .progress-bar {
width: 300px;
height: 8px;
background: rgba(0,0,0,0.1);
border-radius: 4px;
overflow: hidden;
margin-top: 16px;
}
.ep-maintenance .progress-fill {
height: 100%;
background: #6a1b9a;
border-radius: 4px;
transition: width 1s;
}
</style>
</head>
<body>
<div class="container">
<h1>自定义错误页面设计</h1>
<div class="tabs">
<div class="tab-btn active" onclick="showPage('404')">404 未找到</div>
<div class="tab-btn" onclick="showPage('500')">500 服务器错误</div>
<div class="tab-btn" onclick="showPage('403')">403 禁止访问</div>
<div class="tab-btn" onclick="showPage('503')">503 维护中</div>
</div>
<div class="error-page ep-404 active" id="page-404">
<div class="error-code">404</div>
<h2>页面走丢了</h2>
<p>抱歉,您访问的页面不存在或已被移除。请检查URL是否正确,或尝试搜索您需要的内容。</p>
<div class="search-box">
<input type="text" placeholder="搜索内容...">
<button>搜索</button>
</div>
<div class="links">
<a href="#">返回首页</a>
<a href="#">帮助中心</a>
<a href="#">联系客服</a>
</div>
</div>
<div class="error-page ep-500" id="page-500">
<div class="icon">⚠</div>
<h2>服务器开小差了</h2>
<p>我们的服务器遇到了一些问题,技术团队正在紧急修复中。请稍后再试。</p>
<button class="retry-btn" onclick="this.textContent='正在重试...';setTimeout(()=>this.textContent='重试',1500)">重试</button>
</div>
<div class="error-page ep-403" id="page-403">
<div class="lock-icon">🔒</div>
<h2>访问受限</h2>
<p>您没有权限访问此页面。如果您认为这是一个错误,请联系管理员申请权限。</p>
<button class="contact-btn">联系管理员</button>
</div>
<div class="error-page ep-maintenance" id="page-503">
<div class="tool-icon">🔧</div>
<h2>系统维护中</h2>
<p>我们正在进行系统升级维护,预计恢复时间:</p>
<div class="countdown" id="countdown">05:00</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width:60%"></div>
</div>
</div>
</div>
<script>
function showPage(code) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.error-page').forEach(p => p.classList.remove('active'));
event.currentTarget.classList.add('active');
document.getElementById('page-' + code).classList.add('active');
}
// 倒计时
let totalSeconds = 300;
function updateCountdown() {
const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0');
const seconds = (totalSeconds % 60).toString().padStart(2, '0');
document.getElementById('countdown').textContent = `${minutes}:${seconds}`;
const progress = ((300 - totalSeconds) / 300) * 100;
document.getElementById('progressFill').style.width = progress + '%';
if (totalSeconds > 0) totalSeconds--;
}
setInterval(updateCountdown, 1000);
</script>
</body>
</html>浏览器兼容性
注意事项与最佳实践
1. 状态码选择原则
使用最具体的状态码,而非笼统的200或500
201用于资源创建,204用于无内容成功
401和403不要混用,401是未认证,403是无权限
400用于语法错误,422用于语义/验证错误
2. 前端处理原则
fetch API不会因为4xx/5xx状态码而reject,必须手动检查
response.ok或response.status为每种状态码提供用户友好的提示信息
5xx错误可以自动重试,4xx错误不应自动重试
401错误应触发Token刷新或登录跳转
3. 错误页面设计
错误页面应保持与网站一致的视觉风格
提供有用的导航选项(返回首页、搜索、联系客服)
避免技术术语,使用用户易懂的语言
503维护页面应显示预计恢复时间
4. API响应格式规范
代码示例
// 成功响应
{
"status": "success",
"data": { ... }
}
// 错误响应
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "参数验证失败",
"details": {
"email": "邮箱格式不正确"
}
}
}代码规范示例
统一的HTTP响应处理器
代码示例
class HttpResponseHandler {
static async handle(response) {
const status = response.status;
// 2xx 成功
if (status >= 200 && status < 300) {
if (status === 204) return null;
return await response.json();
}
// 304 未修改
if (status === 304) {
return { _cached: true };
}
// 解析错误响应体
let errorData;
try {
errorData = await response.json();
} catch {
errorData = { message: `HTTP Error ${status}` };
}
// 401 未认证
if (status === 401) {
await this.handleUnauthorized();
throw new UnauthorizedError(errorData);
}
// 403 无权限
if (status === 403) {
throw new ForbiddenError(errorData);
}
// 404 未找到
if (status === 404) {
throw new NotFoundError(errorData);
}
// 429 请求过多
if (status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new RateLimitError(errorData, retryAfter);
}
// 4xx 客户端错误
if (status >= 400 && status < 500) {
throw new ClientError(status, errorData);
}
// 5xx 服务器错误
throw new ServerError(status, errorData);
}
static async handleUnauthorized() {
const refreshed = await refreshToken();
if (!refreshed) {
window.location.href = '/login';
}
}
}常见问题与解决方案
问题1:fetch不因4xx/5xx状态码reject
问题描述:fetch API即使收到404或500状态码,Promise也不会reject,只有网络错误才会reject。
解决方案:
代码示例
async function safeFetch(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new HttpError(response.status, error);
}
return response;
}问题2:重定向导致POST变GET
问题描述:302重定向可能将POST请求变为GET请求,丢失请求体数据。
解决方案:使用307(临时)或308(永久)重定向,它们保证保持原HTTP方法。
问题3:401和403混淆使用
问题描述:很多API将权限不足也返回401,导致前端处理混乱。
解决方案:
401:未提供认证凭据或凭据无效 → 跳转登录
403:已认证但权限不足 → 显示权限不足提示
问题4:500错误信息泄露
问题描述:500错误响应中包含堆栈跟踪等敏感信息。
解决方案:生产环境返回通用错误信息,详细信息只记录在服务器日志中:
代码示例
// 生产环境
{"error": "服务器内部错误", "requestId": "req-abc123"}
// 开发环境
{"error": "服务器内部错误", "stack": "...", "requestId": "req-abc123"}总结
HTTP状态码是前后端通信的重要信号系统,正确理解和使用状态码对于构建健壮的Web应用至关重要:
1xx信息性:请求处理中的中间状态,如100 Continue和101 Switching Protocols
2xx成功:请求处理成功的各种情况,201创建、204无内容、206部分内容
3xx重定向:301/308永久重定向、302/307临时重定向、304缓存命中
4xx客户端错误:400参数错误、401未认证、403无权限、404未找到、429限流
5xx服务器错误:500内部错误、502网关错误、503服务不可用、504网关超时
前端开发者必须掌握的关键点:fetch不会因HTTP错误状态码reject、401与403的区别、重定向状态码对HTTP方法的影响、以及如何为每种状态码设计合适的用户提示和错误处理策略。
常见问题
fetch不因4xx/5xx状态码reject
请参考教程中的详细说明和代码示例。
重定向导致POST变GET
- 401:未提供认证凭据或凭据无效 → 跳转登录 - 403:已认证但权限不足 → 显示权限不足提示
什么是HTTP状态码?
HTTP状态码是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。
如何学习HTTP状态码的实际应用?
教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。
HTTP状态码有哪些注意事项?
常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。
HTTP状态码适合初学者吗?
本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。
HTTP状态码的核心要点是什么?
核心要点包括教程简介等内容,建议按教程顺序逐步学习。
本文由小确幸生活整理发布,转载请注明出处
本文涉及AI创作
内容由AI创作,请仔细甄别