Qt 模擬 HTTP 表單提交文字或文件到伺服器

傳統通過 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 上傳對象的指針時,第二個傳遞的參數是當前類的一個指針,你需要實現 onNetworkFinishedonReplyFinishedonUploadProgress,來監視上傳任務的進度和完成資訊,當然你可以自己封裝一下,上傳對象僅僅提供訊號,需要時在外部 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();

相關