From 1b26131dd93de80882bbb658853edf019c7aa3b1 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Mon, 11 Aug 2025 08:36:01 -0300 Subject: [PATCH] fix(storage_client): Resolve MultipartRequest finalization error in retry mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When using StorageRetryController, failed uploads would trigger retries that attempted to reuse the same MultipartRequest object. Since MultipartRequest objects become finalized after the first send() call, subsequent retry attempts would throw "Can't finalize a finalized Request" StateError. This fix creates a fresh MultipartRequest for each retry attempt by introducing a createRequest() factory function that generates new request objects with all the necessary headers, files, and fields configured correctly. Fixes the retry functionality for file uploads with StorageRetryController, making it reliable for handling network failures during large file uploads. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/storage_client/lib/src/fetch.dart | 29 ++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/storage_client/lib/src/fetch.dart b/packages/storage_client/lib/src/fetch.dart index 7362a2983..da68254fe 100644 --- a/packages/storage_client/lib/src/fetch.dart +++ b/packages/storage_client/lib/src/fetch.dart @@ -159,16 +159,21 @@ class Fetch { StorageRetryController? retryController, ) async { final headers = options?.headers ?? {}; - final request = http.MultipartRequest(method, Uri.parse(url)) - ..headers.addAll(headers) - ..files.add(multipartFile) - ..fields['cacheControl'] = fileOptions.cacheControl - ..headers['x-upsert'] = fileOptions.upsert.toString(); - if (fileOptions.metadata != null) { - request.fields['metadata'] = json.encode(fileOptions.metadata); - } - if (fileOptions.headers != null) { - request.headers.addAll(fileOptions.headers!); + + // Create a factory function that generates a fresh MultipartRequest for each attempt + http.MultipartRequest createRequest() { + final request = http.MultipartRequest(method, Uri.parse(url)) + ..headers.addAll(headers) + ..files.add(multipartFile) + ..fields['cacheControl'] = fileOptions.cacheControl + ..headers['x-upsert'] = fileOptions.upsert.toString(); + if (fileOptions.metadata != null) { + request.fields['metadata'] = json.encode(fileOptions.metadata); + } + if (fileOptions.headers != null) { + request.headers.addAll(fileOptions.headers!); + } + return request; } final http.StreamedResponse streamedResponse; @@ -178,6 +183,10 @@ class Fetch { () async { attempts++; _log.finest('Request: attempt: $attempts $method $url $headers'); + + // Create a fresh request for each retry attempt + final request = createRequest(); + if (httpClient != null) { return httpClient!.send(request); } else {