HarmonyOS应用开发中HTTP网络请求的封装与拦截器深度实践

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 架构设计原则

在设计网络请求封装时,我们遵循以下原则:

  1. 单一职责:每个类和方法只负责一个明确的功能
  2. 开闭原则:对扩展开放,对修改关闭
  3. 依赖倒置:依赖于抽象而非具体实现
  4. 配置化:通过配置驱动行为,提高灵活性

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应用开发中构建出高度可维护、可测试、可扩展的网络层架构。这种设计不仅提高了代码的复用性和可读性,还为应对复杂的业务场景提供了灵活的解决方案。

关键优势总结:

  1. 关注点分离:业务代码与网络细节完全解耦
  2. 统一管理:全局的请求、响应、错误处理机制
  3. 灵活扩展:通过拦截器轻松添加新功能
  4. 易于测试:依赖注入和接口抽象便于单元测试
  5. 性能优化:缓存、重试等机制提升用户体验

随着HarmonyOS生态的不断发展,这种架构设计将为应用提供坚实的网络基础,支持业务快速迭代和长期维护。开发者可以根据具体业务需求,进一步扩展和定制拦截器,打造最适合自己项目的网络请求解决方案。

复制代码
这篇文章深入探讨了HarmonyOS应用开发中HTTP网络请求的封装与拦截器机制,从基础实现到高级特性,涵盖了完整的解决方案。通过架构设计、代码实现和最佳实践的结合,为开发者提供了可直接应用于实际项目的技术方案。
相关推荐
爱笑的眼睛113 小时前
HarmonyOS截屏与录屏API深度解析:从系统权限到像素流处理
华为·harmonyos
zhangfeng11334 小时前
医疗智能体(eiHealth) 3.4.0 使用指南(for 华为云Stack 8.5.0) 0. 华为除了这个 还有医疗 和生信方面的 产品
华为·华为云·生物信息
Android疑难杂症5 小时前
鸿蒙Notification Kit通知服务开发快速指南
android·前端·harmonyos
BlackWolfSky6 小时前
鸿蒙三方库httpclient使用
华为·harmonyos·鸿蒙
爱笑的眼睛117 小时前
HarmonyOS 分布式输入法开发指南:实现跨设备无缝输入体验
华为·harmonyos
夏文强7 小时前
HarmonyOS开发-系统AI视觉能力-图片识别
人工智能·华为·harmonyos
Random_index7 小时前
#HarmonyOS篇:管理组件拥有的状态
华为·harmonyos
光芒Shine8 小时前
【HarmonyOS-App发布】
harmonyos
m0_6855350817 小时前
光线追击算法
华为·zemax·光学·光学设计·光学工程