relatica/lib/utils/network_utils.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});
}