postJSON method
- RequestData rd
post_JSON to our backend as the user
Implementation
Future<http.BaseResponse?> postJSON(RequestData rd) async {
var headers = {HttpHeaders.contentTypeHeader: 'application/json'};
rd.json ??= {};
rd.json!['user'] = _user?.toJson();
try {
if (rd.multipartFiles.isNotEmpty) {
http.MultipartRequest? mreq;
try {
var fullURL = Uri.parse(_baseurl + rd.route);
mreq = http.MultipartRequest('POST', fullURL)
..headers.addAll({HttpHeaders.authorizationHeader: _api_key})
..fields.addAll(
/*flatten()*/ rd.json!.map<String, String>((key, value) {
if (value is Map || value is List) {
return MapEntry(key, jsonEncode(value));
}
return MapEntry(key, value.toString());
}),
); // send structured fields as JSON so the backend can parse them reliably
// Build multipart files sequentially to keep peak memory lower.
final usedUploadImageNames = <String>{};
for (final fxfile in rd.multipartFiles) {
final xfile = await fxfile;
String name = xfile.name;
if (rd.route == _uploadImage_r) {
DateTime ts = parseTimestampImageFilename(name) ??
parseTimestampImageFilename(
canonicalTimestampFilenameForXFile(xfile)) ??
DateTime.now();
name = formatTimestampImageFilename(ts);
while (usedUploadImageNames.contains(name)) {
ts = ts.add(const Duration(seconds: 1));
name = formatTimestampImageFilename(ts);
}
usedUploadImageNames.add(name);
}
final creation = FileStat.statSync(xfile.path)
.changed
.toUtc()
.millisecondsSinceEpoch;
final path = xfile.path;
mreq.files.add(await http.MultipartFile.fromPath(
creation.toString(),
path,
filename: name,
));
}
// Avoid logging full fields (can be huge and contains credentials) and reduces memory churn.
debugPrint(
'sending multipart request ${rd.route} (files: ${mreq.files.length})');
var res = (rd.timeout == null)
? await _client.send(mreq)
: await _client.send(mreq).timeout(rd.timeout!);
BackendReachability.instance.clearFailure();
return res;
} on SocketException catch (e) {
if (!_logReachabilityFailureOnce(e)) {
debugPrint('Socket-Fehler bei Multipart-Request: ${e.message}');
}
if (e.message.contains('Write failed') ||
e.message.contains('connection abort') ||
e.message.contains('Connection refused') ||
e.message.contains('Software caused connection abort')) {
debugPrint(
'Hintergrund-Socket-Fehler erkannt, App möglicherweise im Hintergrund');
}
rethrow;
} on Exception catch (e) {
debugPrint('multipartRequest, failed: $e');
rethrow;
}
} else {
final req =
makepost(rd.route, headers: headers, body: jsonEncode(rd.json));
try {
// Erhöhe die Robustheit des Requests speziell für Hintergrundausführung
final response = await send(
req,
timeout:
rd.timeout ?? Duration(minutes: 2), // Längerer Standard-Timeout
returnsBinary: rd.returnsBinary,
);
return response;
} on SocketException catch (e) {
if (!_logReachabilityFailureOnce(e)) {
debugPrint('Socket-Fehler bei HTTP-Request: ${e.message}');
}
if (e.message.contains('Write failed') ||
e.message.contains('connection abort') ||
e.message.contains('Connection refused') ||
e.message.contains('Software caused connection abort')) {
debugPrint(
'Hintergrund-Socket-Fehler erkannt, App möglicherweise im Hintergrund');
}
rethrow;
} catch (e) {
if (!_logReachabilityFailureOnce(e)) {
debugPrint('request failed: $e');
}
rethrow;
}
}
} catch (e) {
// Don't swallow OOM: returning null will trigger retries and worsen memory pressure.
if (e is OutOfMemoryError) rethrow;
final msg = e.toString();
if (msg.contains('Out of Memory') || msg.contains('Exhausted heap')) {
rethrow;
}
if (!_logReachabilityFailureOnce(e)) {
debugPrint("request failed, cause : $e");
}
return null;
}
}