This commit is contained in:
朱子楚\zhuzi
2024-03-27 09:45:56 +08:00
parent e81a2cc849
commit c52439ac39
15 changed files with 396 additions and 481 deletions

View File

@ -1568,7 +1568,7 @@ My only desire is to be permitted to drive out the traitors and restore the Han.
<context>
<name>T_Network</name>
<message>
<location filename="qml/page/T_Network.qml" line="12"/>
<location filename="qml/page/T_Network.qml" line="13"/>
<source>Network</source>
<translation type="unfinished"></translation>
</message>

View File

@ -1608,7 +1608,7 @@ My only desire is to be permitted to drive out the traitors and restore the Han.
<context>
<name>T_Network</name>
<message>
<location filename="qml/page/T_Network.qml" line="12"/>
<location filename="qml/page/T_Network.qml" line="13"/>
<source>Network</source>
<translation type="unfinished">网络</translation>
</message>

View File

@ -25,8 +25,8 @@ FluLauncher {
}
}
Component.onCompleted: {
FluNetwork.openLog = false
FluNetwork.setInterceptor(function(param){
Network.openLog = false
Network.setInterceptor(function(param){
param.addHeader("Token","000000000000000000000")
})
FluApp.init(app,Qt.locale(TranslateHelper.current))

View File

@ -4,6 +4,7 @@ import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import FluentUI 1.0
import Qt.labs.platform 1.0
import example 1.0
import "../component"
FluContentPage{
@ -11,7 +12,7 @@ FluContentPage{
id:root
title: qsTr("Network")
FluNetworkCallable{
NetworkCallable{
id:callable
onStart: {
showLoading()
@ -56,7 +57,7 @@ FluContentPage{
text: "Get"
onClicked: {
text_info.text = ""
FluNetwork.get("https://httpbingo.org/get")
Network.get("https://httpbingo.org/get")
.addQuery("name","孙悟空")
.addQuery("age",500)
.addQuery("address","花果山水帘洞")
@ -70,7 +71,7 @@ FluContentPage{
text: "Head"
onClicked: {
text_info.text = ""
FluNetwork.head("https://httpbingo.org/head")
Network.head("https://httpbingo.org/head")
.addQuery("name","孙悟空")
.addQuery("age",500)
.addQuery("address","花果山水帘洞")
@ -84,7 +85,7 @@ FluContentPage{
text: "Post Body"
onClicked: {
text_info.text = ""
FluNetwork.postBody("https://httpbingo.org/post")
Network.postBody("https://httpbingo.org/post")
.setBody("花果山水帘洞美猴王齐天大圣孙悟空")
.bind(root)
.go(callable)
@ -96,7 +97,7 @@ FluContentPage{
text: "Post Form"
onClicked: {
text_info.text = ""
FluNetwork.postForm("https://httpbingo.org/post")
Network.postForm("https://httpbingo.org/post")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -110,7 +111,7 @@ FluContentPage{
text: "Post JSON"
onClicked: {
text_info.text = ""
FluNetwork.postJson("https://httpbingo.org/post")
Network.postJson("https://httpbingo.org/post")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -124,7 +125,7 @@ FluContentPage{
text: "Post JSON Array"
onClicked: {
text_info.text = ""
FluNetwork.postJsonArray("https://httpbingo.org/post")
Network.postJsonArray("https://httpbingo.org/post")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -138,7 +139,7 @@ FluContentPage{
text: "Put Body"
onClicked: {
text_info.text = ""
FluNetwork.putBody("https://httpbingo.org/put")
Network.putBody("https://httpbingo.org/put")
.setBody("花果山水帘洞美猴王齐天大圣孙悟空")
.bind(root)
.go(callable)
@ -150,7 +151,7 @@ FluContentPage{
text: "Put Form"
onClicked: {
text_info.text = ""
FluNetwork.putForm("https://httpbingo.org/put")
Network.putForm("https://httpbingo.org/put")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -164,7 +165,7 @@ FluContentPage{
text: "Put JSON"
onClicked: {
text_info.text = ""
FluNetwork.putJson("https://httpbingo.org/put")
Network.putJson("https://httpbingo.org/put")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -178,7 +179,7 @@ FluContentPage{
text: "Put JSON Array"
onClicked: {
text_info.text = ""
FluNetwork.putJsonArray("https://httpbingo.org/put")
Network.putJsonArray("https://httpbingo.org/put")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -192,7 +193,7 @@ FluContentPage{
text: "Patch Body"
onClicked: {
text_info.text = ""
FluNetwork.patchBody("https://httpbingo.org/patch")
Network.patchBody("https://httpbingo.org/patch")
.setBody("花果山水帘洞美猴王齐天大圣孙悟空")
.bind(root)
.go(callable)
@ -204,7 +205,7 @@ FluContentPage{
text: "Patch Form"
onClicked: {
text_info.text = ""
FluNetwork.patchForm("https://httpbingo.org/patch")
Network.patchForm("https://httpbingo.org/patch")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -218,7 +219,7 @@ FluContentPage{
text: "Patch JSON"
onClicked: {
text_info.text = ""
FluNetwork.patchJson("https://httpbingo.org/patch")
Network.patchJson("https://httpbingo.org/patch")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -232,7 +233,7 @@ FluContentPage{
text: "Patch JSON Array"
onClicked: {
text_info.text = ""
FluNetwork.patchJsonArray("https://httpbingo.org/patch")
Network.patchJsonArray("https://httpbingo.org/patch")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -246,7 +247,7 @@ FluContentPage{
text: "Delete Body"
onClicked: {
text_info.text = ""
FluNetwork.deleteBody("https://httpbingo.org/delete")
Network.deleteBody("https://httpbingo.org/delete")
.setBody("花果山水帘洞美猴王齐天大圣孙悟空")
.bind(root)
.go(callable)
@ -258,7 +259,7 @@ FluContentPage{
text: "Delete Form"
onClicked: {
text_info.text = ""
FluNetwork.deleteForm("https://httpbingo.org/delete")
Network.deleteForm("https://httpbingo.org/delete")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -272,7 +273,7 @@ FluContentPage{
text: "Delete JSON"
onClicked: {
text_info.text = ""
FluNetwork.deleteJson("https://httpbingo.org/delete")
Network.deleteJson("https://httpbingo.org/delete")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -286,7 +287,7 @@ FluContentPage{
text: "Delete JSON Array"
onClicked: {
text_info.text = ""
FluNetwork.deleteJsonArray("https://httpbingo.org/delete")
Network.deleteJsonArray("https://httpbingo.org/delete")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -300,7 +301,7 @@ FluContentPage{
text: "Open Log"
onClicked: {
text_info.text = ""
FluNetwork.postJson("https://httpbingo.org/post")
Network.postJson("https://httpbingo.org/post")
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -315,7 +316,7 @@ FluContentPage{
text: "Custom Header"
onClicked: {
text_info.text = ""
FluNetwork.postJson("https://httpbingo.org/post")
Network.postJson("https://httpbingo.org/post")
.addHeader("os","PC")
.addHeader("version","1.0.0")
.add("name","孙悟空")
@ -331,8 +332,8 @@ FluContentPage{
text: "RequestFailedReadCache"
onClicked: {
text_info.text = ""
FluNetwork.postJson("https://httpbingo.org/post")
.setCacheMode(FluNetworkType.RequestFailedReadCache)
Network.postJson("https://httpbingo.org/post")
.setCacheMode(NetworkType.RequestFailedReadCache)
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -347,8 +348,8 @@ FluContentPage{
text: "IfNoneCacheRequest"
onClicked: {
text_info.text = ""
FluNetwork.postJson("https://httpbingo.org/post")
.setCacheMode(FluNetworkType.IfNoneCacheRequest)
Network.postJson("https://httpbingo.org/post")
.setCacheMode(NetworkType.IfNoneCacheRequest)
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -363,8 +364,8 @@ FluContentPage{
text: "FirstCacheThenRequest"
onClicked: {
text_info.text = ""
FluNetwork.postJson("https://httpbingo.org/post")
.setCacheMode(FluNetworkType.FirstCacheThenRequest)
Network.postJson("https://httpbingo.org/post")
.setCacheMode(NetworkType.FirstCacheThenRequest)
.add("name","孙悟空")
.add("age",500)
.add("address","花果山水帘洞")
@ -379,7 +380,7 @@ FluContentPage{
text: "Timeout And Retry"
onClicked: {
text_info.text = ""
FluNetwork.postJson("https://httpbingo.org/post")
Network.postJson("https://httpbingo.org/post")
.setTimeout(5000)
.setRetry(3)
.add("name","孙悟空")
@ -407,7 +408,7 @@ FluContentPage{
text: "Download File"
onClicked: {
folder_dialog.showDialog(function(path){
FluNetwork.get("http://vjs.zencdn.net/v/oceans.mp4")
Network.get("http://vjs.zencdn.net/v/oceans.mp4")
.toDownload(path)
.bind(root)
.go(callable_download_file)
@ -421,7 +422,7 @@ FluContentPage{
text: "Breakpoint Download File"
onClicked: {
folder_dialog.showDialog(function(path){
FluNetwork.get("http://vjs.zencdn.net/v/oceans.mp4")
Network.get("http://vjs.zencdn.net/v/oceans.mp4")
.toDownload(path,true)
.bind(root)
.go(callable_breakpoint_download_file)
@ -431,7 +432,7 @@ FluContentPage{
}
}
FluNetworkCallable{
NetworkCallable{
id:callable_upload_file
onStart: {
btn_upload.disabled = true
@ -455,7 +456,7 @@ FluContentPage{
}
}
FluNetworkCallable{
NetworkCallable{
id:callable_download_file
onStart: {
btn_download.progress = 0
@ -480,7 +481,7 @@ FluContentPage{
}
}
FluNetworkCallable{
NetworkCallable{
id:callable_breakpoint_download_file
onStart: {
btn_download_breakpoint.progress = 0
@ -508,7 +509,7 @@ FluContentPage{
FileDialog {
id: file_dialog
onAccepted: {
FluNetwork.postForm("https://httpbingo.org/post")
Network.postForm("https://httpbingo.org/post")
.setRetry(1)//只请求一次
.add("accessToken","12345678")
.addFile("file",FluTools.toLocalPath(file_dialog.currentFile))

View File

@ -351,7 +351,7 @@ FluWindow {
}
}
FluNetworkCallable{
NetworkCallable{
id:callable
property bool silent: true
onStart: {
@ -387,7 +387,7 @@ FluWindow {
function checkUpdate(silent){
callable.silent = silent
FluNetwork.get("https://api.github.com/repos/zhuzichu520/FluentUI/releases/latest")
Network.get("https://api.github.com/repos/zhuzichu520/FluentUI/releases/latest")
.go(callable)
}
}

View File

@ -0,0 +1,676 @@
#include "Network.h"
#include <QUrlQuery>
#include <QBuffer>
#include <QHttpMultiPart>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QJsonObject>
#include <QNetworkDiskCache>
#include <QQmlEngine>
#include <QQmlContext>
#include <QJSEngine>
#include <QJsonArray>
#include <QStandardPaths>
#include <QThreadPool>
#include <QDir>
#include <QCryptographicHash>
#include <QEventLoop>
#include <QGuiApplication>
NetworkCallable::NetworkCallable(QObject *parent):QObject{parent}{
}
QString NetworkParams::method2String(){
switch (_method) {
case METHOD_GET:
return "GET";
case METHOD_HEAD:
return "HEAD";
case METHOD_POST:
return "POST";
case METHOD_PUT:
return "PUT";
case METHOD_PATCH:
return "PATCH";
case METHOD_DELETE:
return "DELETE";
default:
return "";
}
}
int NetworkParams::getTimeout(){
if(_timeout != -1){
return _timeout;
}
return Network::getInstance()->timeout();
}
int NetworkParams::getRetry(){
if(_retry != -1){
return _retry;
}
return Network::getInstance()->retry();
}
bool NetworkParams::getOpenLog(){
if(!_openLog.isNull()){
return _openLog.toBool();
}
return Network::getInstance()->openLog();
}
FluDownloadParam::FluDownloadParam(QObject *parent)
: QObject{parent}
{
}
FluDownloadParam::FluDownloadParam(QString destPath,bool append,QObject *parent)
: QObject{parent}
{
this->_destPath = destPath;
this->_append = append;
}
NetworkParams::NetworkParams(QObject *parent)
: QObject{parent}
{
}
NetworkParams::NetworkParams(QString url,Type type,Method method,QObject *parent)
: QObject{parent}
{
this->_method = method;
this->_url = url;
this->_type = type;
}
NetworkParams* NetworkParams::add(QString key,QVariant val){
_paramMap.insert(key,val);
return this;
}
NetworkParams* NetworkParams::addFile(QString key,QVariant val){
_fileMap.insert(key,val);
return this;
}
NetworkParams* NetworkParams::addHeader(QString key,QVariant val){
_headerMap.insert(key,val);
return this;
}
NetworkParams* NetworkParams::addQuery(QString key,QVariant val){
_queryMap.insert(key,val);
return this;
}
NetworkParams* NetworkParams::setBody(QString val){
_body = val;
return this;
}
NetworkParams* NetworkParams::setTimeout(int val){
_timeout = val;
return this;
}
NetworkParams* NetworkParams::setRetry(int val){
_retry = val;
return this;
}
NetworkParams* NetworkParams::setCacheMode(int val){
_cacheMode = val;
return this;
}
NetworkParams* NetworkParams::toDownload(QString destPath,bool append){
_downloadParam = new FluDownloadParam(destPath,append,this);
return this;
}
NetworkParams* NetworkParams::bind(QObject* target){
_target = target;
return this;
}
NetworkParams* NetworkParams::openLog(QVariant val){
_openLog = val;
return this;
}
QString NetworkParams::buildCacheKey(){
QJsonObject obj;
obj.insert("url",_url);
obj.insert("method",method2String());
obj.insert("body",_body);
obj.insert("query",QJsonDocument::fromVariant(_queryMap).object());
obj.insert("param",QJsonDocument::fromVariant(_paramMap).object());
obj.insert("header",QJsonDocument::fromVariant(_headerMap).object());
obj.insert("file",QJsonDocument::fromVariant(_fileMap).object());
if(_downloadParam){
QJsonObject downObj;
downObj.insert("destPath",_downloadParam->_destPath);
downObj.insert("append",_downloadParam->_append);
obj.insert("download",downObj);
}
QByteArray data = QJsonDocument(obj).toJson(QJsonDocument::Compact);
return QCryptographicHash::hash(data, QCryptographicHash::Sha256).toHex();
}
void NetworkParams::go(NetworkCallable* callable){
QJSValueList data;
data<<qjsEngine(callable)->newQObject(this);
Network::getInstance()->_interceptor.call(data);
if(_downloadParam){
Network::getInstance()->handleDownload(this,callable);
}else{
Network::getInstance()->handle(this,callable);
}
}
void Network::handle(NetworkParams* params,NetworkCallable* c){
QPointer<NetworkCallable> callable(c);
QThreadPool::globalInstance()->start([=](){
if(!callable.isNull()){
callable->start();
}
QString cacheKey = params->buildCacheKey();
if(params->_cacheMode == NetworkType::CacheMode::FirstCacheThenRequest && cacheExists(cacheKey)){
if(!callable.isNull()){
callable->cache(readCache(cacheKey));
}
}
if(params->_cacheMode == NetworkType::CacheMode::IfNoneCacheRequest && cacheExists(cacheKey)){
if(!callable.isNull()){
callable->cache(readCache(cacheKey));
callable->finish();
params->deleteLater();
}
return;
}
QNetworkAccessManager manager;
manager.setTransferTimeout(params->getTimeout());
QEventLoop loop;
connect(&manager,&QNetworkAccessManager::finished,&manager,[&loop](QNetworkReply *reply){loop.quit();});
for (int i = 0; i < params->getRetry(); ++i) {
QUrl url(params->_url);
addQueryParam(&url,params->_queryMap);
QNetworkRequest request(url);
addHeaders(&request,params->_headerMap);
QNetworkReply* reply;
sendRequest(&manager,request,params,reply,i==0,callable);
if(!QPointer<QGuiApplication>(qApp)){
reply->deleteLater();
reply = nullptr;
return;
}
auto abortCallable = [&loop,reply,&i,params]{
if(reply){
i = params->getRetry();
reply->abort();
}
};
QMetaObject::Connection conn_destroyed = {};
QMetaObject::Connection conn_quit = {};
if(params->_target){
conn_destroyed = connect(params->_target,&QObject::destroyed,&manager,abortCallable);
}
conn_quit = connect(qApp,&QGuiApplication::aboutToQuit,&manager, abortCallable);
loop.exec();
if(conn_destroyed){
disconnect(conn_destroyed);
}
if(conn_quit){
disconnect(conn_quit);
}
QString response;
if(params->_method == NetworkParams::METHOD_HEAD){
response = headerList2String(reply->rawHeaderPairs());
}else{
if(reply->isOpen()){
response = QString::fromUtf8(reply->readAll());
}
}
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(httpStatus == 200){
if(!callable.isNull()){
if(params->_cacheMode != NetworkType::CacheMode::NoCache){
saveResponse(cacheKey,response);
}
callable->success(response);
}
printRequestEndLog(request,params,reply,response);
break;
}else{
if(i == params->getRetry()-1){
if(!callable.isNull()){
if(params->_cacheMode == NetworkType::CacheMode::RequestFailedReadCache && cacheExists(cacheKey)){
if(!callable.isNull()){
callable->cache(readCache(cacheKey));
}
}
callable->error(httpStatus,reply->errorString(),response);
}
printRequestEndLog(request,params,reply,response);
}
}
reply->deleteLater();
}
params->deleteLater();
if(!callable.isNull()){
callable->finish();
}
});
}
void Network::handleDownload(NetworkParams* params,NetworkCallable* c){
QPointer<NetworkCallable> callable(c);
QThreadPool::globalInstance()->start([=](){
if(!callable.isNull()){
callable->start();
}
QString cacheKey = params->buildCacheKey();
QUrl url(params->_url);
QNetworkAccessManager manager;
manager.setTransferTimeout(params->getTimeout());
addQueryParam(&url,params->_queryMap);
QNetworkRequest request(url);
addHeaders(&request,params->_headerMap);
QString cachePath = getCacheFilePath(cacheKey);
QString destPath = params->_downloadParam->_destPath;
QFile* destFile = new QFile(destPath);
QFile* cacheFile = new QFile(cachePath);
bool isOpen = false;
qint64 seek = 0;
if(cacheFile->exists() && destFile->exists() && params->_downloadParam->_append){
QJsonObject cacheInfo = QJsonDocument::fromJson(readCache(cacheKey).toUtf8()).object();
qint64 fileSize = cacheInfo.value("fileSize").toDouble();
qint64 contentLength = cacheInfo.value("contentLength").toDouble();
if(fileSize == contentLength && destFile->size() == contentLength){
if(!callable.isNull()){
callable->downloadProgress(fileSize,contentLength);
callable->success(destPath);
callable->finish();
}
return;
}
if(fileSize==destFile->size()){
request.setRawHeader("Range", QString("bytes=%1-").arg(fileSize).toUtf8());
seek = fileSize;
isOpen = destFile->open(QIODevice::WriteOnly|QIODevice::Append);
}else{
isOpen = destFile->open(QIODevice::WriteOnly|QIODevice::Truncate);
}
}else{
isOpen = destFile->open(QIODevice::WriteOnly|QIODevice::Truncate);
}
if(!isOpen){
if(!callable.isNull()){
callable->error(-1,"device not open","");
callable->finish();
}
return;
}
if(params->_downloadParam->_append){
if (!cacheFile->open(QIODevice::WriteOnly|QIODevice::Truncate))
{
if(!callable.isNull()){
callable->error(-1,"cache file device not open","");
callable->finish();
}
return;
}
}
QEventLoop loop;
QNetworkReply *reply = manager.get(request);
destFile->setParent(reply);
cacheFile->setParent(reply);
auto abortCallable = [&loop,reply,params]{
if(reply){
reply->abort();
}
};
connect(&manager,&QNetworkAccessManager::finished,&manager,[&loop](QNetworkReply *reply){loop.quit();});
connect(qApp,&QGuiApplication::aboutToQuit,&manager, [&loop,reply](){reply->abort(),loop.quit();});
QMetaObject::Connection conn_destroyed = {};
QMetaObject::Connection conn_quit = {};
if(params->_target){
conn_destroyed = connect(params->_target,&QObject::destroyed,&manager,abortCallable);
}
conn_quit = connect(qApp,&QGuiApplication::aboutToQuit,&manager, abortCallable);
connect(reply,&QNetworkReply::readyRead,reply,[reply,seek,destFile,cacheFile,callable]{
if (!reply || !destFile || reply->error() != QNetworkReply::NoError)
{
return;
}
QMap<QString, QVariant> downInfo;
qint64 contentLength = reply->header(QNetworkRequest::ContentLengthHeader).toLongLong()+seek;
downInfo.insert("contentLength",contentLength);
QString eTag = reply->header(QNetworkRequest::ETagHeader).toString();
downInfo.insert("eTag",eTag);
destFile->write(reply->readAll());
destFile->flush();
downInfo.insert("fileSize",destFile->size());
if(cacheFile->isOpen()){
cacheFile->resize(0);
cacheFile->write(QJsonDocument::fromVariant(QVariant(downInfo)).toJson().toBase64());
cacheFile->flush();
}
if(!callable.isNull()){
callable->downloadProgress(destFile->size(),contentLength);
}
});
loop.exec();
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(httpStatus == 200){
if(!callable.isNull()){
callable->success(destPath);
}
printRequestEndLog(request,params,reply,destPath);
}else{
if(!callable.isNull()){
callable->error(httpStatus,reply->errorString(),destPath);
}
printRequestEndLog(request,params,reply,destPath);
}
if(conn_destroyed){
disconnect(conn_destroyed);
}
if(conn_quit){
disconnect(conn_quit);
}
params->deleteLater();
reply->deleteLater();
if(!callable.isNull()){
callable->finish();
}
});
}
QString Network::readCache(const QString& key){
auto filePath = getCacheFilePath(key);
QString result;
QFile file(filePath);
if(!file.exists()){
return result;
}
if (file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
result = QString(QByteArray::fromBase64(stream.readAll().toUtf8()));
}
return result;
}
bool Network::cacheExists(const QString& key){
return QFile(getCacheFilePath(key)).exists();
}
QString Network::getCacheFilePath(const QString& key){
QDir cacheDir(_cacheDir);
if(!cacheDir.exists()){
cacheDir.mkpath(_cacheDir);
}
return cacheDir.absoluteFilePath(key);
}
QString Network::headerList2String(const QList<QNetworkReply::RawHeaderPair>& data){
QJsonObject object;
for (auto it = data.constBegin(); it != data.constEnd(); ++it) {
object.insert(QString(it->first),QString(it->second));
}
return QJsonDocument(object).toJson(QJsonDocument::Compact);
}
QString Network::map2String(const QMap<QString, QVariant>& map){
QStringList parameters;
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
parameters << QString("%1=%2").arg(it.key(), it.value().toString());
}
return parameters.join(" ");
}
void Network::sendRequest(QNetworkAccessManager* manager,QNetworkRequest request,NetworkParams* params,QNetworkReply*& reply,bool isFirst,QPointer<NetworkCallable> callable){
QByteArray verb = params->method2String().toUtf8();
switch (params->_type) {
case NetworkParams::TYPE_FORM:{
bool isFormData = !params->_fileMap.isEmpty();
if(isFormData){
QHttpMultiPart *multiPart = new QHttpMultiPart();
multiPart->setContentType(QHttpMultiPart::FormDataType);
for (const auto& each : params->_paramMap.toStdMap())
{
QHttpPart part;
part.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"").arg(each.first));
part.setBody(each.second.toByteArray());
multiPart->append(part);
}
for (const auto& each : params->_fileMap.toStdMap())
{
QString filePath = each.second.toString();
QString name = each.first;
QFile *file = new QFile(filePath);
QString fileName = QFileInfo(filePath).fileName();
file->open(QIODevice::ReadOnly);
file->setParent(multiPart);
QHttpPart part;
part.setHeader(QNetworkRequest::ContentDispositionHeader, QString("form-data; name=\"%1\"; filename=\"%2\"").arg(name,fileName));
part.setBodyDevice(file);
multiPart->append(part);
}
reply = manager->sendCustomRequest(request,verb,multiPart);
multiPart->setParent(reply);
connect(reply,&QNetworkReply::uploadProgress,reply,[callable](qint64 bytesSent, qint64 bytesTotal){
if(!callable.isNull() && bytesSent!=0 && bytesTotal!=0){
Q_EMIT callable->uploadProgress(bytesSent,bytesTotal);
}
});
}else{
request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/x-www-form-urlencoded"));
QString value;
for (const auto& each : params->_paramMap.toStdMap())
{
value += QString("%1=%2").arg(each.first,each.second.toString());
value += "&";
}
if(!params->_paramMap.isEmpty()){
value.chop(1);
}
QByteArray data = value.toUtf8();
reply = manager->sendCustomRequest(request,verb,data);
}
break;
}
case NetworkParams::TYPE_JSON:{
request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json;charset=utf-8"));
QJsonObject json;
for (const auto& each : params->_paramMap.toStdMap())
{
json.insert(each.first,each.second.toJsonValue());
}
QByteArray data = QJsonDocument(json).toJson(QJsonDocument::Compact);
reply = manager->sendCustomRequest(request,verb,data);
break;
}
case NetworkParams::TYPE_JSONARRAY:{
request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json;charset=utf-8"));
QJsonArray jsonArray;
for (const auto& each : params->_paramMap.toStdMap())
{
QJsonObject json;
json.insert(each.first,each.second.toJsonValue());
jsonArray.append(json);
}
QByteArray data = QJsonDocument(jsonArray).toJson(QJsonDocument::Compact);
reply = manager->sendCustomRequest(request,params->method2String().toUtf8(),data);
break;
}
case NetworkParams::TYPE_BODY:{
request.setHeader(QNetworkRequest::ContentTypeHeader, QString("text/plain;charset=utf-8"));
QByteArray data = params->_body.toUtf8();
reply = manager->sendCustomRequest(request,verb,data);
break;
}
default:
reply = manager->sendCustomRequest(request,verb);
break;
}
if(isFirst){
printRequestStartLog(request,params);
}
}
void Network::printRequestStartLog(QNetworkRequest request,NetworkParams* params){
if(!params->getOpenLog()){
return;
}
qDebug()<<"<------"<<qUtf8Printable(request.header(QNetworkRequest::UserAgentHeader).toString())<<"Request Start ------>";
qDebug()<<qUtf8Printable(QString::fromStdString("<%1>").arg(params->method2String()))<<qUtf8Printable(params->_url);
auto contentType = request.header(QNetworkRequest::ContentTypeHeader).toString();
if(!contentType.isEmpty()){
qDebug()<<qUtf8Printable(QString::fromStdString("<Header> %1=%2").arg("Content-Type",contentType));
}
QList<QByteArray> headers = request.rawHeaderList();
for(const QByteArray& header:headers){
qDebug()<<qUtf8Printable(QString::fromStdString("<Header> %1=%2").arg(header,request.rawHeader(header)));
}
if(!params->_queryMap.isEmpty()){
qDebug()<<"<Query>"<<qUtf8Printable(map2String(params->_queryMap));
}
if(!params->_paramMap.isEmpty()){
qDebug()<<"<Param>"<<qUtf8Printable(map2String(params->_paramMap));
}
if(!params->_fileMap.isEmpty()){
qDebug()<<"<File>"<<qUtf8Printable(map2String(params->_fileMap));
}
if(!params->_body.isEmpty()){
qDebug()<<"<Body>"<<qUtf8Printable(params->_body);
}
}
void Network::printRequestEndLog(QNetworkRequest request,NetworkParams* params,QNetworkReply*& reply,const QString& response){
if(!params->getOpenLog()){
return;
}
qDebug()<<"<------"<<qUtf8Printable(request.header(QNetworkRequest::UserAgentHeader).toString())<<"Request End ------>";
qDebug()<<qUtf8Printable(QString::fromStdString("<%1>").arg(params->method2String()))<<qUtf8Printable(params->_url);
qDebug()<<"<Result>"<<qUtf8Printable(response);
}
void Network::saveResponse(QString key,QString response){
QSharedPointer<QFile> file(new QFile(getCacheFilePath(key)));
QIODevice::OpenMode mode = QIODevice::WriteOnly|QIODevice::Truncate;
if (!file->open(mode))
{
return;
}
file->write(response.toUtf8().toBase64());
}
void Network::addHeaders(QNetworkRequest* request,const QMap<QString, QVariant>& headers){
request->setHeader(QNetworkRequest::UserAgentHeader,QString::fromStdString("Mozilla/5.0 %1/%2").arg(QGuiApplication::applicationName(),QGuiApplication::applicationVersion()));
QMapIterator<QString, QVariant> iter(headers);
while (iter.hasNext())
{
iter.next();
request->setRawHeader(iter.key().toUtf8(), iter.value().toString().toUtf8());
}
}
void Network::addQueryParam(QUrl* url,const QMap<QString, QVariant>& params){
QMapIterator<QString, QVariant> iter(params);
QUrlQuery urlQuery(*url);
while (iter.hasNext())
{
iter.next();
urlQuery.addQueryItem(iter.key(), iter.value().toString());
}
url->setQuery(urlQuery);
}
Network::Network(QObject *parent): QObject{parent}
{
timeout(5000);
retry(3);
openLog(false);
cacheDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation).append(QDir::separator()).append("network"));
}
NetworkParams* Network::get(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_NONE,NetworkParams::METHOD_GET,this);
}
NetworkParams* Network::head(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_NONE,NetworkParams::METHOD_HEAD,this);
}
NetworkParams* Network::postBody(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_BODY,NetworkParams::METHOD_POST,this);
}
NetworkParams* Network::putBody(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_BODY,NetworkParams::METHOD_PUT,this);
}
NetworkParams* Network::patchBody(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_BODY,NetworkParams::METHOD_PATCH,this);
}
NetworkParams* Network::deleteBody(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_BODY,NetworkParams::METHOD_DELETE,this);
}
NetworkParams* Network::postForm(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_FORM,NetworkParams::METHOD_POST,this);
}
NetworkParams* Network::putForm(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_FORM,NetworkParams::METHOD_PUT,this);
}
NetworkParams* Network::patchForm(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_FORM,NetworkParams::METHOD_PATCH,this);
}
NetworkParams* Network::deleteForm(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_FORM,NetworkParams::METHOD_DELETE,this);
}
NetworkParams* Network::postJson(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_JSON,NetworkParams::METHOD_POST,this);
}
NetworkParams* Network::putJson(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_JSON,NetworkParams::METHOD_PUT,this);
}
NetworkParams* Network::patchJson(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_JSON,NetworkParams::METHOD_PATCH,this);
}
NetworkParams* Network::deleteJson(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_JSON,NetworkParams::METHOD_DELETE,this);
}
NetworkParams* Network::postJsonArray(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_JSONARRAY,NetworkParams::METHOD_POST,this);
}
NetworkParams* Network::putJsonArray(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_JSONARRAY,NetworkParams::METHOD_PUT,this);
}
NetworkParams* Network::patchJsonArray(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_JSONARRAY,NetworkParams::METHOD_PATCH,this);
}
NetworkParams* Network::deleteJsonArray(const QString& url){
return new NetworkParams(url,NetworkParams::TYPE_JSONARRAY,NetworkParams::METHOD_DELETE,this);
}
void Network::setInterceptor(QJSValue interceptor){
this->_interceptor = interceptor;
}

View File

@ -0,0 +1,169 @@
#ifndef NETWORK_H
#define NETWORK_H
#include <QObject>
#include <QtQml/qqml.h>
#include <QFile>
#include <QJsonValue>
#include <QJSValue>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include "src/stdafx.h"
#include "src/singleton.h"
namespace NetworkType {
Q_NAMESPACE
enum CacheMode {
NoCache = 0x0000,
RequestFailedReadCache = 0x0001,
IfNoneCacheRequest = 0x0002,
FirstCacheThenRequest = 0x0004,
};
Q_ENUM_NS(CacheMode)
QML_NAMED_ELEMENT(NetworkType)
}
/**
* @brief The NetworkCallable class
*/
class NetworkCallable : public QObject{
Q_OBJECT
QML_NAMED_ELEMENT(NetworkCallable)
public:
explicit NetworkCallable(QObject *parent = nullptr);
Q_SIGNAL void start();
Q_SIGNAL void finish();
Q_SIGNAL void error(int status,QString errorString,QString result);
Q_SIGNAL void success(QString result);
Q_SIGNAL void cache(QString result);
Q_SIGNAL void uploadProgress(qint64 sent, qint64 total);
Q_SIGNAL void downloadProgress(qint64 recv, qint64 total);
};
/**
* @brief The FluDownloadParam class
*/
class FluDownloadParam : public QObject{
Q_OBJECT
public:
explicit FluDownloadParam(QObject *parent = nullptr);
FluDownloadParam(QString destPath,bool append,QObject *parent = nullptr);
public:
QString _destPath;
bool _append;
};
/**
* @brief The NetworkParams class
*/
class NetworkParams : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(NetworkParams)
public:
enum Method{
METHOD_GET,
METHOD_HEAD,
METHOD_POST,
METHOD_PUT,
METHOD_PATCH,
METHOD_DELETE
};
enum Type{
TYPE_NONE,
TYPE_FORM,
TYPE_JSON,
TYPE_JSONARRAY,
TYPE_BODY
};
explicit NetworkParams(QObject *parent = nullptr);
NetworkParams(QString url,Type type,Method method,QObject *parent = nullptr);
Q_INVOKABLE NetworkParams* addQuery(QString key,QVariant val);
Q_INVOKABLE NetworkParams* addHeader(QString key,QVariant val);
Q_INVOKABLE NetworkParams* add(QString key,QVariant val);
Q_INVOKABLE NetworkParams* addFile(QString key,QVariant val);
Q_INVOKABLE NetworkParams* setBody(QString val);
Q_INVOKABLE NetworkParams* setTimeout(int val);
Q_INVOKABLE NetworkParams* setRetry(int val);
Q_INVOKABLE NetworkParams* setCacheMode(int val);
Q_INVOKABLE NetworkParams* toDownload(QString destPath,bool append = false);
Q_INVOKABLE NetworkParams* bind(QObject* target);
Q_INVOKABLE NetworkParams* openLog(QVariant val);
Q_INVOKABLE void go(NetworkCallable* result);
QString buildCacheKey();
QString method2String();
int getTimeout();
int getRetry();
bool getOpenLog();
public:
FluDownloadParam* _downloadParam = nullptr;
QObject* _target = nullptr;
Method _method;
Type _type;
QString _url;
QString _body;
QMap<QString, QVariant> _queryMap;
QMap<QString, QVariant> _headerMap;
QMap<QString, QVariant> _paramMap;
QMap<QString, QVariant> _fileMap;
int _timeout = -1;
int _retry = -1;
QVariant _openLog;
int _cacheMode = NetworkType::CacheMode::NoCache;
};
/**
* @brief The Network class
*/
class Network : public QObject
{
Q_OBJECT
Q_PROPERTY_AUTO(int,timeout)
Q_PROPERTY_AUTO(int,retry)
Q_PROPERTY_AUTO(QString,cacheDir)
Q_PROPERTY_AUTO(bool,openLog)
QML_NAMED_ELEMENT(Network)
QML_SINGLETON
private:
explicit Network(QObject *parent = nullptr);
public:
SINGLETON(Network)
static Network *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine){return getInstance();}
Q_INVOKABLE NetworkParams* get(const QString& url);
Q_INVOKABLE NetworkParams* head(const QString& url);
Q_INVOKABLE NetworkParams* postBody(const QString& url);
Q_INVOKABLE NetworkParams* putBody(const QString& url);
Q_INVOKABLE NetworkParams* patchBody(const QString& url);
Q_INVOKABLE NetworkParams* deleteBody(const QString& url);
Q_INVOKABLE NetworkParams* postForm(const QString& url);
Q_INVOKABLE NetworkParams* putForm(const QString& url);
Q_INVOKABLE NetworkParams* patchForm(const QString& url);
Q_INVOKABLE NetworkParams* deleteForm(const QString& url);
Q_INVOKABLE NetworkParams* postJson(const QString& url);
Q_INVOKABLE NetworkParams* putJson(const QString& url);
Q_INVOKABLE NetworkParams* patchJson(const QString& url);
Q_INVOKABLE NetworkParams* deleteJson(const QString& url);
Q_INVOKABLE NetworkParams* postJsonArray(const QString& url);
Q_INVOKABLE NetworkParams* putJsonArray(const QString& url);
Q_INVOKABLE NetworkParams* patchJsonArray(const QString& url);
Q_INVOKABLE NetworkParams* deleteJsonArray(const QString& url);
Q_INVOKABLE void setInterceptor(QJSValue interceptor);
void handle(NetworkParams* params,NetworkCallable* result);
void handleDownload(NetworkParams* params,NetworkCallable* result);
private:
void sendRequest(QNetworkAccessManager* manager,QNetworkRequest request,NetworkParams* params,QNetworkReply*& reply,bool isFirst,QPointer<NetworkCallable> callable);
void addQueryParam(QUrl* url,const QMap<QString, QVariant>& params);
void addHeaders(QNetworkRequest* request,const QMap<QString, QVariant>& headers);
void saveResponse(QString key,QString response);
QString readCache(const QString& key);
bool cacheExists(const QString& key);
QString getCacheFilePath(const QString& key);
QString map2String(const QMap<QString, QVariant>& map);
QString headerList2String(const QList<QNetworkReply::RawHeaderPair>& data);
void printRequestStartLog(QNetworkRequest request,NetworkParams* params);
void printRequestEndLog(QNetworkRequest request,NetworkParams* params,QNetworkReply*& reply,const QString& response);
public:
QJSValue _interceptor;
};
#endif // Network_H

View File

@ -17,6 +17,7 @@
#include "src/helper/SettingsHelper.h"
#include "src/helper/InitalizrHelper.h"
#include "src/helper/TranslateHelper.h"
#include "src/helper/Network.h"
#ifdef FLUENTUI_BUILD_STATIC_LIB
#if (QT_VERSION > QT_VERSION_CHECK(6, 2, 0))
@ -65,18 +66,25 @@ int main(int argc, char *argv[])
#endif
#endif
QGuiApplication app(argc, argv);
const char *uri = "example";
int major = 1;
int minor = 0;
//@uri example
qmlRegisterType<CircularReveal>(uri, major, minor, "CircularReveal");
qmlRegisterType<FileWatcher>(uri, major, minor, "FileWatcher");
qmlRegisterType<FpsItem>(uri, major, minor, "FpsItem");
qmlRegisterType<NetworkCallable>(uri,major,minor,"NetworkCallable");
qmlRegisterType<NetworkParams>(uri,major,minor,"NetworkParams");
QQmlApplicationEngine engine;
TranslateHelper::getInstance()->init(&engine);
engine.rootContext()->setContextProperty("AppInfo",AppInfo::getInstance());
engine.rootContext()->setContextProperty("SettingsHelper",SettingsHelper::getInstance());
engine.rootContext()->setContextProperty("InitalizrHelper",InitalizrHelper::getInstance());
engine.rootContext()->setContextProperty("TranslateHelper",TranslateHelper::getInstance());
engine.rootContext()->setContextProperty("Network",Network::getInstance());
#ifdef FLUENTUI_BUILD_STATIC_LIB
FluentUI::getInstance()->registerTypes(&engine);
#endif
qmlRegisterType<CircularReveal>("example", 1, 0, "CircularReveal");
qmlRegisterType<FileWatcher>("example", 1, 0, "FileWatcher");
qmlRegisterType<FpsItem>("example", 1, 0, "FpsItem");
const QUrl url(QStringLiteral("qrc:/example/qml/App.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {