云计算百科
云计算领域专业知识百科平台

HTTP 状态码:客户端与服务器的通信语言——第六部分:状态码的实践应用(一)

第30章:RESTful API设计中的状态码使用

30.1 RESTful架构与状态码的哲学

RESTful API设计中,HTTP状态码不仅是技术实现细节,更是API契约的重要组成部分。它们构成了客户端与服务器之间通信的语义框架,使得API能够自我描述其处理结果。

30.1.1 Richardson成熟度模型与状态码

Richardson成熟度模型将RESTful服务分为四个等级:

Level 0:POX沼泽

  • 使用单一端点(如/api)

  • 单一HTTP方法(通常POST)

  • 状态信息在响应体中传递

  • 状态码基本只有200和500

http

POST /api HTTP/1.1
Content-Type: application/json

{
"operation": "createUser",
"data": {"name": "John", "email": "john@example.com"}
}

HTTP/1.1 200 OK
Content-Type: application/json

{
"status": "error",
"code": "USER_EXISTS",
"message": "User already exists"
}

Level 1:资源分离

  • 多个端点对应不同资源

  • 仍主要使用单一HTTP方法

  • 状态码开始有更多应用

Level 2:HTTP动词

  • 正确使用HTTP方法(GET、POST、PUT、DELETE等)

  • 状态码被充分利用

  • 这是大多数"RESTful"API所在的层级

Level 3:超媒体控制(HATEOAS)

  • 响应中包含相关操作的链接

  • 状态码引导客户端状态转移

  • 真正的RESTful实现

http

GET /orders/123 HTTP/1.1
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json

{
"id": "123",
"status": "processing",
"total": 99.99,
"_links": {
"self": { "href": "/orders/123" },
"cancel": {
"href": "/orders/123/cancel",
"method": "POST"
},
"payment": {
"href": "/orders/123/payment",
"method": "POST"
}
}
}

30.2 RESTful API状态码选择指南

30.2.1 成功类状态码(2xx)

200 OK – 通用成功

http

GET /users/123 HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json

{
"id": "123",
"name": "John Doe",
"email": "john@example.com"
}

适用场景:

  • GET:资源获取成功

  • PUT/PATCH:资源更新成功

  • POST:非创建操作成功(如计算、验证)

201 Created – 资源创建成功

http

POST /users HTTP/1.1
Content-Type: application/json

{
"name": "Jane Smith",
"email": "jane@example.com"
}

HTTP/1.1 201 Created
Location: /users/456
Content-Type: application/json

{
"id": "456",
"name": "Jane Smith",
"email": "jane@example.com",
"created_at": "2024-01-15T10:30:00Z"
}

