dart-lang / http

A composable API for making HTTP requests in Dart.
https://pub.dev/packages/http
BSD 3-Clause "New" or "Revised" License
1.01k stars 351 forks source link

http.MultipartRequest with utf-8 support for body fields #159

Open Eimji opened 6 years ago

Eimji commented 6 years ago

Hello

As suggested here, I would like to submit one issue with http.MultipartRequest. How to support utf-8 format for the body fields?

I'm writing a Flutter application, which includes a form to upload one image along with form fields

Future<Map<String, dynamic>> multipartPostRequest(String method, String route, Map<String, String> bodyFields,  List<http.MultipartFile> files) async {
    // method = POST or PUT...
    var url = Uri.parse( Application.backendBaseURL + route);
    var request = new http.MultipartRequest(method, url)
    ..fields.addAll(bodyFields);
    if (files != null && files.length > 0)
      request.files.addAll(files);    

    return LocalAuth.getJWToken()
    .then((token) {
      request.headers[HttpHeaders.AUTHORIZATION] = 'Bearer $token';
      return request.send();
    })
    .then((streamedResponse) {
      _updateJWTokenFrom(streamedResponse);
      return http.Response.fromStream(streamedResponse);
    })
    .then((response) {
      return response.body;
    })
    .then((body) {
      if (isJSON(body))
        return json.decode(body);
      else 
        return {'mssg': body};
    })
    .catchError((e) {
      return {'mssg': 'Got error: $e'};
    });
  }
}

This code for making a POST as multipart/form-data works well, but whan a body field contains characters such as é à ç, the field is reset to empty. So my question is how can I make a post multipart/form-data with fields in utf-8 ? Trying to add request.headers[HttpHeaders.CONTENT_TYPE] = 'multipart/form-data;charset=utf-8';does not change.

Thanks a lot in advance

nex3 commented 6 years ago

I'm not sure I understand exactly what's going on. You say "when a body field contains characters such as é à ç, the field is reset to empty"... what does it mean for the field to be "reset to empty"? Is the field empty in the in-flight request data? Or are you checking based on how the server treats the data? Are you sure it's not an issue with the server's handling of UTF-8?

Eimji commented 6 years ago

Thanks. My form in my mobile application mainly contains two text inputs (title and content) and one file input (for image). The API server is developed in Go, and I test the condition if the body parameters corresponding to the text inputs are not empty. My server implementation works. I tested with Postman with the characters ç à é etc Here is code on the server side

