Reputation: 6257
This command works fine on Linux terminal:
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"
As you may have seen there are two multi-value fields,organs
and images
, one is for String objects and the another is for File objects.
I've made this code:
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;
});
}
And it works fine while field names are different, so for this case it doesn't work because I get status code 400
(Bad Request).
request.fields
property is Map<String, String>
so I cannot (apparently) set a List<String>
as value. Similar case is for request.files
.
How to work with multi-value fields?
Upvotes: 2
Views: 668
Reputation: 2049
The files are actually OK having duplicate field names. The 400 error you get is probably because you send two images
but only one organs
. So looks like the only thing you need to fix is sending multiple fields of the same name.
Having no better ideas, you may copy the original MultipartRequest
and create your own class like MultipartListRequest
. Then change fields
from a map to a list (changed lines are commented):
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)}';
}
}
(Would be better to subclass, but many valuable things are private there.)
addAll
instead of addEntries
:request.fields.addAll(fields);
I see that you have already submitted an issue to Dart http
package. This is good.
Upvotes: 2