环境:windows10
版本:Qt 5.15.2
工具:Qt Creator
背景:通过http表单格式上传文件,兼容阿里云和七牛云。
一、记录问题:上传文件到阿里云
问题1:ErrorCode: MalformedPOSTRequest ErrorMessage: The body of your POST request is not well-formed multipart/form-data
分析:最初看到问题有点懵,后来看到这位大佬博客(http multipart上传阿里oss失败(MalformedPOSTRequest)_robin_xie1990的博客-CSDN博客)
Qt源码:
QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, QHttpMultiPart *multiPart)
{
QNetworkRequest newRequest = d_func()->prepareMultipart(request, multiPart);
QIODevice *device = multiPart->d_func()->device;
QNetworkReply *reply = post(newRequest, device);
return reply;
}
QNetworkRequest QNetworkAccessManagerPrivate::prepareMultipart(const QNetworkRequest &request, QHttpMultiPart *multiPart)
{
// copy the request, we probably need to add some headers
QNetworkRequest newRequest(request);
// add Content-Type header if not there already
if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
QByteArray contentType;
contentType.reserve(34 + multiPart->d_func()->boundary.count());
contentType += "multipart/";
switch (multiPart->d_func()->contentType) {
case QHttpMultiPart::RelatedType:
contentType += "related";
break;
case QHttpMultiPart::FormDataType:
contentType += "form-data";
break;
case QHttpMultiPart::AlternativeType:
contentType += "alternative";
break;
default:
contentType += "mixed";
break;
}
// putting the boundary into quotes, recommended in RFC 2046 section 5.1.1
contentType += "; boundary=\"" + multiPart->d_func()->boundary + '"'; //增加了双引号("")
newRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType));
}
// add MIME-Version header if not there already (we must include the header
// if the message conforms to RFC 2045, see section 4 of that RFC)
QByteArray mimeHeader("MIME-Version");
if (!request.hasRawHeader(mimeHeader))
newRequest.setRawHeader(mimeHeader, QByteArray("1.0"));
QIODevice *device = multiPart->d_func()->device;
if (!device->isReadable()) {
if (!device->isOpen()) {
if (!device->open(QIODevice::ReadOnly))
qWarning("could not open device for reading");
} else {
qWarning("device is not readable");
}
}
return newRequest;
}
源码中提到:RFC 2046 section 5.1.1:RFC 2046 - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types (ietf.org)
红窗中意思:
Content-type字段上的参数语法通常需要将boundary的值括在Content-type行上的引号中。这并不总是必要的,但绝对没有坏处。实现者应该仔细学习语法,以避免产生无效的Content-type字段。因此,一个典型的“多部分”Content-Type报头字段可能是这样的。
我们看看阿里给的http表单示例:
POST / HTTP/1.1
Host: BucketName.oss-cn-hangzhou.aliyuncs.com
User-Agent: browser_data
Content-Length:ContentLength
Content-Type: multipart/form-data; boundary=9431149156168
--9431149156168
Content-Disposition: form-data; name="key"
key
--9431149156168
Content-Disposition: form-data; name="success_action_redirect"
success_redirect
--9431149156168
Content-Disposition: form-data; name="Content-Disposition"
attachment;filename=oss_download.jpg
--9431149156168
Content-Disposition: form-data; name="x-oss-meta-uuid"
myuuid
--9431149156168
Content-Disposition: form-data; name="x-oss-meta-tag"
mytag
--9431149156168
Content-Disposition: form-data; name="OSSAccessKeyId"
access-key-id
--9431149156168
Content-Disposition: form-data; name="policy"
encoded_policy
--9431149156168
Content-Disposition: form-data; name="Signature"
signature
--9431149156168
Content-Disposition: form-data; name="file"; filename="MyFilename.jpg"
Content-Type: image/jpeg
file_content
--9431149156168
Content-Disposition: form-data; name="submit"
Upload to OSS
--9431149156168--
示例中的Content-Type: multipart/form-data; boundary=9431149156168 确实boundary值没加“”。
获取Qt中的boundary,然后调用QHttpMultiPart类的setBoundary函数:
QNetworkRequest request(m_url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data;boundary=" + multiPart->boundary());
按照上述操作之后,果然可以正常上传文件到阿里云。
问题2:缺少空格也会导致问题1
问题3:我以为经过前两部设置就可以稳定上传到阿里云了,但是仍然会有偶发的报错,提示内容为The body of your POST request is not well-formed multipart/form-data{\"error\":\"invalid multipart format: mime: invalid media parameter\"}。不过报错情况只在特定情况出现:快速重复上传相同文件。问题原因仍然和boundary有关,后续和七牛云讲解。
一、记录问题:上传文件到七牛云
问题1:为了适配阿里云,去掉了Content-Type: multipart/form-data;boundary=boundary_.oOo._X/yX1rK+2zZTxLWhH9iaw425o/ 的双引号,但是上传到七牛云会报错The body of your POST request is not well-formed multipart/form-data{\"error\":\"invalid multipart format: mime: invalid media parameter\"}。这个错误和阿里云问题3一致。
分析问题:
最初boundary保留双引号时七牛云上传一直正常,去掉双引号之后,文件上传就不稳定。报错内容和阿里云再特定场景一致(问题3)。问题范围锁定在boundary。
分析1:看到报错信息提示为非法多媒体参数,怀疑是上传的文件或者格式是不是有问题。通过抓包分析查看上传的文件格式没问题,用相同文件也能上传成功,只不过不稳定。说明文件本身没问题。
分析2:否定分析1后,怀疑上传的表单数据是不是在解析时候出了问题。因此解决思路定位在boundary的作用上。boundary是分隔符,将上传的信息分隔成很多片段。boundary和上传的参数和数据混在一起,如果我们提取boundary不准确那我们上传的数据和参数可能都会错乱。这就意味着boundary在上传的信息中是个唯一的字符串,否则就会影响数据上传。
查看Qt帮我提供的boundary值:boundary_.oOo._X/yX1rK+2zZTxLWhH9iaw425o/ 看着还是挺复杂的,符号特别多。
怀疑是boundary值有问题后,利用Qt中的uuid生成唯一字符串,设置到表单中:
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QString uuid = QUuid::createUuid().toString();
uuid.remove("{");
uuid.remove("}");
uuid = "test" + uuid;
QString text = "multipart/form-data;boundary=" + QByteArray::fromStdString(uuid.toStdString());
multiPart->setBoundary(QByteArray::fromStdString(uuid.toStdString()));
再次测试,七牛云可以稳定上传!!!后来七牛云给的反馈说boundary中的+和/会被替换掉。至于替换原因不知道。后续在文件上传到阿里云也没在出现过类似问题。
阿里云参考资料:
1.https表单:PostObject - API 参考| 阿里云 (alibabacloud.com)
2.Post Object错误及排查:阿里云帮助中心 (alibabacloud.com)
七牛云参考资料:
1. 问题排查:上传报文组装 - 七牛开发者中心 (qiniu.com)