diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml index 1fe3b5e..b5d05ad 100644 --- a/.github/actions/install-dependencies/action.yml +++ b/.github/actions/install-dependencies/action.yml @@ -13,7 +13,7 @@ inputs: qt_ver: description: 'qt version' required: false - default: '6.7.2' + default: '6.8.1' type: string runs: @@ -48,7 +48,7 @@ runs: gcc --version - name: Install Qt - uses: jurplel/install-qt-action@v3 + uses: jurplel/install-qt-action@v4 with: version: ${{ inputs.qt_ver }} modules: ${{ inputs.qt_modules }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0d08e26 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.vscode/settings.json b/.vscode/settings.json index 497196c..48cff58 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,6 @@ }, "cmake.generator": "Ninja", "cmake.environment": { - "PATH": "C:\\Qt\\6.7.2\\msvc2019_64\\bin;${env:PATH};" + "PATH": "C:\\Qt\\6.8.1\\msvc2022_64\\bin;${env:PATH};" } } \ No newline at end of file diff --git a/HttpClient/CMakeLists.txt b/HttpClient/CMakeLists.txt index d403482..7636ed5 100644 --- a/HttpClient/CMakeLists.txt +++ b/HttpClient/CMakeLists.txt @@ -1,7 +1,5 @@ -set(PROJECT_SOURCES main.cpp mainwindow.cpp mainwindow.h httpclient.h - httpclient.cpp) +set(PROJECT_SOURCES main.cpp httpclient.cc httpclient.hpp) qt_add_executable(HttpClient MANUAL_FINALIZATION ${PROJECT_SOURCES}) -target_link_libraries(HttpClient PRIVATE Qt6::Widgets Qt6::Network - Qt6::Concurrent) +target_link_libraries(HttpClient PRIVATE Qt6::Network Qt6::Concurrent) qt_finalize_executable(HttpClient) diff --git a/HttpClient/HttpClient.pro b/HttpClient/HttpClient.pro index 403a940..8273e3c 100644 --- a/HttpClient/HttpClient.pro +++ b/HttpClient/HttpClient.pro @@ -16,13 +16,11 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ - httpclient.cpp \ - main.cpp \ - mainwindow.cpp + httpclient.cc \ + main.cpp HEADERS += \ - httpclient.h \ - mainwindow.h + httpclient.hpp # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin diff --git a/HttpClient/httpclient.cc b/HttpClient/httpclient.cc new file mode 100644 index 0000000..1b2bb39 --- /dev/null +++ b/HttpClient/httpclient.cc @@ -0,0 +1,383 @@ +#include "httpclient.hpp" + +#include +#include +#include +#include +#include +#include + +class HttpClient::HttpClientPrivate +{ +public: + explicit HttpClientPrivate(HttpClient *q) + : q_ptr(q) + { + sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone); + } + + QNetworkRequest networkRequest(bool verifyCertificate) + { + QNetworkRequest request; + if (!verifyCertificate) { + request.setSslConfiguration(sslConfiguration); + } + return request; + } + + void clearTask(QNetworkReply *reply) + { + tasks.remove(reply); + auto *download = downloads.take(reply); + if (download) { + delete download; + } + auto *file = uploads.take(reply); + if (file) { + file->deleteLater(); + } + } + + QByteArray methodToString(Method method) const + { + switch (method) { + case Method::GET: return "GET"; + case Method::POST: return "POST"; + case Method::PUT: return "PUT"; + case Method::DELETE: return "DELETE"; + default: break; + } + Q_ASSERT(false); + return "UNKNOWN"; + } + + auto jsonFromBytes(const QByteArray &bytes) -> QJsonObject + { + QJsonParseError jsonParseError; + auto jsonDocument = QJsonDocument::fromJson(bytes, &jsonParseError); + if (QJsonParseError::NoError != jsonParseError.error) { + qWarning() << QString("%1\nOffset: %2") + .arg(jsonParseError.errorString(), + QString::number(jsonParseError.offset)) + << bytes; + return {}; + } + return jsonDocument.object(); + } + + struct Download + { + ~Download() + { + if (!filePtr.isNull()) { + filePtr->deleteLater(); + } + } + + QString filePath; + qint64 fileBaseSize; + QPointer filePtr; + ProgressCallBack progressCallBack = nullptr; + }; + + HttpClient *q_ptr; + + QSslConfiguration sslConfiguration = QSslConfiguration::defaultConfiguration(); + QMap tasks; + QMap downloads; + QMap uploads; +}; + +HttpClient::HttpClient(QObject *parent) + : QNetworkAccessManager(parent) + , d_ptr(new HttpClientPrivate(this)) +{} + +HttpClient::~HttpClient() {} + +QNetworkReply *HttpClient::sendRequest(Method method, + const QUrl &url, + const HttpHeaders &httpHeaders, + const QJsonObject &body, + int timeout, + bool verifyCertificate, + CallBack callBack) +{ + auto request = d_ptr->networkRequest(verifyCertificate); + request.setUrl(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); + for (auto iter = httpHeaders.begin(); iter != httpHeaders.end(); iter++) { + request.setRawHeader(iter.key().toUtf8(), iter.value().toUtf8()); + } + + qDebug() << d_ptr->methodToString(method) << url.toString(QUrl::RemoveUserInfo) << body; + + auto *reply = QNetworkAccessManager::sendCustomRequest(request, + d_ptr->methodToString(method), + QJsonDocument(body).toJson( + QJsonDocument::Compact)); + reply->setParent(this); + connect(reply, &QNetworkReply::finished, this, &HttpClient::onReplyFinish); + connect(reply, &QNetworkReply::errorOccurred, this, &HttpClient::onErrorOccurred); + connect(reply, &QNetworkReply::sslErrors, this, &HttpClient::onSslErrors); + d_ptr->tasks.insert(reply, callBack); + if (timeout > 0) { + auto *timer = new QTimer(reply); + connect(timer, &QTimer::timeout, this, &HttpClient::onNetworkTimeout); + timer->start(timeout * 1000); + } + return reply; +} + +QJsonObject HttpClient::sync(QNetworkReply *reply) +{ + QPointer replyPtr(reply); + QJsonObject json; + QEventLoop loop; + connect(this, &HttpClient::ready, &loop, [&](QNetworkReply *reply, const QJsonObject &object) { + if (replyPtr.isNull()) { + loop.quit(); + } + if (reply == replyPtr) { + json = object; + loop.quit(); + } + }); + loop.exec(); + return json; +} + +QNetworkReply *HttpClient::downLoad(const QUrl &url, + const QString &filePath, + int timeout, + bool verifyCertificate, + ProgressCallBack progressCallBack, + CallBack callBack) +{ + Q_ASSERT(!filePath.isEmpty()); + auto *file = new QFile(filePath + ".temp", this); + if (!file->open(QIODevice::WriteOnly | QIODevice::Append)) { + qWarning() << QString("Cannot open the file: %1!").arg(filePath) << file->errorString(); + return nullptr; + } + + auto request = d_ptr->networkRequest(verifyCertificate); + request.setUrl(url); + auto bytes = file->size(); + if (bytes > 0) { + const QByteArray fromRange = "bytes=" + QByteArray::number(bytes) + "-"; + request.setRawHeader("Range", fromRange); + } + qDebug() << QString("Download: %-1>%2").arg(url.toString(QUrl::RemoveUserInfo), filePath); + + auto *reply = QNetworkAccessManager::get(request); + d_ptr->downloads + .insert(reply, new HttpClientPrivate::Download{filePath, bytes, file, progressCallBack}); + d_ptr->tasks.insert(reply, callBack); + connect(reply, &QNetworkReply::errorOccurred, this, &HttpClient::onErrorOccurred); + connect(reply, &QNetworkReply::sslErrors, this, &HttpClient::onSslErrors); + connect(reply, &QNetworkReply::downloadProgress, this, &HttpClient::onDownloadProgress); + connect(reply, &QNetworkReply::readyRead, this, &HttpClient::onDownloadReadyRead); + connect(reply, &QNetworkReply::finished, this, &HttpClient::onDownloadFinish); + if (timeout > 0) { + auto *timer = new QTimer(reply); + connect(timer, &QTimer::timeout, this, &HttpClient::onNetworkTimeout); + timer->start(timeout * 1000); + } + return reply; +} + +QNetworkReply *HttpClient::upload(const QUrl &url, + const QString &filePath, + int timeout, + bool verifyCertificate, + CallBack callBack) +{ + Q_ASSERT(!filePath.isEmpty()); + auto *file = new QFile(filePath, this); + if (!file->open(QIODevice::ReadOnly)) { + qWarning() << QString("Cannot open the file: %1!").arg(filePath) << file->errorString(); + return nullptr; + } + qDebug() << QString("Upload: %1->%2").arg(filePath, url.toString(QUrl::RemoveUserInfo)); + + auto request = d_ptr->networkRequest(verifyCertificate); + request.setUrl(url); + auto *reply = QNetworkAccessManager::put(request, file); + file->setParent(reply); + d_ptr->uploads.insert(reply, file); + d_ptr->tasks.insert(reply, callBack); + connect(reply, &QNetworkReply::errorOccurred, this, &HttpClient::onErrorOccurred); + connect(reply, &QNetworkReply::sslErrors, this, &HttpClient::onSslErrors); + connect(reply, &QNetworkReply::finished, this, &HttpClient::onUploadFinish); + if (timeout > 0) { + auto *timer = new QTimer(reply); + connect(timer, &QTimer::timeout, this, &HttpClient::onNetworkTimeout); + timer->start(timeout * 1000); + } + return reply; +} + +QNetworkReply *HttpClient::upload( + const QUrl &url, const QByteArray &data, int timeout, bool verifyCertificate, CallBack callBack) +{ + qDebug() << QString("Upload To %1").arg(url.toString(QUrl::RemoveUserInfo)); + + auto request = d_ptr->networkRequest(verifyCertificate); + request.setUrl(url); + auto *reply = QNetworkAccessManager::put(request, data); + d_ptr->tasks.insert(reply, callBack); + connect(reply, &QNetworkReply::errorOccurred, this, &HttpClient::onErrorOccurred); + connect(reply, &QNetworkReply::sslErrors, this, &HttpClient::onSslErrors); + connect(reply, &QNetworkReply::finished, this, &HttpClient::onUploadFinish); + if (timeout > 0) { + auto *timer = new QTimer(reply); + connect(timer, &QTimer::timeout, this, &HttpClient::onNetworkTimeout); + timer->start(timeout * 1000); + } + return reply; +} + +void HttpClient::onReplyFinish() +{ + auto *reply = qobject_cast(sender()); + if (!reply) { + return; + } + const auto object(d_ptr->jsonFromBytes(reply->readAll())); + queryResult(reply, object); +} + +void HttpClient::onErrorOccurred(QNetworkReply::NetworkError code) +{ + auto *reply = qobject_cast(sender()); + if (code == QNetworkReply::NoError || !reply) { + return; + } + qWarning() << "Network Error :" << reply->error() << reply->errorString() << reply->readAll(); + + QJsonObject object; + object.insert("error", code); + queryResult(reply, object); +} + +void HttpClient::onSslErrors(const QList &errors) +{ + auto *reply = qobject_cast(sender()); + if (errors.isEmpty() || !reply) { + return; + } + qWarning() << "SSL Errors: "; + for (const auto &error : std::as_const(errors)) { + qWarning() << error.error() << error.errorString(); + } + qWarning() << reply->readAll(); + + QJsonObject object; + object.insert("error", errors.first().error()); + queryResult(reply, object); +} + +void HttpClient::onNetworkTimeout() +{ + qWarning() << "Network Timeout"; + + emit timeOut(); + auto *timer = qobject_cast(sender()); + if (!timer) { + return; + } + timer->stop(); + timer->deleteLater(); + auto *reply = qobject_cast(timer->parent()); + if (!reply) { + return; + } + + QJsonObject object; + object.insert("error", NETWORK_TIMEOUT_ERROR); + queryResult(reply, object); +} + +void HttpClient::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + auto *reply = qobject_cast(sender()); + if (!reply) { + return; + } + auto *download = d_ptr->downloads.value(reply); + if (!download) { + return; + } + auto progressCallBack = download->progressCallBack; + if (!progressCallBack) { + return; + } + progressCallBack(bytesReceived + download->fileBaseSize, bytesTotal + download->fileBaseSize); +} + +void HttpClient::onDownloadReadyRead() +{ + auto *reply = qobject_cast(sender()); + if (!reply) { + return; + } + auto *download = d_ptr->downloads.value(reply); + if (!download) { + return; + } + if (download->filePtr.isNull()) { + return; + } + download->filePtr->write(reply->readAll()); +} + +void HttpClient::onDownloadFinish() +{ + auto *reply = qobject_cast(sender()); + if (!reply) { + return; + } + + auto *download = d_ptr->downloads.value(reply); + if (download) { + if (!download->filePtr.isNull()) { + download->filePtr->write(reply->readAll()); + download->filePtr->flush(); + download->filePtr->close(); + if (QFile::exists(download->filePath)) { + QFile::remove(download->filePath); + } + download->filePtr->rename(download->filePath); + } + } + queryResult(reply, {}); +} + +void HttpClient::onUploadFinish() +{ + auto *reply = qobject_cast(sender()); + if (!reply) { + return; + } + queryResult(reply, {}); +} + +QJsonObject HttpClient::hookResult(const QJsonObject &object) +{ + return object; +} + +void HttpClient::queryResult(QNetworkReply *reply, const QJsonObject &object) +{ + qDebug() << object; + auto callBack = d_ptr->tasks.value(reply); + d_ptr->clearTask(reply); + auto json = hookResult(object); + if (callBack) { + callBack(json); + } + emit ready(reply, json); + + reply->deleteLater(); +} diff --git a/HttpClient/httpclient.cpp b/HttpClient/httpclient.cpp deleted file mode 100644 index 59ddd33..0000000 --- a/HttpClient/httpclient.cpp +++ /dev/null @@ -1,377 +0,0 @@ -#include "httpclient.h" - -#include -#include -#include -#include -#include -#include -#include - -enum Method { GET, POST, PUT, DELETE }; - -class HttpClient::HttpClientPrivate -{ -public: - explicit HttpClientPrivate(QObject *parent) - : q_ptr(parent) - { - manager = new QNetworkAccessManager(q_ptr); - } - - QObject *q_ptr; - - QNetworkAccessManager *manager; - HttpClient::HttpHeaders headers; - QString url; - QUrlQuery params; - QString charset = "UTF-8"; - Method method = GET; - QString json; - bool useJson = false; -}; - -HttpClient::HttpClient(QObject *parent) - : QObject(parent) - , d_ptr(new HttpClientPrivate(this)) -{} - -HttpClient::~HttpClient() = default; - -void HttpClient::setHeaders(const HttpClient::HttpHeaders &headers) -{ - d_ptr->headers = headers; -} - -void HttpClient::setJson(const QString &json) -{ - d_ptr->json = json; -} - -void HttpClient::setParams(const HttpClient::HttpParams ¶ms) -{ - for (HttpParams::const_iterator iter = params.begin(); iter != params.end(); iter++) { - d_ptr->params.addQueryItem(iter.key(), iter.value().toString()); - } -} - -void HttpClient::get(const QString &url) -{ - d_ptr->method = GET; - startRequest(url); -} - -void HttpClient::post(const QString &url) -{ - d_ptr->method = POST; - startRequest(url); -} - -void HttpClient::put(const QString &url) -{ - d_ptr->method = PUT; - startRequest(url); -} - -void HttpClient::remove(const QString &url) -{ - d_ptr->method = DELETE; - startRequest(url); -} - -void HttpClient::download(const QString &url, const QString &savePath) -{ - if (url.isEmpty() || !QUrl(url).isValid()) { - return; - } - d_ptr->url = url; - - if (QFileInfo::exists(savePath)) { - qWarning() << tr("[Error] File already exists: %1.").arg(savePath); - return; - } - - QFile *file = new QFile(savePath, this); - if (!file->open(QIODevice::WriteOnly | QIODevice::Truncate)) { - file->close(); - qWarning() << tr("[Error] Open File Error %1: %2").arg(savePath, file->errorString()); - return; - } - QNetworkRequest request = createRequest(); - QNetworkReply *reply = d_ptr->manager->get(request); - if (!reply) { - return; - } - - connect(reply, &QNetworkReply::readyRead, this, [=] { - if (reply->bytesAvailable() > 0) { - file->write(reply->readAll()); - } - }); - - connect(reply, &QNetworkReply::finished, this, [=] { - if (reply->bytesAvailable() > 0) { - file->write(reply->readAll()); - } - delete file; - reply->deleteLater(); - emit finish(); - }); - - connect(reply, &QNetworkReply::downloadProgress, this, &HttpClient::downloadProgress); - connect(reply, &QNetworkReply::errorOccurred, this, &HttpClient::slotError); - connect(reply, &QNetworkReply::sslErrors, this, &HttpClient::slotSslErrors); -} - -void HttpClient::upload(const QString &url, const QString &path) -{ - upload(url, QStringList() << path); -} - -void HttpClient::upload(const QString &url, const QStringList &paths) -{ - if (paths.isEmpty()) { - return; - } - QHttpMultiPart *multiPart = initMultiPart(url); - - QString inputName = paths.size() == 1 ? "file" - : "files"; // 一个文件时为 file,多个文件时为 files - - for (const QString &path : paths) { - if (path.isEmpty()) - continue; - QFile *file = new QFile(path, multiPart); - - if (!file->open(QIODevice::ReadOnly)) { - qWarning() << tr("[Error] Open File Error %1: %2").arg(path, file->errorString()); - delete multiPart; - return; - } - - // 单个文件时,name 为服务器端获取文件的参数名,为 file - // 多个文件时,name 为服务器端获取文件的参数名,为 files - // 注意: 服务器是 Java 的则用 form-data - // 注意: 服务器是 PHP 的则用 multipart/form-data - QString disposition = QString("form-data; name=\"%1\"; filename=\"%2\"") - .arg(inputName, file->fileName()); - QHttpPart filePart; - filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition)); - filePart.setBodyDevice(file); - multiPart->append(filePart); - } - - execUpload(multiPart); -} - -void HttpClient::upload(const QString &url, const QByteArray &data) -{ - if (data.isEmpty()) { - return; - } - - QHttpMultiPart *multiPart = initMultiPart(url); - - QString disposition = QString("form-data; name=\"file\"; filename=\"no-name\""); - QHttpPart dataPart; - dataPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(disposition)); - dataPart.setBody(data); - multiPart->append(dataPart); - - execUpload(multiPart); -} - -void HttpClient::slotReadyRead() -{ - qDebug() << "slotReadyRead"; - QNetworkReply *reply = qobject_cast(sender()); - if (!reply) { - return; - } - - if (reply->bytesAvailable() <= 0) { - return; - } - - QByteArray buf = reply->readAll(); - qDebug() << buf.size(); - emit readyReady(buf); -} - -void HttpClient::slotReplyFinish() -{ - qDebug() << "slotReplyFinish"; - QNetworkReply *reply = qobject_cast(sender()); - if (!reply) { - return; - } - - if (reply->bytesAvailable() > 0) { - emit readyReady(reply->readAll()); - } - - reply->deleteLater(); - reply = nullptr; - - emit finish(); -} - -void HttpClient::slotError(QNetworkReply::NetworkError replyError) -{ - QNetworkReply *reply = qobject_cast(sender()); - if (!reply) { - return; - } - - if (replyError == QNetworkReply::NoError) { - return; - } - - emit error(reply->errorString()); -} - -void HttpClient::slotSslErrors(const QList &errors) -{ - QNetworkReply *reply = qobject_cast(sender()); - if (!reply) { - return; - } - qDebug() << errors; -} - -auto HttpClient::initMultiPart(const QString &url) -> QHttpMultiPart * -{ - if (url.isEmpty() || !QUrl(url).isValid()) { - return nullptr; - } - d_ptr->url = url; - - QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this); - QList> paramItems = d_ptr->params.queryItems(); - for (int i = 0; i < paramItems.size(); ++i) { - QString name = paramItems.at(i).first; - QString value = paramItems.at(i).second; - - QHttpPart textPart; - textPart.setHeader(QNetworkRequest::ContentDispositionHeader, - QString("form-data; name=\"%1\"").arg(name)); - textPart.setBody(value.toUtf8()); - multiPart->append(textPart); - } - - return multiPart; -} - -void HttpClient::execUpload(QHttpMultiPart *multiPart) -{ - QNetworkRequest request = createRequest(); - QNetworkReply *reply = d_ptr->manager->post(request, multiPart); - if (!reply) { - return; - } - multiPart->setParent(reply); // delete the multiPart with the reply - connect(reply, &QNetworkReply::finished, this, [this, reply] { - reply->deleteLater(); - emit finish(); - }); - connect(reply, &QNetworkReply::uploadProgress, this, &HttpClient::uploadProgress); - connect(reply, &QNetworkReply::errorOccurred, this, &HttpClient::slotError); - connect(reply, &QNetworkReply::sslErrors, this, &HttpClient::slotSslErrors); -} - -void HttpClient::startRequest(const QString &url) -{ - if (url.isEmpty() || !QUrl(url).isValid()) { - return; - } - d_ptr->url = url; - execRequest(); -} - -void HttpClient::execRequest() -{ - QNetworkRequest request = createRequest(); - QNetworkReply *reply = nullptr; - - switch (d_ptr->method) { - case GET: reply = d_ptr->manager->get(request); break; - case POST: - reply = d_ptr->manager->post(request, - d_ptr->useJson - ? d_ptr->json.toUtf8() - : d_ptr->params.toString(QUrl::FullyEncoded).toUtf8()); - break; - case PUT: - reply = d_ptr->manager->put(request, - d_ptr->useJson - ? d_ptr->json.toUtf8() - : d_ptr->params.toString(QUrl::FullyEncoded).toUtf8()); - break; - case DELETE: reply = d_ptr->manager->deleteResource(request); break; - default: return; - } - - buildConnect(reply); -} - -auto HttpClient::createRequest() -> QNetworkRequest -{ - bool get = d_ptr->method == GET; - bool withForm = !get && !d_ptr->useJson; // PUT、POST 或者 DELETE 请求,且 useJson 为 false - bool withJson = !get && d_ptr->useJson; // PUT、POST 或者 DELETE 请求,且 useJson 为 true - - if (get && !d_ptr->params.isEmpty()) { - d_ptr->url += "?" + d_ptr->params.toString(QUrl::FullyEncoded); - } - - qDebug() << tr("URL: ") << d_ptr->url; - - if (withForm) { - QList> paramItems = d_ptr->params.queryItems(); - QString buffer; // 避免多次调用 qDebug() 输入调试信息,每次 qDebug() 都有可能输出行号等 - - // 按键值对的方式输出参数 - for (int i = 0; i < paramItems.size(); ++i) { - QString name = paramItems.at(i).first; - QString value = paramItems.at(i).second; - - if (0 == i) { - buffer += QString("params: %1=%2\n").arg(name, value); - } else { - buffer += QString(" %1=%2\n").arg(name, value); - } - } - - if (!buffer.isEmpty()) { - qDebug() << buffer; - } - } - - // [3] 设置 Content-Type - // 如果是 POST 请求,useJson 为 true 时添加 Json 的请求头,useJson 为 false 时添加 Form 的请求头 - if (withForm) { - d_ptr->headers["Content-Type"] = "application/x-www-form-urlencoded"; - } else if (withJson) { - d_ptr->headers["Content-Type"] = "application/json; charset=utf-8"; - } - - // [4] 添加请求头到 request 中 - QNetworkRequest request(QUrl(d_ptr->url)); - for (auto i = d_ptr->headers.cbegin(); i != d_ptr->headers.cend(); ++i) { - request.setRawHeader(i.key().toUtf8(), i.value().toUtf8()); - } - - return request; -} - -void HttpClient::buildConnect(QNetworkReply *reply) -{ - if (!reply) { - emit error(tr("reply is nullptr.")); - return; - } - connect(reply, &QNetworkReply::readyRead, this, &HttpClient::slotReadyRead); - connect(reply, &QNetworkReply::finished, this, &HttpClient::slotReplyFinish); - connect(reply, &QNetworkReply::errorOccurred, this, &HttpClient::slotError); - connect(reply, &QNetworkReply::sslErrors, this, &HttpClient::slotSslErrors); -} diff --git a/HttpClient/httpclient.h b/HttpClient/httpclient.h deleted file mode 100644 index cddf547..0000000 --- a/HttpClient/httpclient.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef HTTPCLIENT_H -#define HTTPCLIENT_H - -#include -#include - -class HttpClient : public QObject -{ - Q_OBJECT -public: - using HttpHeaders = QHash; - using HttpParams = QMap; - - explicit HttpClient(QObject *parent = nullptr); - ~HttpClient() override; - -public slots: - void setHeaders(const HttpClient::HttpHeaders&); - void setJson(const QString& json); - void setParams(const HttpClient::HttpParams& params); - - void get(const QString &url); - void post(const QString &url); - void put(const QString &url); - void remove(const QString &url); - void download(const QString &url, const QString &savePath); - - // 文件路径 - void upload(const QString &url, const QString &path); - void upload(const QString &url, const QStringList &paths); - - // 数据 - void upload(const QString &url, const QByteArray &data); - -signals: - void error(const QString&); - void readyReady(const QByteArray&); - void downloadProgress(qint64 bytesSent, qint64 bytesTotal); - void uploadProgress(qint64 bytesSent, qint64 bytesTotal); - void finish(); - -private slots: - void slotReadyRead(); - void slotReplyFinish(); - void slotError(QNetworkReply::NetworkError); - void slotSslErrors(const QList &errors); - -private: - auto initMultiPart(const QString &url) -> QHttpMultiPart *; - void execUpload(QHttpMultiPart*); - void startRequest(const QString &url); - void execRequest(); - auto createRequest() -> QNetworkRequest; - void buildConnect(QNetworkReply *reply); - - class HttpClientPrivate; - QScopedPointer d_ptr; -}; - -#endif // HTTPCLIENT_H diff --git a/HttpClient/httpclient.hpp b/HttpClient/httpclient.hpp new file mode 100644 index 0000000..e47b76a --- /dev/null +++ b/HttpClient/httpclient.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +#define NETWORK_TIMEOUT_ERROR -1 + +class HttpClient : public QNetworkAccessManager +{ + Q_OBJECT +public: + using CallBack = std::function; + using ProgressCallBack = std::function; + using HttpHeaders = QHash; + enum class Method : int { GET, POST, PUT, DELETE }; + + explicit HttpClient(QObject *parent = nullptr); + ~HttpClient() override; + + QNetworkReply *sendRequest(Method method, + const QUrl &url, + const HttpHeaders &httpHeaders, + const QJsonObject &body, + int timeout = -1, + bool verifyCertificate = true, + CallBack callBack = nullptr); + + QJsonObject sync(QNetworkReply *reply); + + QNetworkReply *downLoad(const QUrl &url, + const QString &filePath, + int timeout = -1, + bool verifyCertificate = true, + ProgressCallBack progressCallBack = nullptr, + CallBack callBack = nullptr); + + QNetworkReply *upload(const QUrl &url, + const QString &filePath, + int timeout = -1, + bool verifyCertificate = true, + CallBack callBack = nullptr); + QNetworkReply *upload(const QUrl &url, + const QByteArray &data, + int timeout = -1, + bool verifyCertificate = true, + CallBack callBack = nullptr); + +signals: + void timeOut(); + void ready(QNetworkReply *reply, const QJsonObject &object); + +private slots: + void onReplyFinish(); + void onErrorOccurred(QNetworkReply::NetworkError code); + void onSslErrors(const QList &errors); + void onNetworkTimeout(); + void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void onDownloadReadyRead(); + void onDownloadFinish(); + void onUploadFinish(); + +protected: + virtual QJsonObject hookResult(const QJsonObject &object); + +private: + void queryResult(QNetworkReply *reply, const QJsonObject &object); + + class HttpClientPrivate; + QScopedPointer d_ptr; +}; diff --git a/HttpClient/main.cpp b/HttpClient/main.cpp index 24ba3b6..54c169a 100644 --- a/HttpClient/main.cpp +++ b/HttpClient/main.cpp @@ -1,11 +1,25 @@ -#include "mainwindow.h" +#include "httpclient.hpp" -#include +#include +#include auto main(int argc, char *argv[]) -> int { - QApplication a(argc, argv); - MainWindow w; - w.show(); + QCoreApplication a(argc, argv); + + QMetaObject::invokeMethod( + &a, + [] { + HttpClient httpClient; + auto *reply = httpClient.sendRequest(HttpClient::Method::GET, + QUrl("http://www.baidu.com"), + {}, + {}, + 30); + httpClient.sync(reply); + qApp->quit(); + }, + Qt::QueuedConnection); + return a.exec(); } diff --git a/HttpClient/mainwindow.cpp b/HttpClient/mainwindow.cpp deleted file mode 100644 index c4f72a6..0000000 --- a/HttpClient/mainwindow.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "mainwindow.h" -#include "httpclient.h" - -#include - -class MainWindow::MainWindowPrivate -{ -public: - explicit MainWindowPrivate(QWidget *parent) - : q_ptr(parent) - { - urlEdit = new QLineEdit(q_ptr); - urlEdit->setText("http://www.baidu.com"); - urlEdit->setPlaceholderText(QObject::tr("http://url")); - requestButton = new QPushButton(QObject::tr("GET"), q_ptr); - textEdit = new QPlainTextEdit(q_ptr); - httpClient = new HttpClient(q_ptr); - } - - QWidget *q_ptr; - - QLineEdit *urlEdit; - QPushButton *requestButton; - QPlainTextEdit *textEdit; // QPlainTextEdit - HttpClient *httpClient; - - QElapsedTimer elapsedTimer; -}; - -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) - , d_ptr(new MainWindowPrivate(this)) -{ - setupUI(); - buildConnect(); -} - -MainWindow::~MainWindow() {} - -void MainWindow::onRequest() -{ - QString url = d_ptr->urlEdit->text(); - if (url.isEmpty() || !QUrl(url).isValid()) { - return; - } - d_ptr->requestButton->setEnabled(false); - d_ptr->elapsedTimer.start(); - d_ptr->httpClient->get(url); -} - -void MainWindow::onFinish() -{ - d_ptr->requestButton->setEnabled(true); - qDebug() << "onFinish" << d_ptr->elapsedTimer.elapsed(); -} - -void MainWindow::setupUI() -{ - QHBoxLayout *topLayout = new QHBoxLayout; - topLayout->addWidget(d_ptr->urlEdit); - topLayout->addWidget(d_ptr->requestButton); - - QWidget *widget = new QWidget(this); - QVBoxLayout *layout = new QVBoxLayout(widget); - layout->addLayout(topLayout); - layout->addWidget(d_ptr->textEdit); - setCentralWidget(widget); - resize(640, 480); -} - -void MainWindow::buildConnect() -{ - connect(d_ptr->requestButton, &QPushButton::clicked, this, &MainWindow::onRequest); - connect(d_ptr->httpClient, - &HttpClient::readyReady, - d_ptr->textEdit, - &QPlainTextEdit::appendPlainText); - connect(d_ptr->httpClient, - &HttpClient::error, - d_ptr->textEdit, - &QPlainTextEdit::appendPlainText); - connect(d_ptr->httpClient, &HttpClient::finish, this, &MainWindow::onFinish); -} diff --git a/HttpClient/mainwindow.h b/HttpClient/mainwindow.h deleted file mode 100644 index 317245d..0000000 --- a/HttpClient/mainwindow.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include - -class MainWindow : public QMainWindow -{ - Q_OBJECT -public: - explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow() override; - -private slots: - void onRequest(); - void onFinish(); - -private: - void setupUI(); - void buildConnect(); - - class MainWindowPrivate; - QScopedPointer d_ptr; -}; -#endif // MAINWINDOW_H diff --git a/README.md b/README.md index fb6c036..57cc677 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ -## [HttpClient](HttpClient/)——http客户端 +## [HttpClient](HttpClient/)——简单HTTP客户端,支持JSON请求和上传下载功能 ## [IconButton](IconButton/)——支持Icon跟随状态切换的EventFilter和Button diff --git a/cmake/qt.cmake b/cmake/qt.cmake index 56163b4..d81b1f2 100644 --- a/cmake/qt.cmake +++ b/cmake/qt.cmake @@ -1,7 +1,7 @@ if(CMAKE_HOST_WIN32) - list(APPEND CMAKE_PREFIX_PATH "C:\\Qt\\6.7.2\\msvc2019_64") + list(APPEND CMAKE_PREFIX_PATH "C:\\Qt\\6.8.1\\msvc2022_64") elseif(CMAKE_HOST_APPLE) elseif(CMAKE_HOST_LINUX) - list(APPEND CMAKE_PREFIX_PATH "/opt/Qt/6.7.2/gcc_64") + list(APPEND CMAKE_PREFIX_PATH "/opt/Qt/6.8.1/gcc_64") endif() diff --git a/packaging/macos/build.py b/packaging/macos/build.py index 2487a8d..c8aee28 100644 --- a/packaging/macos/build.py +++ b/packaging/macos/build.py @@ -5,11 +5,11 @@ build_list = [ { - "qmake": r"/Users/runner/Qt/6.7.2/clang_64/bin/qmake", + "qmake": r"/Users/runner/Qt/6.8.1/macos/bin/qmake", "qmake_params": r'"CONFIG+=qtquickcompiler"', "make": r"make", "project": r"/Users/runner/code/Qt-App/Qt-App.pro", - "build_directory": r"/Users/runner/code/Qt-App/build/Desktop_Qt_6_7_0_clang_64bit-Release", + "build_directory": r"/Users/runner/code/Qt-App/build/Desktop_Qt_6_8_1_macosbit-Release", } ] diff --git a/packaging/macos/package.sh b/packaging/macos/package.sh index 58d3ca9..804b23a 100644 --- a/packaging/macos/package.sh +++ b/packaging/macos/package.sh @@ -8,8 +8,8 @@ project_root=$PWD echo "Project root: ${project_root}" echo "Start compiling..." -qmake="/Users/runner/Qt/6.7.2/clang_64/bin/qmake" -build_dir="${project_root}/build/Desktop_Qt_6_7_0_clang_64bit-Release" +qmake="/Users/runner/Qt/6.8.1/macos/bin/qmake" +build_dir="${project_root}/build/Desktop_Qt_6_8_1_macosbit-Release" rm -rf ${build_dir} mkdir -p ${build_dir} @@ -29,7 +29,7 @@ cp -af -v ${project_root}/bin-64/Release/Qt-App.app ${packet_dir}/ delete_file_or_dir "${release_dir}/Qt-App.app" # deploy Qt-App -macdeployqt="/Users/fxy/Qt/6.7.2/clang_64/bin/macdeployqt" +macdeployqt="/Users/fxy/Qt/6.8.1/macos/bin/macdeployqt" ${macdeployqt} ${packet_dir}/Qt-App.app -always-overwrite cp -af -v ${packet_dir}/Qt-App.app ${release_dir}/ diff --git a/packaging/ubuntu/build.py b/packaging/ubuntu/build.py index 4e1fbce..43396ea 100644 --- a/packaging/ubuntu/build.py +++ b/packaging/ubuntu/build.py @@ -9,7 +9,7 @@ "qmake_params": r'"CONFIG+=qtquickcompiler"', "make": r"make", "project": r"/home/runner/work/code/Qt-App/Qt-App.pro", - "build_directory": r"/home/runner/work/code/Qt-App/build/Desktop_Qt_6_7_0_clang_64bit-Release", + "build_directory": r"/home/runner/work/code/Qt-App/build/Desktop_Qt_6_8_1_macosbit-Release", } ] diff --git a/packaging/windows/build.py b/packaging/windows/build.py index 4940631..d2ffa19 100644 --- a/packaging/windows/build.py +++ b/packaging/windows/build.py @@ -8,12 +8,12 @@ build_list = [ { - "qmake": r"C:\Qt\6.7.2\msvc2019_64\bin\qmake.exe", + "qmake": r"C:\Qt\6.8.1\msvc2022_64\bin\qmake.exe", "qmake_params": r'"CONFIG+=qtquickcompiler"', "jom": r"C:\Qt\Tools\QtCreator\bin\jom\jom.exe", "env_bat": r'C:\"Program Files (x86)"\"Microsoft Visual Studio"\2019\Community\VC\Auxiliary\Build\vcvarsall.bat x64', "project": r"C:\code\qt-app\qt-app.pro", - "build_directory": r"C:\work\code\qt-app\build\Desktop_Qt_6_7_0_MSVC2019_64bit-Release", + "build_directory": r"C:\work\code\qt-app\build\Desktop_Qt_6_8_1_msvc2022_64bit-Release", } ]