如何解决如何将具有多值的字段设置为 Post 请求?
此命令在 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"
您可能已经看到,有两个多值字段,organs
和 images
,一个用于 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
。
如何处理多值字段?
解决方法
-
这些文件实际上可以有重复的字段名称。您得到的 400 错误可能是因为您发送了两个
images
但只发送了一个organs
。所以看起来你唯一需要解决的就是发送多个同名字段。 -
没有更好的想法,您可以复制原来的
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)}';
}
}
(最好子类化,但许多有价值的东西在那里是私有的。)
- 然后在您的代码中使用
addAll
而不是addEntries
设置字段:
request.fields.addAll(fields);
我看到您已经向 Dart http
包提交了一个问题。这个不错。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。