字
字节笔记本
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 方法:
| 方法 | 说明 |
|---|---|
get | GET 请求,支持查询参数 |
post | POST 请求 |
put | PUT 请求 |
patch | PATCH 请求 |
delete | DELETE 请求 |
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 哈希计算
项目链接
- GitHub 仓库: https://github.com/OkunaOrg/okuna-app
- 项目官网: https://okuna.io
- 当前文件: https://github.com/OkunaOrg/okuna-app/blob/master/lib/services/httpie.dart
总结
HttpieService 是一个设计精良的 Flutter HTTP 封装服务,具有以下优点:
- 统一接口 - 封装了常用的 HTTP 方法,使用简单
- 自动重试 - 智能处理网络异常和服务器错误
- 文件上传 - 支持多表单文件上传,自动处理文件名
- 错误处理 - 完善的错误分类和用户友好的错误信息
- 灵活配置 - 支持代理、认证、自定义 Header 等
该实现可作为 Flutter 应用中 HTTP 通信层的参考实现。
分享: