如何将具有多值的字段设置为 Post 请求?

How to set a field with multi-value into a Post request?

此命令在 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<String, String> 所以我不能(显然)将 List<String> 设置为值。 request.files.

的情况类似

如何使用多值字段?

  1. 文件实际上可以有重复的字段名。您收到的 400 错误可能是因为您发送了两个 images 但只发送了一个 organs。所以看起来您唯一需要修复的是发送多个同名字段。

  2. 没有更好的想法,您可以复制原始的 MultipartRequest 并创建您自己的 class,例如 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)}';
  }
}

(最好是subclass,但是很多有价值的东西都是私有的。)

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

我看到您已经向 Dart http 包提交了一个问题。这个不错。