3、HTTP 状态码与 SDK 错误码的关系

很多同学刚接触 SDK 集成时,拿到一个错误码就跑去翻文档,翻完还是一头雾水。其实大部分 SDK 错误码都是从 HTTP 状态码翻译过来的,就像两台机器的接口协议——你只要看懂底层传输层的信号,就能猜到上层逻辑说了什么。

我个人习惯先看 HTTP 状态码,再去查 SDK 内部码。为什么呢?因为 HTTP 状态码是互联网的基础标准,几百台服务器都一样,而 SDK 内部码各家自定义,容易记混。今天我们就来讲透这层映射关系。

3.1 HTTP 状态码的基础分类

先快速过一下 HTTP 状态码的五个大类,不要嫌基础,这是后面映射的前提。

状态码范围 类别 含义
1xx 信息类 服务器还在处理,告诉客户端等一下
2xx 成功类 请求已收到并正确处理
3xx 重定向类 需要跳转到另一个地址才能完成请求
4xx 客户端错误 你发的东西有问题(参数、鉴权、资源不存在)
5xx 服务器错误 服务器内部炸了,不关你的事

你想想看,SDK 里面常见的错误码,基本上就是在这五个框里来回跳。2xx 对应成功,4xx 对应参数或权限问题,5xx 对应服务不可用。但具体怎么映射,还得看 SDK 的设计哲学。

3.2 映射原则:从 HTTP 状态码到 SDK 内部错误码

我参与过好几套 SDK 的架构设计,总结了三条核心映射原则:

  • 精确对应:每个 HTTP 状态码直接对应一个唯一的 SDK 错误码,不会出现一个状态码映射到多个情况(除非有子类型)。
  • 额外信息保留:HTTP 响应体里可能还有 error_code 和 error_message,SDK 内部码会保留这个结构,方便上游排查。
  • 本地化包装:SDK 会把 500 状态码翻译成 E_SERVER_ERROR,而不是让用户直接看“500 Internal Server Error”。

举个我碰过的真实案例。某云存储 SDK,上传文件时遇到 403 状态码。403 表示无权限,但具体原因是“签名过期”还是“空间满了”?SDK 内部的错误码分了两个:ERR_SIGN_EXPIRED 和 ERR_QUOTA_EXCEED。如果只照着 HTTP 状态码查,你根本不知道是哪一种。所以好的 SDK 会从响应体 JSON 里解析出更细粒度的业务码,再映射到内部枚举。

核心观点:HTTP 状态码是粗粒度信号,SDK 内部错误码是细粒度决策。两者之间通常是一对多关系,需要根据响应体内容做二次解析。

3.3 常见的映射关系表

不同 SDK 的实现略有差异,但绝大多数遵守这张典型映射表。我整理了一下最标准的版本:

HTTP 状态码 场景 SDK 内部错误码(示例) SDK 内部含义
200 正常响应 SUCCESS 请求成功,无错误
400 参数错误 ERR_INVALID_PARAM 请求参数不符合规范
401 未鉴权 ERR_AUTH_FAILED Access Key / 无效或过期
403 禁止访问 ERR_PERMISSION_DENIED 无权限执行该操作
404 资源不存在 ERR_RESOURCE_NOT_FOUND 请求的资源(Bucket/对象/路径)不存在
409 资源冲突 ERR_RESOURCE_CONFLICT 并发操作或状态不匹配
429 请求过多 ERR_RATE_LIMIT_EXCEEDED 超出 QPS 限制,需要重试
500 服务器内部错误 ERR_INTERNAL 服务端异常,SDK 无法定位具体原因
502 网关错误 ERR_GATEWAY_TIMEOUT 代理层或后端无响应
503 服务不可用 ERR_SERVICE_UNAVAILABLE 服务正在维护或过载,建议延迟重试

看到这里你可能想问:“为什么 400 和 401 要分开?” 因为在 HTTP 协议里,401 意味着你根本没提供凭证,400 是你提供了凭证但发错了。很多 SDK 把 401 当作鉴权问题,直接暴露给用户,但 400 则丢给参数校验模块处理。我个人习惯把 400 和 401 分开写日志,这样线上排查时扫一眼日志就能判断是用户误操作还是第三方集成问题。

3.4 映射流程:从网络响应到 SDK 错误对象

好,前面都是静态的表。现在我们来看看真正请求时,SDK 内部是怎么走完这套映射的。下面这张流程图描述了完整链路。

HTTP 状态码 → SDK 错误码映射流程 发起 HTTP 请求 获取 HTTP 状态码 按状态码分类:2xx/4xx/5xx 解析响应体业务码 映射到 SDK 内部错误码 注意:若状态码为 200 但业务码错误,需单独处理 举个例子: HTTP 400 + 响应体 {“error”:”invalid_param”} → SDK 内部 ERR_INVALID_PARAM

