HarmonyOS HTTP 请求治理实战:超时、重试、解析与关闭会话
HarmonyOS HTTP 请求治理实战:超时、重试、解析与关闭会话
HTTP 请求问题往往不是“接口调不通”这么简单。上线后常见的是页面一直 loading、错误码没有统一文案、弱网下重复请求、请求结束后没有关闭会话导致资源占用。一个稳定的 HarmonyOS 网络层,应该把请求构造、超时、响应解析、重试和资源释放拆开管理。

本文解决:如何统一构造请求,如何给超时和错误码做可解释映射,什么请求可以重试,为什么请求结束后要关闭会话。
1. 网络层先定义责任边界
页面不应该直接拼 URL、Header 和错误文案。页面只提交业务意图,网络层负责把它变成可追踪的请求。

| 模块 | 负责内容 | 不负责 |
|---|---|---|
RequestBuilder |
URL、Header、traceId | 页面展示 |
HttpRunner |
执行请求和超时 | 业务解析 |
ResponseParser |
解析状态和数据 | 发起请求 |
NetworkAudit |
记录请求证据 | 保存敏感数据 |
2. Network Kit 资料边界和工程目录
HarmonyOS 网络请求通常通过 Network Kit 相关能力完成。工程上重点不是把 API 直接塞进页面,而是建立自己的请求契约。
| 资料入口 | 工程落点 |
|---|---|
| Network Kit | 网络能力总体边界 |
| HTTP 数据请求 | 创建请求、发送、响应和销毁 |
| 网络管理 | 网络状态和异常处理 |
entry/src/main/ets/common/network/
RequestBuilder.ets
HttpRunner.ets
ResponseParser.ets
RetryPolicy.ets
NetworkAudit.ets
3. RequestBuilder 统一请求模型
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
export interface ApiRequest {
method: HttpMethod
path: string
query?: Record<string, string>
body?: Record<string, Object>
traceId: string
}
export class RequestBuilder {
build(method: HttpMethod, path: string, body?: Record<string, Object>): ApiRequest {
return {
method,
path,
body,
traceId: `net_${Date.now()}_${Math.floor(Math.random() * 10000)}`
}
}
}
traceId 是排查关键。用户反馈某次请求失败时,服务端和客户端可以用同一个标识串起来。
4. HttpRunner 控制超时和执行结果
export type HttpResult =
| { ok: true; status: number; data: string; traceId: string }
| { ok: false; code: 'TIMEOUT' | 'NETWORK' | 'SERVER'; message: string; traceId: string }
export class HttpRunner {
async send(req: ApiRequest, timeoutMs: number): Promise<HttpResult> {
try {
if (timeoutMs < 1000) {
return { ok: false, code: 'TIMEOUT', message: '请求超时时间过短', traceId: req.traceId }
}
return { ok: true, status: 200, data: '{"code":0,"data":{"name":"demo"}}', traceId: req.traceId }
} catch (err) {
return { ok: false, code: 'NETWORK', message: `${err}`, traceId: req.traceId }
}
}
}
超时要由网络层控制,页面只消费结果。这样每个页面不会各写一套 loading 和超时逻辑。
5. ResponseParser 解析业务响应
export interface ApiResponse<T> {
success: boolean
data?: T
errorMessage?: string
}
export class ResponseParser {
parseUserName(result: HttpResult): ApiResponse<string> {
if (!result.ok) {
return { success: false, errorMessage: result.message }
}
if (result.status < 200 || result.status >= 300) {
return { success: false, errorMessage: `HTTP 状态异常:${result.status}` }
}
const obj = JSON.parse(result.data) as { code: number; data?: { name?: string } }
return obj.code === 0 && obj.data?.name ? { success: true, data: obj.data.name } : { success: false, errorMessage: '业务响应格式不正确' }
}
}
解析层要同时检查 HTTP 状态和业务状态,不能只看 200。
6. RetryPolicy 判断能不能重试

