|
@@ -2,19 +2,22 @@ import 'dart:async';
|
|
|
import 'dart:convert';
|
|
import 'dart:convert';
|
|
|
import 'dart:io';
|
|
import 'dart:io';
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-enum HttpClientStatus {
|
|
|
|
|
- idle,
|
|
|
|
|
- connecting,
|
|
|
|
|
- connected,
|
|
|
|
|
- disconnected,
|
|
|
|
|
- error,
|
|
|
|
|
-}
|
|
|
|
|
|
|
+enum HttpClientStatus { idle, connecting, connected, disconnected, error }
|
|
|
|
|
|
|
|
typedef HttpClientStatusHandler = void Function(HttpClientStatus status);
|
|
typedef HttpClientStatusHandler = void Function(HttpClientStatus status);
|
|
|
|
|
|
|
|
|
|
+/// 单次请求的 HTTP 状态与解码正文,不共享并发请求状态。
|
|
|
|
|
+class HttpClientWrapResponse {
|
|
|
|
|
+ final int statusCode;
|
|
|
|
|
+ final dynamic data;
|
|
|
|
|
+
|
|
|
|
|
+ const HttpClientWrapResponse({required this.statusCode, required this.data});
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
class HttpClientWrap {
|
|
class HttpClientWrap {
|
|
|
final Duration retryInterval;
|
|
final Duration retryInterval;
|
|
|
|
|
+ final Duration? connectionTimeout;
|
|
|
|
|
+ final Duration? receiveTimeout;
|
|
|
final Map<String, String>? defaultHeaders;
|
|
final Map<String, String>? defaultHeaders;
|
|
|
|
|
|
|
|
HttpClient? _httpClient;
|
|
HttpClient? _httpClient;
|
|
@@ -28,20 +31,34 @@ class HttpClientWrap {
|
|
|
HttpClientWrap({
|
|
HttpClientWrap({
|
|
|
this.retryInterval = const Duration(seconds: 5),
|
|
this.retryInterval = const Duration(seconds: 5),
|
|
|
this.defaultHeaders,
|
|
this.defaultHeaders,
|
|
|
|
|
+ this.connectionTimeout,
|
|
|
|
|
+ this.receiveTimeout,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
/// 发送 GET 请求
|
|
/// 发送 GET 请求
|
|
|
- Future<dynamic> get(String url, {Map<String, String>? headers, dynamic body}) async {
|
|
|
|
|
|
|
+ Future<dynamic> get(
|
|
|
|
|
+ String url, {
|
|
|
|
|
+ Map<String, String>? headers,
|
|
|
|
|
+ dynamic body,
|
|
|
|
|
+ }) async {
|
|
|
return _sendRequest(url, 'GET', headers: headers, body: body);
|
|
return _sendRequest(url, 'GET', headers: headers, body: body);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// 发送 POST 请求
|
|
/// 发送 POST 请求
|
|
|
- Future<dynamic> post(String url, {Map<String, String>? headers, dynamic body}) async {
|
|
|
|
|
|
|
+ Future<dynamic> post(
|
|
|
|
|
+ String url, {
|
|
|
|
|
+ Map<String, String>? headers,
|
|
|
|
|
+ dynamic body,
|
|
|
|
|
+ }) async {
|
|
|
return _sendRequest(url, 'POST', headers: headers, body: body);
|
|
return _sendRequest(url, 'POST', headers: headers, body: body);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// 发送 PUT 请求
|
|
/// 发送 PUT 请求
|
|
|
- Future<dynamic> put(String url, {Map<String, String>? headers, dynamic body}) async {
|
|
|
|
|
|
|
+ Future<dynamic> put(
|
|
|
|
|
+ String url, {
|
|
|
|
|
+ Map<String, String>? headers,
|
|
|
|
|
+ dynamic body,
|
|
|
|
|
+ }) async {
|
|
|
return _sendRequest(url, 'PUT', headers: headers, body: body);
|
|
return _sendRequest(url, 'PUT', headers: headers, body: body);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -51,20 +68,29 @@ class HttpClientWrap {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Future<dynamic> _sendRequest(
|
|
Future<dynamic> _sendRequest(
|
|
|
- String url,
|
|
|
|
|
- String method, {
|
|
|
|
|
- Map<String, String>? headers,
|
|
|
|
|
- dynamic body,
|
|
|
|
|
- }) async {
|
|
|
|
|
|
|
+ String url,
|
|
|
|
|
+ String method, {
|
|
|
|
|
+ Map<String, String>? headers,
|
|
|
|
|
+ dynamic body,
|
|
|
|
|
+ }) async {
|
|
|
|
|
+ return (await request(url, method, headers: headers, body: body)).data;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// 返回 HTTP 状态与正文;旧 get/post/put/delete 仍仅返回正文。
|
|
|
|
|
+ Future<HttpClientWrapResponse> request(
|
|
|
|
|
+ String url,
|
|
|
|
|
+ String method, {
|
|
|
|
|
+ Map<String, String>? headers,
|
|
|
|
|
+ dynamic body,
|
|
|
|
|
+ }) async {
|
|
|
if (_disposed) {
|
|
if (_disposed) {
|
|
|
throw StateError('HTTP Client has been disposed');
|
|
throw StateError('HTTP Client has been disposed');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_updateStatus(HttpClientStatus.connecting);
|
|
_updateStatus(HttpClientStatus.connecting);
|
|
|
- _httpClient ??= HttpClient();
|
|
|
|
|
|
|
+ _httpClient ??= HttpClient()..connectionTimeout = connectionTimeout;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
-
|
|
|
|
|
late final Uri uri;
|
|
late final Uri uri;
|
|
|
if ((method == 'GET') && (body != null)) {
|
|
if ((method == 'GET') && (body != null)) {
|
|
|
// queryParameters Map value 只接受 String 和 List<String> 两种类型
|
|
// queryParameters Map value 只接受 String 和 List<String> 两种类型
|
|
@@ -82,21 +108,20 @@ class HttpClientWrap {
|
|
|
body.forEach((key, value) {
|
|
body.forEach((key, value) {
|
|
|
if (value is Iterable) {
|
|
if (value is Iterable) {
|
|
|
// 多值:转成 List<String>
|
|
// 多值:转成 List<String>
|
|
|
- queryParams[key.toString()] = value.map((v) => v.toString()).toList();
|
|
|
|
|
|
|
+ queryParams[key.toString()] = value
|
|
|
|
|
+ .map((v) => v.toString())
|
|
|
|
|
+ .toList();
|
|
|
} else {
|
|
} else {
|
|
|
// 单值:转成 String
|
|
// 单值:转成 String
|
|
|
queryParams[key.toString()] = value.toString();
|
|
queryParams[key.toString()] = value.toString();
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
- uri = Uri.parse(url).replace(
|
|
|
|
|
- queryParameters: queryParams,
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ uri = Uri.parse(url).replace(queryParameters: queryParams);
|
|
|
|
|
|
|
|
// uri = Uri.parse(url).replace(
|
|
// uri = Uri.parse(url).replace(
|
|
|
// queryParameters: body,
|
|
// queryParameters: body,
|
|
|
// );
|
|
// );
|
|
|
-
|
|
|
|
|
} else {
|
|
} else {
|
|
|
uri = Uri.parse(url);
|
|
uri = Uri.parse(url);
|
|
|
}
|
|
}
|
|
@@ -118,11 +143,20 @@ class HttpClientWrap {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- final response = await request.close();
|
|
|
|
|
|
|
+ final responseFuture = request.close();
|
|
|
|
|
+ final timeout = receiveTimeout;
|
|
|
|
|
+ final response = await (timeout == null
|
|
|
|
|
+ ? responseFuture
|
|
|
|
|
+ : responseFuture.timeout(timeout));
|
|
|
_updateStatus(HttpClientStatus.connected);
|
|
_updateStatus(HttpClientStatus.connected);
|
|
|
|
|
|
|
|
// 处理响应
|
|
// 处理响应
|
|
|
- final responseBody = await response.transform(utf8.decoder).join();
|
|
|
|
|
|
|
+ final responseStream = response.transform(utf8.decoder);
|
|
|
|
|
+ final responseBody =
|
|
|
|
|
+ await (timeout == null
|
|
|
|
|
+ ? responseStream
|
|
|
|
|
+ : responseStream.timeout(timeout))
|
|
|
|
|
+ .join();
|
|
|
dynamic decodedResponse;
|
|
dynamic decodedResponse;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
@@ -134,8 +168,10 @@ class HttpClientWrap {
|
|
|
// Log.d('get response: $decodedResponse');
|
|
// Log.d('get response: $decodedResponse');
|
|
|
|
|
|
|
|
_updateStatus(HttpClientStatus.idle);
|
|
_updateStatus(HttpClientStatus.idle);
|
|
|
- return decodedResponse; // 直接返回响应数据
|
|
|
|
|
-
|
|
|
|
|
|
|
+ return HttpClientWrapResponse(
|
|
|
|
|
+ statusCode: response.statusCode,
|
|
|
|
|
+ data: decodedResponse,
|
|
|
|
|
+ );
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
_handleError(e);
|
|
_handleError(e);
|
|
|
// _scheduleRetry();
|
|
// _scheduleRetry();
|