kopia lustrzana https://gitlab.com/mysocialportal/relatica
275 wiersze
7.1 KiB
Dart
275 wiersze
7.1 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:logging/logging.dart';
|
|
import 'package:result_monad/result_monad.dart';
|
|
|
|
import '../friendica_client/paged_response.dart';
|
|
import '../globals.dart';
|
|
import '../models/auth/profile.dart';
|
|
import '../models/exec_error.dart';
|
|
|
|
final _logger = Logger('NetworkUtils');
|
|
|
|
http.Response requestTimeout() => http.Response('Client side timeout', 408);
|
|
|
|
enum _RequestType {
|
|
get,
|
|
}
|
|
|
|
const _expireDuration = Duration(seconds: 2);
|
|
|
|
class _CachedResponse {
|
|
final _RequestType requestType;
|
|
final Uri requestUri;
|
|
final Map<String, dynamic> requestBody;
|
|
final Map<String, String> headers;
|
|
final http.Response response;
|
|
final DateTime requestTime;
|
|
|
|
_CachedResponse(
|
|
{required this.requestType,
|
|
required this.requestUri,
|
|
required this.requestBody,
|
|
required this.headers,
|
|
required this.response,
|
|
required this.requestTime});
|
|
|
|
factory _CachedResponse.requestStub(_RequestType type, Uri uri,
|
|
Map<String, String>? headers, Map<String, dynamic>? body) =>
|
|
_CachedResponse(
|
|
requestType: type,
|
|
requestUri: uri,
|
|
requestBody: body ?? {},
|
|
headers: headers ?? {},
|
|
response: http.Response('', 555),
|
|
requestTime: DateTime(0),
|
|
);
|
|
|
|
factory _CachedResponse.response(
|
|
_RequestType type,
|
|
Uri uri,
|
|
Map<String, String>? headers,
|
|
Map<String, dynamic>? body,
|
|
http.Response response,
|
|
) =>
|
|
_CachedResponse(
|
|
requestType: type,
|
|
requestUri: uri,
|
|
requestBody: body ?? {},
|
|
headers: headers ?? {},
|
|
response: response,
|
|
requestTime: DateTime.now(),
|
|
);
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is _CachedResponse &&
|
|
runtimeType == other.runtimeType &&
|
|
requestType == other.requestType &&
|
|
requestUri == other.requestUri;
|
|
|
|
@override
|
|
int get hashCode => requestType.hashCode ^ requestUri.hashCode;
|
|
}
|
|
|
|
class _ExpiringRequestCache {
|
|
final _responses = <_CachedResponse, _CachedResponse>{};
|
|
|
|
void _cleanupCache() {
|
|
final expireTime = DateTime.now().subtract(_expireDuration);
|
|
_logger
|
|
.finest('Cleaning up request cache with ${_responses.length} entries');
|
|
_responses.removeWhere((key, value) {
|
|
final expired = key.requestTime.isBefore(expireTime);
|
|
if (expired) {
|
|
_logger.finest(
|
|
'Expiring request: ${value.requestType} => ${value.requestUri}');
|
|
}
|
|
return expired;
|
|
});
|
|
_logger.finest('Cleaned up request cache has ${_responses.length} entries');
|
|
}
|
|
|
|
Future<http.Response> getRequestOrExecute({
|
|
required Uri url,
|
|
Map<String, String>? headers,
|
|
Map<String, dynamic>? body,
|
|
Duration? timeout,
|
|
}) async {
|
|
_cleanupCache();
|
|
const type = _RequestType.get;
|
|
final requestStub = _CachedResponse.requestStub(
|
|
type,
|
|
url,
|
|
headers,
|
|
null,
|
|
);
|
|
|
|
late final http.Response response;
|
|
if (_responses.containsKey(requestStub)) {
|
|
print('Returning cached response for $type => $url');
|
|
response = _responses[requestStub]?.response ?? http.Response('', 555);
|
|
} else {
|
|
final request = http.get(
|
|
url,
|
|
headers: headers,
|
|
);
|
|
|
|
response = await request.timeout(
|
|
timeout ?? apiCallTimeout,
|
|
onTimeout: requestTimeout,
|
|
);
|
|
|
|
final cacheEntry = _CachedResponse.response(
|
|
type,
|
|
url,
|
|
headers ?? {},
|
|
body ?? {},
|
|
response,
|
|
);
|
|
|
|
print('Adding cached response for $type => $url');
|
|
_responses[cacheEntry] = cacheEntry;
|
|
}
|
|
|
|
return response;
|
|
}
|
|
}
|
|
|
|
final _cache = _ExpiringRequestCache();
|
|
|
|
FutureResult<PagedResponse<String>, ExecError> getUrl(
|
|
Uri url, {
|
|
Map<String, String>? headers,
|
|
Duration? timeout,
|
|
}) async {
|
|
_logger.fine('GET: $url');
|
|
final requestHeaders = headers ?? {};
|
|
if (usePhpDebugging) {
|
|
requestHeaders['Cookie'] = 'XDEBUG_SESSION=PHPSTORM;path=/';
|
|
}
|
|
try {
|
|
final response = await _cache.getRequestOrExecute(
|
|
url: url,
|
|
headers: headers,
|
|
timeout: timeout ?? apiCallTimeout,
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
return Result.error(ExecError(
|
|
type: ErrorType.authentication,
|
|
message: '${response.statusCode}: ${response.reasonPhrase}'));
|
|
}
|
|
return PagedResponse.fromLinkHeader(
|
|
response.headers['link'],
|
|
utf8.decode(response.bodyBytes),
|
|
);
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
FutureResult<String, ExecError> postUrl(
|
|
Uri url,
|
|
Map<String, dynamic> body, {
|
|
Map<String, String>? headers,
|
|
Duration? timeout,
|
|
}) async {
|
|
_logger.fine('POST: $url \n Body: $body');
|
|
final requestHeaders = headers ?? {};
|
|
if (usePhpDebugging) {
|
|
requestHeaders['Cookie'] = 'XDEBUG_SESSION=PHPSTORM;path=/';
|
|
}
|
|
try {
|
|
final request = http.post(
|
|
url,
|
|
headers: requestHeaders,
|
|
body: jsonEncode(body),
|
|
);
|
|
|
|
final response = await request.timeout(
|
|
timeout ?? apiCallTimeout,
|
|
onTimeout: requestTimeout,
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
return Result.error(ExecError(
|
|
type: ErrorType.authentication,
|
|
message: '${response.statusCode}: ${response.reasonPhrase}'));
|
|
}
|
|
return Result.ok(utf8.decode(response.bodyBytes));
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
FutureResult<String, ExecError> putUrl(
|
|
Uri url,
|
|
Map<String, dynamic> body, {
|
|
Map<String, String>? headers,
|
|
Duration? timeout,
|
|
}) async {
|
|
_logger.fine('PUT: $url \n Body: $body');
|
|
try {
|
|
final request = http.put(
|
|
url,
|
|
headers: headers,
|
|
body: jsonEncode(body),
|
|
);
|
|
|
|
final response = await request.timeout(
|
|
timeout ?? apiCallTimeout,
|
|
onTimeout: requestTimeout,
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
return Result.error(ExecError(
|
|
type: ErrorType.authentication,
|
|
message: '${response.statusCode}: ${response.reasonPhrase}'));
|
|
}
|
|
return Result.ok(utf8.decode(response.bodyBytes));
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
FutureResult<String, ExecError> deleteUrl(
|
|
Uri url,
|
|
Map<String, dynamic> body, {
|
|
Map<String, String>? headers,
|
|
Duration? timeout,
|
|
}) async {
|
|
_logger.fine('DELETE: $url');
|
|
try {
|
|
final request = http.delete(
|
|
url,
|
|
headers: headers,
|
|
body: jsonEncode(body),
|
|
);
|
|
|
|
final response = await request.timeout(
|
|
timeout ?? apiCallTimeout,
|
|
onTimeout: requestTimeout,
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
return Result.error(ExecError(
|
|
type: ErrorType.authentication,
|
|
message: '${response.statusCode}: ${response.reasonPhrase}'));
|
|
}
|
|
return Result.ok(utf8.decode(response.bodyBytes));
|
|
} catch (e) {
|
|
return Result.error(
|
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
|
}
|
|
}
|
|
|
|
Uri generateTagUrlFromProfile(Profile profile, String tag) {
|
|
return Uri.https(profile.serverName, '/search', {'tag': tag});
|
|
}
|