Qt 模擬 HTTP 表單提交文字或文件到伺服器
- 2020 年 1 月 4 日
- 筆記
傳統通過 HTTP 表單的方式來上傳文件在 Web 中實現是非常簡單的,一個表單中加幾個域填寫上對應的內容提交就可以了,但如果通過 Qt 來實現就相對麻煩一點,不過我都總結好了程式碼,直接使用就可以了。
需要用到的模組
- QNetworkAccessManager 用來發起 GET/POST 請求
- QNetworkReply 用來描述響應資訊
- QHttpMultiPart 用來模擬表單域
- QNetworkRequest 用來構建請求地址等資訊
Qt 官方簡單例子
Qt 官方基於 QHttpMultiPart
的簡單例子:https://doc.qt.io/archives/qt-4.8/qhttpmultipart.html
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); // we cannot delete the file now, so delete it with the 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); // delete the multiPart with the reply // here connect signals etc.
封裝後的類
頭文件:
#ifndef HTTPUP_LOADER_H #define HTTPUP_LOADER_H #include <QObject> #include <QString> #include <QByteArray> #include <QtNetwork/QHttpPart> #include <QtNetwork/QHttpMultiPart> #include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkRequest> #include <QtNetwork/QNetworkAccessManager> class HttpUploader : public QObject { Q_OBJECT public: HttpUploader(const QString& url, QObject* receiver); ~HttpUploader(); void SetPostURL(const QString& url) { url_ = url; } QString GetPostURL() { return url_; } bool AddTextField(const QString& key, const QByteArray& value); bool AddFileField(const QString& key, const QString& file_path); bool PostRequest(); QNetworkAccessManager* GetNetworkManager() { return net_manager_; } signals: private: QObject* receiver_; QString url_; QByteArray post_content_; // http upload QNetworkAccessManager* net_manager_ = nullptr; QNetworkReply* net_reply_ = nullptr; QHttpMultiPart* multi_part_ = nullptr; }; #endif // HTTPUP_LOADER_H
實現文件
#include <QFile> #include <QUuid> #include <QFileInfo> #include "http_uploader.h" HttpUploader::HttpUploader(const QString& url, QObject* receiver) : receiver_(receiver) , url_(url) , post_content_(QByteArray()) { multi_part_ = new QHttpMultiPart(QHttpMultiPart::FormDataType); net_manager_ = new QNetworkAccessManager(this); connect(net_manager_, SIGNAL(finished(QNetworkReply*)), receiver, SLOT(onNetworkFinished(QNetworkReply*))); } HttpUploader::~HttpUploader() { if (multi_part_ != nullptr) { delete multi_part_; } if (net_manager_ != nullptr) { delete net_manager_; } } bool HttpUploader::AddTextField(const QString &key, const QByteArray &value) { QHttpPart text_part; text_part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name="" + key + """)); text_part.setBody(value); multi_part_->append(text_part); return true; } bool HttpUploader::AddFileField(const QString &key, const QString& file_path) { QHttpPart file_part; QFileInfo upload_file_info(file_path); file_part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); file_part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name="" + key + ""; filename="" + upload_file_info.fileName() + """)); QFile *file = new QFile(file_path); file->open(QIODevice::ReadOnly); file->setParent(multi_part_); file_part.setBodyDevice(file); multi_part_->append(file_part); return true; } bool HttpUploader::PostRequest() { QNetworkRequest request = QNetworkRequest(QUrl(url_)); net_reply_ = net_manager_->post(request, multi_part_); multi_part_->setParent(net_reply_); // delete the multiPart with the reply connect(net_reply_, SIGNAL(finished()), receiver_, SLOT(onReplyFinished())); connect(net_reply_, SIGNAL(uploadProgress(qint64, qint64)), receiver_, SLOT(onUploadProgress(qint64, qint64))); return true; }
外部調用時,像下面這樣調用就可以了
HttpUploader* uploader_ = new HttpUploader(report_url_, this); uploader_->AddTextField("userId", report_id_.toUtf8()); uploader_->AddTextField("reportContent", comment_.toUtf8()); uploader_->AddFileField("logFile", report_zip_file_); ... uploader_->PostRequest();
在 new 上傳對象的指針時,第二個傳遞的參數是當前類的一個指針,你需要實現 onNetworkFinished
,onReplyFinished
,onUploadProgress
,來監視上傳任務的進度和完成資訊,當然你可以自己封裝一下,上傳對象僅僅提供訊號,需要時在外部 connect 就可以了。onNetworkFinished 和 onReplyFinished 的區別是,onReplyFinished 槽函數對應 QNetworkReply 的 finished 訊號,他僅僅通知完成了,不會攜帶任何參數。但是你可以通過 QNetworkReply 的實例對象來獲取各種返回值資訊。而 onNetworkFinished 槽函數對應的是 QNetworkAccessManager 的 finished 訊號,其會攜帶一個 QNetworkReply 對象指針,你只需要在這裡處理返回的對應錯誤碼就可以了。
返回值處理
我使用 QNetworkAccessManager 的 finished 訊號來接收完成事件,在 onNetworkFinished 函數中,我們接收到的資訊是一個 reply 對象。你可以通過 reply 對象獲取 HTTP 返回值:
QVariant variant = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); QDebug() << variant.toInt();
也可以列印 Qt 自由的一套返錯誤程式碼
qDebug() << reply->error(); qDebug() << reply->errorString();
如果沒有錯誤的情況下,你可以使用 reply 對象獲取返回的內容:
qDebug() << reply->readAll();