diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md index bf791d6fe..ceb2726d9 100644 --- a/dio/CHANGELOG.md +++ b/dio/CHANGELOG.md @@ -6,6 +6,7 @@ See the [Migration Guide][] for the complete breaking changes list.** ## Unreleased - Remove `http` from `dev_dependencies`. +- Add support for cloning `MultipartFile` from `FormData`. - Only produce null response body when `ResponseType.json`. ## 5.2.1+1 @@ -29,7 +30,7 @@ See the [Migration Guide][] for the complete breaking changes list.** Dio 6.0.0 - Please use the replacement `IOHttpClientAdapter.createHttpClient` instead. - Using `CancelToken` no longer closes and re-creates `HttpClient` for each request when `IOHttpClientAdapter` is used. - Fix timeout handling for browser `receiveTimeout`. -- Improve performance when sending binary data (`List`/`Uint8List`). +- Improve performance when sending binary data (`List`/`Uint8List`). ## 5.1.2 diff --git a/dio/lib/src/multipart_file.dart b/dio/lib/src/multipart_file.dart index 85fa3e6c0..6bcb42f26 100644 --- a/dio/lib/src/multipart_file.dart +++ b/dio/lib/src/multipart_file.dart @@ -140,4 +140,15 @@ class MultipartFile { _isFinalized = true; return _stream; } + + /// Clone MultipartFile, returning a new instance of the same object. + /// This is useful if your request failed and you wish to retry it, + /// such as an unauthorized exception can be solved by refreshing the token. + MultipartFile clone() => MultipartFile( + _stream, + length, + filename: filename, + contentType: contentType, + headers: headers, + ); } diff --git a/dio/test/formdata_test.dart b/dio/test/formdata_test.dart index b1171ab4b..c1ae7b216 100644 --- a/dio/test/formdata_test.dart +++ b/dio/test/formdata_test.dart @@ -94,6 +94,99 @@ void main() async { testOn: 'vm', ); + // Cloned multipart files should be able to be read again and be the same + // as the original ones. + test( + 'complex with cloning', + () async { + final multipartFile1 = MultipartFile.fromString( + 'hello world.', + headers: { + 'test': ['a'] + }, + ); + final multipartFile2 = await MultipartFile.fromFile( + 'test/mock/_testfile', + filename: '1.txt', + headers: { + 'test': ['b'] + }, + ); + final multipartFile3 = MultipartFile.fromFileSync( + 'test/mock/_testfile', + filename: '2.txt', + headers: { + 'test': ['c'] + }, + ); + + final fm = FormData.fromMap({ + 'name': 'wendux', + 'age': 25, + 'path': '/图片空间/地址', + 'file': multipartFile1, + 'files': [ + multipartFile2, + multipartFile3, + ] + }); + final fmStr = await fm.readAsBytes(); + + // Files are finalized after being read. + try { + multipartFile1.finalize(); + fail('Should not be able to finalize a file twice.'); + } catch (e) { + expect(e, isA()); + expect( + (e as StateError).message, + 'The MultipartFile has already been finalized. This typically ' + 'means you are using the same MultipartFile in repeated requests.', + ); + } + + final fm1 = FormData(); + fm1.fields.add(MapEntry('name', 'wendux')); + fm1.fields.add(MapEntry('age', '25')); + fm1.fields.add(MapEntry('path', '/图片空间/地址')); + fm1.files.add( + MapEntry( + 'file', + multipartFile1.clone(), + ), + ); + fm1.files.add( + MapEntry( + 'files', + multipartFile2.clone(), + ), + ); + fm1.files.add( + MapEntry( + 'files', + multipartFile3.clone(), + ), + ); + expect(fmStr.length, fm1.length); + + // The cloned multipart files should be able to be read again. + expect(fm.files[0].value.isFinalized, true); + expect(fm.files[1].value.isFinalized, true); + expect(fm.files[2].value.isFinalized, true); + expect(fm1.files[0].value.isFinalized, false); + expect(fm1.files[1].value.isFinalized, false); + expect(fm1.files[2].value.isFinalized, false); + + // The cloned multipart files' properties should be the same as the + // original ones. + expect(fm1.files[0].value.filename, multipartFile1.filename); + expect(fm1.files[0].value.contentType, multipartFile1.contentType); + expect(fm1.files[0].value.length, multipartFile1.length); + expect(fm1.files[0].value.headers, multipartFile1.headers); + }, + testOn: 'vm', + ); + test('encodes maps correctly', () async { final fd = FormData.fromMap( {