字节笔记本

2026年2月22日

Okuna HttpieService - Flutter HTTP 请求封装服务解析

本文介绍 Okuna 社交应用中的 HttpieService —— 一个基于 Dart/Flutter 的 HTTP 请求封装服务。该服务提供了统一的网络请求接口、自动重试机制、多表单上传等功能,简化了 Flutter 应用中的 HTTP 通信开发。

项目背景

Okuna 是一个开源的社交网络跨平台移动应用,使用 Flutter 框架开发,支持 iOS 和 Android 平台。该项目在 GitHub 上获得 330+ stars,采用 MIT 许可证开源。

项目特点:

  • 跨平台移动应用(iOS/Android)
  • 开源社交网络解决方案
  • 使用 Flutter + Dart 技术栈
  • 已于 2022 年 2 月归档

HttpieService 核心功能

httpie.dart 是 Okuna 应用中负责 HTTP 通信的核心服务类,主要提供以下功能:

1. HTTP 请求方法封装

支持标准的 RESTful HTTP 方法:

方法说明
getGET 请求,支持查询参数
postPOST 请求
putPUT 请求
patchPATCH 请求
deleteDELETE 请求

2. JSON 请求便捷方法

提供专门的 JSON 请求方法,自动处理请求头:

  • postJSON - POST JSON 数据
  • putJSON - PUT JSON 数据
  • patchJSON - PATCH JSON 数据

3. 多表单文件上传

支持 multipart/form-data 格式的文件上传:

  • postMultiform - POST 多表单数据
  • putMultiform - PUT 多表单数据
  • patchMultiform - PATCH 多表单数据

4. 自动重试机制

内置智能重试逻辑:

dart
// 503-599 状态码自动重试
bool _retryWhenResponse(BaseResponse response) {
  return response.statusCode >= 503 && response.statusCode < 600;
}

// 网络异常自动重试
bool _retryWhenError(error, StackTrace stackTrace) {
  return error is SocketException || error is ClientException;
}

5. 身份验证支持

支持 Token 认证和自定义 Magic Header:

dart
void setAuthorizationToken(String token) {
  authorizationToken = token;
}

void setMagicHeader(String name, String value) {
  magicHeaderName = name;
  magicHeaderValue = value;
}

核心代码解析

服务初始化

dart
class HttpieService {
  late LocalizationService _localizationService;
  late UtilsService _utilsService;
  String? authorizationToken;
  String? magicHeaderName;
  String? magicHeaderValue;
  late Client client;

  HttpieService() {
    client = IOClient();
    client = RetryClient(client,
      when: _retryWhenResponse,
      whenError: _retryWhenError
    );
  }
}

请求头配置

dart
Map<String, String> _getHeadersWithConfig({
  Map<String, String>? headers = const {},
  bool? appendLanguageHeader,
  bool? appendAuthorizationToken
}) {
  headers = headers ?? {};
  Map<String, String> finalHeaders = Map.from(headers);

  // 自动添加语言头
  if (appendLanguageHeader ?? true) {
    finalHeaders['Accept-Language'] = _getLanguage();
  }

  // 自动添加认证头
  if ((appendAuthorizationToken ?? false) && authorizationToken != null) {
    finalHeaders['Authorization'] = 'Token $authorizationToken';
  }

  // 添加 Magic Header
  if (magicHeaderName != null && magicHeaderValue != null) {
    finalHeaders[magicHeaderName!] = magicHeaderValue!;
  }

  return finalHeaders;
}

多表单请求实现