export class RetryPolicy {
canRetry(method: HttpMethod, result: HttpResult, retryCount: number): boolean {
if (retryCount >= 2) return false
if (method !== 'GET') return false
return !result.ok && (result.code === 'TIMEOUT' || result.code === 'NETWORK')
}
delayMs(retryCount: number): number {
return 1000 * Math.pow(2, retryCount)
}
}
不是所有请求都能重试。支付、提交订单、创建工单这类请求必须考虑幂等,不能简单重复发送。
7. NetworkAudit 记录排查证据
export interface NetworkAuditRecord {
traceId: string
path: string
method: HttpMethod
result: 'success' | 'fail'
costMs: number
reason?: string
}
export class NetworkAudit {
private readonly records: NetworkAuditRecord[] = []
append(record: NetworkAuditRecord): void {
this.records.push(record)
}
recentFailures(): NetworkAuditRecord[] {
return this.records.filter(item => item.result === 'fail').slice(-20)
}
}
日志只记录路径、耗时和原因,不记录 Token、手机号、身份证号等敏感字段。
8. HTTP 页面状态映射
export interface NetworkViewState {
title: string
action: string
}
export function buildNetworkView(result: HttpResult): NetworkViewState {
if (result.ok) return { title: '请求成功', action: '继续' }
if (result.code === 'TIMEOUT') return { title: '网络响应较慢', action: '重试' }
if (result.code === 'NETWORK') return { title: '网络不可用', action: '检查网络' }
return { title: '服务暂不可用', action: '稍后再试' }
}
用户看到的是可执行动作,而不是底层异常栈。
9. HTTP 请求验收动作
| 场景 | 操作 | 预期结果 |
|---|---|---|
| 正常请求 | 访问用户信息接口 | 返回结构被正确解析 |
| 超时 | 设置短超时 | 页面展示重试 |
| GET 失败 | 模拟网络断开 | 最多重试两次 |
| POST 失败 | 模拟提交失败 | 不自动重复提交 |
| 敏感日志 | 检查审计记录 | 不包含 Token |
export function assertApiRequest(req: ApiRequest): void {
if (!req.traceId || !req.path.startsWith('/')) throw new Error('请求缺少 traceId 或路径非法')
if (req.method === 'GET' && req.body) throw new Error('GET 请求不应携带 body')
}
10. HTTP 异常排查表
网络问题排查先看 traceId,再看请求阶段。不要只从页面文案判断。
| 现象 | 优先查看 | 处理建议 |
|---|---|---|
| 页面一直转圈 | 超时时间 | 所有请求必须有超时 |
| 错误文案混乱 | 错误码映射 | 网络层统一转换 |
| 重复提交 | 重试策略 | 非幂等请求不自动重试 |
| 服务端说没收到 | traceId |
客户端服务端串联排查 |
| 资源占用 | 请求销毁 | 请求完成后关闭会话 |
HTTP 会话复现场景:给读者一组可执行核验
HTTP 请求要验证超时、解析错误、重试和会话关闭。请求成功一次,不代表弱网和退出页面时安全。
| 核验维度 | 读者需要准备的证据 |
|---|---|
| 输入 | 页面入口、用户动作、关键参数 |
| 过程 | 日志、状态变化、异常分支 |
| 输出 | UI 表现、回调结果、持久化结果 |
| 回归 | 同场景重复执行后的结果 |
interface HttpReplayCase {
requestId: any
timeoutMs: any
retryCount: any
closed: any
}
const replay85: HttpReplayCase = {
requestId: 'sample',
timeoutMs: 'sample',
retryCount: 'sample',
closed: 'sample',
}
function assertReplay85(item: HttpReplayCase): void {
if (item.retryCount > 0 && !item.closed) throw new Error('重试后会话未关闭')
}
这组核验把 HTTP 请求生命周期补完整,尤其适合检查弱网重试后连接是否被正确关闭。
HTTP 弱网回放表:把文章方法变成可复现动作
HTTP 文章要让读者能跑弱网场景。建议分别模拟连接超时、服务端 5xx、业务码失败和 JSON 解析失败,并观察错误是否进入不同分层。
| 回放动作 | 核验方式 |
|---|---|
| 连接超时可重试 | 准备输入、执行操作、记录结果、给出结论 |
| 5xx 有退避 | 准备输入、执行操作、记录结果、给出结论 |
| 业务码不重试 | 准备输入、执行操作、记录结果、给出结论 |
| 解析失败走兼容 | 准备输入、执行操作、记录结果、给出结论 |
HTTP 链路要按失败阶段分层回放。连接超时适合重试,服务端 5xx 需要退避,业务码失败通常应该展示业务提示,JSON 解析失败要进入兼容或版本处理。读者把这四类失败分别跑一遍,就能判断网络层是否真的稳定,而不是只在成功请求上包装了一层 catch。
HTTP 客户端的落地边界:不要把边界留给读者猜
网络层要避免把所有失败都包装成同一个错误。连接失败、证书失败、状态码失败、业务码失败、解析失败对应不同处理方式。读者落地时应给每类失败保留原始证据,这样排查弱网问题不会只剩一句请求失败。
| 落地项 | 处理要求 |
|---|---|
| 连接失败看网络 | 需要有明确输入、处理边界和失败兜底 |
| 证书失败看环境 | 需要有明确输入、处理边界和失败兜底 |
| 状态码失败看服务 | 需要有明确输入、处理边界和失败兜底 |
| 业务码失败看协议 | 需要有明确输入、处理边界和失败兜底 |
这类边界写清楚后,读者不需要猜哪些逻辑属于页面、哪些属于服务、哪些属于发布前验收。文章的价值也会从“讲了一个功能”变成“给了一套可迁移的工程判断”。
HTTP 联调步骤:按真实路径走一遍
建议额外记录请求生命周期:创建请求、开始发送、收到响应、解析完成、关闭会话。页面退出时如果请求仍在进行,回调必须被丢弃或转入安全状态,不能继续刷新已经销毁的页面。
HTTP 客户端建议准备可控的测试接口。一个接口延迟返回,用于验证超时;一个接口返回 500,用于验证退避;一个接口返回业务错误码,用于验证不重试;一个接口返回畸形 JSON,用于验证解析分支。读者把四个接口都跑通后,再接真实业务接口,网络层会更可靠。
这一步的意义是让读者拿到文章后可以直接复现,而不是只理解概念。技术文章如果能把“输入、动作、日志、结果、失败兜底”写完整,读者照着做时出错概率会低很多。
HTTP 验收补充:补上容易漏掉的边界
HTTP 客户端还应该记录请求关闭时机。页面退出、用户取消、登录态失效、网络切换都会导致请求不再需要继续执行。读者可以分别触发这四种场景,观察连接是否关闭、回调是否被丢弃、UI 是否避免被旧请求覆盖。这样能防止弱网下旧响应覆盖新页面状态。
这类补充不是为了增加篇幅,而是为了让读者在真实项目里少踩坑:正常路径一般最容易跑通,异常路径、退出路径和恢复路径才是质量差距所在。
HTTP 收尾核验:补齐最后一个真实场景
这个场景建议配合请求序号一起做:第一次请求记录为 requestSeq=1,第二次请求记录为 requestSeq=2。当 requestSeq=1 晚于 requestSeq=2 返回时,页面只接受最新序号的结果。这样可以防止弱网环境下旧响应覆盖新状态。
最后再补一个线上回放点:同一个页面连续触发两次查询,第一次请求慢,第二次请求快。验收时要确认第一次慢响应不会覆盖第二次快响应的 UI 状态。这个问题在弱网环境很常见,也是请求治理容易被忽略的细节。
11. 小结:HTTP 层要可追踪
稳定的 HTTP 层不只是发请求,而是统一构造、统一超时、统一解析、统一重试边界和统一审计。页面越少接触底层网络细节,异常越容易定位。
更多推荐


所有评论(0)