【问题标题】:How to set a field with multi-value into a Post request?如何将具有多值的字段设置为 Post 请求?
【发布时间】:2021-02-23 04:57:35
【问题描述】:

此命令在 Linux 终端上运行良好:

curl -X POST "https://my-api.plantnet.org/v2/identify/all?api-key=11111111111111111111" -H "accept: application/json" -F "organs=flower" -F "organs=leaf" -F "images=@images/image_1.jpeg" -F "images=@images/image_2.jpeg"

您可能已经看到,有两个多值字段,organsimages,一个用于 String 对象,另一个用于 File 对象。

我已经编写了这个代码:

  static Future<T> postFilesAndGetJson<T>(String url, {List<MapEntry<String, String>> paths, List<MapEntry<String, String>> fields}) async {
    var request = http.MultipartRequest('POST', Uri.parse(url));

    if (paths != null && paths.isNotEmpty) {
      paths.forEach((path) { 
        var file = File.fromUri(Uri.parse(path.value));
        var multipartFile = http.MultipartFile.fromBytes(
          path.key, file.readAsBytesSync(), filename: p.basename(file.path)
        );
        request.files.add(multipartFile);
      });
    }

    if (fields != null && fields.isNotEmpty) {
      request.fields.addEntries(fields);
    }

    return http.Response
      .fromStream(await request.send())
      .then((response) {
        if (response.statusCode == 200) {
          return jsonDecode(response.body) as T;
        }
        print('Status Code : ${response.statusCode}...');
        return null;
      });
  }

当字段名称不同时它工作正常,所以对于这种情况它不起作用,因为我得到状态代码400错误请求)。

request.fields 属性是Map&lt;String, String&gt;,所以我不能(显然)将List&lt;String&gt; 设置为值。 request.files 也有类似的情况。

如何处理多值字段?

【问题讨论】:

    标签: http dart http-post multipartform-data


    【解决方案1】:
    1. 这些文件实际上可以有重复的字段名称。您收到的 400 错误可能是因为您发送了两个 images 但只发送了一个 organs。所以看起来你唯一需要解决的就是发送多个同名字段。

    2. 如果没有更好的想法,您可以复制原始的MultipartRequest 并创建自己的类,例如MultipartListRequest。然后将fields从地图更改为列表(更改的行已注释):

    import 'dart:convert';
    import 'dart:math';
    
    import 'package:http/http.dart';                        // CHANGED
    import 'package:http/src/utils.dart';                   // CHANGED
    import 'package:http/src/boundary_characters.dart';     // CHANGED
    
    final _newlineRegExp = RegExp(r'\r\n|\r|\n');
    
    class MultipartListRequest extends BaseRequest {        // CHANGED
      /// The total length of the multipart boundaries used when building the
      /// request body.
      ///
      /// According to http://tools.ietf.org/html/rfc1341.html, this can't be longer
      /// than 70.
      static const int _boundaryLength = 70;
    
      static final Random _random = Random();
    
      /// The form fields to send for this request.
      final fields = <MapEntry<String, String>>[];          // CHANGED
    
      /// The list of files to upload for this request.
      final files = <MultipartFile>[];
    
      MultipartListRequest(String method, Uri url) : super(method, url);
    
      /// The total length of the request body, in bytes.
      ///
      /// This is calculated from [fields] and [files] and cannot be set manually.
      @override
      int get contentLength {
        var length = 0;
    
        fields.forEach((field) {                            // CHANGED
          final name = field.key;                           // CHANGED
          final value = field.value;                        // CHANGED
    
          length += '--'.length +
              _boundaryLength +
              '\r\n'.length +
              utf8.encode(_headerForField(name, value)).length +
              utf8.encode(value).length +
              '\r\n'.length;
        });
    
        for (var file in files) {
          length += '--'.length +
              _boundaryLength +
              '\r\n'.length +
              utf8.encode(_headerForFile(file)).length +
              file.length +
              '\r\n'.length;
        }
    
        return length + '--'.length + _boundaryLength + '--\r\n'.length;
      }
    
      @override
      set contentLength(int? value) {
        throw UnsupportedError('Cannot set the contentLength property of '
            'multipart requests.');
      }
    
      /// Freezes all mutable fields and returns a single-subscription [ByteStream]
      /// that will emit the request body.
      @override
      ByteStream finalize() {
        // TODO: freeze fields and files
        final boundary = _boundaryString();
        headers['content-type'] = 'multipart/form-data; boundary=$boundary';
        super.finalize();
        return ByteStream(_finalize(boundary));
      }
    
      Stream<List<int>> _finalize(String boundary) async* {
        const line = [13, 10]; // \r\n
        final separator = utf8.encode('--$boundary\r\n');
        final close = utf8.encode('--$boundary--\r\n');
    
        for (var field in fields) {                         // CHANGED
          yield separator;
          yield utf8.encode(_headerForField(field.key, field.value));
          yield utf8.encode(field.value);
          yield line;
        }
    
        for (final file in files) {
          yield separator;
          yield utf8.encode(_headerForFile(file));
          yield* file.finalize();
          yield line;
        }
        yield close;
      }
    
      /// 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';
      }
    
      /// Returns the header string for a file.
      ///
      /// The return value is guaranteed to contain only ASCII characters.
      String _headerForFile(MultipartFile file) {
        var header = 'content-type: ${file.contentType}\r\n'
            'content-disposition: form-data; name="${_browserEncode(file.field)}"';
    
        if (file.filename != null) {
          header = '$header; filename="${_browserEncode(file.filename!)}"';
        }
        return '$header\r\n\r\n';
      }
    
      /// Encode [value] in the same way browsers do.
      String _browserEncode(String value) =>
          // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for
          // field names and file names, but in practice user agents seem not to
          // follow this at all. Instead, they URL-encode `\r`, `\n`, and `\r\n` as
          // `\r\n`; URL-encode `"`; and do nothing else (even for `%` or non-ASCII
          // characters). We follow their behavior.
          value.replaceAll(_newlineRegExp, '%0D%0A').replaceAll('"', '%22');
    
      /// Returns a randomly-generated multipart boundary string
      String _boundaryString() {
        var prefix = 'dart-http-boundary-';
        var list = List<int>.generate(
            _boundaryLength - prefix.length,
            (index) =>
                boundaryCharacters[_random.nextInt(boundaryCharacters.length)],
            growable: false);
        return '$prefix${String.fromCharCodes(list)}';
      }
    }
    
    

    (子类化会更好,但很多有价值的东西都是私有的。)

    1. 然后在您的代码中使用addAll 而不是addEntries 设置字段:
    request.fields.addAll(fields);
    

    我看到你已经向 Dart http 包提交了一个问题。这很好。

    【讨论】:

      猜你喜欢
      • 2017-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-28
      • 2021-06-01
      • 2021-11-23
      相关资源
      最近更新 更多