flutter_curl icon indicating copy to clipboard operation
flutter_curl copied to clipboard

MimeMultipartException: Bad multipart ending

Open aditjoos opened this issue 1 year ago • 2 comments

Excpected goal: I want to upload files to multipart API using these plugins:

  1. shelf_multipart on the API side
  2. this flutter_curl plugin on the mobile side

but it gives me "MimeMultipartException: Bad multipart ending", however it works perfectly executed in CMD as shown below

image

curl -H "Content-Type: multipart/mixed" -F id=2 -F [email protected] -F [email protected] -F [email protected] -F [email protected] -F [email protected] -F [email protected] -F [email protected] -F [email protected] http://127.0.0.1:8080/test_upload

HOW I can achive this using flutter_curl? or what is the solution to this problem?

here are some supporting attachments:

  1. server side
Future<Response> upload(Request request) async {
    try {
      // final description = StringBuffer('Regular multipart request\n');

      int audioFileCount = 1;

      Map<String, String> data = {};

      await for (final part in request.parts) {
        String content = part.headers['content-disposition'];

        if (content.contains('name="id"')) {
          data.addAll({
            'id': await part.readString(),
          });
        } else if (content.contains('name="audio_$audioFileCount"')) {
          Uint8List file = await part.readBytes();

          data.addAll({
            'mfcc_$audioFileCount': extractMfcc(file)
                .toString(), // looks like --> mfcc_1: [[10.1209..], [10.1217..], ..]
          });

          audioFileCount++;
        }
      }

      print(data);

      if (audioFileCount != 9) {
        return Response.forbidden('Fields not filled perfectly.');
      }

      Map<String, dynamic> result = await model.updateMFCC(data);

      if (result['status']) {
        return Response.ok(json.encode({
          'status': true,
          'message': 'Complete processing.',
        }));
      } else {
        return Response.ok(json.encode({
          'status': false,
          'message': 'Processing failed.',
        }));
      }
    } on FormatException catch (e) {
      return Response.internalServerError(
          body: '!!EXCEPTION!!\n${e.toString()}\n${e.message.toString()}');
    } on Exception catch (e) {
      print(e
          .toString()); // Produced: "MimeMultipartException: Bad multipart ending"
      return Response.internalServerError(
          body: '!!EXCEPTION!!\n${e.toString()}');
    }
  }
  1. mobile side
String path = 'test_upload';
APIMethod method = APIMethod.post;
Map<String, String>? parameters = {'id': '1'};
List<File> files = const [File('path/to/file.wav'), File('path/to/file.wav'), ...];
List<String> fields = const ['audio_1', 'audio_2', ...];

Client client = Client(
  verbose: true,
  interceptors: [
    // HTTPCaching(),
  ],
);

await client.init();

List<Multipart> multiparts = [];

parameters.forEach((key, value) {
  multiparts.add(Multipart(
    name: key,
    data: value,
  ));
});

if (files.isNotEmpty && fields.isNotEmpty) {
  if (files.length == fields.length) {
    for (var i = 0; i < files.length; i++) {
      multiparts.add(
        Multipart.file(
          name: fields[i],
          path: files[i].path,
          filename: "audio_$i.wav",
        ),
      );
    }
  } else {
    if (withPop) context.loaderOverlay.hide();

    showFlushbar(context, 'file and field count doesnt same.',
        color: Colors.red);

    return {
      'status': false,
      'message': 'file and field count doesnt same.',
    };
  }
}

final res = await client.send(Request(
  method: method == APIMethod.post ? "POST" : "GET",
  url: "http://$baseUrl/$path",
  headers: {"Content-Type": "multipart/mixed"},
  // body: RequestBody.raw(utf8.encode("hello world")),
  // body: RequestBody.string("hello world"),
  // body: RequestBody.form({"age": "10", "hello": "world"}),
  body: RequestBody.multipart(multiparts),
));

List<int> resBody = res.body;
Map<String, dynamic> resMap = res.headers;
String resText = res.text();

print('body: $resBody');
print('map: $resMap');
print('text: $resText');
  1. output of number 2 above
I/flutter (31072): libcurl/7.72.0-DEV BoringSSL zlib/1.2.11 brotli/1.0.1 nghttp2/1.42.0
I/flutter (31072): body: [33, 33, 69, 88, 67, 69, 80, 84, 73, 79, 78, 33, 33, 10, 77, 105, 109, 101, 77, 117, 108, 116, 105, 112, 97, 114, 116, 69, 120, 99, 101, 112, 116, 105, 111, 110, 58, 32, 66, 97, 100, 32, 109, 117, 108, 116, 105, 112, 97, 114, 116, 32, 101, 110, 100, 105, 110, 103]
I/flutter (31072): map: {date: Wed, 29 Mar 2023 14:52:09 GMT, content-length: 58, x-frame-options: SAMEORIGIN, content-type: text/plain; charset=utf-8, x-xss-protection: 1; mode=block, x-content-type-options: nosniff, server: dart:io with Shelf}
I/flutter (31072): text: !!EXCEPTION!!
I/flutter (31072): MimeMultipartException: Bad multipart ending
  1. flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.7.8, on Microsoft Windows [Version 10.0.19044.1526], locale ja-JP)
[√] Windows Version (Installed version of Windows is version 10 or higher)
Checking Android licenses is taking an unexpectedly long time...[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[√] Chrome - develop for the web
[√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.2.6)
[√] Android Studio (version 2020.3)
[!] Android Studio (version 4.1)
    X Unable to determine bundled Java version.
[√] VS Code (version 1.76.2)
[√] Connected device (4 available)
[√] HTTP Host Availability

! Doctor found issues in 1 category.

aditjoos avatar Mar 29 '23 15:03 aditjoos

Could you provide a minimal server example?

From the first look at the code headers: {"Content-Type": "multipart/mixed"}, could be an issue.

For multipart requests curl will automatically generate a header for this. And multipart requests require the header to be of the format multipart/form-data; boundary=abcdxyz where abcdxyz is the key that will separate the different fields of the request. And this is generated dynamically to avoid the collision with field contents.

ajinasokan avatar Mar 31 '23 12:03 ajinasokan

Do you have a specific reason to use multipart/mixed? Does shelf require this?

ajinasokan avatar Mar 31 '23 12:03 ajinasokan