ajinasokan / flutter_curl

Flutter plugin to use libcurl for HTTP calls
MIT License
51 stars 9 forks source link

MimeMultipartException: Bad multipart ending #5

Open aditjoos opened 1 year ago

aditjoos commented 1 year ago

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()}');
    }
    }
  2. 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 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 resBody = res.body; Map<String, dynamic> resMap = res.headers; String resText = res.text();

print('body: $resBody'); print('map: $resMap'); print('text: $resText');


3. 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


5. 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.

ajinasokan commented 1 year ago

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 commented 1 year ago

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