最佳实践:

  • 必须包含Location头指向新资源

  • 响应体应包含创建的资源表示

  • 适用于同步创建操作

  • 202 Accepted – 请求已接受

    http

    POST /video-transcoding HTTP/1.1
    Content-Type: application/json

    {
    "video_id": "789",
    "format": "mp4"
    }

    HTTP/1.1 202 Accepted
    Location: /jobs/transcode-abc123
    Retry-After: 30
    Content-Type: application/json

    {
    "job_id": "transcode-abc123",
    "status": "pending",
    "estimated_completion": "2024-01-15T10:35:00Z",
    "_links": {
    "self": { "href": "/jobs/transcode-abc123" },
    "status": { "href": "/jobs/transcode-abc123/status" }
    }
    }

    适用场景:

    • 异步处理

    • 长时间运行的操作

    • 需要轮询结果的情况

    204 No Content – 无内容

    http

    DELETE /users/123 HTTP/1.1

    HTTP/1.1 204 No Content

    适用场景:

    • DELETE操作成功

    • PUT/PATCH更新但无需返回资源

    • 某些POST操作(如点赞、关注)

    30.2.2 重定向类状态码(3xx)

    301 Moved Permanently 和 308 Permanent Redirect

    http

    GET /old-api/users HTTP/1.1

    HTTP/1.1 301 Moved Permanently
    Location: /api/v2/users

    307 Temporary Redirect 和 302 Found

    http

    POST /api/maintenance HTTP/1.1
    Content-Type: application/json

    {
    "action": "backup"
    }

    HTTP/1.1 307 Temporary Redirect
    Location: /api/backup-endpoint

    303 See Other – POST后的重定向

    http

    POST /checkout HTTP/1.1
    Content-Type: application/json

    {
    "cart_id": "cart123"
    }

    HTTP/1.1 303 See Other
    Location: /order/456/confirmation

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

    400 Bad Request – 通用客户端错误

    http

    POST /users HTTP/1.1
    Content-Type: application/json

    {
    "name": "John",
    "email": "invalid-email"
    }

    HTTP/1.1 400 Bad Request
    Content-Type: application/json

    {
    "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
    {
    "field": "email",
    "message": "Must be a valid email address"
    }
    ]
    }
    }

    401 Unauthorized – 认证失败

    http

    GET /secure-data HTTP/1.1
    Authorization: Bearer expired-token

    HTTP/1.1 401 Unauthorized
    WWW-Authenticate: Bearer realm="api", error="invalid_token"
    Content-Type: application/json

    {
    "error": {
    "code": "INVALID_TOKEN",
    "message": "The access token is expired or invalid"
    }
    }

    403 Forbidden – 权限不足

    http

    GET /admin/users HTTP/1.1
    Authorization: Bearer user-token

    HTTP/1.1 403 Forbidden
    Content-Type: application/json

    {
    "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "You don't have permission to access this resource"
    }
    }

    404 Not Found – 资源不存在

    http

    GET /users/99999 HTTP/1.1

    HTTP/1.1 404 Not Found
    Content-Type: application/json

    {
    "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "User with id 99999 not found",
    "resource": "user",
    "resource_id": "99999"
    }
    }

    405 Method Not Allowed

    http

    PUT /users HTTP/1.1

    HTTP/1.1 405 Method Not Allowed
    Allow: GET, POST, HEAD, OPTIONS
    Content-Type: application/json

    {
    "error": {
    "code": "METHOD_NOT_ALLOWED",
    "message": "PUT method is not allowed for this resource"
    }
    }

    409 Conflict – 资源冲突

    http

    PUT /users/123 HTTP/1.1
    Content-Type: application/json
    If-Match: "abc123"

    {
    "name": "Updated Name",
    "version": 2
    }

    HTTP/1.1 409 Conflict
    Content-Type: application/json

    {
    "error": {
    "code": "VERSION_CONFLICT",
    "message": "Resource has been modified by another request",
    "current_version": "def456",
    "suggested_action": "Retrieve the latest version and retry"
    }
    }

    422 Unprocessable Entity – 语义错误

    http

    POST /orders HTTP/1.1
    Content-Type: application/json

    {
    "items": [],
    "shipping_address": {
    "street": "123 Main St"
    // missing city and country
    }
    }

    HTTP/1.1 422 Unprocessable Entity
    Content-Type: application/json

    {
    "error": {
    "code": "UNPROCESSABLE_ENTITY",
    "message": "The request was well-formed but contains semantic errors",
    "details": [
    {
    "field": "shipping_address.city",
    "message": "City is required"
    },
    {
    "field": "shipping_address.country",
    "message": "Country is required"
    },
    {
    "field": "items",
    "message": "Order must contain at least one item"
    }
    ]
    }
    }

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

    500 Internal Server Error – 通用服务器错误

    http

    GET /users HTTP/1.1

    HTTP/1.1 500 Internal Server Error
    Content-Type: application/json

    {
    "error": {
    "code": "INTERNAL_SERVER_ERROR",
    "message": "An unexpected error occurred",
    "reference": "ERR-20240115-001",
    "support_contact": "support@api.example.com"
    }
    }

    502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout

    http

    GET /api/third-party-data HTTP/1.1

    HTTP/1.1 503 Service Unavailable
    Retry-After: 300
    Content-Type: application/json

    {
    "error": {
    "code": "SERVICE_UNAVAILABLE",
    "message": "Service is temporarily unavailable for maintenance",
    "estimated_recovery": "2024-01-15T12:00:00Z",
    "status_page": "https://status.example.com"
    }
    }

    30.3 高级状态码使用模式

    30.3.1 条件请求与状态码

    ETag与If-None-Match

    http

    GET /articles/123 HTTP/1.1
    If-None-Match: "abc123"

    # 如果ETag匹配
    HTTP/1.1 304 Not Modified
    ETag: "abc123"

    # 如果ETag不匹配
    HTTP/1.1 200 OK
    ETag: "def456"
    Content-Type: application/json

    {
    "id": "123",
    "title": "Updated Article",
    "content": "…",
    "updated_at": "2024-01-15T10:00:00Z"
    }

    Last-Modified与If-Modified-Since

    http

    GET /articles HTTP/1.1
    If-Modified-Since: Mon, 15 Jan 2024 09:00:00 GMT

    # 如果没有修改
    HTTP/1.1 304 Not Modified
    Last-Modified: Mon, 15 Jan 2024 08:00:00 GMT

    # 如果有修改
    HTTP/1.1 200 OK
    Last-Modified: Mon, 15 Jan 2024 10:00:00 GMT
    Content-Type: application/json

    [
    {
    "id": "123",
    "title": "New Article",
    "created_at": "2024-01-15T09:30:00Z"
    }
    ]

    30.3.2 范围请求与206 Partial Content

    http

    GET /large-file.pdf HTTP/1.1
    Range: bytes=0-999

    HTTP/1.1 206 Partial Content
    Content-Range: bytes 0-999/5000
    Content-Length: 1000
    Content-Type: application/pdf

    <binary data>

    30.3.3 多状态响应与207 Multi-Status

    http

    PROPPATCH /webdav/resources HTTP/1.1
    Content-Type: application/xml

    <?xml version="1.0" encoding="utf-8" ?>
    <D:propertyupdate xmlns:D="DAV:">
    <D:set>
    <D:prop>
    <D:displayname>New Name</D:displayname>
    </D:prop>
    </D:set>
    </D:propertyupdate>

    HTTP/1.1 207 Multi-Status
    Content-Type: application/xml

    <?xml version="1.0" encoding="utf-8" ?>
    <D:multistatus xmlns:D="DAV:">
    <D:response>
    <D:href>/webdav/resource1</D:href>
    <D:status>HTTP/1.1 200 OK</D:status>
    </D:response>
    <D:response>
    <D:href>/webdav/resource2</D:href>
    <D:status>HTTP/1.1 403 Forbidden</D:status>
    <D:responsedescription>Permission denied</D:responsedescription>
    </D:response>
    </D:multistatus>

    30.4 API版本控制与状态码

    30.4.1 版本过期与410 Gone

    http

    GET /api/v1/users HTTP/1.1

    HTTP/1.1 410 Gone
    Content-Type: application/json
    Link: </api/v2/users>; rel="latest-version"

    {
    "error": {
    "code": "API_VERSION_DEPRECATED",
    "message": "API v1 has been deprecated since 2024-01-01",
    "deprecation_date": "2024-01-01T00:00:00Z",
    "sunset_date": "2024-07-01T00:00:00Z",
    "migration_guide": "https://docs.example.com/migration/v1-to-v2"
    }
    }

    30.4.2 版本重定向与301/308

    http

    GET /api/v1/products HTTP/1.1

    HTTP/1.1 308 Permanent Redirect
    Location: /api/v2/products
    Content-Type: application/json

    {
    "message": "API endpoint has been permanently moved",
    "deprecation_notice": "v1 will be sunset on 2024-06-30"
    }

    30.5 错误响应标准化

    30.5.1 RFC 7807问题详情格式

    http

    POST /transfer HTTP/1.1
    Content-Type: application/json

    {
    "from_account": "acc123",
    "to_account": "acc456",
    "amount": 1000
    }

    HTTP/1.1 422 Unprocessable Entity
    Content-Type: application/problem+json

    {
    "type": "https://api.example.com/errors/insufficient-funds",
    "title": "Insufficient Funds",
    "status": 422,
    "detail": "Account acc123 has only 500.00 available, but 1000.00 was requested",
    "instance": "/transfer/attempt/abc-123-def-456",
    "balance": 500.00,
    "accounts": ["acc123", "acc456"],
    "extensions": {
    "code": "INSUFFICIENT_FUNDS",
    "request_id": "req-123456",
    "timestamp": "2024-01-15T10:30:00Z"
    }
    }

    30.5.2 Google API错误格式

    http

    GET /v1/users/invalid-id HTTP/1.1

    HTTP/1.1 404 Not Found
    Content-Type: application/json

    {
    "error": {
    "code": 404,
    "message": "User not found: invalid-id",
    "status": "NOT_FOUND",
    "details": [
    {
    "@type": "type.googleapis.com/google.rpc.ErrorInfo",
    "reason": "USER_NOT_FOUND",
    "domain": "users.googleapis.com",
    "metadata": {
    "resource": "users/invalid-id",
    "service": "user-service"
    }
    },
    {
    "@type": "type.googleapis.com/google.rpc.LocalizedMessage",
    "locale": "en-US",
    "message": "The requested user does not exist."
    }
    ]
    }
    }

    30.6 监控与可观测性

    30.6.1 状态码分布监控

    prometheus

    # Prometheus指标示例
    http_requests_total{method="POST", endpoint="/users", status="201"} 153
    http_requests_total{method="POST", endpoint="/users", status="400"} 12
    http_requests_total{method="GET", endpoint="/users/{id}", status="404"} 8
    http_requests_total{method="GET", endpoint="/users", status="500"} 3

    # 错误率计算
    rate(http_requests_total{status=~"4..|5.."}[5m]) / rate(http_requests_total[5m])

    30.6.2 结构化日志中的状态码

    json

    {
    "timestamp": "2024-01-15T10:30:15.123Z",
    "level": "INFO",
    "message": "HTTP request processed",
    "http": {
    "method": "POST",
    "path": "/users",
    "status_code": 201,
    "duration_ms": 45
    },
    "user": {
    "id": "user-123",
    "role": "admin"
    },
    "request_id": "req-abc-123",
    "correlation_id": "corr-xyz-789",
    "tags": ["api", "user-service"]
    }

    30.7 安全考虑

    30.7.1 信息泄露预防

    不当示例(泄露内部信息):

    http

    HTTP/1.1 500 Internal Server Error
    Content-Type: application/json

    {
    "error": {
    "message": "Database connection failed: Connection refused to 10.0.0.5:5432",
    "stack_trace": "java.sql.SQLException: Connection refused…",
    "internal_code": "DB_CONN_REFUSED",
    "database_ip": "10.0.0.5"
    }
    }

    正确示例:

    http

    HTTP/1.1 500 Internal Server Error
    Content-Type: application/json

    {
    "error": {
    "code": "INTERNAL_ERROR",
    "message": "An internal server error occurred",
    "reference": "ERR-20240115-001",
    "support_contact": "support@example.com"
    }
    }

    30.7.2 安全头与状态码

    http

    HTTP/1.1 400 Bad Request
    Content-Type: application/json
    Strict-Transport-Security: max-age=31536000; includeSubDomains
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    Content-Security-Policy: default-src 'self'

    {
    "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input provided"
    }
    }

    30.8 实际案例分析

    30.8.1 GitHub REST API状态码使用

    http

    # 创建仓库
    POST /user/repos HTTP/1.1
    Authorization: token ghp_abc123
    Content-Type: application/json

    {
    "name": "new-repo",
    "description": "A new repository"
    }

    # 成功响应
    HTTP/1.1 201 Created
    Location: https://api.github.com/repos/octocat/new-repo
    Content-Type: application/json

    {
    "id": 1296269,
    "name": "new-repo",
    "full_name": "octocat/new-repo",
    "private": false,
    "html_url": "https://github.com/octocat/new-repo",
    "created_at": "2011-01-26T19:01:12Z"
    }

    # 冲突响应(仓库已存在)
    HTTP/1.1 422 Unprocessable Entity
    Content-Type: application/json

    {
    "message": "Repository creation failed.",
    "errors": [
    {
    "resource": "Repository",
    "code": "custom",
    "field": "name",
    "message": "name already exists on this account"
    }
    ],
    "documentation_url": "https://docs.github.com/rest/repos/repos#create-a-repository-for-the-authenticated-user"
    }

    30.8.2 Stripe API状态码使用

    http

    # 创建支付意图
    POST /v1/payment_intents HTTP/1.1
    Authorization: Bearer sk_test_abc123
    Content-Type: application/x-www-form-urlencoded

    amount=2000&currency=usd&payment_method_types[]=card

    # 成功响应
    HTTP/1.1 200 OK
    Content-Type: application/json

    {
    "id": "pi_1JABC123",
    "object": "payment_intent",
    "amount": 2000,
    "currency": "usd",
    "status": "requires_payment_method",
    "client_secret": "pi_1JABC123_secret_xyz789"
    }

    # 无效参数响应
    HTTP/1.1 400 Bad Request
    Content-Type: application/json

    {
    "error": {
    "type": "invalid_request_error",
    "message": "Invalid integer: abc",
    "param": "amount"
    }
    }

    30.9 最佳实践总结

  • 一致性是关键:在整个API中保持状态码使用的一致性

  • 正确的状态码:为每种情况选择语义最准确的状态码

  • 丰富的错误信息:提供可操作的错误详情,但避免信息泄露

  • 适当的重试指导:通过Retry-After头或错误信息指导客户端重试

  • 监控与告警:基于状态码分布设置监控和告警

  • 版本控制:明确处理API版本变更和废弃

  • 安全考虑:在错误响应中实施适当的安全措施

  • 文档化:完整记录每个端点可能返回的状态码及其含义

  • 30.10 常见陷阱与解决方案

    30.10.1 陷阱:总是返回200 OK

    错误示例:

    http

    POST /login HTTP/1.1
    Content-Type: application/json

    {
    "username": "wrong",
    "password": "wrong"
    }

    HTTP/1.1 200 OK
    Content-Type: application/json

    {
    "success": false,
    "error": "Invalid credentials"
    }

    正确做法:

    http

    HTTP/1.1 401 Unauthorized
    Content-Type: application/json

    {
    "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "Username or password is incorrect"
    }
    }

    30.10.2 陷阱:过度使用500错误

    错误示例:

    http

    GET /users/999 HTTP/1.1

    HTTP/1.1 500 Internal Server Error
    Content-Type: application/json

    {
    "error": "User not found"
    }

    正确做法:

    http

    HTTP/1.1 404 Not Found
    Content-Type: application/json

    {
    "error": {
    "code": "USER_NOT_FOUND",
    "message": "User with id 999 does not exist"
    }
    }


    第31章:Web开发中的状态码最佳实践

    31.1 现代Web架构中的状态码

    31.1.1 单页应用(SPA)与状态码

    在现代单页应用中,状态码的处理呈现双重性:客户端路由和API通信。

    客户端路由状态码模拟

    javascript

    // React Router v6中的状态码处理
    import { useRouteError, isRouteErrorResponse } from 'react-router-dom';

    function ErrorBoundary() {
    const error = useRouteError();

    if (isRouteErrorResponse(error)) {
    return (
    <div className="error-page">
    <h1>{error.status} {error.statusText}</h1>
    {error.status === 404 && (
    <div>
    <h2>页面未找到</h2>
    <p>您访问的页面不存在或已被移动</p>
    <Link to="/">返回首页</Link>
    </div>
    )}
    {error.status === 401 && (
    <div>
    <h2>需要登录</h2>
    <p>请登录后访问此页面</p>
    <button onClick={() => navigate('/login')}>前往登录</button>
    </div>
    )}
    </div>
    );
    }

    return <div>发生了未知错误</div>;
    }

    API请求状态码统一处理

    javascript

    // Axios拦截器配置
    const apiClient = axios.create({
    baseURL: process.env.REACT_APP_API_URL,
    timeout: 10000,
    });

    // 请求拦截器
    apiClient.interceptors.request.use(
    config => {
    const token = localStorage.getItem('access_token');
    if (token) {
    config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
    },
    error => Promise.reject(error)
    );

    // 响应拦截器
    apiClient.interceptors.response.use(
    response => response,
    error => {
    if (!error.response) {
    // 网络错误
    return Promise.reject({
    code: 'NETWORK_ERROR',
    message: '网络连接失败,请检查网络设置',
    originalError: error
    });
    }

    const { status, data } = error.response;

    switch (status) {
    case 400:
    // 统一处理验证错误
    return Promise.reject({
    code: 'VALIDATION_ERROR',
    message: data.error?.message || '请求参数无效',
    details: data.error?.details,
    originalError: error
    });

    case 401:
    // Token过期处理
    if (data.error?.code === 'TOKEN_EXPIRED') {
    return handleTokenRefresh(error);
    }
    // 强制登出
    localStorage.removeItem('access_token');
    localStorage.removeItem('refresh_token');
    window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname);
    return Promise.reject({
    code: 'UNAUTHORIZED',
    message: '登录已过期,请重新登录',
    originalError: error
    });

    case 403:
    // 权限不足
    return Promise.reject({
    code: 'FORBIDDEN',
    message: '您没有权限执行此操作',
    originalError: error
    });

    case 404:
    return Promise.reject({
    code: 'NOT_FOUND',
    message: data.error?.message || '请求的资源不存在',
    originalError: error
    });

    case 429:
    // 请求频率限制
    const retryAfter = error.response.headers['retry-after'];
    return Promise.reject({
    code: 'RATE_LIMITED',
    message: '请求过于频繁,请稍后重试',
    retryAfter,
    originalError: error
    });

    case 500:
    case 502:
    case 503:
    case 504:
    // 服务器错误
    return Promise.reject({
    code: 'SERVER_ERROR',
    message: '服务器暂时不可用,请稍后重试',
    status,
    originalError: error
    });

    default:
    return Promise.reject({
    code: 'UNKNOWN_ERROR',
    message: `未知错误 (${status})`,
    originalError: error
    });
    }
    }
    );

    31.1.2 服务端渲染(SSR)中的状态码

    Next.js中的状态码处理

    javascript

    // pages/404.js – 自定义404页面
    export default function Custom404() {
    return (
    <div className="container">
    <h1>404 – 页面未找到</h1>
    <p>抱歉,您访问的页面不存在</p>
    <Link href="/">
    <a>返回首页</a>
    </Link>
    </div>
    );
    }

    // 页面级别的错误处理
    export async function getServerSideProps(context) {
    try {
    const { id } = context.params;
    const res = await fetch(`https://api.example.com/products/${id}`);

    if (res.status === 404) {
    return {
    notFound: true, // 触发404页面
    };
    }

    if (!res.ok) {
    throw new Error(`Failed to fetch product: ${res.status}`);
    }

    const product = await res.json();

    return {
    props: { product },
    };
    } catch (error) {
    // 记录错误到日志系统
    console.error('Product page error:', error);

    // 返回500错误页面
    return {
    props: {
    error: {
    message: '加载产品信息失败',
    statusCode: 500
    }
    }
    };
    }
    }

    // 自定义错误页面
    function ProductPage({ product, error }) {
    if (error) {
    return (
    <ErrorPage
    statusCode={error.statusCode}
    message={error.message}
    />
    );
    }

    return (
    <div>
    <h1>{product.name}</h1>
    {/* 产品详情 */}
    </div>
    );
    }

    31.2 状态码与用户体验

    31.2.1 友好的错误页面设计

    html

    <!– 404错误页面示例 –>
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 – 页面未找到</title>
    <style>
    .error-container {
    max-width: 600px;
    margin: 100px auto;
    padding: 40px;
    text-align: center;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    }

    .error-code {
    font-size: 120px;
    font-weight: 300;
    color: #e0e0e0;
    margin: 0;
    }

    .error-message {
    font-size: 24px;
    margin: 20px 0;
    color: #333;
    }

    .error-description {
    color: #666;
    margin-bottom: 30px;
    line-height: 1.6;
    }

    .error-actions {
    display: flex;
    gap: 15px;
    justify-content: center;
    flex-wrap: wrap;
    }

    .btn {
    padding: 12px 24px;
    border-radius: 6px;
    text-decoration: none;
    font-weight: 500;
    transition: all 0.2s;
    }

    .btn-primary {
    background: #0070f3;
    color: white;
    }

    .btn-secondary {
    background: #f5f5f5;
    color: #333;
    border: 1px solid #ddd;
    }

    .btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
    }

    .search-form {
    margin-top: 30px;
    display: flex;
    gap: 10px;
    }

    .search-input {
    flex: 1;
    padding: 12px;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-size: 16px;
    }

    @media (max-width: 600px) {
    .error-container {
    margin: 50px 20px;
    padding: 20px;
    }

    .error-code {
    font-size: 80px;
    }
    }
    </style>
    </head>
    <body>
    <div class="error-container">
    <h1 class="error-code">404</h1>
    <h2 class="error-message">页面走丢了</h2>
    <p class="error-description">
    抱歉,您访问的页面可能已被移除、重命名或暂时不可用。
    请检查网址是否正确,或使用搜索功能查找相关内容。
    </p>

    <div class="error-actions">
    <a href="/" class="btn btn-primary">返回首页</a>
    <a href="javascript:history.back()" class="btn btn-secondary">返回上一页</a>
    <a href="/contact" class="btn btn-secondary">联系支持</a>
    </div>

    <form class="search-form" action="/search" method="GET">
    <input
    type="text"
    class="search-input"
    name="q"
    placeholder="搜索您需要的内容…"
    autofocus
    >
    <button type="submit" class="btn btn-primary">搜索</button>
    </form>

    <!– 热门链接推荐 –>
    <div style="margin-top: 40px; text-align: left;">
    <h3 style="margin-bottom: 15px;">热门页面</h3>
    <ul style="list-style: none; padding: 0; margin: 0;">
    <li style="margin-bottom: 10px;">
    <a href="/products" style="color: #0070f3; text-decoration: none;">
    产品中心
    </a>
    </li>
    <li style="margin-bottom: 10px;">
    <a href="/docs" style="color: #0070f3; text-decoration: none;">
    文档中心
    </a>
    </li>
    <li style="margin-bottom: 10px;">
    <a href="/blog" style="color: #0070f3; text-decoration: none;">
    技术博客
    </a>
    </li>
    </ul>
    </div>
    </div>
    </body>
    </html>

    31.2.2 渐进式错误处理

    javascript

    // 渐进式增强的错误处理
    class ProgressiveErrorHandler {
    constructor() {
    this.errorQueue = [];
    this.maxRetries = 3;
    this.retryDelays = [1000, 3000, 10000]; // 指数退避
    }

    async handleRequest(requestFunc, options = {}) {
    const {
    retryCount = 0,
    userMessage = '操作失败,请重试',
    fallbackAction = null
    } = options;

    try {
    return await requestFunc();
    } catch (error) {
    // 记录错误
    this.logError(error);

    // 根据错误类型处理
    switch (error.code) {
    case 'NETWORK_ERROR':
    return this.handleNetworkError(error, {
    requestFunc,
    retryCount,
    userMessage,
    fallbackAction
    });

    case 'SERVER_ERROR':
    return this.handleServerError(error, {
    requestFunc,
    retryCount,
    userMessage,
    fallbackAction
    });

    case 'RATE_LIMITED':
    return this.handleRateLimit(error, {
    requestFunc,
    retryCount,
    userMessage
    });

    default:
    return this.showUserError(error, userMessage, fallbackAction);
    }
    }
    }

    async handleNetworkError(error, options) {
    const { requestFunc, retryCount, userMessage, fallbackAction } = options;

    if (retryCount < this.maxRetries) {
    // 显示重试提示
    this.showToast({
    type: 'warning',
    message: `网络连接不稳定,${retryCount + 1}秒后重试…`,
    duration: retryCount + 1 * 1000
    });

    // 延迟重试
    await this.delay(this.retryDelays[retryCount]);

    return this.handleRequest(requestFunc, {
    …options,
    retryCount: retryCount + 1
    });
    }

    // 达到最大重试次数
    return this.showOfflineMode(error, userMessage, fallbackAction);
    }

    async handleServerError(error, options) {
    const { status, originalError } = error;

    if ([502, 503, 504].includes(status)) {
    // 临时性服务器错误,可以重试
    if (options.retryCount < 2) { // 服务器错误只重试2次
    await this.delay(this.retryDelays[options.retryCount]);

    return this.handleRequest(options.requestFunc, {
    …options,
    retryCount: options.retryCount + 1
    });
    }
    }

    // 永久性服务器错误或达到重试次数
    return this.showUserError(error, options.userMessage, options.fallbackAction);
    }

    async handleRateLimit(error, options) {
    const { retryAfter } = error;
    const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : 60000;

    this.showToast({
    type: 'info',
    message: `操作过于频繁,请${Math.ceil(waitTime / 1000)}秒后重试`,
    duration: 5000
    });

    await this.delay(waitTime);

    return this.handleRequest(options.requestFunc, {
    …options,
    retryCount: options.retryCount + 1
    });
    }

    showOfflineMode(error, userMessage, fallbackAction) {
    // 显示离线模式UI
    this.showOfflineUI();

    // 如果有备用操作,执行它
    if (fallbackAction) {
    return fallbackAction();
    }

    // 否则返回一个特殊的离线结果
    return {
    success: false,
    offline: true,
    message: '当前处于离线模式,数据将在网络恢复后同步'
    };
    }

    showUserError(error, userMessage, fallbackAction) {
    // 显示用户友好的错误提示
    this.showDialog({
    title: '操作失败',
    message: userMessage,
    type: 'error',
    buttons: [
    {
    text: '重试',
    action: () => {
    if (fallbackAction) {
    fallbackAction();
    } else {
    window.location.reload();
    }
    }
    },
    {
    text: '联系支持',
    action: () => {
    window.open('/contact', '_blank');
    }
    }
    ]
    });

    return {
    success: false,
    error: error.code || 'UNKNOWN_ERROR',
    message: userMessage
    };
    }

    logError(error) {
    // 发送错误到监控系统
    if (window.analytics) {
    window.analytics.track('error_occurred', {
    error_code: error.code,
    error_message: error.message,
    url: window.location.href,
    timestamp: new Date().toISOString()
    });
    }

    // 本地存储错误队列(用于离线时重试)
    this.errorQueue.push({
    error,
    timestamp: Date.now(),
    context: {
    url: window.location.href,
    userAgent: navigator.userAgent
    }
    });

    // 保持队列大小
    if (this.errorQueue.length > 100) {
    this.errorQueue.shift();
    }

    // 存储到localStorage
    localStorage.setItem('error_queue', JSON.stringify(this.errorQueue));
    }

    showToast(options) {
    // 实现Toast提示
    const toast = document.createElement('div');
    toast.className = `toast toast-${options.type}`;
    toast.textContent = options.message;
    toast.style.cssText = `
    position: fixed;
    top: 20px;
    right: 20px;
    padding: 12px 24px;
    border-radius: 6px;
    color: white;
    background: ${options.type === 'warning' ? '#f0ad4e' : '#5bc0de'};
    z-index: 10000;
    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
    animation: slideIn 0.3s ease;
    `;

    document.body.appendChild(toast);

    setTimeout(() => {
    toast.style.animation = 'slideOut 0.3s ease';
    setTimeout(() => toast.remove(), 300);
    }, options.duration || 3000);
    }

    delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
    }
    }

    // 使用示例
    const errorHandler = new ProgressiveErrorHandler();

    async function loadUserProfile() {
    return errorHandler.handleRequest(
    async () => {
    const response = await fetch('/api/user/profile');
    if (!response.ok) {
    throw {
    code: response.status >= 500 ? 'SERVER_ERROR' : 'CLIENT_ERROR',
    status: response.status,
    message: 'Failed to load profile'
    };
    }
    return response.json();
    },
    {
    userMessage: '加载个人资料失败',
    fallbackAction: () => {
    // 显示缓存的用户数据
    const cached = localStorage.getItem('cached_profile');
    if (cached) {
    return JSON.parse(cached);
    }
    }
    }
    );
    }

    31.3 性能优化与状态码

    31.3.1 缓存策略与状态码

    http

    # 缓存控制头示例
    GET /api/products/123 HTTP/1.1

    HTTP/1.1 200 OK
    Content-Type: application/json
    Cache-Control: public, max-age=3600, stale-while-revalidate=7200
    ETag: "abc123def456"
    Last-Modified: Mon, 15 Jan 2024 10:00:00 GMT

    {
    "id": "123",
    "name": "Product Name",
    "price": 99.99
    }

    Service Worker中的缓存策略

    javascript

    // service-worker.js
    const CACHE_NAME = 'app-v1';
    const STATIC_CACHE = 'static-v1';

    // 安装时缓存静态资源
    self.addEventListener('install', event => {
    event.waitUntil(
    caches.open(STATIC_CACHE).then(cache => {
    return cache.addAll([
    '/',
    '/index.html',
    '/styles/main.css',
    '/scripts/app.js',
    '/offline.html'
    ]);
    })
    );
    });

    // 拦截请求
    self.addEventListener('fetch', event => {
    // API请求 – 网络优先,失败时使用缓存
    if (event.request.url.includes('/api/')) {
    event.respondWith(
    fetch(event.request)
    .then(response => {
    // 缓存成功的响应
    if (response.status === 200) {
    const clonedResponse = response.clone();
    caches.open(CACHE_NAME).then(cache => {
    cache.put(event.request, clonedResponse);
    });
    }
    return response;
    })
    .catch(() => {
    // 网络失败,尝试从缓存获取
    return caches.match(event.request).then(cachedResponse => {
    if (cachedResponse) {
    // 添加缓存标识头
    const headers = new Headers(cachedResponse.headers);
    headers.set('X-Cache', 'HIT');

    return new Response(cachedResponse.body, {
    status: cachedResponse.status,
    statusText: cachedResponse.statusText,
    headers
    });
    }

    // 连缓存也没有,返回离线页面
    return caches.match('/offline.html');
    });
    })
    );
    } else {
    // 静态资源 – 缓存优先
    event.respondWith(
    caches.match(event.request).then(cachedResponse => {
    if (cachedResponse) {
    // 添加缓存标识
    const headers = new Headers(cachedResponse.headers);
    headers.set('X-Cache', 'HIT');

    return new Response(cachedResponse.body, {
    status: cachedResponse.status,
    statusText: cachedResponse.statusText,
    headers
    });
    }

    return fetch(event.request).then(response => {
    // 只缓存成功的响应
    if (response.status === 200) {
    const clonedResponse = response.clone();
    caches.open(STATIC_CACHE).then(cache => {
    cache.put(event.request, clonedResponse);
    });
    }
    return response;
    });
    })
    );
    }
    });

    // 激活时清理旧缓存
    self.addEventListener('activate', event => {
    event.waitUntil(
    caches.keys().then(cacheNames => {
    return Promise.all(
    cacheNames.map(cacheName => {
    if (cacheName !== CACHE_NAME && cacheName !== STATIC_CACHE) {
    return caches.delete(cacheName);
    }
    })
    );
    })
    );
    });

    31.3.2 预加载与预连接

    html

    <!– 文档头部添加预加载提示 –>
    <head>
    <!– 预加载关键CSS –>
    <link rel="preload" href="/styles/critical.css" as="style">

    <!– 预加载关键字体 –>
    <link rel="preload" href="/fonts/iconfont.woff2" as="font" type="font/woff2" crossorigin>

    <!– 预连接API域名 –>
    <link rel="preconnect" href="https://api.example.com">
    <link rel="dns-prefetch" href="https://api.example.com">

    <!– 预加载重要API端点 –>
    <link rel="prefetch" href="/api/user/profile" as="fetch" crossorigin>

    <!– 状态码相关的备用资源 –>
    <noscript>
    <!– 无JavaScript时的降级处理 –>
    <meta http-equiv="refresh" content="0;url=/noscript.html">
    </noscript>
    </head>

    31.4 安全最佳实践

    31.4.1 跨站请求伪造(CSRF)防护

    javascript

    // 服务端CSRF防护中间件
    const csrfProtection = (req, res, next) => {
    // 排除某些路由(如API文档)
    if (req.path.startsWith('/api-docs')) {
    return next();
    }

    // 安全方法不需要CSRF保护
    const safeMethods = ['GET', 'HEAD', 'OPTIONS'];
    if (safeMethods.includes(req.method)) {
    return next();
    }

    // 验证CSRF令牌
    const csrfToken = req.headers['x-csrf-token'] || req.body._csrf;
    const sessionToken = req.session.csrfToken;

    if (!csrfToken || csrfToken !== sessionToken) {
    // 记录安全事件
    logSecurityEvent({
    type: 'CSRF_ATTEMPT',
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    path: req.path,
    timestamp: new Date()
    });

    // 返回403状态码
    return res.status(403).json({
    error: {
    code: 'CSRF_TOKEN_INVALID',
    message: 'Invalid or missing CSRF token',
    documentation: 'https://api.example.com/docs/security#csrf-protection'
    }
    });
    }

    // 令牌验证成功,生成新令牌(双提交cookie模式)
    const newToken = generateCsrfToken();
    req.session.csrfToken = newToken;
    res.cookie('XSRF-TOKEN', newToken, {
    httpOnly: false, // 需要客户端JavaScript访问
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict'
    });

    next();
    };

    // 客户端自动发送CSRF令牌
    axios.interceptors.request.use(config => {
    const token = getCsrfToken(); // 从cookie或meta标签获取

    if (token && !config.headers['X-CSRF-Token']) {
    config.headers['X-CSRF-Token'] = token;
    }

    return config;
    });

    31.4.2 内容安全策略(CSP)与状态码

    javascript

    // Express中间件设置CSP
    app.use((req, res, next) => {
    const nonce = crypto.randomBytes(16).toString('base64');
    res.locals.cspNonce = nonce;

    res.setHeader(
    'Content-Security-Policy',
    `default-src 'self';
    script-src 'self' 'nonce-${nonce}' https://analytics.example.com;
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https://cdn.example.com;
    font-src 'self' https://fonts.googleapis.com;
    connect-src 'self' https://api.example.com;
    frame-ancestors 'none';
    form-action 'self';
    base-uri 'self';`
    );

    next();
    });

    // 在HTML模板中使用nonce
    app.get('/', (req, res) => {
    res.render('index', {
    nonce: res.locals.cspNonce
    });
    });

    html

    <!– 模板中使用nonce –>
    <!DOCTYPE html>
    <html>
    <head>
    <!– 内联样式仍然允许 –>
    <style>
    .error { color: red; }
    </style>
    </head>
    <body>
    <!– 使用nonce的内联脚本 –>
    <script nonce="{{nonce}}">
    window.config = {
    apiUrl: '{{apiUrl}}',
    userId: '{{userId}}'
    };
    </script>

    <!– 外部脚本 –>
    <script src="/app.js" nonce="{{nonce}}"></script>

    <!– 被CSP阻止的脚本 –>
    <script>
    // 这个脚本会被CSP阻止,因为没有nonce
    console.log('This will be blocked');
    </script>
    </body>
    </html>

    31.5 监控与日志

    31.5.1 结构化错误日志

    javascript

    // Winston日志配置
    const winston = require('winston');
    const { combine, timestamp, json, errors } = winston.format;

    const logger = winston.createLogger({
    level: process.env.LOG_LEVEL || 'info',
    format: combine(
    timestamp(),
    errors({ stack: true }),
    json()
    ),
    transports: [
    // 控制台输出(开发环境)
    new winston.transports.Console({
    format: winston.format.simple()
    }),

    // 错误日志文件
    new winston.transports.File({
    filename: 'logs/error.log',
    level: 'error',
    maxsize: 5242880, // 5MB
    maxFiles: 5
    }),

    // 访问日志文件
    new winston.transports.File({
    filename: 'logs/access.log',
    level: 'info',
    maxsize: 5242880,
    maxFiles: 10
    })
    ]
    });

    // Express访问日志中间件
    app.use((req, res, next) => {
    const startTime = Date.now();

    // 捕获响应完成事件
    res.on('finish', () => {
    const duration = Date.now() – startTime;

    logger.info('HTTP Request', {
    timestamp: new Date().toISOString(),
    method: req.method,
    url: req.originalUrl,
    statusCode: res.statusCode,
    statusMessage: res.statusMessage,
    duration,
    userAgent: req.get('User-Agent'),
    ip: req.ip,
    userId: req.user?.id || 'anonymous',
    requestId: req.id,
    // 特定状态码的额外信息
    …(res.statusCode >= 400 && {
    errorDetails: {
    message: res.locals.error?.message,
    stack: process.env.NODE_ENV === 'development'
    ? res.locals.error?.stack
    : undefined
    }
    }),
    …(res.statusCode === 429 && {
    rateLimit: {
    limit: req.rateLimit?.limit,
    remaining: req.rateLimit?.remaining,
    resetTime: req.rateLimit?.resetTime
    }
    })
    });
    });

    next();
    });

    // 错误处理中间件
    app.use((error, req, res, next) => {
    // 记录错误
    logger.error('Application Error', {
    timestamp: new Date().toISOString(),
    requestId: req.id,
    url: req.originalUrl,
    method: req.method,
    userId: req.user?.id,
    error: {
    name: error.name,
    message: error.message,
    stack: error.stack,
    code: error.code,
    statusCode: error.statusCode
    }
    });

    // 根据环境返回错误
    const isProduction = process.env.NODE_ENV === 'production';

    res.status(error.statusCode || 500).json({
    error: {
    code: error.code || 'INTERNAL_ERROR',
    message: isProduction ? 'Internal server error' : error.message,
    …(!isProduction && { stack: error.stack }),
    requestId: req.id,
    timestamp: new Date().toISOString()
    }
    });
    });

    31.5.2 实时监控仪表板

    javascript

    // WebSocket实时监控
    const WebSocket = require('ws');
    const wss = new WebSocket.Server({ port: 8080 });

    // 状态码统计
    const statusStats = {
    '1xx': 0, '2xx': 0, '3xx': 0, '4xx': 0, '5xx': 0,
    details: {}
    };

    // 实时错误队列
    const recentErrors = [];

    wss.on('connection', (ws) => {
    // 发送当前状态
    ws.send(JSON.stringify({
    type: 'INITIAL_STATS',
    data: statusStats
    }));

    // 发送最近错误
    ws.send(JSON.stringify({
    type: 'RECENT_ERRORS',
    data: recentErrors.slice(-50) // 最近50个错误
    }));
    });

    // 更新统计信息
    function updateStats(statusCode) {
    const statusClass = `${Math.floor(statusCode / 100)}xx`;
    statusStats[statusClass]++;

    if (!statusStats.details[statusCode]) {
    statusStats.details[statusCode] = 0;
    }
    statusStats.details[statusCode]++;

    // 广播更新
    broadcast({
    type: 'STATS_UPDATE',
    data: {
    statusClass,
    statusCode,
    counts: statusStats
    }
    });
    }

    // 添加错误到队列
    function addError(error) {
    recentErrors.push({
    …error,
    timestamp: new Date().toISOString()
    });

    // 保持队列大小
    if (recentErrors.length > 1000) {
    recentErrors.shift();
    }

    // 广播新错误
    broadcast({
    type: 'NEW_ERROR',
    data: error
    });
    }

    // 广播消息给所有客户端
    function broadcast(message) {
    wss.clients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
    client.send(JSON.stringify(message));
    }
    });
    }

    // 每小时重置计数器
    setInterval(() => {
    statusStats['1xx'] = 0;
    statusStats['2xx'] = 0;
    statusStats['3xx'] = 0;
    statusStats['4xx'] = 0;
    statusStats['5xx'] = 0;
    statusStats.details = {};

    broadcast({
    type: 'STATS_RESET',
    data: { timestamp: new Date().toISOString() }
    });
    }, 60 * 60 * 1000);

    31.6 渐进式Web应用(PWA)中的状态码

    31.6.1 离线状态处理

    javascript

    // 离线检测与服务状态管理
    class NetworkStatus {
    constructor() {
    this.isOnline = navigator.onLine;
    this.isServerReachable = true;
    this.lastCheck = null;
    this.listeners = [];

    this.init();
    }

    init() {
    // 监听网络状态变化
    window.addEventListener('online', () => this.handleOnline());
    window.addEventListener('offline', () => this.handleOffline());

    // 定期检查服务器可用性
    setInterval(() => this.checkServerHealth(), 30000);
    }

    async checkServerHealth() {
    try {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 5000);

    const response = await fetch('/health', {
    method: 'HEAD',
    signal: controller.signal,
    cache: 'no-store'
    });

    clearTimeout(timeoutId);

    this.isServerReachable = response.ok;
    this.lastCheck = new Date();

    this.notifyListeners();
    } catch (error) {
    this.isServerReachable = false;
    this.lastCheck = new Date();
    this.notifyListeners();
    }
    }

    handleOnline() {
    this.isOnline = true;
    this.notifyListeners();

    // 在线时立即检查服务器
    this.checkServerHealth();

    // 同步离线期间的操作
    this.syncOfflineActions();
    }

    handleOffline() {
    this.isOnline = false;
    this.notifyListeners();

    // 显示离线提示
    this.showOfflineNotification();
    }

    async syncOfflineActions() {
    const offlineActions = this.getOfflineActions();

    for (const action of offlineActions) {
    try {
    await this.retryAction(action);
    this.removeOfflineAction(action.id);
    } catch (error) {
    console.error('Failed to sync action:', action, error);
    }
    }
    }

    getOfflineActions() {
    const actions = localStorage.getItem('offline_actions');
    return actions ? JSON.parse(actions) : [];
    }

    saveOfflineAction(action) {
    const actions = this.getOfflineActions();
    actions.push({
    …action,
    id: Date.now().toString(),
    timestamp: new Date().toISOString()
    });
    localStorage.setItem('offline_actions', JSON.stringify(actions));
    }

    removeOfflineAction(id) {
    const actions = this.getOfflineActions();
    const filtered = actions.filter(a => a.id !== id);
    localStorage.setItem('offline_actions', JSON.stringify(filtered));
    }

    async retryAction(action) {
    // 根据action类型重试请求
    const { type, data, url, method } = action;

    const response = await fetch(url, {
    method,
    headers: {
    'Content-Type': 'application/json',
    'X-Offline-Retry': 'true',
    'X-Original-Timestamp': data.timestamp
    },
    body: JSON.stringify(data)
    });

    if (!response.ok) {
    throw new Error(`Retry failed: ${response.status}`);
    }

    return response.json();
    }

    addListener(callback) {
    this.listeners.push(callback);
    }

    removeListener(callback) {
    this.listeners = this.listeners.filter(l => l !== callback);
    }

    notifyListeners() {
    const status = this.getStatus();
    this.listeners.forEach(callback => callback(status));
    }

    getStatus() {
    return {
    isOnline: this.isOnline,
    isServerReachable: this.isServerReachable,
    lastCheck: this.lastCheck,
    canPerformAction: this.isOnline && this.isServerReachable,
    isOffline: !this.isOnline,
    isUnreachable: this.isOnline && !this.isServerReachable
    };
    }

    showOfflineNotification() {
    // 显示离线通知
    if ('Notification' in window && Notification.permission === 'granted') {
    new Notification('应用已离线', {
    body: '您当前处于离线状态,某些功能可能受限',
    icon: '/icons/offline.png',
    tag: 'network-status'
    });
    }

    // 更新UI状态
    document.dispatchEvent(new CustomEvent('networkStatusChange', {
    detail: this.getStatus()
    }));
    }
    }

    // 使用示例
    const networkStatus = new NetworkStatus();

    // 在需要网络请求的地方
    async function submitForm(data) {
    const status = networkStatus.getStatus();

    if (!status.canPerformAction) {
    // 保存到离线队列
    networkStatus.saveOfflineAction({
    type: 'form_submission',
    url: '/api/submit',
    method: 'POST',
    data
    });

    // 显示离线提示
    return {
    success: false,
    offline: true,
    message: '当前离线,数据已保存,将在恢复网络后提交'
    };
    }

    try {
    const response = await fetch('/api/submit', {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
    });

    if (!response.ok) {
    throw new Error(`Submission failed: ${response.status}`);
    }

    return response.json();
    } catch (error) {
    if (!navigator.onLine) {
    // 网络错误,保存到离线队列
    networkStatus.saveOfflineAction({
    type: 'form_submission',
    url: '/api/submit',
    method: 'POST',
    data
    });

    return {
    success: false,
    offline: true,
    message: '网络连接失败,数据已保存到本地'
    };
    }

    throw error;
    }
    }

    31.7 测试策略

    31.7.1 状态码测试套件

    javascript

    // Jest状态码测试示例
    describe('API Status Codes', () => {
    describe('GET /api/users', () => {
    test('should return 200 with user list for authenticated admin', async () => {
    const response = await request(app)
    .get('/api/users')
    .set('Authorization', `Bearer ${adminToken}`);

    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('data');
    expect(Array.isArray(response.body.data)).toBe(true);
    });

    test('should return 401 for unauthenticated requests', async () => {
    const response = await request(app)
    .get('/api/users');

    expect(response.status).toBe(401);
    expect(response.body.error.code).toBe('UNAUTHORIZED');
    });

    test('should return 403 for non-admin users', async () => {
    const response = await request(app)
    .get('/api/users')
    .set('Authorization', `Bearer ${userToken}`);

    expect(response.status).toBe(403);
    expect(response.body.error.code).toBe('INSUFFICIENT_PERMISSIONS');
    });
    });

    describe('POST /api/users', () => {
    test('should return 201 on successful creation', async () => {
    const userData = {
    name: 'Test User',
    email: 'test@example.com',
    password: 'securepassword123'
    };

    const response = await request(app)
    .post('/api/users')
    .send(userData);

    expect(response.status).toBe(201);
    expect(response.headers.location).toMatch(/\\/api\\/users\\/\\w+/);
    expect(response.body).toHaveProperty('id');
    expect(response.body.email).toBe(userData.email);
    });

    test('should return 400 for invalid email', async () => {
    const userData = {
    name: 'Test User',
    email: 'invalid-email',
    password: 'password123'
    };

    const response = await request(app)
    .post('/api/users')
    .send(userData);

    expect(response.status).toBe(400);
    expect(response.body.error.code).toBe('VALIDATION_ERROR');
    expect(response.body.error.details[0].field).toBe('email');
    });

    test('should return 409 for duplicate email', async () => {
    // 先创建一个用户
    await request(app)
    .post('/api/users')
    .send({
    name: 'Existing User',
    email: 'duplicate@example.com',
    password: 'password123'
    });

    // 尝试用相同邮箱创建
    const response = await request(app)
    .post('/api/users')
    .send({
    name: 'New User',
    email: 'duplicate@example.com',
    password: 'newpassword123'
    });

    expect(response.status).toBe(409);
    expect(response.body.error.code).toBe('DUPLICATE_EMAIL');
    });
    });

    describe('Error handling', () => {
    test('should return 500 for unhandled exceptions', async () => {
    // 模拟数据库错误
    jest.spyOn(UserModel, 'findAll').mockImplementation(() => {
    throw new Error('Database connection failed');
    });

    const response = await request(app)
    .get('/api/users')
    .set('Authorization', `Bearer ${adminToken}`);

    expect(response.status).toBe(500);
    expect(response.body.error.code).toBe('INTERNAL_SERVER_ERROR');

    // 验证错误被记录
    expect(logger.error).toHaveBeenCalled();
    });

    test('should return 503 during maintenance', async () => {
    // 启用维护模式
    process.env.MAINTENANCE_MODE = 'true';

    const response = await request(app)
    .get('/api/users')
    .set('Authorization', `Bearer ${adminToken}`);

    expect(response.status).toBe(503);
    expect(response.headers['retry-after']).toBeDefined();
    expect(response.body.error.code).toBe('MAINTENANCE_MODE');

    // 清理
    delete process.env.MAINTENANCE_MODE;
    });
    });
    });

    // E2E测试 – Cypress
    describe('Status Code E2E Tests', () => {
    beforeEach(() => {
    cy.intercept('GET', '/api/user/profile').as('getProfile');
    });

    it('should handle 401 gracefully', () => {
    // 模拟401响应
    cy.intercept('GET', '/api/user/profile', {
    statusCode: 401,
    body: {
    error: {
    code: 'UNAUTHORIZED',
    message: 'Authentication required'
    }
    }
    }).as('unauthorized');

    cy.visit('/profile');
    cy.wait('@unauthorized');

    // 验证被重定向到登录页
    cy.url().should('include', '/login');
    cy.get('.error-message').should('contain', '请重新登录');
    });

    it('should handle 404 gracefully', () => {
    cy.visit('/nonexistent-page');

    // 验证显示404页面
    cy.get('h1').should('contain', '404');
    cy.get('a').contains('返回首页').should('be.visible');
    });

    it('should retry on 503', () => {
    let requestCount = 0;

    cy.intercept('GET', '/api/data', (req) => {
    requestCount++;

    if (requestCount === 1) {
    req.reply({
    statusCode: 503,
    headers: {
    'retry-after': '1'
    }
    });
    } else {
    req.reply({
    statusCode: 200,
    body: { data: 'success' }
    });
    }
    }).as('getData');

    cy.visit('/dashboard');
    cy.wait('@getData');

    // 验证最终成功
    cy.get('.data-content').should('contain', 'success');
    });
    });

    31.8 性能监控与告警

    31.8.1 基于状态码的监控指标

    yaml

    # Prometheus规则配置
    groups:
    – name: http_status_alerts
    rules:
    # 高错误率告警
    – alert: HighErrorRate
    expr: |
    rate(http_requests_total{status=~"5.."}[5m]) /
    rate(http_requests_total[5m]) > 0.05
    for: 5m
    labels:
    severity: critical
    annotations:
    summary: "High 5xx error rate detected"
    description: |
    Error rate is {{ $value }}% for endpoint {{ $labels.endpoint }}.
    This exceeds the 5% threshold.

    # 4xx错误率增加
    – alert: HighClientErrorRate
    expr: |
    rate(http_requests_total{status=~"4.."}[5m]) /
    rate(http_requests_total[5m]) > 0.10
    for: 10m
    labels:
    severity: warning
    annotations:
    summary: "High 4xx error rate detected"
    description: |
    Client error rate is {{ $value }}% for endpoint {{ $labels.endpoint }}.
    This may indicate issues with client requests.

    # 特定端点不可用
    – alert: EndpointDown
    expr: |
    sum(rate(http_requests_total{endpoint="/api/health"}[5m])) == 0
    for: 2m
    labels:
    severity: critical
    annotations:
    summary: "Health endpoint is down"
    description: "The health endpoint has not received any requests in the last 2 minutes."

    # 响应时间增加与状态码关联
    – alert: SlowResponsesWithErrors
    expr: |
    histogram_quantile(0.95,
    rate(http_request_duration_seconds_bucket{status=~"5.."}[5m])
    ) > 2
    for: 5m
    labels:
    severity: warning
    annotations:
    summary: "Slow responses with server errors"
    description: |
    95th percentile response time for error responses is {{ $value }}s.
    Slow responses with errors may indicate deeper issues.

    31.8.2 分布式追踪中的状态码

    javascript

    // OpenTelemetry追踪配置
    const { NodeTracerProvider } = require('@opentelemetry/node');
    const { SimpleSpanProcessor } = require('@opentelemetry/tracing');
    const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');
    const { ZipkinExporter } = require('@opentelemetry/exporter-zipkin');

    const tracerProvider = new NodeTracerProvider();
    tracerProvider.register();

    // Jaeger导出器
    const jaegerExporter = new JaegerExporter({
    serviceName: 'api-service',
    tags: [
    { key: 'environment', value: process.env.NODE_ENV }
    ]
    });

    // 添加跨度处理器
    tracerProvider.addSpanProcessor(
    new SimpleSpanProcessor(jaegerExporter)
    );

    // Express中间件
    const { expressMiddleware } = require('@opentelemetry/express');

    app.use(expressMiddleware('api-service'));

    // 自定义跨度属性
    app.use((req, res, next) => {
    const span = tracerProvider.getTracer('api').getCurrentSpan();

    if (span) {
    // 添加请求信息
    span.setAttribute('http.method', req.method);
    span.setAttribute('http.url', req.url);
    span.setAttribute('http.user_agent', req.get('User-Agent'));

    // 响应完成后添加状态码
    const originalEnd = res.end;
    res.end = function(…args) {
    span.setAttribute('http.status_code', res.statusCode);
    span.setAttribute('http.status_text', res.statusMessage);

    // 错误相关属性
    if (res.statusCode >= 400) {
    span.setAttribute('error', true);
    span.setAttribute('error.type', `HTTP_${res.statusCode}`);

    if (res.locals.error) {
    span.setAttribute('error.message', res.locals.error.message);
    span.setAttribute('error.stack', res.locals.error.stack);
    }
    }

    originalEnd.apply(this, args);
    };
    }

    next();
    });

    // 业务逻辑中的追踪
    async function processOrder(orderId) {
    const tracer = tracerProvider.getTracer('order-service');
    const span = tracer.startSpan('processOrder', {
    attributes: {
    'order.id': orderId,
    'component': 'order-processor'
    }
    });

    try {
    const order = await fetchOrder(orderId);
    span.addEvent('order_fetched');

    if (!order) {
    span.setAttribute('error', true);
    span.setAttribute('error.type', 'ORDER_NOT_FOUND');
    throw new Error('Order not found');
    }

    const result = await validateOrder(order);
    span.addEvent('order_validated');

    return result;
    } catch (error) {
    // 记录错误
    span.recordException(error);
    span.setStatus({
    code: 2, // ERROR
    message: error.message
    });

    throw error;
    } finally {
    span.end();
    }
    }

    31.9 国际化与本地化

    31.9.1 多语言错误消息

    javascript

    // 错误消息国际化
    class LocalizedError extends Error {
    constructor(messageKey, params = {}, statusCode = 500) {
    super(messageKey);
    this.name = 'LocalizedError';
    this.messageKey = messageKey;
    this.params = params;
    this.statusCode = statusCode;
    this.timestamp = new Date();
    }

    getLocalizedMessage(locale = 'zh-CN') {
    const messages = {
    'zh-CN': {
    'USER_NOT_FOUND': '用户不存在',
    'INVALID_CREDENTIALS': '用户名或密码错误',
    'EMAIL_ALREADY_EXISTS': '邮箱已被注册',
    'PERMISSION_DENIED': '权限不足',
    'NETWORK_ERROR': '网络连接失败,请检查网络设置',
    'SERVER_ERROR': '服务器错误,请稍后重试',
    'VALIDATION_ERROR': '输入验证失败',
    'RATE_LIMITED': '请求过于频繁,请稍后重试',
    'MAINTENANCE_MODE': '系统维护中,请稍后访问'
    },
    'en-US': {
    'USER_NOT_FOUND': 'User not found',
    'INVALID_CREDENTIALS': 'Invalid username or password',
    'EMAIL_ALREADY_EXISTS': 'Email already exists',
    'PERMISSION_DENIED': 'Permission denied',
    'NETWORK_ERROR': 'Network connection failed',
    'SERVER_ERROR': 'Server error, please try again later',
    'VALIDATION_ERROR': 'Validation failed',
    'RATE_LIMITED': 'Too many requests, please try again later',
    'MAINTENANCE_MODE': 'System under maintenance'
    },
    'ja-JP': {
    'USER_NOT_FOUND': 'ユーザーが見つかりません',
    'INVALID_CREDENTIALS': 'ユーザー名またはパスワードが間違っています',
    'EMAIL_ALREADY_EXISTS': 'メールアドレスは既に登録されています',
    'PERMISSION_DENIED': '権限が不足しています',
    'NETWORK_ERROR': 'ネットワーク接続に失敗しました',
    'SERVER_ERROR': 'サーバーエラーが発生しました',
    'VALIDATION_ERROR': '入力検証に失敗しました',
    'RATE_LIMITED': 'リクエストが多すぎます。後でもう一度お試しください',
    'MAINTENANCE_MODE': 'システムメンテナンス中です'
    }
    };

    const localeMessages = messages[locale] || messages['en-US'];
    let message = localeMessages[this.messageKey] || this.messageKey;

    // 替换参数
    Object.keys(this.params).forEach(key => {
    message = message.replace(`{${key}}`, this.params[key]);
    });

    return message;
    }
    }

    // 使用示例
    app.get('/api/users/:id', async (req, res, next) => {
    try {
    const user = await User.findById(req.params.id);

    if (!user) {
    throw new LocalizedError('USER_NOT_FOUND',
    { userId: req.params.id },
    404
    );
    }

    res.json(user);
    } catch (error) {
    next(error);
    }
    });

    // 错误处理中间件
    app.use((error, req, res, next) => {
    // 获取客户端首选语言
    const acceptLanguage = req.get('Accept-Language');
    const locale = acceptLanguage?.split(',')[0] || 'zh-CN';

    if (error instanceof LocalizedError) {
    return res.status(error.statusCode).json({
    error: {
    code: error.messageKey,
    message: error.getLocalizedMessage(locale),
    params: error.params,
    timestamp: error.timestamp.toISOString(),
    documentation: `https://api.example.com/docs/errors/${error.messageKey}`
    }
    });
    }

    // 处理其他错误
    const isProduction = process.env.NODE_ENV === 'production';

    res.status(error.statusCode || 500).json({
    error: {
    code: 'INTERNAL_ERROR',
    message: isProduction
    ? new LocalizedError('SERVER_ERROR').getLocalizedMessage(locale)
    : error.message,
    …(!isProduction && { stack: error.stack }),
    timestamp: new Date().toISOString()
    }
    });
    });

    31.10 总结与最佳实践清单

    31.10.1 Web开发状态码最佳实践总结
  • 语义正确性

    • 为每种情况选择最准确的状态码

    • 避免滥用200 OK表示所有情况

    • 使用422代替400进行输入验证错误

  • 用户体验

    • 提供用户友好的错误页面

    • 包含有用的操作建议

    • 支持多语言错误消息

  • 安全性

    • 避免在错误响应中泄露敏感信息

    • 实施适当的安全头

    • 防止通过错误信息进行信息收集

  • 可观测性

    • 记录结构化的错误日志

    • 监控状态码分布

    • 设置基于状态码的告警

  • 性能

    • 利用缓存相关状态码(304)

    • 实现适当的重试机制

    • 监控慢速响应与错误的关系

  • 兼容性

    • 考虑旧客户端兼容性

    • 提供适当的降级方案

    • 支持渐进式增强

  • 测试

    • 为所有状态码编写测试

    • 包括错误场景测试

    • 实施端到端错误处理测试

  • 31.10.2 完整的状态码处理流程图

    text

    ┌─────────────────┐
    │ 收到请求 │
    └────────┬────────┘


    ┌─────────────────┐
    │ 验证请求 │
    │ – 认证 │
    │ – 授权 │
    │ – 输入验证 │
    └────────┬────────┘

    ┌────┴────┐
    │ │
    ▼ ▼
    401/403 400/422
    │ │
    │ │
    ▼ ▼
    返回错误 返回错误
    │ │
    │ │
    └────┬────┘


    ┌─────────────────┐
    │ 处理业务逻辑 │
    └────────┬────────┘

    ┌────┴────┐
    │ │
    ▼ ▼
    404 409
    │ │
    │ │
    ▼ ▼
    返回错误 返回错误
    │ │
    │ │
    └────┬────┘


    ┌─────────────────┐
    │ 执行操作 │
    └────────┬────────┘

    ┌────┴────┐
    │ │
    ▼ ▼
    201 200
    │ │
    │ │
    ▼ ▼
    创建成功 操作成功
    │ │
    │ │
    └────┬────┘


    ┌─────────────────┐
    │ 异常处理 │
    │ – 记录错误 │
    │ – 返回500 │
    └─────────────────┘


    ┌─────────────────┐
    │ 监控与告警 │
    │ – 更新指标 │
    │ – 触发告警 │
    └─────────────────┘

    第32章:移动应用与状态码处理

    32.1 移动应用网络环境的特殊性

    32.1.1 移动网络的特点与挑战

    移动应用面临着独特的网络环境挑战:

  • 网络不稳定性

    • 移动网络切换(4G/5G/Wi-Fi)

    • 信号强弱变化

    • 频繁的断线重连

  • 资源限制

    • 电池电量有限

    • 内存和存储限制

    • 数据流量成本

  • 用户体验期望

    • 快速响应需求

    • 离线功能支持

    • 无缝的用户体验

  • 32.1.2 移动应用的状态码处理原则

    swift

    // iOS网络层设计
    class NetworkManager {
    // 移动网络特有的状态码处理
    enum MobileError: Error {
    case networkUnavailable
    case poorConnection
    case rateLimited(retryAfter: TimeInterval)
    case requestTimeout
    case serverUnavailable
    case dataLimitExceeded
    }

    // 网络状态监测
    private let monitor = NWPathMonitor()
    private var currentNetworkType: NetworkType = .unknown

    func setupNetworkMonitoring() {
    monitor.pathUpdateHandler = { [weak self] path in
    if path.status == .satisfied {
    if path.isExpensive {
    self?.currentNetworkType = .cellular
    self?.applyCellularNetworkPolicies()
    } else {
    self?.currentNetworkType = .wifi
    }
    } else {
    self?.currentNetworkType = .disconnected
    }
    }
    monitor.start(queue: DispatchQueue.global())
    }

    private func applyCellularNetworkPolicies() {
    // 蜂窝网络下的策略
    // 1. 降低图片质量
    // 2. 减少预加载
    // 3. 压缩数据
    // 4. 限制大文件下载
    }
    }

    32.2 移动端状态码处理策略

    32.2.1 智能重试机制

    kotlin

    // Android重试策略实现
    class SmartRetryInterceptor : Interceptor {
    private val maxRetries = 3
    private val retryDelayBase = 1000L // 1秒

    override fun intercept(chain: Interceptor.Chain): Response {
    val request = chain.request()
    var response: Response? = null
    var lastException: IOException? = null

    for (attempt in 1..maxRetries) {
    try {
    response = chain.proceed(request)

    // 根据状态码决定是否重试
    when (response.code) {
    in 500..599 -> {
    // 服务器错误,需要重试
    if (attempt < maxRetries) {
    response.close()
    delay(calculateBackoff(attempt))
    continue
    }
    }
    408 -> { // 请求超时
    if (attempt < maxRetries) {
    response.close()
    delay(calculateBackoff(attempt))
    continue
    }
    }
    429 -> { // 请求过多
    val retryAfter = response.headers["Retry-After"]?.toLongOrNull()
    if (retryAfter != null) {
    response.close()
    delay(retryAfter * 1000)
    continue
    }
    }
    503 -> { // 服务不可用
    val retryAfter = response.headers["Retry-After"]?.toLongOrNull()
    if (retryAfter != null && attempt < maxRetries) {
    response.close()
    delay(retryAfter * 1000)
    continue
    }
    }
    }

    return response

    } catch (e: IOException) {
    lastException = e

    // 网络异常,根据网络类型决定是否重试
    if (isNetworkErrorRecoverable(e) && attempt < maxRetries) {
    delay(calculateBackoff(attempt))
    continue
    }
    break
    }
    }

    throw lastException ?: IOException("Request failed after $maxRetries attempts")
    }

    private fun calculateBackoff(attempt: Int): Long {
    // 指数退避算法
    return retryDelayBase * (2.0.pow(attempt – 1).toLong())
    }

    private fun isNetworkErrorRecoverable(e: IOException): Boolean {
    return when {
    e is SocketTimeoutException -> true
    e is ConnectException -> true
    e.message?.contains("ETIMEDOUT") == true -> true
    e.message?.contains("ENETUNREACH") == true -> false // 网络不可达,不重试
    else -> true
    }
    }

    private fun delay(millis: Long) {
    try {
    Thread.sleep(millis)
    } catch (e: InterruptedException) {
    Thread.currentThread().interrupt()
    }
    }
    }

    32.2.2 离线优先架构

    dart

    // Flutter离线优先实现
    class OfflineFirstRepository {
    final LocalDataSource local;
    final RemoteDataSource remote;
    final Connectivity connectivity;

    OfflineFirstRepository({
    required this.local,
    required this.remote,
    required this.connectivity,
    });

    Future<Result<T>> fetchData<T>({
    required String key,
    required Future<T> Function() remoteFetch,
    Duration cacheDuration = const Duration(hours: 1),
    }) async {
    // 1. 首先尝试从缓存获取
    final cached = await local.get<T>(key);
    if (cached != null && !isCacheExpired(cached, cacheDuration)) {
    return Result.success(cached.data, source: DataSource.cache);
    }

    // 2. 检查网络连接
    final isConnected = await connectivity.checkConnection();

    if (!isConnected) {
    // 离线状态,返回缓存数据或错误
    if (cached != null) {
    return Result.success(
    cached.data,
    source: DataSource.cache,
    isStale: true,
    );
    } else {
    return Result.error(
    error: NetworkError.offline(),
    source: DataSource.none,
    );
    }
    }

    try {
    // 3. 网络可用,从远程获取
    final remoteData = await remoteFetch();

    // 4. 更新缓存
    await local.save(
    key: key,
    data: remoteData,
    timestamp: DateTime.now(),
    );

    return Result.success(remoteData, source: DataSource.remote);

    } catch (error) {
    // 5. 网络请求失败,回退到缓存
    if (cached != null) {
    return Result.success(
    cached.data,
    source: DataSource.cache,
    isStale: true,
    error: error,
    );
    } else {
    return Result.error(
    error: error,
    source: DataSource.none,
    );
    }
    }
    }

    Future<Result<T>> postData<T>({
    required String endpoint,
    required dynamic data,
    required T Function(dynamic) fromJson,
    }) async {
    final isConnected = await connectivity.checkConnection();

    if (!isConnected) {
    // 离线状态,保存到本地队列
    final operation = OfflineOperation(
    id: Uuid().v4(),
    type: OperationType.post,
    endpoint: endpoint,
    data: data,
    createdAt: DateTime.now(),
    status: OfflineStatus.pending,
    );

    await local.saveOfflineOperation(operation);

    return Result.success(
    fromJson(data), // 乐观更新
    source: DataSource.local,
    isOffline: true,
    offlineOperationId: operation.id,
    );
    }

    try {
    // 在线状态,直接发送
    final response = await remote.post(endpoint, data);

    if (response.statusCode == 201 || response.statusCode == 200) {
    final result = fromJson(response.data);
    return Result.success(result, source: DataSource.remote);
    } else {
    throw HttpException(response.statusCode, response.data);
    }

    } catch (error) {
    // 网络错误,保存到离线队列
    if (error is SocketException || error is TimeoutException) {
    final operation = OfflineOperation(
    id: Uuid().v4(),
    type: OperationType.post,
    endpoint: endpoint,
    data: data,
    createdAt: DateTime.now(),
    status: OfflineStatus.failed,
    lastError: error.toString(),
    );

    await local.saveOfflineOperation(operation);

    return Result.success(
    fromJson(data), // 乐观更新
    source: DataSource.local,
    isOffline: true,
    offlineOperationId: operation.id,
    error: error,
    );
    }

    rethrow;
    }
    }

    Future<void> syncOfflineOperations() async {
    final operations = await local.getPendingOperations();

    for (final operation in operations) {
    try {
    dynamic response;

    switch (operation.type) {
    case OperationType.post:
    response = await remote.post(operation.endpoint, operation.data);
    break;
    case OperationType.put:
    response = await remote.put(operation.endpoint, operation.data);
    break;
    case OperationType.delete:
    response = await remote.delete(operation.endpoint);
    break;
    }

    // 同步成功,标记为已完成
    await local.updateOperationStatus(
    operation.id,
    OfflineStatus.completed,
    );

    } catch (error) {
    // 同步失败,更新错误信息
    await local.updateOperationError(
    operation.id,
    error.toString(),
    );

    // 根据错误类型决定是否继续重试
    if (error is HttpException && error.statusCode >= 500) {
    // 服务器错误,稍后重试
    continue;
    } else if (error is SocketException) {
    // 网络错误,停止同步
    break;
    }
    }
    }
    }
    }

    32.3 移动端状态码的UI反馈

    32.3.1 优雅的错误提示

    swift

    // iOS错误提示组件
    class ErrorToastView: UIView {
    private let titleLabel = UILabel()
    private let messageLabel = UILabel()
    private let iconImageView = UIImageView()
    private let actionButton = UIButton()
    private var actionHandler: (() -> Void)?

    enum ErrorType {
    case networkError
    case serverError
    case clientError(Int)
    case offline
    case timeout
    }

    static func show(
    for error: Error,
    in viewController: UIViewController,
    autoDismiss: Bool = true
    ) {
    let errorView = ErrorToastView()
    errorView.configure(with: error)

    // 添加到视图
    viewController.view.addSubview(errorView)

    // 动画显示
    UIView.animate(withDuration: 0.3) {
    errorView.alpha = 1
    errorView.transform = .identity
    }

    if autoDismiss {
    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    errorView.dismiss()
    }
    }
    }

    private func configure(with error: Error) {
    var title = "错误"
    var message = error.localizedDescription
    var iconName = "exclamationmark.circle"
    var actionTitle: String? = nil

    if let httpError = error as? HTTPError {
    switch httpError.statusCode {
    case 400:
    title = "请求错误"
    message = "请检查输入内容"
    iconName = "pencil.circle"

    case 401:
    title = "登录过期"
    message = "请重新登录"
    iconName = "person.crop.circle.badge.exclamationmark"
    actionTitle = "重新登录"
    actionHandler = { [weak self] in
    self?.navigateToLogin()
    }

    case 403:
    title = "权限不足"
    message = "您没有权限执行此操作"
    iconName = "lock.circle"

    case 404:
    title = "内容不存在"
    message = "您访问的内容可能已被删除"
    iconName = "questionmark.circle"

    case 408:
    title = "请求超时"
    message = "网络连接较慢,请稍后重试"
    iconName = "clock.badge.exclamationmark"
    actionTitle = "重试"
    actionHandler = { [weak self] in
    self?.retryLastRequest()
    }

    case 429:
    title = "请求过于频繁"
    message = "请稍后再试"
    iconName = "hand.raised.circle"

    case 500..<600:
    title = "服务器错误"
    message = "服务器暂时不可用,请稍后重试"
    iconName = "server.rack"
    actionTitle = "重试"
    actionHandler = { [weak self] in
    self?.retryLastRequest()
    }

    default:
    break
    }

    } else if let networkError = error as? NetworkError {
    switch networkError {
    case .noConnection:
    title = "网络连接失败"
    message = "请检查网络设置"
    iconName = "wifi.exclamationmark"
    actionTitle = "设置"
    actionHandler = { [weak self] in
    self?.openNetworkSettings()
    }

    case .timeout:
    title = "连接超时"
    message = "网络响应缓慢,请检查网络"
    iconName = "clock.badge.exclamationmark"

    case .cellularDataRestricted:
    title = "蜂窝数据限制"
    message = "当前设置限制了蜂窝数据使用"
    iconName = "cellularbars"
    actionTitle = "设置"
    actionHandler = { [weak self] in
    self?.openAppSettings()
    }
    }
    }

    // 配置UI
    titleLabel.text = title
    messageLabel.text = message
    iconImageView.image = UIImage(systemName: iconName)

    if let actionTitle = actionTitle {
    actionButton.setTitle(actionTitle, for: .normal)
    actionButton.isHidden = false
    } else {
    actionButton.isHidden = true
    }
    }

    private func navigateToLogin() {
    // 导航到登录页面
    dismiss()
    }

    private func retryLastRequest() {
    // 重试最后一次请求
    dismiss()
    }

    private func openNetworkSettings() {
    if let url = URL(string: "App-Prefs:root=WIFI") {
    UIApplication.shared.open(url)
    }
    dismiss()
    }

    private func dismiss() {
    UIView.animate(withDuration: 0.3, animations: {
    self.alpha = 0
    self.transform = CGAffineTransform(translationX: 0, y: -20)
    }) { _ in
    self.removeFromSuperview()
    }
    }
    }

    32.3.2 状态码驱动的UI状态

    kotlin

    // Android Compose状态管理
    @Composable
    fun DataScreen(
    viewModel: DataViewModel = viewModel()
    ) {
    val uiState by viewModel.uiState.collectAsState()

    Box(modifier = Modifier.fillMaxSize()) {
    when (val state = uiState) {
    is DataUiState.Loading -> {
    LoadingView()
    }

    is DataUiState.Success -> {
    DataListView(data = state.data)
    }

    is DataUiState.Error -> {
    when (state.error) {
    is HttpException -> {
    when (state.error.code()) {
    401 -> {
    AuthErrorView(
    message = "登录已过期",
    onRetry = { viewModel.retry() },
    onLogin = { viewModel.navigateToLogin() }
    )
    }
    403 -> {
    PermissionErrorView(
    message = "权限不足",
    onRequestPermission = { viewModel.requestPermission() }
    )
    }
    404 -> {
    NotFoundView(
    message = "内容不存在",
    onGoBack = { viewModel.navigateBack() }
    )
    }
    408, 504 -> {
    TimeoutErrorView(
    message = "请求超时",
    onRetry = { viewModel.retry() }
    )
    }
    429 -> {
    RateLimitView(
    message = "请求过于频繁",
    retryAfter = state.error.getRetryAfter(),
    onRetry = { viewModel.retry() }
    )
    }
    in 500..599 -> {
    ServerErrorView(
    message = "服务器错误",
    onRetry = { viewModel.retry() },
    onRefresh = { viewModel.refresh() }
    )
    }
    else -> {
    GenericErrorView(
    error = state.error,
    onRetry = { viewModel.retry() }
    )
    }
    }
    }

    is IOException -> {
    NetworkErrorView(
    message = "网络连接失败",
    onRetry = { viewModel.retry() },
    onCheckNetwork = { viewModel.openNetworkSettings() }
    )
    }

    else -> {
    GenericErrorView(
    error = state.error,
    onRetry = { viewModel.retry() }
    )
    }
    }
    }

    is DataUiState.Offline -> {
    OfflineView(
    data = state.cachedData,
    isStale = state.isStale,
    onRefresh = { viewModel.refresh() }
    )
    }
    }

    // 全局网络状态指示器
    NetworkStatusIndicator(
    isOnline = viewModel.isOnline,
    isCellular = viewModel.isCellular
    )
    }
    }

    // 网络状态指示器组件
    @Composable
    fun NetworkStatusIndicator(
    isOnline: Boolean,
    isCellular: Boolean
    ) {
    AnimatedVisibility(
    visible = !isOnline,
    enter = slideInVertically(initialOffsetY = { -it }),
    exit = slideOutVertically(targetOffsetY = { -it })
    ) {
    Surface(
    modifier = Modifier.fillMaxWidth(),
    color = MaterialTheme.colors.error.copy(alpha = 0.9f)
    ) {
    Row(
    modifier = Modifier
    .fillMaxWidth()
    .padding(12.dp),
    verticalAlignment = Alignment.CenterVertically
    ) {
    Icon(
    imageVector = Icons.Default.CloudOff,
    contentDescription = "离线",
    tint = Color.White
    )

    Spacer(modifier = Modifier.width(8.dp))

    Text(
    text = if (isCellular) {
    "当前使用蜂窝数据,建议连接Wi-Fi"
    } else {
    "网络连接已断开"
    },
    color = Color.White,
    fontSize = 14.sp
    )
    }
    }
    }
    }

    32.4 移动端性能优化

    32.4.1 状态码驱动的缓存策略

    typescript

    // React Native缓存管理器
    class CacheManager {
    private cache: AsyncStorage;
    private maxAge: number;
    private networkFirstFor: number[]; // 这些状态码优先使用网络

    constructor() {
    this.cache = AsyncStorage;
    this.maxAge = 5 * 60 * 1000; // 5分钟
    this.networkFirstFor = [401, 403, 404, 422];
    }

    async get<T>(
    key: string,
    fetcher: () => Promise<T>,
    options: CacheOptions = {}
    ): Promise<CacheResult<T>> {
    const {
    forceNetwork = false,
    maxAge = this.maxAge,
    staleWhileRevalidate = true
    } = options;

    // 1. 检查缓存
    const cached = await this.getFromCache<T>(key);
    const isCacheValid = cached && !this.isExpired(cached.timestamp, maxAge);

    // 2. 如果强制使用网络或缓存无效,直接获取网络数据
    if (forceNetwork || !isCacheValid) {
    return this.fetchAndCache(key, fetcher);
    }

    // 3. 如果缓存有效,立即返回缓存数据
    const result: CacheResult<T> = {
    data: cached.data,
    source: 'cache',
    timestamp: cached.timestamp
    };

    // 4. 如果需要,在后台更新缓存
    if (staleWhileRevalidate && !this.isExpired(cached.timestamp, maxAge * 2)) {
    this.backgroundRefresh(key, fetcher);
    }

    return result;
    }

    private async fetchAndCache<T>(
    key: string,
    fetcher: () => Promise<T>
    ): Promise<CacheResult<T>> {
    try {
    const data = await fetcher();
    const timestamp = Date.now();

    // 保存到缓存
    await this.cache.setItem(
    key,
    JSON.stringify({ data, timestamp })
    );

    return {
    data,
    source: 'network',
    timestamp
    };

    } catch (error) {
    // 网络失败,尝试返回缓存
    const cached = await this.getFromCache<T>(key);

    if (cached) {
    return {
    data: cached.data,
    source: 'cache',
    timestamp: cached.timestamp,
    isStale: true,
    error
    };
    }

    throw error;
    }
    }

    private async backgroundRefresh(key: string, fetcher: () => Promise<T>) {
    // 使用低优先级进行后台刷新
    InteractionManager.runAfterInteractions(async () => {
    try {
    const data = await fetcher();
    const timestamp = Date.now();

    await this.cache.setItem(
    key,
    JSON.stringify({ data, timestamp })
    );

    // 通知数据已更新
    DeviceEventEmitter.emit('cache_updated', { key });

    } catch (error) {
    console.warn(`Background refresh failed for ${key}:`, error);
    }
    });
    }

    // 基于状态码的缓存清理策略
    async handleResponseError(
    key: string,
    statusCode: number,
    error: any
    ): Promise<void> {
    switch (statusCode) {
    case 401: // 认证失败
    case 403: // 权限变更
    // 清除用户相关缓存
    await this.clearUserRelatedCache();
    break;

    case 404: // 资源不存在
    // 清除该资源的缓存
    await this.cache.removeItem(key);
    break;

    case 410: // 资源已删除
    // 清除缓存并标记为已删除
    await this.cache.setItem(
    `${key}_deleted`,
    JSON.stringify({ deletedAt: Date.now() })
    );
    await this.cache.removeItem(key);
    break;

    case 422: // 验证错误
    // 保留缓存,但标记为可能过时
    await this.cache.setItem(
    `${key}_stale`,
    JSON.stringify({ lastError: error, timestamp: Date.now() })
    );
    break;
    }
    }
    }

    32.4.2 图片加载优化

    swift

    // iOS图片加载器
    class SmartImageLoader {
    private let memoryCache = NSCache<NSString, UIImage>()
    private let diskCache = URLCache.shared
    private let session: URLSession
    private let maxConcurrentDownloads = 3

    // 网络状况感知的图片质量
    func optimalImageURL(for originalURL: URL, networkType: NetworkType) -> URL {
    var components = URLComponents(url: originalURL, resolvingAgainstBaseURL: false)

    switch networkType {
    case .wifi:
    // Wi-Fi下使用高质量图片
    components?.queryItems = [
    URLQueryItem(name: "quality", value: "high"),
    URLQueryItem(name: "width", value: "1080")
    ]

    case .cellular:
    // 蜂窝网络下使用中等质量
    components?.queryItems = [
    URLQueryItem(name: "quality", value: "medium"),
    URLQueryItem(name: "width", value: "720")
    ]

    case .slow:
    // 慢速网络使用低质量
    components?.queryItems = [
    URLQueryItem(name: "quality", value: "low"),
    URLQueryItem(name: "width", value: "480")
    ]

    case .disconnected:
    // 离线状态,返回占位图
    return Bundle.main.url(forResource: "placeholder", withExtension: "png")!
    }

    return components?.url ?? originalURL
    }

    func loadImage(
    from url: URL,
    into imageView: UIImageView,
    placeholder: UIImage? = nil
    ) {
    let cacheKey = url.absoluteString as NSString

    // 1. 检查内存缓存
    if let cachedImage = memoryCache.object(forKey: cacheKey) {
    imageView.image = cachedImage
    return
    }

    // 2. 设置占位图
    imageView.image = placeholder

    // 3. 检查网络状况
    let networkType = NetworkMonitor.shared.currentType
    let optimalURL = optimalImageURL(for: url, networkType: networkType)

    // 4. 如果是离线状态且没有缓存,直接返回
    if networkType == .disconnected {
    // 尝试从磁盘加载上次缓存的图片
    if let diskCached = loadFromDiskCache(url: url) {
    imageView.image = diskCached
    }
    return
    }

    // 5. 异步加载图片
    DispatchQueue.global(qos: .userInitiated).async {
    let task = self.session.dataTask(with: optimalURL) { [weak self] data, response, error in
    guard let self = self else { return }

    DispatchQueue.main.async {
    if let error = error {
    self.handleImageLoadError(
    error: error,
    url: url,
    imageView: imageView,
    placeholder: placeholder
    )
    return
    }

    guard let data = data,
    let image = UIImage(data: data),
    let response = response else {
    return
    }

    // 根据状态码处理
    if let httpResponse = response as? HTTPURLResponse {
    switch httpResponse.statusCode {
    case 200:
    // 成功,更新UI并缓存
    imageView.image = image
    self.cacheImage(image, for: cacheKey)

    case 304:
    // 未修改,使用缓存
    if let cached = self.memoryCache.object(forKey: cacheKey) {
    imageView.image = cached
    }

    case 404, 410:
    // 图片不存在或已删除
    imageView.image = UIImage(named: "image_not_found")

    default:
    // 其他错误
    imageView.image = placeholder
    }
    }
    }
    }

    task.resume()
    }
    }

    private func handleImageLoadError(
    error: Error,
    url: URL,
    imageView: UIImageView,
    placeholder: UIImage?
    ) {
    if let urlError = error as? URLError {
    switch urlError.code {
    case .timedOut:
    // 超时,尝试使用低质量版本
    if let lowQualityURL = getLowQualityVersion(of: url) {
    loadImage(from: lowQualityURL, into: imageView, placeholder: placeholder)
    }

    case .notConnectedToInternet:
    // 无网络,尝试从磁盘加载
    if let diskCached = loadFromDiskCache(url: url) {
    imageView.image = diskCached
    } else {
    imageView.image = placeholder
    }

    case .cancelled:
    // 请求被取消,忽略
    break

    default:
    imageView.image = placeholder
    }
    } else {
    imageView.image = placeholder
    }
    }
    }

    32.5 移动端安全考虑

    32.5.1 安全的错误处理

    java

    // Android安全错误处理
    public class SecureErrorHandler {
    private static final String TAG = "SecureErrorHandler";

    public static void handleException(Context context, Exception exception) {
    // 记录错误但不泄露敏感信息
    if (exception instanceof HttpException) {
    HttpException httpException = (HttpException) exception;

    logSafeError("HTTP Error", httpException.code());

    // 根据状态码显示用户友好的消息
    String userMessage = getSafeUserMessage(context, httpException.code());

    // 显示给用户
    showUserFriendlyError(context, userMessage);

    } else if (exception instanceof SSLException) {
    // SSL错误,可能是中间人攻击
    logSafeError("SSL Error", "SSL handshake failed");

    // 提示用户检查网络安全性
    showSecurityWarning(context);

    } else if (exception instanceof UnknownHostException) {
    // DNS解析失败
    logSafeError("Network Error", "Cannot resolve host");

    showUserFriendlyError(
    context,
    context.getString(R.string.network_unavailable)
    );

    } else {
    // 通用错误处理
    logSafeError("Unknown Error", "An unexpected error occurred");

    showUserFriendlyError(
    context,
    context.getString(R.string.generic_error)
    );
    }
    }

    private static void logSafeError(String type, Object details) {
    // 安全的日志记录,避免泄露敏感信息
    Map<String, Object> safeDetails = new HashMap<>();
    safeDetails.put("type", type);
    safeDetails.put("details", details);
    safeDetails.put("timestamp", System.currentTimeMillis());
    safeDetails.put("app_version", BuildConfig.VERSION_NAME);

    // 移除可能的敏感信息
    if (details instanceof String) {
    String detailString = (String) details;
    detailString = sanitizeString(detailString);
    safeDetails.put("details", detailString);
    }

    Log.e(TAG, safeDetails.toString());

    // 发送到安全的错误收集服务
    sendToErrorReporting(safeDetails);
    }

    private static String sanitizeString(String input) {
    // 移除可能敏感的信息
    return input
    .replaceAll("(?i)password=[^&]*", "password=***")
    .replaceAll("(?i)token=[^&]*", "token=***")
    .replaceAll("(?i)api_key=[^&]*", "api_key=***")
    .replaceAll("\\\\d{4}-\\\\d{4}-\\\\d{4}-\\\\d{4}", "****-****-****-****") // 信用卡
    .replaceAll("\\\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Z|a-z]{2,}\\\\b", "***@***.***"); // 邮箱
    }

    private static String getSafeUserMessage(Context context, int statusCode) {
    // 返回用户友好的错误消息,不包含技术细节
    switch (statusCode) {
    case 400:
    return context.getString(R.string.error_bad_request);
    case 401:
    case 403:
    return context.getString(R.string.error_authentication);
    case 404:
    return context.getString(R.string.error_not_found);
    case 408:
    case 504:
    return context.getString(R.string.error_timeout);
    case 429:
    return context.getString(R.string.error_too_many_requests);
    case 500:
    case 502:
    case 503:
    return context.getString(R.string.error_server);
    default:
    return context.getString(R.string.error_generic);
    }
    }

    private static void showUserFriendlyError(Context context, String message) {
    // 在主线程显示错误
    new Handler(Looper.getMainLooper()).post(() -> {
    Toast.makeText(context, message, Toast.LENGTH_LONG).show();
    });
    }

    private static void showSecurityWarning(Context context) {
    // 显示安全警告对话框
    new Handler(Looper.getMainLooper()).post(() -> {
    new AlertDialog.Builder(context)
    .setTitle(R.string.security_warning_title)
    .setMessage(R.string.security_warning_message)
    .setPositiveButton(R.string.ok, null)
    .show();
    });
    }
    }

    32.6 移动端调试与监控

    32.6.1 网络请求调试工具

    javascript

    // React Native网络调试器
    class NetworkDebugger {
    constructor() {
    this.requests = [];
    this.maxRequests = 1000;

    // 拦截所有fetch请求
    this.originalFetch = global.fetch;
    this.setupInterceptor();
    }

    setupInterceptor() {
    global.fetch = async (url, options = {}) => {
    const startTime = Date.now();
    const requestId = uuid.v4();

    const requestInfo = {
    id: requestId,
    url,
    method: options.method || 'GET',
    headers: options.headers || {},
    body: options.body,
    startTime,
    status: 'pending'
    };

    this.addRequest(requestInfo);

    try {
    const response = await this.originalFetch(url, options);
    const endTime = Date.now();

    const responseInfo = {
    …requestInfo,
    endTime,
    duration: endTime – startTime,
    statusCode: response.status,
    statusText: response.statusText,
    headers: Object.fromEntries(response.headers.entries()),
    status: 'completed'
    };

    // 克隆响应以便读取body
    const clonedResponse = response.clone();

    // 尝试读取响应体
    try {
    const body = await clonedResponse.text();
    responseInfo.body = this.sanitizeBody(body);
    } catch (error) {
    responseInfo.bodyError = 'Unable to read response body';
    }

    this.updateRequest(requestId, responseInfo);

    // 触发事件
    this.emit('request_completed', responseInfo);

    return response;

    } catch (error) {
    const endTime = Date.now();

    const errorInfo = {
    …requestInfo,
    endTime,
    duration: endTime – startTime,
    error: error.message,
    status: 'failed'
    };

    this.updateRequest(requestId, errorInfo);

    // 触发事件
    this.emit('request_failed', errorInfo);

    throw error;
    }
    };
    }

    addRequest(request) {
    this.requests.unshift(request);

    // 限制请求数量
    if (this.requests.length > this.maxRequests) {
    this.requests = this.requests.slice(0, this.maxRequests);
    }

    // 存储到AsyncStorage以便离线查看
    AsyncStorage.setItem(
    'network_debug_logs',
    JSON.stringify(this.requests.slice(0, 100))
    );
    }

    updateRequest(id, updates) {
    const index = this.requests.findIndex(req => req.id === id);
    if (index !== -1) {
    this.requests[index] = { …this.requests[index], …updates };
    }
    }

    sanitizeBody(body) {
    // 移除敏感信息
    try {
    const parsed = JSON.parse(body);
    return this.sanitizeObject(parsed);
    } catch {
    // 如果不是JSON,返回原字符串
    return body.replace(/password=[^&]*/gi, 'password=***')
    .replace(/token=[^&]*/gi, 'token=***');
    }
    }

    sanitizeObject(obj) {
    const sensitiveKeys = ['password', 'token', 'secret', 'key', 'auth'];

    if (Array.isArray(obj)) {
    return obj.map(item => this.sanitizeObject(item));
    }

    if (obj !== null && typeof obj === 'object') {
    const sanitized = {};

    for (const [key, value] of Object.entries(obj)) {
    if (sensitiveKeys.some(sensitive =>
    key.toLowerCase().includes(sensitive))) {
    sanitized[key] = '***';
    } else if (typeof value === 'object') {
    sanitized[key] = this.sanitizeObject(value);
    } else {
    sanitized[key] = value;
    }
    }

    return sanitized;
    }

    return obj;
    }

    getStats() {
    const stats = {
    total: this.requests.length,
    success: this.requests.filter(r => r.status === 'completed').length,
    failed: this.requests.filter(r => r.status === 'failed').length,
    pending: this.requests.filter(r => r.status === 'pending').length,

    statusCodes: {},
    averageDuration: 0,
    byEndpoint: {}
    };

    // 统计状态码分布
    this.requests.forEach(request => {
    if (request.statusCode) {
    const code = request.statusCode;
    stats.statusCodes[code] = (stats.statusCodes[code] || 0) + 1;
    }

    // 统计接口耗时
    if (request.duration) {
    stats.totalDuration = (stats.totalDuration || 0) + request.duration;
    }

    // 按端点统计
    if (request.url) {
    const endpoint = this.extractEndpoint(request.url);
    if (!stats.byEndpoint[endpoint]) {
    stats.byEndpoint[endpoint] = {
    count: 0,
    success: 0,
    failed: 0,
    totalDuration: 0
    };
    }

    stats.byEndpoint[endpoint].count++;
    if (request.status === 'completed') {
    stats.byEndpoint[endpoint].success++;
    } else if (request.status === 'failed') {
    stats.byEndpoint[endpoint].failed++;
    }

    if (request.duration) {
    stats.byEndpoint[endpoint].totalDuration += request.duration;
    }
    }
    });

    if (stats.success > 0) {
    stats.averageDuration = stats.totalDuration / stats.success;
    }

    return stats;
    }

    extractEndpoint(url) {
    try {
    const urlObj = new URL(url);
    return urlObj.pathname;
    } catch {
    return url;
    }
    }

    // 导出为cURL命令,便于调试
    exportAsCurl(requestId) {
    const request = this.requests.find(req => req.id === requestId);
    if (!request) return '';

    let curl = `curl -X ${request.method} '${request.url}'`;

    // 添加headers
    Object.entries(request.headers || {}).forEach(([key, value]) => {
    curl += ` -H '${key}: ${value}'`;
    });

    // 添加body
    if (request.body) {
    curl += ` -d '${request.body}'`;
    }

    return curl;
    }
    }

    32.7 跨平台状态码处理

    32.7.1 React Native统一网络层

    typescript

    // React Native统一网络客户端
    class RNHttpClient {
    private baseURL: string;
    private timeout: number;
    private retryCount: number;
    private interceptors: Interceptor[];

    constructor(config: HttpClientConfig) {
    this.baseURL = config.baseURL;
    this.timeout = config.timeout || 30000;
    this.retryCount = config.retryCount || 3;
    this.interceptors = config.interceptors || [];
    }

    async request<T>(
    endpoint: string,
    options: RequestOptions = {}
    ): Promise<ApiResponse<T>> {
    const {
    method = 'GET',
    data,
    headers = {},
    retry = this.retryCount,
    timeout = this.timeout,
    responseType = 'json'
    } = options;

    const url = `${this.baseURL}${endpoint}`;

    // 执行请求拦截器
    let requestConfig = { method, url, headers, data, timeout };
    for (const interceptor of this.interceptors) {
    if (interceptor.request) {
    requestConfig = await interceptor.request(requestConfig);
    }
    }

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    try {
    const fetchOptions: RequestInit = {
    method: requestConfig.method,
    headers: {
    'Content-Type': 'application/json',
    …requestConfig.headers,
    },
    signal: controller.signal,
    };

    if (data && method !== 'GET' && method !== 'HEAD') {
    fetchOptions.body = JSON.stringify(data);
    }

    const response = await fetch(requestConfig.url, fetchOptions);
    clearTimeout(timeoutId);

    // 执行响应拦截器
    let processedResponse = response;
    for (const interceptor of this.interceptors) {
    if (interceptor.response) {
    processedResponse = await interceptor.response(processedResponse);
    }
    }

    // 处理响应
    const result = await this.handleResponse<T>(
    processedResponse,
    responseType
    );

    // 检查是否需要重试
    if (this.shouldRetry(result.status, retry)) {
    return this.retryRequest(endpoint, options, retry – 1);
    }

    return result;

    } catch (error) {
    clearTimeout(timeoutId);

    // 网络错误,根据错误类型决定是否重试
    if (this.isNetworkErrorRetryable(error) && retry > 0) {
    return this.retryRequest(endpoint, options, retry – 1);
    }

    throw this.normalizeError(error);
    }
    }

    private async handleResponse<T>(
    response: Response,
    responseType: ResponseType
    ): Promise<ApiResponse<T>> {
    const status = response.status;
    const headers = Object.fromEntries(response.headers.entries());

    let data: any;
    let error: any;

    try {
    if (status >= 200 && status < 300) {
    // 成功响应
    switch (responseType) {
    case 'json':
    data = await response.json();
    break;
    case 'text':
    data = await response.text();
    break;
    case 'blob':
    data = await response.blob();
    break;
    case 'arraybuffer':
    data = await response.arrayBuffer();
    break;
    default:
    data = await response.json();
    }
    } else {
    // 错误响应
    error = await this.parseError(response);
    }
    } catch (parseError) {
    error = {
    code: 'PARSE_ERROR',
    message: 'Failed to parse response',
    originalError: parseError,
    };
    }

    return {
    status,
    headers,
    data,
    error,
    ok: status >= 200 && status < 300,
    };
    }

    private async parseError(response: Response): Promise<ApiError> {
    try {
    const errorData = await response.json();

    // 标准化错误格式
    return {
    code: errorData.code || `HTTP_${response.status}`,
    message: errorData.message || response.statusText,
    details: errorData.details,
    status: response.status,
    timestamp: new Date().toISOString(),
    };
    } catch {
    // 如果无法解析为JSON,返回通用错误
    return {
    code: `HTTP_${response.status}`,
    message: response.statusText,
    status: response.status,
    timestamp: new Date().toISOString(),
    };
    }
    }

    private shouldRetry(status: number, retryCount: number): boolean {
    if (retryCount <= 0) return false;

    // 以下状态码需要重试
    const retryStatusCodes = [408, 429, 500, 502, 503, 504];

    // 如果是5xx错误或者特定4xx错误,且还有重试次数,则重试
    return retryStatusCodes.includes(status);
    }

    private isNetworkErrorRetryable(error: any): boolean {
    // 网络错误通常可以重试
    const retryableErrors = [
    'AbortError', // 超时
    'TypeError', // 网络错误
    'Network request failed',
    ];

    return (
    error.name === 'AbortError' ||
    retryableErrors.some(msg => error.message?.includes(msg))
    );
    }

    private async retryRequest<T>(
    endpoint: string,
    options: RequestOptions,
    retryCount: number
    ): Promise<ApiResponse<T>> {
    // 指数退避
    const delay = Math.pow(2, this.retryCount – retryCount) * 1000;

    await new Promise(resolve => setTimeout(resolve, delay));

    return this.request(endpoint, { …options, retry: retryCount });
    }

    private normalizeError(error: any): ApiError {
    if (error.name === 'AbortError') {
    return {
    code: 'TIMEOUT',
    message: 'Request timeout',
    originalError: error,
    };
    }

    if (error.message?.includes('Network request failed')) {
    return {
    code: 'NETWORK_ERROR',
    message: 'Network connection failed',
    originalError: error,
    };
    }

    return {
    code: 'UNKNOWN_ERROR',
    message: error.message || 'An unknown error occurred',
    originalError: error,
    };
    }

    // 添加拦截器
    use(interceptor: Interceptor): void {
    this.interceptors.push(interceptor);
    }

    // 移除拦截器
    eject(interceptor: Interceptor): void {
    const index = this.interceptors.indexOf(interceptor);
    if (index !== -1) {
    this.interceptors.splice(index, 1);
    }
    }
    }

    // 使用示例
    const httpClient = new RNHttpClient({
    baseURL: 'https://api.example.com',
    timeout: 30000,
    retryCount: 3,
    });

    // 添加认证拦截器
    httpClient.use({
    request: async (config) => {
    const token = await AsyncStorage.getItem('access_token');
    if (token) {
    config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
    },

    response: async (response) => {
    // 检查token是否过期
    if (response.status === 401) {
    // 尝试刷新token
    const newToken = await refreshToken();
    if (newToken) {
    // 更新存储
    await AsyncStorage.setItem('access_token', newToken);
    // 可以在这里重新发送原始请求
    }
    }
    return response;
    },
    });

    // 添加日志拦截器
    httpClient.use({
    request: async (config) => {
    console.log('Request:', {
    url: config.url,
    method: config.method,
    headers: config.headers,
    });
    return config;
    },

    response: async (response) => {
    console.log('Response:', {
    status: response.status,
    statusText: response.statusText,
    headers: Object.fromEntries(response.headers.entries()),
    });
    return response;
    },
    });

    32.8 移动端测试策略

    32.8.1 网络状态模拟测试

    typescript

    // React Native网络测试工具
    describe('Network Handling', () => {
    let httpClient: RNHttpClient;

    beforeEach(() => {
    httpClient = new RNHttpClient({
    baseURL: 'https://api.example.com',
    });
    });

    test('should handle 401 Unauthorized gracefully', async () => {
    // 模拟401响应
    fetchMock.mockResponseOnce('', { status: 401 });

    const response = await httpClient.request('/protected');

    expect(response.status).toBe(401);
    expect(response.error?.code).toBe('HTTP_401');
    expect(response.ok).toBe(false);
    });

    test('should retry on 500 error', async () => {
    // 模拟第一次500,第二次成功
    fetchMock
    .mockResponseOnce('', { status: 500 })
    .mockResponseOnce(JSON.stringify({ success: true }));

    const response = await httpClient.request('/unstable', { retry: 2 });

    expect(response.status).toBe(200);
    expect(response.data).toEqual({ success: true });
    expect(fetchMock).toHaveBeenCalledTimes(2);
    });

    test('should handle offline mode', async () => {
    // 模拟网络错误
    fetchMock.mockReject(new Error('Network request failed'));

    await expect(httpClient.request('/data')).rejects.toMatchObject({
    code: 'NETWORK_ERROR',
    });
    });

    test('should timeout after specified time', async () => {
    // 模拟长时间请求
    fetchMock.mockResponse(async () => {
    await new Promise(resolve => setTimeout(resolve, 40000));
    return { body: 'OK' };
    });

    const response = httpClient.request('/slow', { timeout: 1000 });

    await expect(response).rejects.toMatchObject({
    code: 'TIMEOUT',
    });
    });
    });

    // 网络状态切换测试
    describe('Network State Changes', () => {
    test('should adapt to cellular network', async () => {
    const imageLoader = new SmartImageLoader();

    // 模拟切换到蜂窝网络
    NetworkMonitor.setNetworkType('cellular');

    const url = new URL('https://example.com/image.jpg');
    const optimalURL = imageLoader.optimalImageURL(url, 'cellular');

    expect(optimalURL.searchParams.get('quality')).toBe('medium');
    expect(optimalURL.searchParams.get('width')).toBe('720');
    });

    test('should use cached data when offline', async () => {
    const repository = new OfflineFirstRepository();

    // 预加载缓存
    await repository.local.save({
    key: 'test-data',
    data: { cached: true },
    timestamp: Date.now(),
    });

    // 模拟离线状态
    NetworkMonitor.setConnected(false);

    const result = await repository.fetchData({
    key: 'test-data',
    remoteFetch: () => Promise.resolve({ fresh: true }),
    });

    expect(result.source).toBe('cache');
    expect(result.data).toEqual({ cached: true });
    expect(result.isStale).toBe(true);
    });
    });

    // 性能测试
    describe('Network Performance', () => {
    test('should not exceed maximum concurrent requests', async () => {
    const loader = new SmartImageLoader();

    // 同时发起多个请求
    const promises = Array(10)
    .fill(0)
    .map((_, i) =>
    loader.loadImage(
    new URL(`https://example.com/image${i}.jpg`),
    new UIImageView()
    )
    );

    // 检查并发数
    expect(loader.activeRequests).toBeLessThanOrEqual(loader.maxConcurrentDownloads);

    await Promise.all(promises);
    });

    test('should cache images appropriately', async () => {
    const loader = new SmartImageLoader();
    const imageView = new UIImageView();
    const url = new URL('https://example.com/test.jpg');

    // 第一次加载
    await loader.loadImage(url, imageView);
    expect(loader.memoryCache.object(forKey: url.absoluteString)).toBeDefined();

    // 第二次应该从缓存加载
    const startTime = Date.now();
    await loader.loadImage(url, imageView);
    const endTime = Date.now();

    // 缓存加载应该很快
    expect(endTime – startTime).toBeLessThan(10);
    });
    });

    32.9 移动端最佳实践总结

    32.9.1 关键原则

  • 优雅降级

    • 优先提供功能,即使数据不是最新的

    • 缓存上次成功的结果

    • 提供离线模式

  • 智能重试

    • 指数退避算法

    • 根据状态码决定是否重试

    • 考虑网络类型调整重试策略

  • 用户体验优先

    • 快速响应用户操作

    • 提供有意义的错误提示

    • 避免阻塞UI

  • 32.9.2 技术策略

  • 网络优化

    • 压缩请求和响应

    • 批量处理请求

    • 预加载重要数据

  • 缓存策略

    • 内存缓存 + 磁盘缓存

    • 基于TTL的过期策略

    • 离线队列管理

  • 错误处理

    • 区分可恢复和不可恢复错误

    • 安全的错误日志记录

    • 用户友好的错误消息

  • 32.9.3 监控指标

    yaml

    移动应用网络性能指标:
    – 请求成功率: > 99%
    – 平均响应时间: < 2秒
    – 错误率: < 1%
    – 缓存命中率: > 60%
    – 离线功能可用性: 100%

    状态码分布监控:
    – 2xx: > 95%
    – 4xx: < 3%
    – 5xx: < 2%

    用户体验指标:
    – 首次加载时间: < 3秒
    – 交互响应时间: < 100毫秒
    – 离线操作成功率: > 90%

    32.10 未来趋势

    32.10.1 5G网络的影响

    typescript

    // 5G网络感知的优化策略
    class FiveGNetworkOptimizer {
    private networkInfo: NetworkInformation;

    constructor() {
    if ('connection' in navigator) {
    this.networkInfo = (navigator as any).connection;
    this.setupNetworkMonitoring();
    }
    }

    private setupNetworkMonitoring() {
    this.networkInfo.addEventListener('change', () => {
    this.adaptToNetworkType();
    });
    }

    private adaptToNetworkType() {
    const { effectiveType, downlink, rtt } = this.networkInfo;

    if (effectiveType === '5g' && downlink > 100) {
    // 5G高速网络
    this.enableHighQualityStreaming();
    this.prefetchMoreContent();
    this.useLessCompression();
    } else if (effectiveType === '4g') {
    // 4G网络
    this.useBalancedStrategy();
    } else {
    // 慢速网络
    this.useConservativeStrategy();
    }
    }

    private enableHighQualityStreaming() {
    // 启用高清视频
    // 提高图片质量
    // 减少压缩率
    }

    private prefetchMoreContent() {
    // 预加载更多内容
    // 提前获取下一页数据
    // 缓存相关资源
    }

    private useLessCompression() {
    // 使用原始质量图片
    // 减少数据压缩
    // 使用更高效但不一定高压缩的格式
    }
    }

    32.10.2 边缘计算与移动端

    随着边缘计算的发展,移动应用可以:

  • 更低的延迟:通过边缘节点减少网络延迟

  • 更好的离线体验:边缘缓存提供更丰富的离线内容

  • 智能路由:根据用户位置选择最优服务节点

  • 实时同步:边缘节点间的数据同步更快速

  • typescript

    // 边缘计算感知的请求路由
    class EdgeAwareHttpClient extends RNHttpClient {
    private edgeNodes: EdgeNode[];
    private userLocation: Location | null;

    async getOptimalEndpoint(service: string): Promise<string> {
    if (!this.userLocation) {
    return `${this.baseURL}/${service}`;
    }

    // 找到最近的边缘节点
    const nearestNode = this.findNearestEdgeNode(this.userLocation);

    if (nearestNode) {
    // 检查边缘节点健康状态
    const isHealthy = await this.checkNodeHealth(nearestNode);

    if (isHealthy) {
    return `${nearestNode.url}/${service}`;
    }
    }

    // 回退到主节点
    return `${this.baseURL}/${service}`;
    }

    private findNearestEdgeNode(location: Location): EdgeNode | null {
    let nearest: EdgeNode | null = null;
    let minDistance = Infinity;

    for (const node of this.edgeNodes) {
    const distance = this.calculateDistance(location, node.location);
    if (distance < minDistance) {
    minDistance = distance;
    nearest = node;
    }
    }

    return nearest;
    }

    private async checkNodeHealth(node: EdgeNode): Promise<boolean> {
    try {
    const response = await fetch(`${node.url}/health`, {
    timeout: 5000,
    });

    return response.ok && response.status === 200;
    } catch {
    return false;
    }
    }

    async request<T>(
    endpoint: string,
    options: RequestOptions = {}
    ): Promise<ApiResponse<T>> {
    // 获取最优端点
    const optimalEndpoint = await this.getOptimalEndpoint(endpoint);

    // 使用父类方法发送请求
    return super.request(optimalEndpoint, options);
    }
    }

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » HTTP 状态码:客户端与服务器的通信语言——第六部分:状态码的实践应用(一)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!