Flutter技术实现:HTTP请求如何获取上传/下载进度
Absolutely! You can absolutely stream large files without loading them entirely into memory and track upload progress using Dart/Flutter's built-in APIs. Let's break down how to fix your broken pipe issue and add progress tracking step by step.
The Problem with Your Current Code
Your existing code uses request.addStream(stream) which hands off the entire file stream to the HttpClient in one go. This means you can't track how much data has been sent, and it can lead to broken pipe errors if the stream is too large or the connection can't keep up with the data flow.
Solution: Manual Chunk Processing for Progress Tracking
Instead of using addStream, we'll process the file stream in chunks, update our progress after each chunk is sent, and write each chunk to the request individually. This keeps memory usage low (since we only load one chunk at a time) and gives us granular progress updates.
Here's a revised version of your upload function with progress tracking:
typedef UploadProgressCallback = void Function(int sentBytes, int totalBytes); Future<String> _uploadFile(File assetFile, UploadProgressCallback onProgress) async { final url = '<removed>'; final totalLength = assetFile.lengthSync(); int sentBytes = 0; final client = HttpClient() ..connectionTimeout = const Duration(seconds: 30); // Prevent timeout issues try { final request = await client.postUrl(Uri.parse(url)); request.headers.add(HttpHeaders.contentTypeHeader, "application/octet-stream"); request.contentLength = totalLength; // Process the file stream chunk by chunk await for (final chunk in assetFile.openRead()) { await request.add(chunk); sentBytes += chunk.length; // Trigger progress update onProgress(sentBytes, totalLength); // Optional: Add a small delay if you're still getting broken pipe errors // This helps prevent overwhelming the connection // await Future.delayed(const Duration(milliseconds: 10)); } final response = await request.close(); if (response.statusCode == HttpStatus.ok) { return await response.transform(utf8.decoder).join(); } else { throw Exception('Upload failed with status code: ${response.statusCode}'); } } finally { client.close(); // Ensure we clean up the client } }
How to Use This Function
Call it with your file and a progress callback to update your UI:
_uploadFile(yourFile, (sent, total) { final progressPercentage = (sent / total) * 100; print('Upload progress: ${progressPercentage.toStringAsFixed(1)}%'); // Update your progress indicator here (e.g., a LinearProgressIndicator) });
Fixing the Broken Pipe Issue
Broken pipe errors usually happen when the connection is interrupted mid-transfer, or the server can't handle the data flow speed. Here are some targeted fixes:
- Add a connection timeout: As shown in the code, setting
connectionTimeoutensures the client doesn't hang indefinitely if the connection drops. - Throttle chunk sending: The optional delay in the chunk loop can prevent overwhelming the server or network buffer.
- Verify server configuration: Make sure your server accepts large
application/octet-streamrequests and has sufficient timeout settings. - Handle retries: For unstable networks, you might want to add retry logic for failed chunks (stick to idempotent requests only to avoid duplicate uploads).
Alternative: Using Stream Transformer
If you prefer to use addStream, you can wrap the file stream in a StreamTransformer to track progress as chunks pass through:
Future<String> _uploadFileWithTransformer(File assetFile, UploadProgressCallback onProgress) async { final url = '<removed>'; final totalLength = assetFile.lengthSync(); int sentBytes = 0; final client = HttpClient(); try { final request = await client.postUrl(Uri.parse(url)); request.headers.add(HttpHeaders.contentTypeHeader, "application/octet-stream"); request.contentLength = totalLength; final progressStream = assetFile.openRead().transform( StreamTransformer.fromHandlers( handleData: (chunk, sink) { sentBytes += chunk.length; onProgress(sentBytes, totalLength); sink.add(chunk); }, ), ); await request.addStream(progressStream); final response = await request.close(); return await response.transform(utf8.decoder).join(); } finally { client.close(); } }
This works too, but the chunk-by-chunk await for approach gives you more control over the transmission flow, which can help mitigate broken pipe issues.
Key Notes
assetFile.lengthSync()is safe even for large files—it only reads file metadata, not the entire content.- Always close the
HttpClientin afinallyblock to avoid resource leaks. - Double-check that your server correctly handles
application/octet-streamrequests (not expecting multipart data).
内容的提问来源于stack exchange,提问作者Jeff