func CreateNewPost(c *gin.Context) {
    title := strings.TrimSpace(c.PostForm("title"))
    content := strings.TrimSpace(c.PostForm("content"))
    latitude := c.PostForm("latitude")
    longitude := c.PostForm("longitude")
    imageFile, _ := c.FormFile("images")
    replyTo := c.PostForm("reply_to")
    private := c.PostForm("private") != ""
    createdBy := c.PostForm("from")

    language := c.Query("lang")

    var (
        imageFilename string = ""
        user          User
        newPost       Post
    )

    if title == "" && content == "" && (imageFile == nil || imageFile.Filename == "") {
        if language == "fr" {
            c.JSON(http.StatusBadRequest, gin.H{"code": 400, "mssg": "Votre ping est vide !"})
        } else {
            c.JSON(http.StatusBadRequest, gin.H{"code": 400, "mssg": "Your ping is empty!"})
        }
        return
    }
....

Dart http.MultipartRequest works well with my implementation. However, I'm in France, and we have letters such as é à ç ö etc, so when filling my form in French with those characters, on the server side, the body parameters are empty ! I think it is a utf8 issue with http.MultipartRequest ?

Eimji commented 6 years ago

Does anybody can help me? This code doesn't work, the content field is reset to empty string

    var url = Uri.parse( Application.backendBaseURL + route);
    var request = new http.MultipartRequest('POST', url);
    request.fields['content'] = 'à éviter, ça marche pas!';

    if (files != null && files.length > 0)
      request.files.addAll(files);    

    request.send().then((response) {
       if (response.statusCode == 200) print("Uploaded!");
    });

But if I write request.fields['content'] = 'a eviter, ca marche pas!'; The content field is sent correctly to the backend API This means there may be an issue with utf-8 fields, right ?

nex3 commented 6 years ago

Can you post a pure-Dart reproduction, that includes server code as well?

lucasjinreal commented 6 years ago

Damn, I got this issue too...... I think this is imergency cause Not-English has seriously problem with http.MultipartRequest this class. I am in China, I do exactly like @Eimji , with field contains Chinese such as 妈的,我日了, and the server side gots empty. However I am not test on both Android or iOS side, at least, on iOS side we have this trouble.

I wanna suggested Flutter authors or it's contributors work on this issue quickly, cause flutter is groth very fast, but this basic package are still with so many bugs... Just report this, help solve it very soon.

BTW, @Eimji Did'u find any solution on this? #

Eimji commented 6 years ago

Hello

I didn't find any solution. My backend implementation works (tested with Postman and browser) I tried many workarounds to fix the issue but no success. Due to deadlines in my dev planning, I finally implemented a trick in my Flutter application. Instead of sending one POST request with multipart I send two successive POST requests: one for text inputs and one for file inputs. Not the best workaround but it works! Here is my app https://play.google.com/store/apps/details?id=com.orange.labs.spilhenn You can also find the iOS version here https://itunes.apple.com/fr/app/spilhenn/id1385377725

I will post a sample code in Dart for client and in Go for backend to reproduce the issue

asmh1989 commented 6 years ago

@Eimji @jinfagang

In my flutter app, I commented on the following code, I got the Go backend to recognize it properly.

multipart_request.dart

  /// Returns the header string for a field. The return value is guaranteed to
  /// contain only ASCII characters.
  String _headerForField(String name, String value) {
    var header =
        'content-disposition: form-data; name="${_browserEncode(name)}"';
//    if (!isPlainAscii(value)) {
//      header = '$header\r\n'
//          'content-type: text/plain; charset=utf-8\r\n'
//          'content-transfer-encoding: binary';
//    }
    return '$header\r\n\r\n';
  }
iamsaurabhc commented 5 years ago

Hello

I didn't find any solution. My backend implementation works (tested with Postman and browser) I tried many workarounds to fix the issue but no success. Due to deadlines in my dev planning, I finally implemented a trick in my Flutter application. Instead of sending one POST request with multipart I send two successive POST requests: one for text inputs and one for file inputs. Not the best workaround but it works! Here is my app https://play.google.com/store/apps/details?id=com.orange.labs.spilhenn You can also find the iOS version here https://itunes.apple.com/fr/app/spilhenn/id1385377725

I will post a sample code in Dart for client and in Go for backend to reproduce the issue

Did you find any answer to this? I am also facing the same

phuquy2114 commented 5 years ago

please follow my code, It's not working when upload file audio and video, please help me! Map<String, String> headers = { "Authorization": "Bearer $token", "Content-Type": "multipart/form-data" }; var uri = Uri.parse(settings.baseUrl + "/update-introduce"); print(uri); var request = new http.MultipartRequest("POST", uri); // add header request.headers.addAll(headers);

request.fields['bio'] =  " Hello hi everybody ",

request.files.add(await http.MultipartFile.fromPath( 'audio_file', _formData['audio_file'].path)); request.files.add(await http.MultipartFile.fromPath( 'movie_file', _formData['movie_file'].path));

StreamedResponse response = await request.send().then((response) {
  print("successful");
  print(response.toString());
});

print(response.toString());
response.stream.transform(utf8.decoder).listen((value) {
  print(value);
});

this is error show log I/flutter ( 9636): null E/flutter ( 9636): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception: E/flutter ( 9636): NoSuchMethodError: The getter 'stream' was called on null. E/flutter ( 9636): Receiver: null

jeeali commented 4 years ago

Does anybody can help me? This code doesn't work, the content field is reset to empty string

    var url = Uri.parse( Application.backendBaseURL + route);
    var request = new http.MultipartRequest('POST', url);
    request.fields['content'] = 'à éviter, ça marche pas!';

    if (files != null && files.length > 0)
      request.files.addAll(files);    

    request.send().then((response) {
       if (response.statusCode == 200) print("Uploaded!");
    });

But if I write request.fields['content'] = 'a eviter, ca marche pas!'; The content field is sent correctly to the backend API This means there may be an issue with utf-8 fields, right ?

I'm getting the same error for Arabic. It is sending the English text easily. But, I Can't send the Arabic.

asdk2006 commented 4 years ago

any solution i face that in arabic

wfe8006 commented 4 years ago

So I faced the same issue too (using utf8 / non-ascii charset) when doing http.MutipartRequest with slightly a different result:

HttpException: Unsupported contentTransferEncoding: binary
#0      new HttpMultipartFormDataImpl (package:http_server/src/http_multipart_form_data_impl.dart:36:7)
#1      HttpMultipartFormDataImpl.parse (package:http_server/src/http_multipart_form_data_impl.dart:86:12)
#2      HttpMultipartFormData.parse (package:http_server/src/http_multipart_form_data.dart:70:33)
#3      HttpBodyHandlerImpl.process.asFormData.<anonymous closure> (package:http_server/src/http_body_impl.dart:100:48)
#4      _MapStream._handleData (dart:async/stream_pipe.dart:229:21)
---snip---

In my case the dart backend threw Unsupported contentTransferEncoding: binary and a good chap came to my rescue: https://github.com/dart-lang/http_server/commit/1cf78534e681f21d75c2d47659d9f9249be3d039

So I just changed the source to the commit id directly and it works ok now.

  #http_server: ^0.9.8+3
  http_server:
    git:
      url: https://github.com/dart-lang/http_server.git
      ref: df31d20
sotisoti@debian:~/dart_api$ pub get
Resolving dependencies... (2.6s)
* http_server 0.9.8+4-dev from git https://github.com/dart-lang/http_server.git at df31d2 (was 0.9.8+3)
Changed 1 dependency!
insinfo commented 4 years ago

@sotisoti

I'm also having this problem with a backend made with Angel Framework

caseyryan commented 3 years ago

Any dart backend is having this issue. But the funny thing is that Postman can send the same data and dart will accept it normally. I've been struggling with it for several days already and still haven't found a solution

insinfo commented 3 years ago

I made a fork of the Angel Framework, called Galileo Framework which is working very well for me, even without this problem and already updated to work with dart 2.12, I use it already in production together with AngularDart 6 with dart 2.12 without problems.

https://pub.dev/packages/galileo_framework

caseyryan commented 3 years ago

Hi

I made a fork of the Angel Framework, called Galileo Framework which is working very well for me, even without this problem and already updated to work with dart 2.12, I use it already in production together with AngularDart 6 with dart 2.12 without problems.

https://pub.dev/packages/galileo_framework

Hi! Thanks but this is not really a solution, at least for me because I'm using Jaguar (I'll take a look at your framework though, for further projects maybe). I found a true reason why it happens. It happens because dart formdata adds a content-transport-encoding: binary for all text fields that are non english. Probably dart 2.12 already has a fix for this but I didn't check. I've implemented FormData and created my on class that sends them without this header. BTW Postman also doesn't add this header. So, it's not only a backend or frontend error but an error of dart in all

busslina commented 2 years ago

Facing same issue with spanish accent characters. As HTTP client I'm using dio with multipart support. The funny thing is that dio's default request encoder is UTF8 which would normally work with different language and culture charsets.

jibon0070 commented 8 months ago

I have the same problem, any solution???