HarmonyOS开发HTTP请求配置:超时、重试、拦截器
HarmonyOS开发HTTP请求配置:超时、重试、拦截器
打造健壮的网络请求层,让应用在弱网环境也能稳如泰山
一、为什么需要高级配置?
你有没有遇到过这种情况:用户在电梯里打开你的App,页面转圈转了半分钟最后提示"网络错误";或者提交订单时因为网络抖动,用户以为没成功又点了一次,结果下了两单。
这些问题的根源都是网络请求配置不当。移动端网络环境复杂多变——WiFi、4G、5G、弱信号、网络切换、服务器响应慢……如果只是简单地发请求、等响应,用户体验必然糟糕。
高级HTTP配置要解决三个核心问题:
超时控制:不能让用户无休止地等待。该断就断,断得优雅,给用户明确反馈。
重试机制:网络瞬断很常见,自动重试能大幅提升成功率。但重试也要有策略,不能无脑重试。
拦截器设计:统一处理token注入、日志记录、错误转换等横切逻辑,让业务代码更纯粹。
二、核心原理:请求配置的架构设计
2.1 超时机制详解
超时分为两种:
| 超时类型 | 含义 | 默认值 | 建议值 |
|---|---|---|---|
| connectTimeout | 建立TCP连接的超时时间 | 60000ms | 15000-30000ms |
| readTimeout | 等待服务器响应的超时时间 | 60000ms | 根据接口特性调整 |
连接超时:从发起请求到TCP连接建立完成。如果服务器宕机或网络不通,这个超时会触发。建议设置较短,快速失败。
读取超时:从发送完请求到收到完整响应。如果服务器处理慢、响应体大,这个超时会触发。建议根据接口特性设置。
2.2 重试策略设计
重试不是简单的"失败就再来一次",需要考虑:
重试条件:哪些错误值得重试?网络错误可以重试,业务错误(如密码错误)不能重试。
重试次数:重试几次合适?通常2-3次,太多会浪费资源。
重试间隔:立即重试还是等待一会儿?建议指数退避,避免雪崩。
幂等性:GET请求天然幂等,POST请求要谨慎重试。
2.3 拦截器模式
拦截器借鉴了OkHttp的设计思想,分为请求拦截器和响应拦截器:
请求拦截器:在请求发出前介入,可以修改请求参数、添加通用头、记录日志。
响应拦截器:在响应返回后介入,可以统一解析、错误转换、缓存处理。
三、代码实战:完整配置实现
场景一:智能超时配置
根据不同接口类型设置不同的超时策略。
import http from '@ohos.net.http';
// 超时配置策略
class TimeoutStrategy {
// API类型枚举
static readonly API = {
// 普通查询接口:快速响应
QUERY: { connect: 15000, read: 20000 },
// 登录认证:要求快速
AUTH: { connect: 10000, read: 15000 },
// 数据提交:允许较长时间
SUBMIT: { connect: 20000, read: 30000 },
// 文件上传:长时间等待
UPLOAD: { connect: 30000, read: 120000 },
// 文件下载:长时间等待
DOWNLOAD: { connect: 30000, read: 300000 },
// 实时数据:低延迟要求
REALTIME: { connect: 5000, read: 10000 }
};
// 根据URL自动判断类型
static detectByUrl(url: string): { connect: number; read: number } {
if (url.includes('/auth/') || url.includes('/login')) {
return this.API.AUTH;
}
if (url.includes('/upload') || url.includes('/file')) {
return this.API.UPLOAD;
}
if (url.includes('/download')) {
return this.API.DOWNLOAD;
}
if (url.includes('/submit') || url.includes('/save')) {
return this.API.SUBMIT;
}
return this.API.QUERY;
}
}
// 使用示例
async function smartRequest(url: string, method: http.RequestMethod): Promise<http.HttpResponse> {
let httpRequest = http.createHttp();
try {
// 自动检测并应用超时策略
let timeout = TimeoutStrategy.detectByUrl(url);
let response = await httpRequest.request(url, {
method: method,
connectTimeout: timeout.connect,
readTimeout: timeout.read
});
return response;
} finally {
httpRequest.destroy();
}
}
场景二:指数退避重试机制
实现智能重试,失败后等待时间逐渐增加。
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';
// 重试配置
interface RetryConfig {
maxRetries: number; // 最大重试次数
initialDelay: number; // 初始延迟(毫秒)
maxDelay: number; // 最大延迟(毫秒)
backoffFactor: number; // 退避因子(指数基数)
retryableErrors: number[]; // 可重试的错误码
}
// 默认重试配置
const DEFAULT_RETRY_CONFIG: RetryConfig = {
maxRetries: 3,
initialDelay: 1000,
maxDelay: 30000,
backoffFactor: 2,
retryableErrors: [
http.ResponseCode.INTERNAL_ERROR, // 500
http.ResponseCode.BAD_GATEWAY, // 502
http.ResponseCode.SERVICE_UNAVAILABLE, // 503
http.ResponseCode.GATEWAY_TIMEOUT // 504
]
};
// 延迟工具函数
function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 带重试的HTTP请求
async function requestWithRetry(
url: string,
options: http.HttpRequestOptions,
retryConfig: RetryConfig = DEFAULT_RETRY_CONFIG
): Promise<http.HttpResponse | null> {
let lastError: BusinessError | null = null;
let attempt = 0;
while (attempt <= retryConfig.maxRetries) {
let httpRequest = http.createHttp();
try {
console.info(`请求尝试 ${attempt + 1}/${retryConfig.maxRetries + 1}: ${url}`);
let response = await httpRequest.request(url, options);
// 检查是否需要重试
if (response.responseCode >= 200 && response.responseCode < 300) {
// 成功,直接返回
return response;
}
// 判断是否可重试的错误码
if (!retryConfig.retryableErrors.includes(response.responseCode)) {
// 不可重试的错误,直接返回响应
return response;
}
console.warn(`服务器错误 ${response.responseCode},准备重试`);
} catch (error) {
lastError = error as BusinessError;
console.error(`请求异常: ${lastError.code} - ${lastError.message}`);
// 网络错误通常可以重试
} finally {
httpRequest.destroy();
}
// 检查是否还有重试机会
if (attempt < retryConfig.maxRetries) {
// 计算退避延迟:initialDelay * backoffFactor^attempt
let delayTime = Math.min(
retryConfig.initialDelay * Math.pow(retryConfig.backoffFactor, attempt),
retryConfig.maxDelay
);
console.info(`等待 ${delayTime}ms 后重试...`);
await delay(delayTime);
}
attempt++;
}
// 所有重试都失败了
console.error(`重试${retryConfig.maxRetries}次后仍然失败`);
throw lastError || new Error('请求失败');
}
// 使用示例:提交订单(带重试)
async function submitOrder(orderData: object): Promise<boolean> {
try {
let response = await requestWithRetry(
'https://api.shop.com/v1/orders',
{
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: JSON.stringify(orderData)
},
{
maxRetries: 2, // 订单提交最多重试2次
initialDelay: 2000, // 初始等待2秒
maxDelay: 10000, // 最多等待10秒
backoffFactor: 2,
retryableErrors: [500, 502, 503, 504]
}
);
return response !== null && response.responseCode === 200;
} catch (error) {
console.error('订单提交失败:', error);
return false;
}
}
场景三:拦截器实现
实现类似OkHttp的拦截器机制,统一处理请求和响应。
import http from '@ohos.net.http';
// 请求上下文
interface RequestContext {
url: string;
options: http.HttpRequestOptions;
metadata: Map<string, any>; // 拦截器间传递的数据
}
// 响应上下文
interface ResponseContext {
response: http.HttpResponse;
request: RequestContext;
}
// 拦截器接口
interface Interceptor {
// 请求拦截:返回修改后的请求配置
interceptRequest?(context: RequestContext): RequestContext;
// 响应拦截:返回修改后的响应或抛出错误
interceptResponse?(context: ResponseContext): http.HttpResponse;
}
// Token注入拦截器
class AuthInterceptor implements Interceptor {
private tokenProvider: () => string | null;
constructor(tokenProvider: () => string | null) {
this.tokenProvider = tokenProvider;
}
interceptRequest(context: RequestContext): RequestContext {
let token = this.tokenProvider();
if (token) {
// 添加Authorization头
if (!context.options.header) {
context.options.header = {};
}
(context.options.header as Record<string, string>)['Authorization'] = `Bearer ${token}`;
console.info('已注入认证Token');
}
return context;
}
}
// 日志拦截器
class LoggingInterceptor implements Interceptor {
interceptRequest(context: RequestContext): RequestContext {
console.info(`[HTTP] >>> ${context.options.method} ${context.url}`);
console.info(`[HTTP] Headers: ${JSON.stringify(context.options.header)}`);
// 记录请求开始时间
context.metadata.set('startTime', Date.now());
return context;
}
interceptResponse(context: ResponseContext): http.HttpResponse {
let startTime = context.request.metadata.get('startTime') as number;
let duration = Date.now() - startTime;
console.info(`[HTTP] <<< ${context.response.responseCode} (${duration}ms)`);
return context.response;
}
}
// 错误处理拦截器
class ErrorHandlerInterceptor implements Interceptor {
interceptResponse(context: ResponseContext): http.HttpResponse {
let code = context.response.responseCode;
// 401未授权:跳转登录
if (code === http.ResponseCode.UNAUTHORIZED) {
console.warn('Token已过期,需要重新登录');
// 触发全局登录状态更新
// EventBus.emit('auth:expired');
}
// 403禁止访问:提示无权限
if (code === http.ResponseCode.FORBIDDEN) {
console.error('无权限访问该资源');
}
// 5xx服务器错误:记录日志
if (code >= 500) {
console.error(`服务器错误: ${code}`);
// 上报错误日志
// ErrorReporter.report(context.request.url, code);
}
return context.response;
}
}
// HTTP客户端(带拦截器)
class HttpClient {
private interceptors: Interceptor[] = [];
// 添加拦截器
addInterceptor(interceptor: Interceptor): void {
this.interceptors.push(interceptor);
}
// 执行请求
async request(url: string, options: http.HttpRequestOptions): Promise<http.HttpResponse> {
// 构建请求上下文
let context: RequestContext = {
url: url,
options: { ...options },
metadata: new Map()
};
// 执行请求拦截器链
for (let interceptor of this.interceptors) {
if (interceptor.interceptRequest) {
context = interceptor.interceptRequest(context);
}
}
// 发送实际请求
let httpRequest = http.createHttp();
try {
let response = await httpRequest.request(context.url, context.options);
// 执行响应拦截器链
let responseContext: ResponseContext = {
response: response,
request: context
};
for (let interceptor of this.interceptors) {
if (interceptor.interceptResponse) {
response = interceptor.interceptResponse(responseContext);
responseContext.response = response;
}
}
return response;
} finally {
httpRequest.destroy();
}
}
}
// 全局HTTP客户端实例
const globalHttpClient = new HttpClient();
// 初始化拦截器
function initHttpClient() {
// Token注入
globalHttpClient.addInterceptor(new AuthInterceptor(() => {
// 从本地存储获取token
return 'your_stored_token';
}));
// 日志记录
globalHttpClient.addInterceptor(new LoggingInterceptor());
// 错误处理
globalHttpClient.addInterceptor(new ErrorHandlerInterceptor());
}
// 使用示例
@Entry
@Component
struct ApiDemoPage {
@State result: string = '';
async aboutToAppear() {
initHttpClient();
try {
let response = await globalHttpClient.request(
'https://api.example.com/user/profile',
{
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' }
}
);
if (response.responseCode === 200) {
this.result = response.result as string;
}
} catch (error) {
console.error('请求失败:', error);
}
}
build() {
Column() {
Text(this.result || '加载中...')
.width('100%')
.padding(20)
}
}
}
四、踩坑与注意事项
坑点一:重试导致的重复提交
POST请求重试可能导致重复提交数据,需要特别注意。
// ❌ 错误:POST请求无条件重试
async function badExample() {
await requestWithRetry(url, {
method: http.RequestMethod.POST,
extraData: orderData
}); // 可能导致重复下单!
}
// ✅ 正确:POST请求使用幂等键
async function goodExample() {
// 生成唯一幂等键
let idempotencyKey = `order_${Date.now()}_${Math.random()}`;
await requestWithRetry(url, {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'X-Idempotency-Key': idempotencyKey // 服务器根据此键去重
},
extraData: orderData
});
}
// ✅ 另一种方案:POST请求不自动重试,由用户决定
async function safePostRequest(url: string, data: object) {
// POST请求默认不重试
return requestWithRetry(url, {
method: http.RequestMethod.POST,
extraData: JSON.stringify(data)
}, {
maxRetries: 0, // 不重试
initialDelay: 0,
maxDelay: 0,
backoffFactor: 1,
retryableErrors: []
});
}
坑点二:超时时间单位混淆
鸿蒙HTTP超时单位是毫秒,容易与其他库混淆。
// ❌ 错误:误以为是秒
let options = {
connectTimeout: 30, // 实际只有30毫秒!
readTimeout: 60 // 实际只有60毫秒!
};
// ✅ 正确:明确使用毫秒
let options = {
connectTimeout: 30000, // 30秒
readTimeout: 60000 // 60秒
};
// ✅ 推荐:使用常量定义
const SECOND = 1000;
const MINUTE = 60 * SECOND;
let options = {
connectTimeout: 30 * SECOND,
readTimeout: 1 * MINUTE
};
坑点三:拦截器顺序问题
拦截器的执行顺序很重要,顺序错误可能导致问题。
// ❌ 错误顺序:日志拦截器在Token注入之前
client.addInterceptor(new LoggingInterceptor()); // 先记录日志
client.addInterceptor(new AuthInterceptor(...)); // 后注入Token
// 结果:日志中没有Token信息
// ✅ 正确顺序:先注入Token,再记录日志
client.addInterceptor(new AuthInterceptor(...)); // 先注入Token
client.addInterceptor(new LoggingInterceptor()); // 后记录日志
// 结果:日志中包含完整请求信息
坑点四:超时后的资源未释放
超时异常发生后,HTTP请求对象可能未被正确销毁。
// ❌ 错误:超时后未销毁
async function badTimeout() {
let httpRequest = http.createHttp();
try {
await httpRequest.request(url, {
connectTimeout: 5000
});
} catch (error) {
// 超时异常,但没有销毁对象
console.error('超时');
}
// httpRequest未销毁,内存泄漏!
}
// ✅ 正确:使用finally确保销毁
async function goodTimeout() {
let httpRequest = http.createHttp();
try {
await httpRequest.request(url, {
connectTimeout: 5000
});
} catch (error) {
console.error('超时:', error);
} finally {
httpRequest.destroy(); // 无论如何都销毁
}
}
五、HarmonyOS 6适配指南
5.1 新增配置项
HarmonyOS 6新增了若干请求配置选项:
// HarmonyOS 6 新增配置
let options: http.HttpRequestOptions = {
method: http.RequestMethod.GET,
// 新增:请求优先级
priority: http.RequestPriority.HIGH,
// 新增:HTTP协议版本
usingProtocol: http.HttpProtocol.HTTP2, // 支持HTTP/2
// 新增:期望的数据类型
expectDataType: http.HttpDataType.OBJECT, // 自动解析为对象
// 原有配置
connectTimeout: 30000,
readTimeout: 30000
};
5.2 重试机制优化
HarmonyOS 6提供了系统级的重试支持:
// HarmonyOS 6:系统级重试配置
let options: http.HttpRequestOptions = {
method: http.RequestMethod.GET,
// 新增:自动重试配置
autoRetry: {
maxRetries: 3, // 最大重试次数
retryDelay: 1000, // 重试延迟(毫秒)
retryOn: [500, 502, 503] // 触发重试的状态码
}
};
5.3 拦截器性能优化
HarmonyOS 6对拦截器链的执行进行了优化:
// 使用async/await确保拦截器顺序执行
class AsyncInterceptor implements Interceptor {
async interceptRequest(context: RequestContext): Promise<RequestContext> {
// 异步操作,如从存储读取token
let token = await this.getStoredToken();
if (token) {
context.options.header = {
...context.options.header,
'Authorization': `Bearer ${token}`
};
}
return context;
}
private async getStoredToken(): Promise<string | null> {
// 模拟异步读取
return new Promise(resolve => {
setTimeout(() => resolve('async_token'), 100);
});
}
}
六、总结一下下
HTTP请求的高级配置是打造健壮应用的关键一环。本文从三个维度展开:
超时配置:根据接口特性设置合理的超时时间。快速接口用短超时,耗时操作用长超时。自动检测URL类型并应用对应策略,既保证用户体验,又避免无谓等待。
重试机制:指数退避是重试策略的核心。失败后等待时间逐渐增加,既给服务器喘息机会,又避免雪崩效应。特别注意POST请求的重试问题,使用幂等键或禁用重试。
拦截器设计:借鉴成熟框架的设计思想,将横切逻辑抽离到拦截器中。Token注入、日志记录、错误处理各司其职,业务代码保持纯粹。注意拦截器顺序,先修改再记录。
HarmonyOS 6带来了更多系统级支持,如请求优先级、HTTP/2协议、自动重试等。合理利用这些新特性,可以让网络层更加健壮高效。
下一篇文章,我们将进入HTTPS与证书校验的世界,探讨移动端网络安全的最佳实践!
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)