0%

使用 http 协议上传文件的协议细节和实现。

使用 curl 上传

调试 Swagger API 时一般会使用 postman 工具,但可以有更加短平快的手段、工具满足我们轻量的调试需求。

curl ,linux 平台下常用,或者 windows 平台下 git bash 中也带有 curl 命令。

1
2
3
4
5
6
7
 -X, --request <method>      Specify request method to use 
-d, --data <data> HTTP POST data
-H, --header <header/@file> Pass custom header(s) to server
-v, --verbose Make the operation more talkative
-F, --form <name=content> Specify multipart MIME data
To force the 'content' part to be a file, prefix the filename with an @ sign.
@ makes a file get attached in the post as a file upload, ...

使用上述参数,实际演练一下:使用 -d-F 参数时,curl 能够自行推断出要使用 POST 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ curl 'http://localhost:3001/api/v1/document/upload' -v \
-H 'accept: application/json' \
-H 'Authorization: Bearer S0QSFCC-MYTMPGN-M9WEQBZ-3V579DV' \
-H 'Content-Type: multipart/form-data' \
-F 'file=@tmp.txt' # -F 'user=niel'
... # 此处有省略
> POST /api/v1/document/upload HTTP/1.1
> Host: localhost:3001
> User-Agent: curl/8.6.0
> accept: application/json
> Authorization: Bearer S0QSFCC-MYTMPGN-M9WEQBZ-3V579DV
> Content-Length: 202
> Content-Type: multipart/form-data; boundary=------------------------pF28TYDnB4wmWf8sPjKctM
>
} [202 bytes data]
* We are completely uploaded and fine
< HTTP/1.1 200 OK
... # 此处有省略

接下来结合 curl 输出和 wireshark 抓包,来学习标准。

Encapsulation 封装;Disposition 布置;

协议

多用途互联网邮件扩展(英语:Multipurpose Internet Mail Extensions,缩写:MIME)是一个互联网标准,它扩展了电子邮件标准。

此外,在万维网中使用的 HTTP 协议中也使用了 MIME 的框架,标准被扩展为互联网媒体形式

MIME 是通过标准化电子邮件报文的头部的附加域(fields)而实现的;这些头部的附加域,描述新的报文类型的内容和组织形式。

更多细节请参考 MIME 类型 的详细内容!

Content-Type

MIME 类型的格式:Content-Type: type/subtype;parameter=value ,参数可选。

类型可分为两类:独立的(discrete)和多部分的(multipart)。

  • 独立类型代表单一文件或媒介,比如一段文字、一个音乐文件、一个视频文件等。

  • 而多部份类型,可以代表由多个部件组合成的文档,其中每个部分都可能有各自的 MIME 类型;此外,也可以代表多个文件被封装在单次事务中一同发送。

    multipart/form-data 作为多部分文档格式,它由 boundary 边界线划分出的不同部分组成。

1
Content-Type: multipart/form-data; boundary=aBoundaryString

每一部分有自己的 content header (零个或多个 Content- header fields) 和 body 。

Content-Disposition

最初的 MIME 规范仅描述了邮件消息的结构。它们没有解决呈现样式的问题。

在 RFC 2183 中添加了 content-disposition 标头字段来指定呈现样式。

此表头用在 http 协议中有两个使用场景:

  • As a response header for the main body

  • As a header for a multipart body ,第一个指令始终是 form-data ,并且还必须包含一个 name 参数来标识表单字段名。

    filename 参数是可选的,指示要传送的文件的初始名称的字符串。

实现

使用 Qt 库完成文件上传的功能, by Google’s Gemini

如果手动添加文件头 Content-Type:multipart/form-data 似乎会造成 boundary= 参数丢失: #TODO# 有待验证

  • 虽然手动设置文件头在前,->post(request, multiPart) 在后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void uploadFile() {
QNetworkRequest request(QUrl("http://example.com/api/upload")); // 替换为您的服务器上传接口 URL

QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);

// 1. 文件数据部分
QHttpPart filePart;
QString disposition = QString(R"(form-data; name="file"; filename="%1")").arg(QFileInfo(filePath).fileName());
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition);
QFile *file = new QFile(filePath);
if (!file->open(QIODevice::ReadOnly)) {
QMessageBox::critical(this, "错误", "无法打开文件: " + filePath);
delete multiPart;
return;
}
filePart.setBodyDevice(file); // file的所有权转移给filePart
file->setParent(multiPart); // 设置父对象,防止被提前析构
multiPart->append(filePart);

// 2. 其他表单字段 (可选)
QHttpPart namePart;
namePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"user\""));
namePart.setBody("niel");
multiPart->append(namePart);

QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, this, &MyWidget::uploadFinished);

QNetworkReply *reply = manager->post(request, multiPart);
multiPart->setParent(reply); // 设置父对象,确保multiPart在reply完成前有效

// 可以添加请求头,例如设置认证信息
// request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data"); // QHttpMultiPart 会自动设置
// request.setHeader(QNetworkRequest::AuthorizationHeader, "Bearer YourAccessToken");
}
void uploadFinished(QNetworkReply *reply) {
reply->deleteLater(); // 必须 deleteLater 释放 reply 对象
}