/********************************************************** static AeaQt::HttpClient client; client.get("https://qthub.com") .onSuccess([](QString result) { qDebug()<<"success!"; }) .onFailed([](QString error) { qDebug()<<"failed!"; }) .exec(); ========================================================== **********************************************************/ #ifndef QTHUB_COM_HTTPCLIENT_HPP #define QTHUB_COM_HTTPCLIENT_HPP #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() {return "1.1.2";} 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 del(const QString &url); inline HttpRequest send(const QString &url, Operation op = GetOperation); 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 ¶ms); /* 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); 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); 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(QString result); void finished(QVariantMap resultMap); void finished(QJsonObject resultJsonObject); void downloadProgress(qint64, qint64); void uploadProgress(qint64, qint64); void error(QByteArray error); void error(QString 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() { // todo: fixme HttpClient &client = *HttpClient::instance(); client.get(m_httpRequest.m_request.url().toString()) .timeout(30) .block(m_httpRequest.m_isBlock) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) .attribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy) // Qt 6 #else // .attribute(QNetworkRequest::FollowRedirectsAttribute, true) .attribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy) #endif .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(dispositionHeader); 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(QString("")); 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 public: HttpResponseTimeout(HttpResponse *parent, const int timeout = -1) : QObject(parent) { if (timeout > 0) { QTimer::singleShot(timeout, parent, SLOT(onTimeout())); } else { // do nothing } } }; class HttpBlocker : public QEventLoop { Q_OBJECT public: HttpBlocker(QNetworkReply *reply) : QEventLoop(nullptr) { if (reply) { connect(reply, SIGNAL(finished()), this, SLOT(quit())); this->exec(); } this->deleteLater(); } }; #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; } QString value; QMapIterator i(m_formUrlencodedMap); while (i.hasNext()) { i.next(); value += QString("%1=%2") .arg(QUrl::toPercentEncoding(i.key()).data()) .arg(QUrl::toPercentEncoding(i.value().toString()).data()); if (i.hasNext()) { value += "&"; } } 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) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) this->attribute(QNetworkRequest::RedirectPolicyAttribute, true); #else this->attribute(QNetworkRequest::FollowRedirectsAttribute, true); #endif 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::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::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); 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; } static int count = 0; httpRequest.m_reply->setProperty("count", count++); // 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()); 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< std::function > ( \ 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 ¶ms) { 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) { } HttpRequest HttpClient::head(const QString &url) { return HttpRequest(QNetworkAccessManager::HeadOperation, this).url(url); } HttpRequest HttpClient::get(const QString &url) { return HttpRequest(QNetworkAccessManager::GetOperation, this).url(url); } HttpRequest HttpClient::post(const QString &url) { return HttpRequest(QNetworkAccessManager::PostOperation, this).url(url); } HttpRequest HttpClient::put(const QString &url) { return HttpRequest(QNetworkAccessManager::PutOperation, this).url(url); } HttpRequest HttpClient::del(const QString &url) { return HttpRequest(QNetworkAccessManager::DeleteOperation, this).url(url); } HttpRequest HttpClient::send(const QString &url, QNetworkAccessManager::Operation op) { return HttpRequest(op, this).url(url); } #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())); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError))); #else connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onError(QNetworkReply::NetworkError))); #endif 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(onHandleHead())); connect(reply, SIGNAL(readyRead()), this, SLOT(onReadyRead())); 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, QString); ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, QVariantMap); ret += HTTP_RESPONSE_CONNECT_X(this, finished, lambdaString, lambda, QJsonObject); 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, QString); 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)); } 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)); } } } } const HttpRequest &oldRequest = m_httpRequest; m_httpRequest = httpRequest; if (oldRequest.m_reply != httpRequest.m_reply) { emit replyChanged(httpRequest.m_reply); } if (reply && httpRequest.m_isBlock) { new HttpBlocker(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()); } } 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(QString))) > 0 || this->receivers(SIGNAL(finished(QVariantMap))) > 0 || this->receivers(SIGNAL(finished(QJsonObject))) > 0 ) { QByteArray result = reply->readAll(); emit finished(); emit finished(result); emit finished(QString(result)); QJsonObject json = QJsonDocument::fromJson(result).object(); emit finished(json); QVariantMap resultMap = json.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)); 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); 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 && 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.count()-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.count()-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.count()-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"}, {QNetworkAccessManager::DeleteOperation, "DELETE"}, }; return verbMap.value(o, ""); } } #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(QJsonObject) 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 // QTHUB_COM_HTTPCLIENT_HPP