HarmonyOS应用开发中HTTP网络请求的封装与拦截器深度实践
引言
在移动应用开发中,网络请求是连接客户端与服务器的重要桥梁。随着HarmonyOS生态的不断发展,构建高效、可靠、易维护的网络层架构成为开发者关注的重点。传统的网络请求编写方式往往存在代码重复、难以统一管理、缺乏全局控制等问题。本文将深入探讨如何在HarmonyOS应用开发中实现HTTP网络请求的优雅封装与拦截器机制,通过架构层面的优化提升应用的整体质量。
1. HarmonyOS网络请求基础
1.1 原生HTTP客户端
HarmonyOS提供了@ohos.net.http模块作为基础的HTTP客户端能力。让我们先回顾一下原生API的使用方式:
typescript
import http from '@ohos.net.http';
// 创建HTTP请求
let httpRequest = http.createHttp();
// 设置请求参数
let options = {
method: http.RequestMethod.GET,
header: {
'Content-Type': 'application/json'
},
readTimeout: 60000,
connectTimeout: 60000
};
// 发起请求
httpRequest.request('https://api.example.com/data', options)
.then((data) => {
console.info('Result:' + JSON.stringify(data.result));
})
.catch((error) => {
console.error('Error:' + JSON.stringify(error));
});
这种基础用法虽然简单直接,但在复杂业务场景下会暴露出诸多问题:代码冗余、错误处理不统一、缺乏全局配置等。
1.2 原生方案的局限性
- 重复代码:每个请求都需要设置超时时间、请求头等通用参数
- 分散的错误处理:难以实现统一的错误处理逻辑
- 缺乏请求管理:无法取消请求、批量处理等
- 可测试性差:难以进行单元测试和模拟测试
2. 网络请求的深度封装设计
2.1 架构设计原则
在设计网络请求封装时,我们遵循以下原则:
- 单一职责:每个类和方法只负责一个明确的功能
- 开闭原则:对扩展开放,对修改关闭
- 依赖倒置:依赖于抽象而非具体实现
- 配置化:通过配置驱动行为,提高灵活性
2.2 核心接口设计
首先定义核心接口,建立抽象层:
typescript
// 请求配置接口
interface RequestConfig {
url: string;
method: HttpMethod;
baseURL?: string;
headers?: Record<string, string>;
params?: Record<string, any>;
data?: any;
timeout?: number;
retryCount?: number;
retryDelay?: number;
}
// 响应接口
interface Response<T = any> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
config: RequestConfig;
}
// HTTP客户端接口
interface HttpClient {
request<T = any>(config: RequestConfig): Promise<Response<T>>;
get<T = any>(url: string, config?: Partial<RequestConfig>): Promise<Response<T>>;
post<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<Response<T>>;
// 其他HTTP方法...
}
// 拦截器接口
interface Interceptor {
onRequest?(config: RequestConfig): Promise<RequestConfig>;
onResponse?<T = any>(response: Response<T>): Promise<Response<T>>;
onError?(error: any): Promise<any>;
}
2.3 基础HTTP客户端实现
基于HarmonyOS原生API实现基础HTTP客户端:
typescript
import http from '@ohos.net.http';
class HarmonyHttpClient implements HttpClient {
private instance: http.HttpRequest;
constructor() {
this.instance = http.createHttp();
}
async request<T = any>(config: RequestConfig): Promise<Response<T>> {
try {
// 转换配置格式
const httpOptions = this.transformConfig(config);
// 执行请求
const result = await this.instance.request(config.url, httpOptions);
// 构建响应对象
return this.buildResponse<T>(result, config);
} catch (error) {
throw this.buildError(error, config);
}
}
private transformConfig(config: RequestConfig): http.HttpRequestOptions {
return {
method: this.mapMethod(config.method),
header: config.headers,
readTimeout: config.timeout || 60000,
connectTimeout: config.timeout || 60000,
extraData: config.data
};
}
private mapMethod(method: HttpMethod): number {
const methodMap = {
GET: http.RequestMethod.GET,
POST: http.RequestMethod.POST,
PUT: http.RequestMethod.PUT,
DELETE: http.RequestMethod.DELETE,
PATCH: http.RequestMethod.PATCH
};
return methodMap[method] || http.RequestMethod.GET;
}
private buildResponse<T>(result: any, config: RequestConfig): Response<T> {
return {
data: result.result as T,
status: result.responseCode,
statusText: this.getStatusText(result.responseCode),
headers: result.header || {},
config
};
}
private buildError(error: any, config: RequestConfig): HttpError {
return new HttpError(
error.message || 'Network Error',
error.code,
config,
error
);
}
// 简化其他方法实现...
async get<T = any>(url: string, config?: Partial<RequestConfig>): Promise<Response<T>> {
return this.request<T>({
method: 'GET',
url,
...config
});
}
async post<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<Response<T>> {
return this.request<T>({
method: 'POST',
url,
data,
...config
});
}
}
3. 拦截器机制的高级实现
3.1 拦截器链设计
拦截器机制的核心在于构建一个处理链,让请求和响应能够依次通过各个拦截器:
typescript
class InterceptorManager {
private interceptors: Interceptor[] = [];
// 添加拦截器
use(interceptor: Interceptor): number {
this.interceptors.push(interceptor);
return this.interceptors.length - 1;
}
// 移除拦截器
eject(id: number): void {
if (this.interceptors[id]) {
this.interceptors[id] = null;
}
}
// 执行请求拦截器链
async runRequestInterceptors(config: RequestConfig): Promise<RequestConfig> {
let currentConfig = { ...config };
for (const interceptor of this.interceptors) {
if (interceptor?.onRequest) {
currentConfig = await interceptor.onRequest(currentConfig);
}
}
return currentConfig;
}
// 执行响应拦截器链
async runResponseInterceptors<T>(response: Response<T>): Promise<Response<T>> {
let currentResponse = { ...response };
for (const interceptor of this.interceptors) {
if (interceptor?.onResponse) {
currentResponse = await interceptor.onResponse(currentResponse);
}
}
return currentResponse;
}
// 执行错误拦截器链
async runErrorInterceptors(error: any): Promise<any> {
let currentError = error;
for (const interceptor of this.interceptors.reverse()) {
if (interceptor?.onError) {
currentError = await interceptor.onError(currentError);
}
}
return currentError;
}
}
3.2 增强的HTTP客户端
将拦截器机制集成到HTTP客户端中:
typescript
class EnhancedHttpClient implements HttpClient {
private httpClient: HttpClient;
private interceptorManager: InterceptorManager;
constructor(baseClient: HttpClient = new HarmonyHttpClient()) {
this.httpClient = baseClient;
this.interceptorManager = new InterceptorManager();
}
async request<T = any>(config: RequestConfig): Promise<Response<T>> {
try {
// 执行请求拦截器
const processedConfig = await this.interceptorManager.runRequestInterceptors(config);
// 发送请求
const response = await this.httpClient.request<T>(processedConfig);
// 执行响应拦截器
return await this.interceptorManager.runResponseInterceptors(response);
} catch (error) {
// 执行错误拦截器
const processedError = await this.interceptorManager.runErrorInterceptors(error);
throw processedError;
}
}
get interceptors(): InterceptorManager {
return this.interceptorManager;
}
// 实现其他HTTP方法...
}
4. 实用拦截器实现
4.1 认证令牌拦截器
实现自动添加和刷新认证令牌的拦截器:
typescript
class AuthInterceptor implements Interceptor {
private token: string = '';
private refreshToken: string = '';
private isRefreshing: boolean = false;
private refreshSubscribers: ((token: string) => void)[] = [];
constructor(private tokenStorage: TokenStorage) {}
async onRequest(config: RequestConfig): Promise<RequestConfig> {
// 排除认证相关的请求
if (this.isAuthRequest(config.url)) {
return config;
}
const token = await this.getValidToken();
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`
};
}
return config;
}
async onResponse<T>(response: Response<T>): Promise<Response<T>> {
// 处理token过期的情况
if (response.status === 401) {
return await this.handleUnauthorized(response);
}
return response;
}
private async getValidToken(): Promise<string> {
if (!this.token || this.isTokenExpired(this.token)) {
await this.refreshAuthToken();
}
return this.token;
}
private async refreshAuthToken(): Promise<void> {
if (this.isRefreshing) {
return new Promise(resolve => {
this.refreshSubscribers.push((token: string) => {
this.token = token;
resolve();
});
});
}
this.isRefreshing = true;
try {
// 调用刷新token的接口
const newToken = await this.performTokenRefresh();
this.token = newToken;
this.notifySubscribers(newToken);
} catch (error) {
this.notifySubscribers(null);
throw error;
} finally {
this.isRefreshing = false;
this.refreshSubscribers = [];
}
}
private notifySubscribers(token: string): void {
this.refreshSubscribers.forEach(callback => callback(token));
}
}
4.2 请求重试拦截器
实现智能重试机制,针对不同的错误类型采用不同的重试策略:
typescript
class RetryInterceptor implements Interceptor {
constructor(
private maxRetries: number = 3,
private baseDelay: number = 1000
) {}
async onError(error: any): Promise<any> {
const config = error.config;
// 检查是否应该重试
if (!this.shouldRetry(error) || !config || config.retryCount >= this.maxRetries) {
throw error;
}
config.retryCount = (config.retryCount || 0) + 1;
// 计算延迟时间(指数退避)
const delay = this.calculateDelay(config.retryCount);
await this.delay(delay);
// 重新发送请求
return this.retryRequest(config);
}
private shouldRetry(error: any): boolean {
// 网络错误、超时、5xx服务器错误可以重试
return error.code === 'NETWORK_ERROR' ||
error.code === 'TIMEOUT' ||
(error.response && error.response.status >= 500);
}
private calculateDelay(retryCount: number): number {
// 指数退避算法,增加随机抖动避免惊群效应
const exponentialDelay = this.baseDelay * Math.pow(2, retryCount - 1);
const jitter = exponentialDelay * 0.1 * Math.random();
return exponentialDelay + jitter;
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
private async retryRequest(config: RequestConfig): Promise<any> {
const httpClient = new HarmonyHttpClient();
return httpClient.request(config);
}
}
4.3 缓存拦截器
实现请求缓存机制,提升应用性能和用户体验:
typescript
class CacheInterceptor implements Interceptor {
private cache = new Map<string, CacheEntry>();
constructor(private defaultTTL: number = 300000) {} // 5分钟默认缓存时间
async onRequest(config: RequestConfig): Promise<RequestConfig> {
// 只缓存GET请求
if (config.method !== 'GET' || config.headers['Cache-Control'] === 'no-cache') {
return config;
}
const cacheKey = this.generateCacheKey(config);
const cached = this.cache.get(cacheKey);
if (cached && !this.isExpired(cached)) {
// 直接返回缓存数据,不再发送请求
throw new CacheHitError(cached.data, config);
}
return config;
}
async onResponse<T>(response: Response<T>): Promise<Response<T>> {
const config = response.config;
// 只缓存成功的GET请求
if (config.method === 'GET' && response.status >= 200 && response.status < 300) {
const cacheKey = this.generateCacheKey(config);
const ttl = this.getTTLFromHeaders(response.headers) || this.defaultTTL;
this.cache.set(cacheKey, {
data: response.data,
timestamp: Date.now(),
ttl
});
}
return response;
}
async onError(error: any): Promise<any> {
// 如果是缓存命中,直接返回缓存数据
if (error instanceof CacheHitError) {
return {
data: error.cachedData,
status: 200,
statusText: 'OK (from cache)',
headers: {},
config: error.config
};
}
throw error;
}
private generateCacheKey(config: RequestConfig): string {
return `${config.method}:${config.url}:${JSON.stringify(config.params)}`;
}
private isExpired(entry: CacheEntry): boolean {
return Date.now() - entry.timestamp > entry.ttl;
}
private getTTLFromHeaders(headers: Record<string, string>): number {
const cacheControl = headers['cache-control'];
if (cacheControl) {
const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
if (maxAgeMatch) {
return parseInt(maxAgeMatch[1]) * 1000;
}
}
return 0;
}
}
class CacheHitError extends Error {
constructor(
public cachedData: any,
public config: RequestConfig
) {
super('Cache hit');
}
}
5. 高级特性与最佳实践
5.1 请求取消机制
实现请求取消功能,避免不必要的网络请求:
typescript
class CancelToken {
private reason?: string;
constructor(executor: (cancel: (message?: string) => void) => void) {
executor((message?: string) => {
if (this.reason) return;
this.reason = message || 'Request canceled';
});
}
throwIfRequested(): void {
if (this.reason) {
throw new CancelError(this.reason);
}
}
static source(): { token: CancelToken; cancel: (message?: string) => void } {
let cancel: (message?: string) => void;
const token = new CancelToken(c => {
cancel = c;
});
return {
token,
cancel: cancel!
};
}
}
class CancelError extends Error {
constructor(message: string) {
super(message);
this.name = 'CancelError';
}
}
// 在HTTP客户端中集成取消功能
class CancelableHttpClient extends EnhancedHttpClient {
private cancelTokens = new Map<string, CancelToken>();
async request<T = any>(config: RequestConfig): Promise<Response<T>> {
const cancelToken = config.cancelToken;
if (cancelToken) {
cancelToken.throwIfRequested();
// 存储cancelToken用于后续取消
const requestId = this.generateRequestId(config);
this.cancelTokens.set(requestId, cancelToken);
}
try {
return await super.request<T>(config);
} finally {
if (cancelToken) {
const requestId = this.generateRequestId(config);
this.cancelTokens.delete(requestId);
}
}
}
cancelRequest(requestId: string, message?: string): void {
const token = this.cancelTokens.get(requestId);
if (token) {
// 这里需要通过反射调用私有方法,实际实现可能需要调整
token.cancel(message);
}
}
private generateRequestId(config: RequestConfig): string {
return `${config.method}-${config.url}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
}
5.2 性能监控拦截器
实现请求性能监控,帮助优化应用性能:
typescript
class PerformanceInterceptor implements Interceptor {
private metrics: RequestMetric[] = [];
async onRequest(config: RequestConfig): Promise<RequestConfig> {
const metric: RequestMetric = {
url: config.url,
method: config.method,
startTime: performance.now(),
endTime: 0,
duration: 0,
success: false
};
// 将metric存储在config中供后续使用
(config as any).__metric = metric;
return config;
}
async onResponse<T>(response: Response<T>): Promise<Response<T>> {
const metric = (response.config as any).__metric as RequestMetric;
if (metric) {
metric.endTime = performance.now();
metric.duration = metric.endTime - metric.startTime;
metric.success = true;
this.metrics.push(metric);
this.reportMetric(metric);
}
return response;
}
async onError(error: any): Promise<any> {
const config = error.config;
if (config) {
const metric = (config as any).__metric as RequestMetric;
if (metric) {
metric.endTime = performance.now();
metric.duration = metric.endTime - metric.startTime;
metric.success = false;
metric.error = error.message;
this.metrics.push(metric);
this.reportMetric(metric);
}
}
throw error;
}
private reportMetric(metric: RequestMetric): void {
// 上报性能数据到监控系统
console.info(`[Performance] ${metric.method} ${metric.url} - ${metric.duration.toFixed(2)}ms`);
// 可以在这里添加上报逻辑
// this.reportToAnalytics(metric);
}
getMetrics(): RequestMetric[] {
return [...this.metrics];
}
clearMetrics(): void {
this.metrics = [];
}
}
6. 完整示例与使用方式
6.1 创建配置化的HTTP客户端
typescript
// 创建配置化的HTTP客户端工厂
class HttpClientFactory {
static createDefaultClient(): EnhancedHttpClient {
const client = new CancelableHttpClient();
// 添加拦截器
client.interceptors.use(new AuthInterceptor(new TokenStorage()));
client.interceptors.use(new CacheInterceptor());
client.interceptors.use(new RetryInterceptor());
client.interceptors.use(new PerformanceInterceptor());
client.interceptors.use(new LoggingInterceptor());
return client;
}
static createMockClient(): EnhancedHttpClient {
const client = new EnhancedHttpClient(new MockHttpClient());
client.interceptors.use(new LoggingInterceptor());
return client;
}
}
// 使用示例
class UserService {
private httpClient: HttpClient;
constructor(httpClient?: HttpClient) {
this.httpClient = httpClient || HttpClientFactory.createDefaultClient();
}
async getUserProfile(userId: string): Promise<User> {
const response = await this.httpClient.get<User>(`/api/users/${userId}`);
return response.data;
}
async updateUserProfile(userId: string, profile: Partial<User>): Promise<User> {
const response = await this.httpClient.put<User>(`/api/users/${userId}`, profile);
return response.data;
}
async searchUsers(query: string, page: number = 1): Promise<User[]> {
const { token, cancel } = CancelToken.source();
try {
const response = await this.httpClient.get<User[]>('/api/users/search', {
params: { q: query, page },
cancelToken: token
});
return response.data;
} catch (error) {
if (error instanceof CancelError) {
console.info('Search request was canceled');
return [];
}
throw error;
}
}
}
6.2 在HarmonyOS页面中使用
typescript
@Entry
@Component
struct UserProfilePage {
private userService: UserService = new UserService();
@State user: User = null;
@State loading: boolean = false;
aboutToAppear() {
this.loadUserProfile();
}
async loadUserProfile() {
this.loading = true;
try {
this.user = await this.userService.getUserProfile('123');
} catch (error) {
console.error('Failed to load user profile:', error);
// 显示错误提示
} finally {
this.loading = false;
}
}
build() {
Column() {
if (this.loading) {
LoadingIndicator()
.width(50)
.height(50)
} else if (this.user) {
UserProfileComponent({ user: this.user })
} else {
Text('Failed to load user data')
.fontSize(16)
.fontColor(Color.Red)
}
}
.width('100%')
.height('100%')
.padding(20)
}
}
7. 测试策略
7.1 单元测试示例
typescript
// 测试HTTP客户端
describe('EnhancedHttpClient', () => {
let httpClient: EnhancedHttpClient;
let mockClient: MockHttpClient;
beforeEach(() => {
mockClient = new MockHttpClient();
httpClient = new EnhancedHttpClient(mockClient);
});
it('should add authorization header with auth interceptor', async () => {
// 设置mock响应
mockClient.setMockResponse('https://api.example.com/data', {
data: { result: 'test' },
status: 200
});
// 添加认证拦截器
const authInterceptor = new AuthInterceptor(new MockTokenStorage('test-token'));
httpClient.interceptors.use(authInterceptor);
await httpClient.get('https://api.example.com/data');
const lastRequest = mockClient.getLastRequest();
expect(lastRequest.headers['Authorization']).toBe('Bearer test-token');
});
it('should retry on network error', async () => {
const retryInterceptor = new RetryInterceptor(3, 100);
httpClient.interceptors.use(retryInterceptor);
// 模拟前两次失败,第三次成功
mockClient.setMockResponse('https://api.example.com/data',
{ success: false, error: 'Network error' },
{ success: false, error: 'Network error' },
{ data: { result: 'success' }, status: 200 }
);
const response = await httpClient.get('https://api.example.com/data');
expect(response.data.result).toBe('success');
expect(mockClient.getCallCount('https://api.example.com/data')).toBe(3);
});
});
结论
通过深度封装HTTP网络请求和实现强大的拦截器机制,我们能够在HarmonyOS应用开发中构建出高度可维护、可测试、可扩展的网络层架构。这种设计不仅提高了代码的复用性和可读性,还为应对复杂的业务场景提供了灵活的解决方案。
关键优势总结:
- 关注点分离:业务代码与网络细节完全解耦
- 统一管理:全局的请求、响应、错误处理机制
- 灵活扩展:通过拦截器轻松添加新功能
- 易于测试:依赖注入和接口抽象便于单元测试
- 性能优化:缓存、重试等机制提升用户体验
随着HarmonyOS生态的不断发展,这种架构设计将为应用提供坚实的网络基础,支持业务快速迭代和长期维护。开发者可以根据具体业务需求,进一步扩展和定制拦截器,打造最适合自己项目的网络请求解决方案。
这篇文章深入探讨了HarmonyOS应用开发中HTTP网络请求的封装与拦截器机制,从基础实现到高级特性,涵盖了完整的解决方案。通过架构设计、代码实现和最佳实践的结合,为开发者提供了可直接应用于实际项目的技术方案。