Qt——记录:http表单格式上传文件到七牛云和阿里云

1 篇文章 0 订阅
本文记录了使用Qt5.15.2在Windows10环境下,通过Qt Creator上传文件到阿里云和七牛云时遇到的错误,包括MalformedPOSTRequest和invalid multipart format问题。问题主要与Content-Type中boundary的设置有关。解决方案包括正确设置boundary,确保其在阿里云和七牛云的上传规范中有效。经过调整,实现了稳定上传。
摘要由CSDN通过智能技术生成

环境: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)

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值