这张图画得很清楚了。要注意的是,很多 SDK 会在“解析响应体”这一步读出服务器下发的业务错误码(比如 400 配了一个业务码 1001),然后根据业务码做二次映射。这也是为什么有些时候你看到 HTTP 状态码是 200,但 SDK 却给你报了个错误——因为响应体的业务码表示“操作失败但请求本身成功”。

3.5 常见陷阱与避坑指南

我在项目中遇到过好几次这样的案例:

  • 陷阱一:状态码与业务码冲突。服务器返回 200,但响应体里 error_code 是 1001,表示“资源不存在”。有些封装不好的 SDK 只看 HTTP 状态码,直接返回 SUCCESS,导致开发者以为成功了。避坑方法:检查 SDK 的 isSuccess() 方法实现,看它到底是只用状态码还是也检查业务码。
  • 陷阱二:重定向状态码被吞掉。3xx 状态码在某些 SDK 里被底层 HTTP 库自动跳转了,不会暴露给上层。这会导致你明明发了一个请求,结果实际指向了另一个地址,调试时一脸懵。我以前遇到过一个 OAuth 流程,SDK 自动跟了 302 跳转,但跳转后鉴权没带 Cookie,请求直接返回 401。我当时排查了整整一天才发现是自动重定向的锅。建议在调试阶段关闭自动重定向,手动跟踪跳转链。
  • 陷阱三:5xx 状态码的重试策略失效。有些 SDK 遇到 503 会自动重试 3 次,但如果服务器返回的是 502(网关超时),重试可能把网关打崩。我的建议是:对 5xx 类错误分别设定重试策略。502 重试间隔可以长一些,503 可以短一些,500 基本不重试直接报错。

特别注意:不要把 HTTP 状态码直接抛给最终用户。用户不需要知道 500、503 是什么,他们只需要知道“服务暂时不可用”或“参数有误”。SDK 内部错误码应该做一次翻译,输出友好的中文提示。

3.6 代码示例:一个简单的映射实现

下面是一个典型 SDK 中映射逻辑的伪代码(JavaScript 版)。你可以看到它是怎么把 HTTP 状态码和业务码结合在一起生成内部错误码的。

function parseResponse(resp) {
    const statusCode = resp.statusCode;
    let body = JSON.parse(resp.body);
    
    // 先依据 HTTP 状态码做一级映射
    let sdkErrorCode;
    switch (true) {
        case (statusCode >= 200 && statusCode < 300):
            sdkErrorCode = 'SUCCESS';
            break;
        case (statusCode === 400):
            sdkErrorCode = 'ERR_INVALID_PARAM';
            break;
        case (statusCode === 401):
            sdkErrorCode = 'ERR_AUTH_FAILED';
            break;
        case (statusCode === 403):
            sdkErrorCode = 'ERR_PERMISSION_DENIED';
            break;
        case (statusCode === 404):
            sdkErrorCode = 'ERR_RESOURCE_NOT_FOUND';
            break;
        case (statusCode === 429):
            sdkErrorCode = 'ERR_RATE_LIMIT_EXCEEDED';
            break;
        case (statusCode >= 500):
            sdkErrorCode = 'ERR_SERVER_INTERNAL';
            break;
        default:
            sdkErrorCode = 'ERR_UNKNOWN';
    }
    
    // 然后根据业务码做二次修正(如果有)
    if (body && body.error_code) {
        // 某些场景下业务码可以覆盖默认映射
        if (body.error_code === 'no_such_bucket') {
            sdkErrorCode = 'ERR_BUCKET_NOT_FOUND';
        }
        // 或者 200 状态码但业务码表示失败
        if (body.error_code === 'quota_exceeded') {
            sdkErrorCode = 'ERR_QUOTA_EXCEEDED';
        }
    }
    
    return sdkErrorCode;
}

你看,这段代码先按状态码分类,再用业务码做补充。这种做法很常见。我曾在某家电商平台的 SDK 里看到他们把 200 状态码 + 业务码 “stock_out” 映射成 ERR_STOCK_OUT,而只通过 HTTP 状态码是根本不可能判断库存的。

3.7 总结一句话

HTTP 状态码告诉你“请求能否到达服务器并返回”,SDK 内部错误码告诉你“业务逻辑是否真正成功了”。两者是补充关系,不是替代关系。以后你遇到莫名其妙的 SDK 错误,先看一眼 HTTP 状态码,再找响应体里的业务码,八成能把问题定位到。如果还搞不定……嗯,那可能是 SDK 本身有 bug,这时候就去提 issue 吧。

我的小技巧:在开发环境把 SDK 的日志级别调到 DEBUG,让它输出完整 HTTP 请求和响应。状态码和业务码都打出来,对照着看,比翻文档快得多。