简述
HTTP 协议对 MIME 类型有详细描述,multipart/...
是单个消息头包含多个消息体的解决方案,multipart 类型对发送非文本类型非常有用。
multipart 子类型
首先,来看 QHttpMultiPart 中关于 multipart 子类型(subtype)的描述。
枚举 QHttpMultiPart::ContentType
RFC 2046 和其它地方描述的已知 multipart 子类型。
常量 | 值 | 描述 |
---|---|---|
QHttpMultiPart::MixedType | 0 | 对应于 "multipart/mixed" 子类型,意味着 body 部位是相互独立的。如 RFC 2046 所述。 |
QHttpMultiPart::RelatedType | 1 | 对应于 "multipart/related" 子类型,意味着 body 部位是相互关联的。如 RFC 2387 所述。 |
QHttpMultiPart::FormDataType | 2 | 对应 "multipart/form-data" 子类型,意味着 body 部位包含表单元素。如 RFC 2388 所述。 |
QHttpMultiPart::AlternativeType | 3 | 对应 "multipart/alternative" 子类型,意味着 body 部位是相同信息的替代表示。如 RFC 2046 所述。 |
QHttpPart
QHttpPart 类拥有一个 body 部位,用于 HTTP multipart MIME消息中(由 QHttpMultiPart 表示)。一个 QHttpPart 由一个 header 块和数据块组成,彼此之间存在两个连续换行。
一个 part 例子:
Content-Type: text/plain
Content-Disposition: form-data; name="text"
here goes the body
AI 代码解读
要设置 headers,使用 setHeader() 和 setRawHeader(),他们完全类似于 QNetworkRequest::setHeader() 和 QNetworkRequest::setRawHeader()。
对于读取小数据块,使用 setBody();如果是更大数据块,例如:图像,使用 setBodyDevice()。后一种方法由于内部没有复制数据,而是直接从设备读取,所以更节省内存。这意味着,当由 QNetworkAccessManager::post() 在网络上发送包含 body 部分的 multipart 消息时,设备必须打开并且可读。
构建一个小 body 的 QHttpPart,考虑下面的代码片段(将产生上述示例中显示的数据):
QHttpPart textPart;
textPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\""));
textPart.setBody("here goes the body");
AI 代码解读
如果要从一个设备(例如:文件)读取,可以用这种方式:
QHttpPart imagePart;
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"image\""));
imagePart.setRawHeader("Content-ID", "my@content.id"); // 添加任何你喜欢的 headers
QFile *file = new QFile("image.jpg");
file->open(QIODevice::ReadOnly);
imagePart.setBodyDevice(file);
AI 代码解读
注意: QHttpPart 在设置时不需要设备的所有权,所以应该在不需要的时候销毁该设备。比较好的办法是将 multipart 消息设置为设备的父对象,QHttpMultiPart 部分会有说明。
QHttpMultiPart
QHttpMultiPart 类似于一个 RFC 2046 所描述的 MIME multipart 消息,通过 HTTP 发送。
一个 multipart 消息包含任意数量的 body 部分(QHttpPart),由一个独特的 boundary 分割开来。QHttpMultiPart 的 boundary 由字符串 "boundary_.oOo._"
后面跟随机字符构造而成。并提供足够的唯一性,以确保它在每个部分内不重复。如果需要的话,boundary 仍然可以通过 setBoundary() 来设置。
例如,构造一个 multipart 消息,其包含一个文本部分,紧随其后的是一个图像部分:
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart textPart;
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"text\""));
textPart.setBody("my text");
QHttpPart imagePart;
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/jpeg"));
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"image\""));
QFile *file = new QFile("image.jpg");
file->open(QIODevice::ReadOnly);
imagePart.setBodyDevice(file);
file->setParent(multiPart); // 现在不能删除文件,所以用 multiPart 删除
multiPart->append(textPart);
multiPart->append(imagePart);
QUrl url("http://my.server.tld");
QNetworkRequest request(url);
QNetworkAccessManager manager;
QNetworkReply *reply = manager.post(request, multiPart);
multiPart->setParent(reply); // 用 reply 删除 multiPart
// 这里连接信号等
AI 代码解读
使用示例
比如multipart/form-data
,其实就是浏览器用表单上传文件的方式。最常见的情景:发送邮件时添加附件,通常使用表单添加,也就是用 multipart/form-data
格式上传到服务器。
例如,上传一个本地的 png 格式图片:
可以看到请求内容的类型:
Content-Type: multipart/form-data; boundary="boundary_.oOo._MTA0NzE=MjcyNDY=ODk2Ng=="
其中表单类型为 multipart/form-data
,boundary 是分隔符,和请求体中的分隔符内容一致。
由于上传附件不再使用原有的 HTTP 协议,所以请求体不再以 key=value
方式发送,而使用下述方式:
分隔符
字段内容1
分隔符
字段内容2
正如上图橙色区域显示方式一样。由于我们上传了一个图片,所以字段内容表示的是图片本身的字节(看上去像是乱码)。
注意:这里只是做了一个简单的演示,并没有把附件以表单的形式上传到任何服务器。如果要实际应用,可以调用一些第三方 API 进行尝试。