send method
Implementation
Future<http.Response?> send(
http.Request request, {
Duration? timeout,
bool returnsBinary = false,
}) async {
Future<http.Response?> doSendOnce(http.Request req) async {
final streamed = await _client.send(req).timeout(timeout!, onTimeout: () {
debugPrint('HTTP Request Timeout nach ${timeout?.inSeconds} Sekunden');
throw TimeoutException('HTTP Request Timeout', timeout);
});
if (!returnsBinary) {
return http.Response.fromStream(streamed);
}
// For binary responses (images/docs): read manually to reduce stream errors
// and avoid unhandled exceptions from partially closed connections.
final builder = BytesBuilder(copy: false);
await for (final chunk in streamed.stream) {
builder.add(chunk);
}
return http.Response.bytes(
builder.takeBytes(),
streamed.statusCode,
headers: streamed.headers,
request: streamed.request,
isRedirect: streamed.isRedirect,
persistentConnection: streamed.persistentConnection,
reasonPhrase: streamed.reasonPhrase,
);
}
http.Request clone(http.Request req) {
final r = http.Request(req.method, req.url);
r.headers.addAll(req.headers);
r.bodyBytes = req.bodyBytes;
r.encoding = req.encoding;
r.followRedirects = req.followRedirects;
r.maxRedirects = req.maxRedirects;
r.persistentConnection = req.persistentConnection;
return r;
}
bool isRetryableStreamError(Object e) {
if (e is http.ClientException) {
final msg = e.message.toLowerCase();
return msg.contains('connection closed') ||
msg.contains('connection reset') ||
msg.contains('broken pipe');
}
if (e is IOException) return true;
return false;
}
// Verwende einen längeren Standard-Timeout, wenn keiner angegeben ist
timeout ??= Duration(minutes: 2);
final runner = returnsBinary
? _binaryDownloadSemaphore.withPermit<http.Response?>(() async {
return _sendWithRetries(
() => doSendOnce(clone(request)),
isRetryable: isRetryableStreamError,
);
})
: _sendWithRetries(
() => doSendOnce(clone(request)),
isRetryable: isRetryableStreamError,
);
try {
final response = await runner;
BackendReachability.instance.clearFailure();
return response;
} on SocketException catch (e) {
if (!_logReachabilityFailureOnce(e)) {
debugPrint('Socket-Fehler beim Senden des Requests: ${e.message}');
}
if (e.message.contains('Software caused connection abort') ||
e.message.contains('Write failed')) {
debugPrint(
'App wahrscheinlich im Hintergrund, Socket wurde vom System geschlossen');
}
rethrow;
} catch (e) {
if (!_logReachabilityFailureOnce(e)) {
debugPrint('Fehler beim Senden des Requests: $e');
}
rethrow;
}
}