diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 673fa255..b62b0884 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -26,7 +26,7 @@ file(TO_CMAKE_PATH "/" PATH_SEPARATOR) #设置版本号 add_definitions(-DVERSION=1,3,7,4) -find_package(Qt6 REQUIRED COMPONENTS Quick Svg) +find_package(Qt6 REQUIRED COMPONENTS Quick Svg Network) if(QT_VERSION VERSION_GREATER_EQUAL "6.3") qt_standard_project_setup() @@ -106,7 +106,8 @@ set_target_properties(example PROPERTIES if (FLUENTUI_BUILD_STATIC_LIB) target_link_libraries(example PRIVATE Qt6::Quick - Qt::Svg + Qt6::Svg + Qt6::Network fluentui fluentuiplugin FramelessHelper::Core @@ -115,7 +116,8 @@ if (FLUENTUI_BUILD_STATIC_LIB) else() target_link_libraries(example PRIVATE Qt6::Quick - Qt::Svg + Qt6::Svg + Qt6::Network fluentuiplugin FramelessHelper::Core FramelessHelper::Quick diff --git a/example/qml/App.qml b/example/qml/App.qml index 594417cc..f0af5371 100644 --- a/example/qml/App.qml +++ b/example/qml/App.qml @@ -7,6 +7,23 @@ import FluentUI Window { id: app flags: Qt.SplashScreen + + FluHttpInterceptor{ + id:interceptor + function onIntercept(request){ + if(request.method === "get"){ + request.params["method"] = "get" + } + if(request.method === "post"){ + request.params["method"] = "post" + } + request.headers["token"] ="yyds" + request.headers["os"] ="pc" + console.debug(JSON.stringify(request)) + return request + } + } + Component.onCompleted: { FluApp.init(app) FluTheme.darkMode = FluThemeType.System @@ -21,6 +38,7 @@ Window { "/singleInstanceWindow":"qrc:/example/qml/window/SingleInstanceWindow.qml" } FluApp.initialRoute = "/" + FluApp.httpInterceptor = interceptor FluApp.run() } } diff --git a/example/qml/global/ItemsOriginal.qml b/example/qml/global/ItemsOriginal.qml index 1c4d8b7b..9b20c427 100644 --- a/example/qml/global/ItemsOriginal.qml +++ b/example/qml/global/ItemsOriginal.qml @@ -320,6 +320,12 @@ FluObject{ FluPaneItemExpander{ title:lang.other icon:FluentIcons.Shop + FluPaneItem{ + title:"Http" + onTap:{ + navigationView.push("qrc:/example/qml/page/T_Http.qml") + } + } FluPaneItem{ id:item_other title:"RemoteLoader" diff --git a/example/qml/page/T_Http.qml b/example/qml/page/T_Http.qml new file mode 100644 index 00000000..3f50e676 --- /dev/null +++ b/example/qml/page/T_Http.qml @@ -0,0 +1,147 @@ +import QtQuick +import QtCore +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Dialogs +import FluentUI +import "qrc:///example/qml/component" + +FluScrollablePage{ + + title:"Http" + + + FluHttp{ + id:http_get + url:"https://api.github.com/search/repositories" + onStart: { + showLoading() + } + onFinish: { + hideLoading() + } + onError: + (status,errorString)=>{ + showError(errorString) + } + onSuccess: + (result)=>{ + window_result.result = result + window_result.show() + } + } + + FluHttp{ + id:http_post + url:"https://www.wanandroid.com/article/query/0/json" + onStart: { + showLoading() + } + onFinish: { + hideLoading() + } + onError: + (status,errorString)=>{ + showError(errorString) + } + onSuccess: + (result)=>{ + window_result.result = result + window_result.show() + } + } + + FluHttp{ + id:http_download + url:"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" + onStart: { + btn_download.disabled = true + } + onFinish: { + btn_download.disabled = false + } + onDownloadFileProgress: + (recv,total)=>{ + var precent = (recv/total * 100).toFixed(0) + "%" + btn_download.text = "下载中..."+precent + } + onError: + (status,errorString)=>{ + showError(errorString) + } + onSuccess: + (result)=>{ + showSuccess(result) + } + } + + FluArea{ + Layout.fillWidth: true + Layout.topMargin: 20 + height: 160 + paddings: 10 + + ColumnLayout{ + spacing: 14 + anchors.verticalCenter: parent.verticalCenter + FluButton{ + text:"Get请求" + onClicked: { + http_get.get({q:"FluentUI"}) + } + } + FluButton{ + text:"Post请求" + onClicked: { + http_post.post({k:"jitpack"}) + } + } + FluButton{ + id:btn_download + text:disabled ? "下载中..." : "下载文件" + onClicked: { + file_dialog.open() + } + } + } + } + + FolderDialog { + id: file_dialog + currentFolder: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0] + onAccepted: { + var path = selectedFolder.toString().replace("file:///","") + "/big_buck_bunny.mp4" + http_download.download(path) + } + } + + Window{ + property string result : "" + id:window_result + width: 600 + height: 400 + Item{ + anchors.fill: parent + + Flickable{ + id:scrollview + width: parent.width + height: parent.height + contentWidth: width + contentHeight: text_info.height + ScrollBar.vertical: FluScrollBar {} + FluText{ + id:text_info + width: scrollview.width + wrapMode: Text.WrapAnywhere + text:window_result.result + padding: 14 + } + } + } + } + + + +} diff --git a/src/FluApp.cpp b/src/FluApp.cpp index a4ad12b0..ba688e83 100644 --- a/src/FluApp.cpp +++ b/src/FluApp.cpp @@ -30,7 +30,6 @@ FluApp::~FluApp(){ void FluApp::init(QQuickWindow *window){ this->appWindow = window; - FluContentDialogType::ButtonFlag::NegativeButton; } void FluApp::run(){ diff --git a/src/FluApp.h b/src/FluApp.h index 90b057a1..e383baa4 100644 --- a/src/FluApp.h +++ b/src/FluApp.h @@ -9,6 +9,7 @@ #include #include #include "FluRegister.h" +#include "FluHttpInterceptor.h" #include "stdafx.h" /** @@ -27,6 +28,11 @@ class FluApp : public QObject */ Q_PROPERTY_AUTO(QJsonObject,routes); + /** + * @brief http拦截器 + */ + Q_PROPERTY_AUTO(FluHttpInterceptor*,httpInterceptor); + QML_NAMED_ELEMENT(FluApp) QML_SINGLETON private: diff --git a/src/FluHttp.cpp b/src/FluHttp.cpp new file mode 100644 index 00000000..c2a773ee --- /dev/null +++ b/src/FluHttp.cpp @@ -0,0 +1,137 @@ +#include "FluHttp.h" + +#include +#include "HttpClient.h" +#include "FluApp.h" + +using namespace AeaQt; + +FluHttp::FluHttp(QObject *parent) + : QObject{parent} +{ + enabledBreakpointDownload(false); +} + +Q_INVOKABLE void FluHttp::postString(QString params,QVariantMap headers){ + QVariantMap request = invokeIntercept(params,headers,"postString").toMap(); + QThreadPool::globalInstance()->start([=](){ + HttpClient client; + Q_EMIT start(); + client.post(_url) + .bodyWithRaw(request[params].toString().toUtf8()) + .headers(request["headers"].toMap()) + .onSuccess([=](QString result) { + Q_EMIT success(result); + Q_EMIT finish(); + }) + .onFailed([=](QNetworkReply* reply) { + Q_EMIT error(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(),reply->errorString()); + Q_EMIT finish(); + }) + .block() + .exec(); + }); +} + +void FluHttp::post(QVariantMap params,QVariantMap headers){ + QVariantMap request = invokeIntercept(params,headers,"post").toMap(); + QThreadPool::globalInstance()->start([=](){ + HttpClient client; + Q_EMIT start(); + client.post(_url) + .headers(headers) + .bodyWithFormData(request["params"].toMap()) + .headers(request["headers"].toMap()) + .onSuccess([=](QString result) { + Q_EMIT success(result); + Q_EMIT finish(); + }) + .onFailed([=](QNetworkReply* reply) { + Q_EMIT error(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(),reply->errorString()); + Q_EMIT finish(); + }) + .block() + .exec(); + }); +} + +void FluHttp::postJson(QVariantMap params,QVariantMap headers){ + QVariantMap request = invokeIntercept(params,headers,"postJson").toMap(); + QThreadPool::globalInstance()->start([=](){ + HttpClient client; + Q_EMIT start(); + client.post(_url) + .headers(headers) + .bodyWithRaw(QJsonDocument::fromVariant(request["params"]).toJson()) + .headers(request["headers"].toMap()) + .onSuccess([=](QString result) { + Q_EMIT success(result); + Q_EMIT finish(); + }) + .onFailed([=](QNetworkReply* reply) { + Q_EMIT error(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(),reply->errorString()); + Q_EMIT finish(); + }) + .block() + .exec(); + }); +} + +void FluHttp::get(QVariantMap params,QVariantMap headers){ + QVariantMap request = invokeIntercept(params,headers,"get").toMap(); + QThreadPool::globalInstance()->start([=](){ + HttpClient client; + Q_EMIT start(); + client.get(_url) + .queryParams(request["params"].toMap()) + .headers(request["headers"].toMap()) + .onSuccess([=](QString result) { + Q_EMIT success(result); + Q_EMIT finish(); + }) + .onFailed([=](QNetworkReply* reply) { + Q_EMIT error(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(),reply->errorString()); + Q_EMIT finish(); + }) + .block() + .exec(); + }); +} + +Q_INVOKABLE void FluHttp::download(QString path,QVariantMap params,QVariantMap headers){ + QVariantMap request = invokeIntercept(params,headers,"download").toMap(); + qDebug()<start([=](){ + HttpClient client; + Q_EMIT start(); + client.get(_url) + .download(path) + .enabledBreakpointDownload(_enabledBreakpointDownload) + .queryParams(request["params"].toMap()) + .headers(request["headers"].toMap()) + .onDownloadProgress([=](qint64 recv, qint64 total) { + Q_EMIT downloadFileProgress(recv,total); + }) + .onDownloadFileSuccess([=](QString result) { + Q_EMIT success(result); + Q_EMIT finish(); + }) + .onDownloadFileFailed([=](QString errorString) { + Q_EMIT error(-1,errorString); + Q_EMIT finish(); + }) + .block() + .exec(); + }); +} + +QVariant FluHttp::invokeIntercept(const QVariant& params,const QVariant& headers,const QString& method){ + QVariantMap requet = { + {"params",params}, + {"headers",headers}, + {"method",method} + }; + QVariant target; + QMetaObject::invokeMethod(FluApp::getInstance()->httpInterceptor(), "onIntercept",Q_RETURN_ARG(QVariant,target),Q_ARG(QVariant, requet)); + return target; +} diff --git a/src/FluHttp.h b/src/FluHttp.h new file mode 100644 index 00000000..e28e63ab --- /dev/null +++ b/src/FluHttp.h @@ -0,0 +1,32 @@ +#ifndef FLUHTTP_H +#define FLUHTTP_H + +#include +#include +#include +#include "stdafx.h" + +class FluHttp : public QObject +{ + Q_OBJECT + Q_PROPERTY_AUTO(QString,url); + Q_PROPERTY_AUTO(bool,enabledBreakpointDownload) + QML_NAMED_ELEMENT(FluHttp) +private: + QVariant invokeIntercept(const QVariant& params,const QVariant& headers,const QString& method); +public: + explicit FluHttp(QObject *parent = nullptr); + Q_SIGNAL void start(); + Q_SIGNAL void finish(); + Q_SIGNAL void error(int status,QString errorString); + Q_SIGNAL void success(QString result); + Q_SIGNAL void downloadFileProgress(qint64 recv, qint64 total); + Q_INVOKABLE void get(QVariantMap params = {},QVariantMap headers = {}); + Q_INVOKABLE void post(QVariantMap params = {},QVariantMap headers = {}); + Q_INVOKABLE void postJson(QVariantMap params = {},QVariantMap headers = {}); + Q_INVOKABLE void postString(QString params = "",QVariantMap headers = {}); + + Q_INVOKABLE void download(QString path,QVariantMap params = {},QVariantMap headers = {}); +}; + +#endif // FLUHTTP_H diff --git a/src/FluHttpInterceptor.cpp b/src/FluHttpInterceptor.cpp new file mode 100644 index 00000000..38b8a7a4 --- /dev/null +++ b/src/FluHttpInterceptor.cpp @@ -0,0 +1,7 @@ +#include "FluHttpInterceptor.h" + +FluHttpInterceptor::FluHttpInterceptor(QObject *parent) + : QObject{parent} +{ + +} diff --git a/src/FluHttpInterceptor.h b/src/FluHttpInterceptor.h new file mode 100644 index 00000000..dcef4b60 --- /dev/null +++ b/src/FluHttpInterceptor.h @@ -0,0 +1,18 @@ +#ifndef FLUHTTPINTERCEPTOR_H +#define FLUHTTPINTERCEPTOR_H + +#include +#include + +class FluHttpInterceptor : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(FluHttpInterceptor) +public: + explicit FluHttpInterceptor(QObject *parent = nullptr); + +signals: + +}; + +#endif // FLUHTTPINTERCEPTOR_H diff --git a/src/HttpClient.h b/src/HttpClient.h new file mode 100644 index 00000000..6e33122c --- /dev/null +++ b/src/HttpClient.h @@ -0,0 +1,2321 @@ +/********************************************************** + * Author(作者) : Qt君 + * 微信公众号 : Qt君 + * Website(网站) : qthub.com + * QQ交流群 : 1039852727 + * Email(邮箱) : 2088201923@qq.com + * Support(技术支持&合作) :2088201923(QQ) + * Source Code(源码): https://github.com/aeagean/QtNetworkService + * LISCENSE(开源协议): MIT + * Demo(演示): + ========================================================== + static AeaQt::HttpClient client; + client.get("https://qthub.com") + .onSuccess([](QString result) { qDebug()<<"success!"; }) + .onFailed([](QString error) { qDebug()<<"failed!"; }) + .exec(); + ========================================================== +**********************************************************/ +#ifndef HTTPCLIENT_HPP +#define HTTPCLIENT_HPP + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace AeaQt +{ + +class HttpClient; +class HttpRequest; +class HttpResponse; + +class HttpClient : public QNetworkAccessManager +{ + Q_OBJECT +public: + inline static HttpClient* instance(); + inline HttpClient(QObject* parent = nullptr); + inline QString getVersion() const; + + inline HttpRequest head(const QString& url); + inline HttpRequest get(const QString& url); + inline HttpRequest post(const QString& url); + inline HttpRequest put(const QString& url); + + inline HttpRequest send(const QString& url, Operation op = GetOperation); + + HttpClient* timeout(const int& second = 60) + { + timeoutSecond = second; + return this; + } + int timeoutSecond = 60; + + HttpClient* header(const QString& key, const QVariant& value) + { + globalHeader.insert(key, value); + return this; + } + QMap globalHeader; + +private: +#if (QT_VERSION < QT_VERSION_CHECK(5, 8, 0)) + inline QNetworkReply* sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, + const QByteArray& data); + inline QNetworkReply* sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, + QHttpMultiPart* multiPart); +#endif + friend class HttpRequest; +}; + +class HttpRequest +{ +public: + enum LogLevel + { + Off, + Fatal, + Error, + Warn, + Debug, + Info, + Trace, + All + }; + + inline explicit HttpRequest(QNetworkAccessManager::Operation op, HttpClient* httpClient); + inline virtual ~HttpRequest(); + + inline HttpRequest& url(const QString& url); + + inline HttpRequest& header(const QString& key, const QVariant& value); + inline HttpRequest& header(QNetworkRequest::KnownHeaders header, const QVariant& value); + inline HttpRequest& headers(const QMap& headers); + inline HttpRequest& headers(const QMap& headers); + + inline HttpRequest& queryParam(const QString& key, const QVariant& value); + inline HttpRequest& queryParams(const QMap& params); + + /* Mainly used for identification */ + inline HttpRequest& userAttribute(const QVariant& value); + inline HttpRequest& attribute(QNetworkRequest::Attribute attribute, const QVariant& value); + + inline HttpRequest& body(const QByteArray& raw); + inline HttpRequest& bodyWithRaw(const QByteArray& raw); + + inline HttpRequest& body(const QJsonObject& json); + inline HttpRequest& bodyWithJson(const QJsonObject& json); + + inline HttpRequest& body(const QVariantMap& formUrlencodedMap); + inline HttpRequest& bodyWithFormUrlencoded(const QString& key, const QVariant& value); + inline HttpRequest& bodyWithFormUrlencoded(const QVariantMap& keyValueMap); + + inline HttpRequest& bodyWithFormData(const QString& key, const QVariant& value); + inline HttpRequest& bodyWithFormData(const QVariantMap& keyValueMap); + + inline HttpRequest& body(QHttpMultiPart* multiPart); + inline HttpRequest& bodyWithMultiPart(QHttpMultiPart* multiPart); + + // multi-params + inline HttpRequest& body(const QString& key, const QString& file); + inline HttpRequest& bodyWithFile(const QString& key, const QString& file); + inline HttpRequest& bodyWithFile(const QMap& fileMap); // => QMap; + // like: { "key": + // "/home/example/car.jpeg" + // } + + inline HttpRequest& ignoreSslErrors(const QList& errors); + inline HttpRequest& sslConfiguration(const QSslConfiguration& config); + + inline HttpRequest& priority(QNetworkRequest::Priority priority); + inline HttpRequest& maximumRedirectsAllowed(int maxRedirectsAllowed); + inline HttpRequest& originatingObject(QObject* object); + inline HttpRequest& readBufferSize(qint64 size); + + // Authentication + inline HttpRequest& autoAuthenticationRequired(const QAuthenticator& authenticator); + inline HttpRequest& autoAuthenticationRequired(const QString& user, const QString& password); + + // 超过身份验证计数则触发失败并中断请求。 + // count >= 0 => count + // count < 0 => infinite + inline HttpRequest& authenticationRequiredCount(int count = 1); + + /** + * @brief msec <= 0, disable timeout + * msec > 0, enable timeout + */ + inline HttpRequest& timeout(const int& second = -1); + inline HttpRequest& timeoutMs(const int& msec = -1); + + inline HttpRequest& download(); + inline HttpRequest& download(const QString& file); + inline HttpRequest& enabledBreakpointDownload(bool enabled = true); + + inline HttpRequest& retry(int count); + inline HttpRequest& repeat(int count); + + /** + * @brief Block(or sync) current thread, entering an event loop. + */ + inline HttpRequest& block(bool isBlock = true); + inline HttpRequest& sync(bool isSync = true); + + inline HttpRequest& logLevel(LogLevel level = Warn); + + inline HttpResponse* exec(); + + // onFinished == onSuccess + inline HttpRequest& onSuccess(const QObject* receiver, const char* method); + inline HttpRequest& onSuccess(std::function lambda); + inline HttpRequest& onSuccess(std::function lambda); + inline HttpRequest& onSuccess(std::function lambda); + inline HttpRequest& onSuccess(std::function lambda); + + // onFinished == onSuccess + inline HttpRequest& onFinished(const QObject* receiver, const char* method); + inline HttpRequest& onFinished(std::function lambda); + inline HttpRequest& onFinished(std::function lambda); + inline HttpRequest& onFinished(std::function lambda); + inline HttpRequest& onFinished(std::function lambda); + + // onError == onFailed + inline HttpRequest& onError(const QObject* receiver, const char* method); + inline HttpRequest& onError(std::function lambda); + inline HttpRequest& onError(std::function lambda); + inline HttpRequest& onError(std::function lambda); + inline HttpRequest& onError(std::function lambda); + + // onError == onFailed + inline HttpRequest& onFailed(const QObject* receiver, const char* method); + inline HttpRequest& onFailed(std::function lambda); + inline HttpRequest& onFailed(std::function lambda); + inline HttpRequest& onFailed(std::function lambda); + inline HttpRequest& onFailed(std::function lambda); + + inline HttpRequest& onReadyRead(const QObject* receiver, const char* method); + inline HttpRequest& onReadyRead(std::function lambda); + + inline HttpRequest& onHead(const QObject* receiver, const char* method); + inline HttpRequest& onHead(std::function)> lambda); + inline HttpRequest& onHead(std::function)> lambda); + + inline HttpRequest& onTimeout(const QObject* receiver, const char* method); + inline HttpRequest& onTimeout(std::function lambda); + inline HttpRequest& onTimeout(std::function lambda); + + inline HttpRequest& onUploadProgress(const QObject* receiver, const char* method); + inline HttpRequest& onUploadProgress(std::function lambda); + + inline HttpRequest& onDownloadProgress(const QObject* receiver, const char* method); + inline HttpRequest& onDownloadProgress(std::function lambda); + + /// file download interface + inline HttpRequest& onDownloadFileNameChanged(const QObject* receiver, const char* method); + inline HttpRequest& onDownloadFileNameChanged(std::function lambda); + + inline HttpRequest& onDownloadFileProgress(const QObject* receiver, const char* method); + inline HttpRequest& onDownloadFileProgress(std::function lambda); + + inline HttpRequest& onDownloadFileSuccess(const QObject* receiver, const char* method); + inline HttpRequest& onDownloadFileSuccess(std::function lambda); + inline HttpRequest& onDownloadFileSuccess(std::function lambda); + + inline HttpRequest& onDownloadFileFailed(const QObject* receiver, const char* method); + inline HttpRequest& onDownloadFileFailed(std::function lambda); + inline HttpRequest& onDownloadFileFailed(std::function lambda); + /// file download interface + + inline HttpRequest& onRetried(const QObject* receiver, const char* method); + inline HttpRequest& onRetried(std::function lambda); + + inline HttpRequest& onRepeated(const QObject* receiver, const char* method); + inline HttpRequest& onRepeated(std::function lambda); + + inline HttpRequest& onAuthenticationRequired(const QObject* receiver, const char* method); + inline HttpRequest& onAuthenticationRequired(std::function lambda); + + inline HttpRequest& onAuthenticationRequireFailed(const QObject* receiver, const char* method); + inline HttpRequest& onAuthenticationRequireFailed(std::function lambda); + inline HttpRequest& onAuthenticationRequireFailed(std::function lambda); + + // onResponse == onSuccess. note: DEPRECATED + inline HttpRequest& onResponse(const QObject* receiver, const char* method); + inline HttpRequest& onResponse(std::function lambda); + inline HttpRequest& onResponse(std::function lambda); + inline HttpRequest& onResponse(std::function lambda); + + enum HandleType + { + h_onFinished = 0, + h_onError, + h_onDownloadProgress, + h_onUploadProgress, + h_onTimeout, + h_onReadyRead, + h_onDownloadFileSuccess, + h_onDownloadFileFailed, + h_onEncrypted, + h_onMetaDataChanged, + h_onPreSharedKeyAuthenticationRequired, + h_onRedirectAllowed, + h_onRedirected, + h_onSslErrors, + h_onRetried, + h_onRepeated, + h_onAuthenticationRequired, + h_onAuthenticationRequireFailed, + h_onHead, + h_onDownloadFileProgess, + h_onDownloadFileNameChanged, + }; + + enum BodyType + { + None = 0, // This request does not have a body. + Raw, + Raw_Json, // application/json + X_Www_Form_Urlencoded, // x-www-form-urlencoded + FileMap, // multipart/form-data + MultiPart, // multipart/form-data + FormData // multipart/form-data + }; + +protected: + struct Downloader + { + bool isEnabled; + QString fileName; + bool enabledBreakpointDownload; + bool isSupportBreakpointDownload; + qint64 currentSize; + qint64 totalSize; + + Downloader() + { + isEnabled = false; + fileName = ""; + enabledBreakpointDownload = true; + isSupportBreakpointDownload = false; + totalSize = 0; + currentSize = 0; + } + }; + + QNetworkAccessManager::Operation m_op; + HttpClient* m_httpClient = nullptr; + QNetworkReply* m_reply = nullptr; + QNetworkRequest m_request; + + QPair m_body = qMakePair(BodyType::None, QByteArray()); + int m_timeoutMs = -1; // ms + bool m_isBlock = false; + int m_retryCount = 0; + bool m_enabledRetry = false; + int m_repeatCount = 1; + QAuthenticator m_authenticator; + int m_authenticationRequiredCount = 1; + + qint64 m_readBufferSize = -1; + QList m_ignoreSslErrors; + Downloader m_downloader; + QMap>> m_handleMap; + QVariantMap m_formUrlencodedMap; + QVariantMap m_formDataMap; + LogLevel m_logLevel = Warn; + +protected: + inline QString toString(); + + inline HttpRequest& enabledRetry(bool isEnabled); + inline HttpResponse* exec(const HttpRequest& httpRequest, HttpResponse* httpResponse = nullptr); + + friend class HttpResponse; + friend class HttpDownloader; + +private: + inline HttpRequest() = delete; + inline HttpRequest& onResponse(HandleType type, const QObject* receiver, const char* method); + inline HttpRequest& onResponse(HandleType type, QVariant lambda); + inline HttpRequest& onResponse(HandleType type, QString key, QVariant value); +}; + +class HttpResponse : public QObject +{ + Q_OBJECT +public: + inline explicit HttpResponse(const HttpRequest& httpRequest, QObject* parent = nullptr); + inline virtual ~HttpResponse(); + + inline void setHttpRequest(const HttpRequest& httpRequest); + inline QNetworkReply* reply() + { + return m_httpRequest.m_reply; + } + inline QString toString() const; +signals: + void replyChanged(QNetworkReply* reply); + void finished(QNetworkReply* reply); + void finished(); + void finished(QByteArray result); + void finished(QVariantMap resultMap); + + void downloadProgress(qint64, qint64); + void uploadProgress(qint64, qint64); + + void error(QByteArray error); + void error(); + void error(QNetworkReply::NetworkError error); + void error(QNetworkReply* reply); + + void timeout(); + void timeout(QNetworkReply* reply); + + void readyRead(QNetworkReply* reply); + + void downloadFileNameChanged(QString fileName); + + void downloadFileFinished(); + void downloadFileFinished(QString file); + + void downloadFileError(); + void downloadFileError(QString errorString); + + void encrypted(); + void metaDataChanged(); + + void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator* authenticator); + void redirectAllowed(); + void redirected(QUrl url); + void sslErrors(QList errors); + + void retried(); + void repeated(); + + void authenticationRequired(QAuthenticator* authentication); + void authenticationRequireFailed(); + void authenticationRequireFailed(QNetworkReply*); + + void head(QList); + void head(QMap); + + void downloadFileProgress(qint64 bytesReceived, qint64 bytesTotal); + +private slots: + inline void onFinished(); + inline void onError(QNetworkReply::NetworkError error); + inline void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + inline void onUploadProgress(qint64 bytesSent, qint64 bytesTotal); + inline void onTimeout(); + inline void onReadyRead(); + inline void onReadOnceReplyHeader(); + + inline void onEncrypted(); + inline void onMetaDataChanged(); + inline void onPreSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator* authenticator); + inline void onRedirectAllowed(); + inline void onRedirected(const QUrl& url); + inline void onSslErrors(const QList& errors); + + inline void onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator); + + inline void onHandleHead(); + +private: + HttpRequest m_httpRequest; + QFile m_downloadFile; + int m_retriesRemaining = 0; + int m_authenticationCount = 0; + bool m_isHandleHead = false; +}; + +inline QString lineIndent(const QString& source, const QString& indentString); +inline QString networkOperation2String(QNetworkAccessManager::Operation o); +inline QString networkHeader2String(const QNetworkRequest& request); +inline QString networkBody2String(const QPair& body); +inline QString networkReplyHeader2String(const QNetworkReply* reply); + +class HttpDownloader : public QObject +{ + Q_OBJECT + HttpRequest m_httpRequest; + bool m_isSupportContinueDownload = false; + QString m_fileName; + qint64 m_contentLength = 0; + QList m_headerForPair; + QMap m_headerForMap; + HttpResponse* m_response = nullptr; + +public: + HttpDownloader(const HttpRequest& httpRequest, QObject* parent) : m_httpRequest(httpRequest), QObject(parent) + { + m_fileName = this->m_httpRequest.m_request.url().fileName(); + if (m_fileName.isEmpty()) + { + m_fileName = this->m_httpRequest.m_request.url().host(); + } + } + + virtual ~HttpDownloader() + { + } + + HttpResponse* exec() + { + HttpClient& client = *HttpClient::instance(); + client.get(m_httpRequest.m_request.url().toString()) + .timeout(30) + .block(m_httpRequest.m_isBlock) + .onHead(this, SLOT(onHead(QList))) + .onHead(this, SLOT(onHead(QMap))) + .onReadyRead(this, SLOT(onReadyRead(QNetworkReply*))) + .onFailed(this, SLOT(onResponse(QNetworkReply::NetworkError))) + .exec(); + m_response = new HttpResponse(m_httpRequest, nullptr); + return m_response; + } + +private slots: + void onResponse(QNetworkReply::NetworkError) + { + HttpResponse* response = m_httpRequest.exec(m_httpRequest, m_response); + this->setParent(response); + } + + void onHead(QList headerForPair) + { + m_headerForPair = headerForPair; + } + + void onHead(QMap headerForMap) + { + m_headerForMap = headerForMap; + for (auto each : m_headerForMap.toStdMap()) + { + QString key = each.first; + QString value = each.second; + + if (key.contains("Content-Disposition", Qt::CaseInsensitive)) + { + QString dispositionHeader = value; + // fixme rx + + QRegularExpression rx("attachment;\\s*filename=([\\S]+)"); + QRegularExpressionMatch match = rx.match(value); + if (match.hasMatch()) + { + m_fileName = match.captured(1); + } + } + + if (key.contains("Content-Length", Qt::CaseInsensitive)) + { + m_contentLength = value.toLongLong(); + } + + if (key.contains("Content-Range", Qt::CaseInsensitive) || key.contains("Accept-Ranges", Qt::CaseInsensitive)) + { + m_isSupportContinueDownload = true; + } + } + } + + void onReadyRead(QNetworkReply* reply) + { + HttpResponse* response = new HttpResponse(m_httpRequest, m_httpRequest.m_reply); + if (m_httpRequest.m_downloader.fileName.isEmpty()) + { + m_httpRequest.m_downloader.fileName = m_fileName; + emit response->downloadFileNameChanged(m_fileName); + } + + m_httpRequest.m_downloader.totalSize = m_contentLength; + m_httpRequest.m_downloader.isSupportBreakpointDownload = m_isSupportContinueDownload; + + if (m_httpRequest.m_downloader.enabledBreakpointDownload && m_httpRequest.m_downloader.isSupportBreakpointDownload) + { + QFile file(m_httpRequest.m_downloader.fileName); + if (file.exists() && file.open(QIODevice::ReadOnly)) + { + m_httpRequest.m_downloader.currentSize = file.size(); + + if (file.size() == m_contentLength) + { + emit response->head(m_headerForPair); + emit response->head(m_headerForMap); + + emit response->downloadFileProgress(m_contentLength, m_contentLength); + + emit response->downloadFileFinished(); + emit response->downloadFileFinished(m_httpRequest.m_downloader.fileName); + + emit response->finished(); + emit response->finished(QByteArray("")); + emit response->finished(QVariantMap{}); + emit response->finished(nullptr); + + response->deleteLater(); + } + else if (file.size() > m_contentLength) + { + file.close(); + // Clear file content + file.open(QIODevice::Truncate); + file.close(); + } + else + { + m_httpRequest.m_request.setRawHeader("Range", QString("bytes=%1-").arg(file.size()).toUtf8()); + } + } + } + + response->deleteLater(); + reply->abort(); + } +}; + + +class HttpResponseTimeout : public QObject +{ + Q_OBJECT + +private: + HttpResponse* _parent; + int _timeout; + Q_SLOT void handleTimeOut(){ + QTimer::singleShot(_timeout, _parent, SLOT(onTimeout())); + } +public: + HttpResponseTimeout(HttpResponse* parent, const int timeout = -1) : QObject(parent) + { + _timeout = timeout; + _parent = parent; + if (timeout > 0) + { + QMetaObject::invokeMethod(this, "handleTimeOut",Qt::QueuedConnection); + } + else + { + // do nothing + } + } +}; + +class HttpBlocker : public QEventLoop +{ + Q_OBJECT +public: + HttpBlocker(QNetworkReply* reply, bool isBlock) : QEventLoop(reply) + { + if (isBlock) + { + connect(reply, SIGNAL(finished()), this, SLOT(quit())); + this->exec(); + } + } +}; + +#define _logger(l1, l2, str) \ +do \ + { \ + if (l1 >= l2) \ + { \ + if (l2 >= HttpRequest::Debug) \ + { \ + qDebug().noquote() << str; \ + } \ + else if (l2 == HttpRequest::Warn) \ + { \ + qWarning().noquote() << str; \ + } \ + else if (l2 == HttpRequest::Error) \ + { \ + qCritical().noquote() << str; \ + } \ + else if (l2 == HttpRequest::Fatal) \ + { \ + qFatal("%s\n", str); \ + } \ + } \ + } while (0); + +#define printTrace(level, str) _logger(level, HttpRequest::Trace, str) +#define printInfo(level, str) _logger(level, HttpRequest::Info, str) +#define printDebug(level, str) _logger(level, HttpRequest::Debug, str) +#define printWarn(level, str) _logger(level, HttpRequest::Warn, str) +#define printError(level, str) _logger(level, HttpRequest::Error, str) +#define printFatal(level, str) _logger(level, HttpRequest::Fatal, str) + +HttpRequest::~HttpRequest() +{ +} + +HttpRequest::HttpRequest(QNetworkAccessManager::Operation op, HttpClient* httpClient) +{ + m_op = op; + m_httpClient = httpClient; +} + +HttpRequest& HttpRequest::url(const QString& url) +{ + m_request.setUrl(QUrl(url)); + return *this; +} + +HttpRequest& HttpRequest::header(QNetworkRequest::KnownHeaders header, const QVariant& value) +{ + m_request.setHeader(header, value); + return *this; +} + +HttpRequest& HttpRequest::headers(const QMap& headers) +{ + QMapIterator iter(headers); + while (iter.hasNext()) + { + iter.next(); + header(iter.key(), iter.value()); + } + + return *this; +} + +HttpRequest& HttpRequest::header(const QString& key, const QVariant& value) +{ + m_request.setRawHeader(QByteArray(key.toStdString().data()), QByteArray(value.toString().toStdString().data())); + return *this; +} + +HttpRequest& HttpRequest::headers(const QMap& headers) +{ + QMapIterator iter(headers); + while (iter.hasNext()) + { + iter.next(); + header(iter.key(), iter.value()); + } + + return *this; +} + +HttpRequest& HttpRequest::body(const QVariantMap& keyValueMap) +{ + return bodyWithFormUrlencoded(keyValueMap); +} + +HttpRequest& HttpRequest::bodyWithFormUrlencoded(const QString& key, const QVariant& value) +{ + QVariantMap map; + map[key] = value; + + return bodyWithFormUrlencoded(map); +} + +HttpRequest& HttpRequest::bodyWithFormUrlencoded(const QVariantMap& keyValueMap) +{ + // merge map + for (auto each : keyValueMap.toStdMap()) + { + m_formUrlencodedMap[each.first] = each.second; + } + + QUrl url; + QUrlQuery urlQuery(url); + QMapIterator i(m_formUrlencodedMap); + while (i.hasNext()) + { + i.next(); + urlQuery.addQueryItem(i.key(), i.value().toString()); + } + + url.setQuery(urlQuery); + const QString& value = url.toString(QUrl::FullyEncoded).toUtf8().remove(0, 1); + + m_body = qMakePair(X_Www_Form_Urlencoded, value); + m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + return *this; +} + +HttpRequest& HttpRequest::bodyWithFormData(const QString& key, const QVariant& value) +{ + QVariantMap map; + map[key] = value; + + return bodyWithFormData(map); +} + +HttpRequest& HttpRequest::bodyWithFormData(const QVariantMap& keyValueMap) +{ + // merge map + for (auto each : keyValueMap.toStdMap()) + { + m_formDataMap[each.first] = each.second; + } + + m_body = qMakePair(FormData, m_formDataMap); + m_request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data"); + return *this; +} + +HttpRequest& HttpRequest::body(const QJsonObject& json) +{ + return bodyWithJson(json); +} + +HttpRequest& HttpRequest::bodyWithJson(const QJsonObject& json) +{ + const QByteArray& value = QJsonDocument(json).toJson(); + m_body = qMakePair(Raw_Json, value); + m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + return *this; +} + +HttpRequest& HttpRequest::body(const QByteArray& raw) +{ + return bodyWithRaw(raw); +} + +HttpRequest& HttpRequest::bodyWithRaw(const QByteArray& raw) +{ + m_body = qMakePair(Raw, raw); + return *this; +} + +HttpRequest& HttpRequest::body(QHttpMultiPart* multiPart) +{ + return bodyWithMultiPart(multiPart); +} + +HttpRequest& HttpRequest::bodyWithMultiPart(QHttpMultiPart* multiPart) +{ + m_body = qMakePair(MultiPart, QVariant::fromValue(multiPart)); + return *this; +} + +HttpRequest& HttpRequest::download() +{ + return download(""); +} + +HttpRequest& HttpRequest::download(const QString& file) +{ + m_downloader.isEnabled = true; + m_downloader.fileName = file; + return *this; +} + +HttpRequest& HttpRequest::enabledBreakpointDownload(bool enabled) +{ + m_downloader.enabledBreakpointDownload = enabled; + return *this; +} + +HttpRequest& HttpRequest::onDownloadFileProgress(const QObject* receiver, const char* method) +{ + return onResponse(h_onDownloadFileProgess, receiver, method); +} + +HttpRequest& HttpRequest::onDownloadFileProgress(std::function lambda) +{ + return onResponse(h_onDownloadFileProgess, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::body(const QString& key, const QString& file) +{ + return bodyWithFile(key, file); +} + +HttpRequest& HttpRequest::bodyWithFile(const QString& key, const QString& filePath) +{ + QMap map; + map[key] = filePath; + + return bodyWithFile(map); +} + +HttpRequest& HttpRequest::bodyWithFile(const QMap& fileMap) +{ + auto& body = m_body; + auto map = body.second.value>(); + for (auto each : fileMap.toStdMap()) + { + map[each.first] = each.second; + } + + body.first = BodyType::FileMap; + body.second = QVariant::fromValue(map); + return *this; +} + +HttpRequest& HttpRequest::ignoreSslErrors(const QList& errors) +{ + m_ignoreSslErrors = errors; + return *this; +} + +HttpRequest& HttpRequest::sslConfiguration(const QSslConfiguration& config) +{ + m_request.setSslConfiguration(config); + return *this; +} + +HttpRequest& HttpRequest::priority(QNetworkRequest::Priority priority) +{ + m_request.setPriority(priority); + return *this; +} + +HttpRequest& HttpRequest::maximumRedirectsAllowed(int maxRedirectsAllowed) +{ + m_request.setMaximumRedirectsAllowed(maxRedirectsAllowed); + return *this; +} + +HttpRequest& HttpRequest::originatingObject(QObject* object) +{ + m_request.setOriginatingObject(object); + return *this; +} + +HttpRequest& HttpRequest::readBufferSize(qint64 size) +{ + m_readBufferSize = size; + return *this; +} + +HttpRequest& HttpRequest::autoAuthenticationRequired(const QAuthenticator& authenticator) +{ + m_authenticator = authenticator; + return *this; +} + +HttpRequest& HttpRequest::autoAuthenticationRequired(const QString& user, const QString& password) +{ + QAuthenticator a; + a.setUser(user); + a.setPassword(password); + + return autoAuthenticationRequired(a); +} + +HttpRequest& HttpRequest::authenticationRequiredCount(int count) +{ + m_authenticationRequiredCount = count; + return *this; +} + +HttpRequest& HttpRequest::timeout(const int& second) +{ + return timeoutMs(second * 1000); +} +HttpRequest& HttpRequest::timeoutMs(const int& msec) +{ + m_timeoutMs = msec; + return *this; +} + +HttpRequest& HttpRequest::retry(int count) +{ + m_retryCount = count; + return *this; +} + +HttpRequest& HttpRequest::repeat(int count) +{ + m_repeatCount = count; + return *this; +} + +HttpRequest& HttpRequest::block(bool isBlock) +{ + m_isBlock = isBlock; + return *this; +} +HttpRequest& HttpRequest::sync(bool isSync) +{ + return block(isSync); +} + +HttpRequest& HttpRequest::logLevel(HttpRequest::LogLevel level) +{ + m_logLevel = level; + return *this; +} + +HttpRequest& HttpRequest::onFinished(const QObject* receiver, const char* method) +{ + return onResponse(h_onFinished, receiver, method); +} +HttpRequest& HttpRequest::onFinished(std::function lambda) +{ + return onResponse(h_onFinished, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onFinished(std::function lambda) +{ + return onResponse(h_onFinished, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onFinished(std::function lambda) +{ + return onResponse(h_onFinished, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onFinished(std::function lambda) +{ + return onResponse(h_onFinished, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onSuccess(const QObject* receiver, const char* method) +{ + return onFinished(receiver, method); +} +HttpRequest& HttpRequest::onSuccess(std::function lambda) +{ + return onFinished(lambda); +} +HttpRequest& HttpRequest::onSuccess(std::function lambda) +{ + return onFinished(lambda); +} +HttpRequest& HttpRequest::onSuccess(std::function lambda) +{ + return onFinished(lambda); +} +HttpRequest& HttpRequest::onSuccess(std::function lambda) +{ + return onFinished(lambda); +} + +HttpRequest& HttpRequest::onFailed(const QObject* receiver, const char* method) +{ + return onError(receiver, method); +} +HttpRequest& HttpRequest::onFailed(std::function lambda) +{ + return onError(lambda); +} +HttpRequest& HttpRequest::onFailed(std::function lambda) +{ + return onError(lambda); +} +HttpRequest& HttpRequest::onFailed(std::function lambda) +{ + return onError(lambda); +} +HttpRequest& HttpRequest::onFailed(std::function lambda) +{ + return onError(lambda); +} + +HttpRequest& HttpRequest::onError(const QObject* receiver, const char* method) +{ + return onResponse(h_onError, receiver, method); +} +HttpRequest& HttpRequest::onError(std::function lambda) +{ + return onResponse(h_onError, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onError(std::function lambda) +{ + return onResponse(h_onError, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onError(std::function lambda) +{ + return onResponse(h_onError, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onError(std::function lambda) +{ + return onResponse(h_onError, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onReadyRead(const QObject* receiver, const char* method) +{ + return onResponse(h_onReadyRead, receiver, method); +} +HttpRequest& HttpRequest::onReadyRead(std::function lambda) +{ + return onResponse(h_onReadyRead, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onHead(const QObject* receiver, const char* method) +{ + return onResponse(h_onHead, receiver, method); +} +HttpRequest& HttpRequest::onHead(std::function)> lambda) +{ + return onResponse(h_onHead, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onHead(std::function)> lambda) +{ + return onResponse(h_onHead, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onDownloadProgress(const QObject* receiver, const char* method) +{ + return onResponse(h_onDownloadProgress, receiver, method); +} +HttpRequest& HttpRequest::onDownloadProgress(std::function lambda) +{ + return onResponse(h_onDownloadProgress, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onDownloadFileNameChanged(const QObject* receiver, const char* method) +{ + return onResponse(h_onDownloadFileNameChanged, receiver, method); +} +HttpRequest& HttpRequest::onDownloadFileNameChanged(std::function lambda) +{ + return onResponse(h_onDownloadFileNameChanged, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onDownloadFileSuccess(const QObject* receiver, const char* method) +{ + return onResponse(h_onDownloadFileSuccess, receiver, method); +} +HttpRequest& HttpRequest::onDownloadFileSuccess(std::function lambda) +{ + return onResponse(h_onDownloadFileSuccess, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onDownloadFileSuccess(std::function lambda) +{ + return onResponse(h_onDownloadFileSuccess, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onDownloadFileFailed(const QObject* receiver, const char* method) +{ + return onResponse(h_onDownloadFileFailed, receiver, method); +} +HttpRequest& HttpRequest::onDownloadFileFailed(std::function lambda) +{ + return onResponse(h_onDownloadFileFailed, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onDownloadFileFailed(std::function lambda) +{ + return onResponse(h_onDownloadFileFailed, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onUploadProgress(const QObject* receiver, const char* method) +{ + return onResponse(h_onUploadProgress, receiver, method); +} +HttpRequest& HttpRequest::onUploadProgress(std::function lambda) +{ + return onResponse(h_onUploadProgress, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onTimeout(const QObject* receiver, const char* method) +{ + return onResponse(h_onTimeout, receiver, method); +} +HttpRequest& HttpRequest::onTimeout(std::function lambda) +{ + return onResponse(h_onTimeout, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onTimeout(std::function lambda) +{ + return onResponse(h_onTimeout, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onRetried(const QObject* receiver, const char* method) +{ + return onResponse(h_onRetried, receiver, method); +} +HttpRequest& HttpRequest::onRetried(std::function lambda) +{ + return onResponse(h_onRetried, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onRepeated(const QObject* receiver, const char* method) +{ + return onResponse(h_onRepeated, receiver, method); +} +HttpRequest& HttpRequest::onRepeated(std::function lambda) +{ + return onResponse(h_onRepeated, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onAuthenticationRequired(const QObject* receiver, const char* method) +{ + return onResponse(h_onAuthenticationRequired, receiver, method); +} +HttpRequest& HttpRequest::onAuthenticationRequired(std::function lambda) +{ + return onResponse(h_onAuthenticationRequired, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onAuthenticationRequireFailed(const QObject* receiver, const char* method) +{ + return onResponse(h_onAuthenticationRequireFailed, receiver, method); +} +HttpRequest& HttpRequest::onAuthenticationRequireFailed(std::function lambda) +{ + return onResponse(h_onAuthenticationRequireFailed, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onAuthenticationRequireFailed(std::function lambda) +{ + return onResponse(h_onAuthenticationRequireFailed, QVariant::fromValue(lambda)); +} + +HttpRequest& HttpRequest::onResponse(const QObject* receiver, const char* method) +{ + return onResponse(h_onFinished, receiver, method); +} +HttpRequest& HttpRequest::onResponse(std::function lambda) +{ + return onResponse(h_onFinished, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onResponse(std::function lambda) +{ + return onResponse(h_onFinished, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onResponse(std::function lambda) +{ + return onResponse(h_onFinished, QVariant::fromValue(lambda)); +} +HttpRequest& HttpRequest::onResponse(HandleType type, QVariant lambda) +{ + return onResponse(type, lambda.typeName(), lambda); +} +HttpRequest& HttpRequest::onResponse(HandleType type, const QObject* receiver, const char* method) +{ + return onResponse(type, method, QVariant::fromValue((QObject*)receiver)); +} +HttpRequest& HttpRequest::onResponse(HandleType type, QString key, QVariant value) +{ + if (!m_handleMap.contains(type)) + { + QList> handleList; + m_handleMap.insert(type, handleList); + } + + auto handleList = m_handleMap[type]; + handleList.append({ key, value }); + + m_handleMap.insert(type, handleList); + return *this; +} + +QString HttpRequest::toString() +{ + QString str = + "General: \n" + " Request URL: %{url} \n" + " Request Method: %{method} \n" + "Request Headers: \n" + "%{requestHeaders} \n" + "Request Body: \n" + "%{requestBody}"; + + str.replace("%{url}", m_request.url().toString()); + str.replace("%{method}", networkOperation2String(m_op)); + str.replace("%{requestHeaders}", lineIndent(networkHeader2String(m_request), " ")); + str.replace("%{requestBody}", lineIndent(networkBody2String(m_body), " ")); + + return str; +} + +HttpResponse* HttpRequest::exec(const HttpRequest& _httpRequest, HttpResponse* httpResponse) +{ + HttpRequest httpRequest = _httpRequest; + + QByteArray op = networkOperation2String(httpRequest.m_op).toUtf8(); + if (op.isEmpty()) + { + QString str = + QString("Url: [%1]; Method: [%2] not support!").arg(httpRequest.m_request.url().toString()).arg(QString(op)); + printError(httpRequest.m_logLevel, str.toStdString().c_str()); + return nullptr; + } + + using BodyType = HttpRequest::BodyType; + BodyType bodyType = httpRequest.m_body.first; + QVariant body = httpRequest.m_body.second; + QNetworkRequest request = httpRequest.m_request; + HttpClient* httpClient = httpRequest.m_httpClient; + + if (bodyType == BodyType::MultiPart) + { + QHttpMultiPart* multiPart = body.value(); + QString contentType = QString("multipart/form-data;boundary=%1").arg(multiPart->boundary().data()); + + request.setHeader(QNetworkRequest::ContentTypeHeader, contentType); + + httpRequest.m_reply = httpRequest.m_httpClient->sendCustomRequest(request, op, multiPart); + multiPart->setParent(httpRequest.m_reply); + } + else if (bodyType == BodyType::FileMap) + { + QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QString contentType = QString("multipart/form-data;boundary=%1").arg(multiPart->boundary().data()); + request.setHeader(QNetworkRequest::ContentTypeHeader, contentType); + + const auto& fileMap = body.value>(); + for (const auto& each : fileMap.toStdMap()) + { + const QString& key = each.first; + const QString& filePath = each.second; + + QFile* file = new QFile(filePath); + file->open(QIODevice::ReadOnly); + file->setParent(multiPart); + + // todo + // part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + + // note: "form-data; name=\"%1\";filename=\"%2\"" != "form-data; name=\"%1\";filename=\"%2\";" + QString dispositionHeader = + QString("form-data; name=\"%1\";filename=\"%2\"").arg(key).arg(QFileInfo(filePath).fileName()); + QHttpPart part; + part.setHeader(QNetworkRequest::ContentDispositionHeader, dispositionHeader); + part.setBodyDevice(file); + + multiPart->append(part); + } + + httpRequest.m_reply = httpClient->sendCustomRequest(request, op, multiPart); + if (httpRequest.m_reply) + multiPart->setParent(httpRequest.m_reply); + else + delete multiPart; + } + else if (bodyType == BodyType::FormData) + { + QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QString contentType = QString("multipart/form-data;boundary=%1").arg(multiPart->boundary().data()); + request.setHeader(QNetworkRequest::ContentTypeHeader, contentType); + + const auto& formDataMap = body.value>(); + for (const auto& each : formDataMap.toStdMap()) + { + const QString& key = each.first; + const QString& value = each.second.toString(); + + QString dispositionHeader = QString("form-data; name=\"%1\"").arg(key); + + QHttpPart part; + part.setHeader(QNetworkRequest::ContentDispositionHeader, dispositionHeader); + part.setBody(value.toUtf8()); + + multiPart->append(part); + } + + httpRequest.m_reply = httpClient->sendCustomRequest(request, op, multiPart); + + if (httpRequest.m_reply) + multiPart->setParent(httpRequest.m_reply); + else + delete multiPart; + } + else + { + httpRequest.m_reply = httpClient->sendCustomRequest(request, op, body.toByteArray()); + } + + if (httpRequest.m_reply == nullptr) + { + // fixme: todo onError + printError(httpRequest.m_logLevel, "Http reply invalid"); + Q_ASSERT(httpRequest.m_reply); + return nullptr; + } + + // fixme + if (!httpRequest.m_ignoreSslErrors.isEmpty()) + { + httpRequest.m_reply->ignoreSslErrors(httpRequest.m_ignoreSslErrors); + } + + if (httpRequest.m_readBufferSize >= 0) + { + httpRequest.m_reply->setReadBufferSize(httpRequest.m_readBufferSize); + } + + printDebug(httpRequest.m_logLevel, toString().toStdString().c_str()); + + if (httpResponse) + { + httpResponse->setParent(httpRequest.m_reply); + httpResponse->setHttpRequest(httpRequest); + return httpResponse; + } + else + { + return new HttpResponse(httpRequest, httpRequest.m_reply); + } +} + +inline QDebug operator<<(QDebug debug, const QNetworkAccessManager::Operation& op) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + + switch (op) + { + case QNetworkAccessManager::HeadOperation: + return debug << "HeadOperation"; + case QNetworkAccessManager::GetOperation: + return debug << "GetOperation"; + case QNetworkAccessManager::PostOperation: + return debug << "PostOperation"; + case QNetworkAccessManager::PutOperation: + return debug << "PutOperation"; + case QNetworkAccessManager::DeleteOperation: + return debug << "DeleteOperation"; + case QNetworkAccessManager::CustomOperation: + return debug << "CustomOperation"; + default: + return debug << "UnknownOperation"; + } +} + +inline QDebug operator<<(QDebug debug, const HttpRequest::HandleType& handleType) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + + switch (handleType) + { + case HttpRequest::h_onFinished: + return debug << "onFinished"; + case HttpRequest::h_onError: + return debug << "onError"; + case HttpRequest::h_onDownloadProgress: + return debug << "onDownloadProgress"; + case HttpRequest::h_onUploadProgress: + return debug << "onUploadProgress"; + // todo: onUploadProgressSuccess and onUploadProgressFaied + case HttpRequest::h_onDownloadFileSuccess: + return debug << "onDownloadFileSuccess"; + case HttpRequest::h_onDownloadFileFailed: + return debug << "onDownloadFileFailed"; + case HttpRequest::h_onTimeout: + return debug << "onTimeout"; + case HttpRequest::h_onReadyRead: + return debug << "onReadyRead"; + case HttpRequest::h_onEncrypted: + return debug << "onEncrypted"; + case HttpRequest::h_onMetaDataChanged: + return debug << "onMetaChanged"; + case HttpRequest::h_onPreSharedKeyAuthenticationRequired: + return debug << "onPreSharedKeyAuthenticationRequired"; + case HttpRequest::h_onRedirectAllowed: + return debug << "onRedirectAllowed"; + case HttpRequest::h_onRedirected: + return debug << "onRedirected"; + case HttpRequest::h_onSslErrors: + return debug << "onSslErrors"; + case HttpRequest::h_onRetried: + return debug << "onRetried"; + case HttpRequest::h_onRepeated: + return debug << "onRepeated"; + case HttpRequest::h_onAuthenticationRequired: + return debug << "onAuthenticationRequired"; + case HttpRequest::h_onAuthenticationRequireFailed: + return debug << "onAuthenticationRequireFailed"; + case HttpRequest::h_onHead: + return debug << "onHead"; + case HttpRequest::h_onDownloadFileProgess: + return debug << "onDownloadFileProgress"; + case HttpRequest::h_onDownloadFileNameChanged: + return debug << "onDownloadFileNameChanged"; + default: + return debug << "Unknow"; + } +} + +static int extractCode(const char* member) +{ + /* extract code, ensure QMETHOD_CODE <= code <= QSIGNAL_CODE */ + return (((int)(*member) - '0') & 0x3); +} + +static bool isMethod(const char* member) +{ + int ret = extractCode(member); + return ret >= QMETHOD_CODE && ret <= QSIGNAL_CODE; +} + +template +bool httpResponseConnect(L sender, T senderSignal, const QString& lambdaString, const QVariant& lambda) +{ + if (lambdaString == QVariant::fromValue(M()).typeName()) + { + return QObject::connect(sender, senderSignal, lambda.value()); + } + else if (isMethod(qPrintable(lambdaString))) + { + QString signal = QMetaMethod::fromSignal(senderSignal).methodSignature(); + signal.insert(0, "2"); + signal.replace("qlonglong", "qint64"); + + const QObject* receiver = lambda.value(); + QString method = + QMetaObject::normalizedSignature(qPrintable(lambdaString)); // remove 'const', like: const QString => QString + + if (QMetaObject::checkConnectArgs(qPrintable(signal), qPrintable(method))) + { + return QObject::connect(sender, qPrintable(signal), receiver, qPrintable(method)); + } + else + { + return false; + } + } + else + { + return false; + } +} + +#define HTTP_RESPONSE_CONNECT_X(sender, senderSignal, lambdaString, lambda, ...) \ +httpResponseConnect>( \ + sender, static_cast(&HttpResponse::senderSignal), lambdaString, lambda); + +HttpResponse* HttpRequest::exec() +{ + if (this->m_downloader.isEnabled) + { + HttpDownloader* downloader = new HttpDownloader(*this, nullptr); + HttpResponse* response = downloader->exec(); + downloader->setParent(response); + return response; + } + else + { + return exec(*this); + } +} + +HttpRequest& HttpRequest::enabledRetry(bool isEnabled) +{ + m_enabledRetry = isEnabled; + return *this; +} + +HttpRequest& HttpRequest::queryParam(const QString& key, const QVariant& value) +{ + QUrl url(m_request.url()); + QUrlQuery urlQuery(url); + + urlQuery.addQueryItem(key, value.toString()); + url.setQuery(urlQuery); + + m_request.setUrl(url); + + return *this; +} + +HttpRequest& HttpRequest::queryParams(const QMap& params) +{ + QMapIterator iter(params); + while (iter.hasNext()) + { + iter.next(); + queryParam(iter.key(), iter.value()); + } + + return *this; +} + +HttpRequest& HttpRequest::userAttribute(const QVariant& value) +{ + m_request.setAttribute(QNetworkRequest::User, value); + return *this; +} + +HttpRequest& HttpRequest::attribute(QNetworkRequest::Attribute attribute, const QVariant& value) +{ + m_request.setAttribute(attribute, value); + return *this; +} + +HttpClient* HttpClient::instance() +{ + static HttpClient client; + return &client; +} + +HttpClient::HttpClient(QObject* parent) : QNetworkAccessManager(parent) +{ +} + +QString HttpClient::getVersion() const +{ + return "1.1.0"; +} + +HttpRequest HttpClient::head(const QString& url) +{ + return HttpRequest(QNetworkAccessManager::HeadOperation, this).headers(globalHeader).url(url).timeout(timeoutSecond); +} + +HttpRequest HttpClient::get(const QString& url) +{ + return HttpRequest(QNetworkAccessManager::GetOperation, this).headers(globalHeader).url(url).timeout(timeoutSecond); +} + +HttpRequest HttpClient::post(const QString& url) +{ + return HttpRequest(QNetworkAccessManager::PostOperation, this).headers(globalHeader).url(url).timeout(timeoutSecond); +} + +HttpRequest HttpClient::put(const QString& url) +{ + return HttpRequest(QNetworkAccessManager::PutOperation, this).headers(globalHeader).url(url).timeout(timeoutSecond); +} + +HttpRequest HttpClient::send(const QString& url, QNetworkAccessManager::Operation op) +{ + return HttpRequest(op, this).headers(globalHeader).url(url).timeout(timeoutSecond); +} + +#if (QT_VERSION < QT_VERSION_CHECK(5, 8, 0)) +QNetworkReply* HttpClient::sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, + const QByteArray& data) +{ + QBuffer* buffer = new QBuffer; + buffer->setData(data); + buffer->open(QIODevice::ReadOnly); + QNetworkReply* reply = QNetworkAccessManager::sendCustomRequest(request, verb, buffer); + buffer->setParent(reply); + + return reply; +} + +QNetworkReply* HttpClient::sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, + QHttpMultiPart* multiPart) +{ + if (verb == "PUT") + { + return QNetworkAccessManager::put(request, multiPart); + } + else if (verb == "POST") + { + return QNetworkAccessManager::post(request, multiPart); + } + else + { + qWarning() << "not support " << verb << "multi part."; + return nullptr; + } +} +#endif + +HttpResponse::HttpResponse(const HttpRequest& httpRequest, QObject* parent) + : QObject(parent), m_httpRequest(httpRequest), m_retriesRemaining(httpRequest.m_retryCount) +{ + this->setHttpRequest(httpRequest); +} + +HttpResponse::~HttpResponse() +{ +} + +void HttpResponse::setHttpRequest(const HttpRequest& httpRequest) +{ + if (httpRequest.m_timeoutMs > 0) + { + new HttpResponseTimeout(this, httpRequest.m_timeoutMs); + } + + QNetworkReply* reply = httpRequest.m_reply; + if (reply) + { + connect(reply, SIGNAL(finished()), this, SLOT(onFinished())); + connect(reply, SIGNAL(finished()), this, SLOT(onHandleHead())); + connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError))); + + connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(onDownloadProgress(qint64, qint64))); + connect(reply, SIGNAL(uploadProgress(qint64, qint64)), this, SLOT(onUploadProgress(qint64, qint64))); + + connect(reply, SIGNAL(readyRead()), this, SLOT(onReadOnceReplyHeader())); + connect(reply, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + connect(reply, SIGNAL(readyRead()), this, SLOT(onHandleHead())); + + connect(reply, SIGNAL(encrypted()), this, SLOT(onEncrypted())); + connect(reply, SIGNAL(metaDataChanged()), this, SLOT(onMetaDataChanged())); + + connect(reply, SIGNAL(redirected(QUrl)), this, SLOT(onRedirected(QUrl))); + connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(onSslErrors(QList))); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + connect(reply, SIGNAL(redirectAllowed()), this, SLOT(onRedirectAllowed())); +#endif + + connect(reply, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)), this, + SLOT(onPreSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*))); + + connect(reply->manager(), SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this, + SLOT(onAuthenticationRequired(QNetworkReply*, QAuthenticator*))); + + // fixme: Too cumbersome + for (auto each : httpRequest.m_handleMap.toStdMap()) + { + const HttpRequest::HandleType& key = each.first; + const QList>& value = each.second; + + for (auto iter : value) + { + const QVariant& lambda = iter.second; + const QString& lambdaString = iter.first; + int ret = 0; + + if (key == HttpRequest::h_onFinished) + { + ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, void); + ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, QByteArray); + ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, QVariantMap); + ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, QNetworkReply*); + } + else if (key == HttpRequest::h_onDownloadProgress) + { + ret += HTTP_RESPONSE_CONNECT_X(this, downloadProgress, lambdaString, lambda, qint64, qint64); + } + else if (key == HttpRequest::h_onUploadProgress) + { + ret += HTTP_RESPONSE_CONNECT_X(this, uploadProgress, lambdaString, lambda, qint64, qint64); + } + else if (key == HttpRequest::h_onError) + { + ret += HTTP_RESPONSE_CONNECT_X(this, error, lambdaString, lambda, void); + ret += HTTP_RESPONSE_CONNECT_X(this, error, lambdaString, lambda, QByteArray); + ret += HTTP_RESPONSE_CONNECT_X(this, error, lambdaString, lambda, QNetworkReply*); + ret += HTTP_RESPONSE_CONNECT_X(this, error, lambdaString, lambda, QNetworkReply::NetworkError); + } + else if (key == HttpRequest::h_onTimeout) + { + ret += HTTP_RESPONSE_CONNECT_X(this, timeout, lambdaString, lambda, QNetworkReply*); + ret += HTTP_RESPONSE_CONNECT_X(this, timeout, lambdaString, lambda, void); + } + else if (key == HttpRequest::h_onReadyRead) + { + ret += HTTP_RESPONSE_CONNECT_X(this, readyRead, lambdaString, lambda, QNetworkReply*); + } + else if (key == HttpRequest::h_onDownloadFileSuccess) + { + ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileFinished, lambdaString, lambda, void); + ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileFinished, lambdaString, lambda, QString); + } + else if (key == HttpRequest::h_onDownloadFileFailed) + { + ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileError, lambdaString, lambda, void); + ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileError, lambdaString, lambda, QString); + } + else if (key == HttpRequest::h_onEncrypted) + { + ret += HTTP_RESPONSE_CONNECT_X(this, encrypted, lambdaString, lambda, void); + } + else if (key == HttpRequest::h_onMetaDataChanged) + { + ret += HTTP_RESPONSE_CONNECT_X(this, metaDataChanged, lambdaString, lambda, void); + } + else if (key == HttpRequest::h_onPreSharedKeyAuthenticationRequired) + { + ret += HTTP_RESPONSE_CONNECT_X(this, preSharedKeyAuthenticationRequired, lambdaString, lambda, + QSslPreSharedKeyAuthenticator*); + } + else if (key == HttpRequest::h_onRedirectAllowed) + { + ret += HTTP_RESPONSE_CONNECT_X(this, redirectAllowed, lambdaString, lambda, void); + } + else if (key == HttpRequest::h_onRedirected) + { + ret += HTTP_RESPONSE_CONNECT_X(this, redirected, lambdaString, lambda, QUrl); + } + else if (key == HttpRequest::h_onSslErrors) + { + ret += HTTP_RESPONSE_CONNECT_X(this, sslErrors, lambdaString, lambda, QList); + } + else if (key == HttpRequest::h_onRetried) + { + ret += HTTP_RESPONSE_CONNECT_X(this, retried, lambdaString, lambda, void); + } + else if (key == HttpRequest::h_onRepeated) + { + ret += HTTP_RESPONSE_CONNECT_X(this, repeated, lambdaString, lambda, void); + } + else if (key == HttpRequest::h_onAuthenticationRequired) + { + ret += HTTP_RESPONSE_CONNECT_X(this, authenticationRequired, lambdaString, lambda, QAuthenticator*); + } + else if (key == HttpRequest::h_onAuthenticationRequireFailed) + { + ret += HTTP_RESPONSE_CONNECT_X(this, authenticationRequireFailed, lambdaString, lambda, void); + ret += HTTP_RESPONSE_CONNECT_X(this, authenticationRequireFailed, lambdaString, lambda, QNetworkReply*); + } + else if (key == HttpRequest::h_onHead) + { + ret += HTTP_RESPONSE_CONNECT_X(this, head, lambdaString, lambda, QList); + ret += HTTP_RESPONSE_CONNECT_X(this, head, lambdaString, lambda, QMap); + } + else if (key == HttpRequest::h_onDownloadFileProgess) + { + ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileProgress, lambdaString, lambda, qint64, qint64); + } + else if (key == HttpRequest::h_onDownloadFileNameChanged) + { + ret += HTTP_RESPONSE_CONNECT_X(this, downloadFileNameChanged, lambdaString, lambda, QString); + } + else + { + printWarn(httpRequest.m_logLevel, QString("%1 unsupported").arg(key).toStdString().c_str()); + } + + if (ret == 0) + { + QString method = lambdaString; + if (isMethod(qPrintable(method))) + method.remove(0, 1); + + printWarn(httpRequest.m_logLevel, + QString("%1 method[%2] is invalid").arg(key).arg(method).toStdString().c_str()); + } + } + } + } + + if (reply && httpRequest.m_isBlock) + { + new HttpBlocker(reply, httpRequest.m_isBlock); + } + + HttpRequest oldRequest = m_httpRequest; + m_httpRequest = httpRequest; + + if (oldRequest.m_reply != httpRequest.m_reply) + { + emit replyChanged(httpRequest.m_reply); + } +} + +QString HttpResponse::toString() const +{ + QString str = + "General: \n" + " Request URL: %{url} \n" + " Request Method: %{method} \n" + " Request Status: %{status}(%{statusString}) \n" + "Request Headers: \n" + "%{requestHeaders} \n" + "Response Headers: \n" + "%{responseHeaders} \n" + "Request Body: \n" + "%{requestBody}"; + + QNetworkReply* reply = this->m_httpRequest.m_reply; + str.replace("%{url}", this->m_httpRequest.m_request.url().toString()); + str.replace("%{method}", networkOperation2String(m_httpRequest.m_op)); + str.replace("%{status}", QString::number(reply->error())); + str.replace("%{statusString}", reply->errorString()); + str.replace("%{requestHeaders}", lineIndent(networkHeader2String(reply->request()), " ")); + str.replace("%{responseHeaders}", lineIndent(networkReplyHeader2String(reply), " ")); + str.replace("%{requestBody}", lineIndent(networkBody2String(m_httpRequest.m_body), " ")); + return str; +} + +void HttpResponse::onFinished() +{ + QNetworkReply* reply = m_httpRequest.m_reply; + if (reply->error() != QNetworkReply::NoError) + { + return; + } + + for (QObject* o : reply->children()) + { + HttpResponse* response = qobject_cast(o); + if (response) + { + printDebug(m_httpRequest.m_logLevel, response->toString().toStdString().c_str()); + } + } + + if (m_httpRequest.m_enabledRetry) + { + emit retried(); + } + + if (m_downloadFile.isOpen()) + { + emit downloadFileFinished(); + emit downloadFileFinished(m_downloadFile.fileName()); + + m_downloadFile.close(); + } + + bool isAutoDelete = true; + if (this->receivers(SIGNAL(finished(QNetworkReply*))) > 0) + { + emit finished(reply); + isAutoDelete = false; + } + + if (this->receivers(SIGNAL(finished())) > 0 || this->receivers(SIGNAL(finished(QByteArray))) > 0 || + this->receivers(SIGNAL(finished(QVariantMap))) > 0) + { + QByteArray result = reply->readAll(); + emit finished(); + + emit finished(result); + + QVariantMap resultMap = QJsonDocument::fromJson(result).object().toVariantMap(); + emit finished(resultMap); + } + + if (--m_httpRequest.m_repeatCount > 0) + { + HttpRequest httpRequest = m_httpRequest; + httpRequest.repeat(m_httpRequest.m_repeatCount).exec(); + } + else + { + emit repeated(); + } + + if (isAutoDelete) + { + reply->deleteLater(); + } +} + +void HttpResponse::onError(QNetworkReply::NetworkError error) +{ + QNetworkReply* reply = m_httpRequest.m_reply; + + printInfo(m_httpRequest.m_logLevel, + QString("%1 error: %2").arg(reply->url().toString()).arg(error).toStdString().c_str()); + + if (m_retriesRemaining-- > 0) + { + HttpRequest httpRequest = m_httpRequest; + httpRequest.retry(m_retriesRemaining).enabledRetry(true).exec(); + reply->deleteLater(); + return; + } + + if (m_httpRequest.m_enabledRetry) + { + emit retried(); + } + + const QMetaObject& metaObject = QNetworkReply::staticMetaObject; + QMetaEnum metaEnum = metaObject.enumerator(metaObject.indexOfEnumerator("NetworkError")); + QString errorString = reply->errorString().isEmpty() ? metaEnum.valueToKey(error) : reply->errorString(); + + if (m_httpRequest.m_downloader.isEnabled) + { + QString error = QString("Url: %1 file: %2 error: %3") + .arg(m_httpRequest.m_request.url().toString()) // fixme + .arg(m_downloadFile.fileName()) + .arg(errorString); + + emit downloadFileError(); + emit downloadFileError(error); + + m_downloadFile.close(); + } + + bool isAutoDelete = true; + if (this->receivers(SIGNAL(error(QNetworkReply*))) > 0) + { + emit this->error(reply); + isAutoDelete = false; + } + + emit this->error(); + emit this->error(error); + emit this->error(errorString.toLocal8Bit()); + + if (--m_httpRequest.m_repeatCount > 0) + { + HttpRequest httpRequest = m_httpRequest; + httpRequest.repeat(m_httpRequest.m_repeatCount).exec(); + } + else + { + emit repeated(); + } + + if (isAutoDelete) + { + reply->deleteLater(); + } +} + +void HttpResponse::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit this->downloadProgress(bytesReceived, bytesTotal); +} + +void HttpResponse::onUploadProgress(qint64 bytesSent, qint64 bytesTotal) +{ + emit this->uploadProgress(bytesSent, bytesTotal); +} + +void HttpResponse::onTimeout() +{ + QNetworkReply* reply = m_httpRequest.m_reply; + if (reply->isRunning()) + { + reply->abort(); + + bool isAutoDelete = true; + if (this->receivers(SIGNAL(timeout(QNetworkReply*))) > 0) + { + emit this->timeout(reply); + isAutoDelete = false; + } + + if (this->receivers(SIGNAL(timeout())) > 0) + { + emit this->timeout(); + } + + if (isAutoDelete) + { + reply->deleteLater(); + } + } +} + +void HttpResponse::onReadyRead() +{ + QNetworkReply* reply = m_httpRequest.m_reply; + if (m_httpRequest.m_downloader.isEnabled) + { + if (m_downloadFile.isOpen()) + { + int size = m_downloadFile.write(reply->readAll()); + if (size == -1) + { + QString error = QString("Url: %1 %2 Write failed!") + .arg(m_httpRequest.m_request.url().toString()) + .arg(m_downloadFile.fileName()); + emit downloadFileError(); + emit downloadFileError(error); + } + else + { + m_httpRequest.m_downloader.currentSize += size; + emit downloadFileProgress(m_httpRequest.m_downloader.currentSize, m_httpRequest.m_downloader.totalSize); + } + } + else + { + // do nothing + } + } + else + { + // do nothing + } + + emit readyRead(reply); +} + +void HttpResponse::onReadOnceReplyHeader() +{ + if (!m_httpRequest.m_downloader.isEnabled) + return; + + QNetworkReply* reply = m_httpRequest.m_reply; + disconnect(reply, SIGNAL(readyRead()), this, SLOT(onReadOnceReplyHeader())); + + QString fileName = m_httpRequest.m_downloader.fileName; + m_downloadFile.setFileName(fileName); + + QIODevice::OpenMode mode = QIODevice::WriteOnly; + if (m_httpRequest.m_downloader.isSupportBreakpointDownload && m_httpRequest.m_downloader.enabledBreakpointDownload && + QFile::exists(fileName)) + { + mode = QIODevice::Append; + } + + if (!m_downloadFile.open(mode)) + { + QString error = + QString("Url: %1 %2 Non-Writable").arg(m_httpRequest.m_request.url().toString()).arg(m_downloadFile.fileName()); + emit downloadFileError(); + emit downloadFileError(error); + } + else + { + // todo startDownload + } +} + +void HttpResponse::onEncrypted() +{ + emit encrypted(); +} + +void HttpResponse::onMetaDataChanged() +{ + emit metaDataChanged(); +} + +void HttpResponse::onPreSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator* authenticator) +{ + emit preSharedKeyAuthenticationRequired(authenticator); +} + +void HttpResponse::onRedirectAllowed() +{ + emit redirectAllowed(); +} + +void HttpResponse::onRedirected(const QUrl& url) +{ + emit redirected(url); +} + +void HttpResponse::onSslErrors(const QList& errors) +{ + emit sslErrors(errors); +} + +void HttpResponse::onAuthenticationRequired(QNetworkReply* reply, QAuthenticator* authenticator) +{ + if (this->reply() != reply) + { + return; + } + + m_authenticationCount++; + + bool isAuthenticationSuccessed = (m_authenticationCount >= 2); + if (isAuthenticationSuccessed) + { + emit authenticationRequireFailed(); + emit authenticationRequireFailed(this->reply()); + } + + if (m_httpRequest.m_authenticationRequiredCount >= 0 && + m_authenticationCount > m_httpRequest.m_authenticationRequiredCount) + { + return; + } + + if (m_httpRequest.m_authenticator.isNull()) + { + emit authenticationRequired(authenticator); + } + else + { + authenticator->setUser(m_httpRequest.m_authenticator.user()); + authenticator->setPassword(m_httpRequest.m_authenticator.password()); + // todo setOption.... + } +} + +void HttpResponse::onHandleHead() +{ + if (m_isHandleHead) + { + return; + } + + m_isHandleHead = true; + + QNetworkReply* reply = m_httpRequest.m_reply; + if (this->receivers(SIGNAL(head(QList))) || + this->receivers(SIGNAL(head(QMap)))) + { + emit head(reply->rawHeaderPairs()); + QMap map; + foreach (auto each, reply->rawHeaderPairs()) + { + map[each.first] = each.second; + } + + emit head(map); + } +} + +inline QString lineIndent(const QString& source, const QString& indentString) +{ + QRegularExpression rx("^(.*)"); + QRegularExpression::PatternOptions patternOptions; + patternOptions |= QRegularExpression::MultilineOption; + rx.setPatternOptions(patternOptions); + return QString(source).replace(rx, indentString + "\\1"); +} + +inline QString networkHeader2String(const QNetworkRequest& request) +{ + QString headerString; + for (const QByteArray& each : request.rawHeaderList()) + { + QByteArray value = request.rawHeader(each); + headerString += QString("%1: %2\n").arg(QString(each)).arg(QString(value)); + } + + if (headerString.isEmpty()) + { + return "null"; + } + + if (headerString.at(headerString.size() - 1) == '\n') + { + headerString.chop(1); + } + + return headerString; +} + +inline QString networkReplyHeader2String(const QNetworkReply* reply) +{ + QString headerString; + for (const QByteArray& each : reply->rawHeaderList()) + { + QByteArray value = reply->rawHeader(each); + headerString += QString("%1: %2\n").arg(QString(each)).arg(QString(value)); + } + + if (headerString.isEmpty()) + { + return "null"; + } + + if (headerString.at(headerString.size() - 1) == '\n') + { + headerString.chop(1); + } + + return headerString; +} + +inline QString networkBodyType2String(HttpRequest::BodyType t) +{ + if (t == HttpRequest::MultiPart) + { + return "MultiPart"; + } + else if (t == HttpRequest::FileMap) + { + return "FileMap"; + } + else if (t == HttpRequest::FormData) + { + return "FormData"; + } + else if (t == HttpRequest::Raw) + { + return "Raw"; + } + else if (t == HttpRequest::Raw_Json) + { + return "RawJson"; + } + else if (t == HttpRequest::X_Www_Form_Urlencoded) + { + return "x_www_form_urlencoded"; + } + else + { + return "None"; + } +} + +inline QString networkBody2String(const QPair& body) +{ + QString bodyTypeString; + bodyTypeString += "Type: " + networkBodyType2String(body.first) + "\n"; + bodyTypeString += "Data: \n"; + + QString bodyDataString; + if (body.first == HttpRequest::MultiPart) + { + QDebug d(&bodyDataString); + d << body.second; + } + else if (body.first == HttpRequest::FileMap) + { + const auto& fileMap = body.second.value>(); + for (const auto& each : fileMap.toStdMap()) + { + const QString& key = each.first; + const QString& filePath = each.second; + bodyDataString += key + ": " + filePath + "\n"; + } + } + else if (body.first == HttpRequest::FormData) + { + const auto& formDataMap = body.second.value>(); + for (const auto& each : formDataMap.toStdMap()) + { + const QString& key = each.first; + const QString& value = each.second.toString(); + bodyDataString += key + ": " + value + "\n"; + } + } + else if (body.first == HttpRequest::X_Www_Form_Urlencoded || body.first == HttpRequest::Raw || + body.first == HttpRequest::Raw_Json) + { + bodyDataString += body.second.toByteArray(); + } + + if (bodyDataString.isEmpty()) + { + bodyDataString = "null"; + } + + bodyDataString = lineIndent(bodyDataString, "=> "); + + QString bodyString = bodyTypeString + bodyDataString; + if (bodyString.at(bodyString.size() - 1) == '\n') + { + bodyString.chop(1); + } + + return bodyString; +} + +inline QString networkOperation2String(QNetworkAccessManager::Operation o) +{ + static QMap verbMap = { + { QNetworkAccessManager::HeadOperation, "HEAD" }, + { QNetworkAccessManager::GetOperation, "GET" }, + { QNetworkAccessManager::PostOperation, "POST" }, + { QNetworkAccessManager::PutOperation, "PUT" }, + }; + + return verbMap.value(o, ""); +} +} // namespace AeaQt + +#define HTTPRESPONSE_DECLARE_METATYPE(...) Q_DECLARE_METATYPE(std::function) + +HTTPRESPONSE_DECLARE_METATYPE(void) +HTTPRESPONSE_DECLARE_METATYPE(QByteArray) +HTTPRESPONSE_DECLARE_METATYPE(QString) +HTTPRESPONSE_DECLARE_METATYPE(QVariantMap) +HTTPRESPONSE_DECLARE_METATYPE(QNetworkReply*) +HTTPRESPONSE_DECLARE_METATYPE(qint64, qint64) +HTTPRESPONSE_DECLARE_METATYPE(QNetworkReply::NetworkError) +HTTPRESPONSE_DECLARE_METATYPE(QSslPreSharedKeyAuthenticator*) +HTTPRESPONSE_DECLARE_METATYPE(QUrl) +HTTPRESPONSE_DECLARE_METATYPE(QList) +HTTPRESPONSE_DECLARE_METATYPE(QAuthenticator*) +HTTPRESPONSE_DECLARE_METATYPE(QList) +HTTPRESPONSE_DECLARE_METATYPE(QMap) + +#endif // HTTPCLIENT_HPP