dart
Future<HttpieStreamedResponse> _multipartRequest(
  String url, {
  required String method,
  Map<String, dynamic>? body,
  // ... 其他参数
}) async {
  var request = new http.MultipartRequest(method, Uri.parse(url));

  // 添加请求头
  request.headers.addAll(finalHeaders);

  // 处理表单字段
  for (final String key in bodyKeys) {
    dynamic value = body![key];
    if (value is String || value is bool) {
      request.fields[key] = value.toString();
    } else if (value is List) {
      request.fields[key] = value.map((item) => item.toString())
        .toList().join(',');
    } else if (value is File) {
      // 文件处理:计算 SHA256 哈希作为文件名
      String fileMimeType = await _utilsService.getFileMimeType(value);
      var bytes = utf8.encode(value.path);
      var digest = sha256.convert(bytes);
      String newFileName = digest.toString() + '.' + fileExtension;

      var fileFuture = http.MultipartFile.fromPath(
        key,
        value.path,
        filename: newFileName,
        contentType: fileMediaType
      );
      fileFields.add(fileFuture);
    }
  }

  var files = await Future.wait(fileFields);
  files.forEach((file) => request.files.add(file));

  var response = await client.send(request);
  return HttpieStreamedResponse(response);
}

响应处理

HttpieResponse 类

dart
class HttpieResponse extends HttpieBaseResponse<http.Response> {
  HttpieResponse(_httpResponse) : super(_httpResponse);

  String get body {
    return utf8.decode(_httpResponse.bodyBytes);
  }

  Map<String, dynamic> parseJsonBody() {
    return json.decode(body);
  }
}

状态码快捷判断

dart
abstract class HttpieBaseResponse<T extends http.BaseResponse> {
  bool isOk() => _httpResponse.statusCode == HttpStatus.ok;
  bool isCreated() => _httpResponse.statusCode == HttpStatus.created;
  bool isBadRequest() => _httpResponse.statusCode == HttpStatus.badRequest;
  bool isUnauthorized() => _httpResponse.statusCode == HttpStatus.unauthorized;
  bool isForbidden() => _httpResponse.statusCode == HttpStatus.forbidden;
  bool isNotFound() => _httpResponse.statusCode == HttpStatus.notFound;
  bool isInternalServerError() =>
    _httpResponse.statusCode == HttpStatus.internalServerError;
}

错误处理

连接错误处理

dart
void _handleRequestError(error) {
  if (error is SocketException) {
    var errorCode = error.osError?.errorCode;
    // 处理常见网络错误码
    if (errorCode == 61 || errorCode == 60 || errorCode == 111 ||
        errorCode == 101 || errorCode == 104 || errorCode == 51 ||
        errorCode == 8 || errorCode == 113 || errorCode == 7 ||
        errorCode == 64) {
      throw HttpieConnectionRefusedError(error);
    }
  }
  throw error;
}

HTTP 错误转换

dart
class HttpieRequestError<T extends HttpieBaseResponse> implements Exception {
  static String convertStatusCodeToHumanReadableMessage(int statusCode) {
    switch (statusCode) {
      case HttpStatus.notFound:
        return 'Not found';
      case HttpStatus.forbidden:
        return 'You are not allowed to do this';
      case HttpStatus.badRequest:
        return 'Bad request';
      case HttpStatus.internalServerError:
      case HttpStatus.serviceUnavailable:
        return 'We\'re experiencing server errors. Please try again later.';
      default:
        return 'Server error';
    }
  }
}

代理支持

通过 HttpieOverrides 实现代理配置:

dart
class HttpieOverrides extends HttpOverrides {
  String? _proxy;

  void setProxy(String proxy) {
    _proxy = proxy;
  }

  @override
  String findProxyFromEnvironment(Uri uri, Map<String, String>? environment) {
    if (_proxy != null) return _proxy!;
    return super.findProxyFromEnvironment(uri, environment);
  }
}

技术栈

  • Dart - 编程语言
  • Flutter - 跨平台框架
  • http - Dart HTTP 客户端
  • http_retry - 自动重试机制
  • crypto - SHA256 哈希计算

项目链接

总结

HttpieService 是一个设计精良的 Flutter HTTP 封装服务,具有以下优点:

  1. 统一接口 - 封装了常用的 HTTP 方法,使用简单
  2. 自动重试 - 智能处理网络异常和服务器错误
  3. 文件上传 - 支持多表单文件上传,自动处理文件名
  4. 错误处理 - 完善的错误分类和用户友好的错误信息
  5. 灵活配置 - 支持代理、认证、自定义 Header 等

该实现可作为 Flutter 应用中 HTTP 通信层的参考实现。

分享: