使用 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' ... # 此处有省略 > 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 协议中有两个使用场景:
实现 使用 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" )) ; QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); 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->setParent(multiPart); multiPart->append(filePart); 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); } void uploadFinished(QNetworkReply *reply) { reply->deleteLater(); }