ajinasokan / flutter_curl

Flutter plugin to use libcurl for HTTP calls

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MimeMultipartException: Bad multipart ending

aditjoos opened this issue · 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 audio_1=@test.wav -F audio_2=@test.wav -F audio_3=@test.wav -F audio_4=@test.wav -F audio_5=@test.wav -F audio_6=@test.wav -F audio_7=@test.wav -F audio_8=@test.wav 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.

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.

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