25#include <QNetworkRequest>
26#include <QNetworkReply>
28#include <QWaitCondition>
29#include <QNetworkCacheMetaData>
30#include <QAuthenticator>
43void QgsBlockingNetworkRequest::requestTimedOut( QNetworkReply *reply )
45 if (
reply == mReply )
61 return doRequest( Get, request, forceRefresh, feedback );
66 QByteArray ldata( data );
67 QBuffer buffer( &ldata );
68 buffer.open( QIODevice::ReadOnly );
69 return post( request, &buffer, forceRefresh, feedback );
75 return doRequest( Post, request, forceRefresh, feedback );
80 return doRequest( Head, request, forceRefresh, feedback );
85 QByteArray ldata( data );
86 QBuffer buffer( &ldata );
87 buffer.open( QIODevice::ReadOnly );
88 return put( request, &buffer, feedback );
94 return doRequest( Put, request,
true, feedback );
99 return doRequest( Delete, request,
true, feedback );
102void QgsBlockingNetworkRequest::sendRequestToNetworkAccessManager(
const QNetworkRequest &request )
131 mFeedback = feedback;
136 mGotNonEmptyResponse =
false;
138 mErrorMessage.clear();
140 mForceRefresh = forceRefresh;
141 mReplyContent.
clear();
146 mErrorMessage = errorMessageFailedAuth();
151 QgsDebugMsgLevel( QStringLiteral(
"Calling: %1" ).arg( request.url().toString() ), 2 );
153 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, forceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache );
154 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
156 QWaitCondition authRequestBufferNotEmpty;
157 QMutex waitConditionMutex;
159 bool threadFinished =
false;
160 bool success =
false;
162 const bool requestMadeFromMainThread = QThread::currentThread() == QApplication::instance()->thread();
167 const std::function<void()> downloaderFunction = [
this, request, &waitConditionMutex, &authRequestBufferNotEmpty, &threadFinished, &success, requestMadeFromMainThread ]()
177 sendRequestToNetworkAccessManager( request );
185 mErrorMessage = errorMessageFailedAuth();
187 if ( requestMadeFromMainThread )
188 authRequestBufferNotEmpty.wakeAll();
196 connect( mReply, &QNetworkReply::finished,
this, &QgsBlockingNetworkRequest::replyFinished, Qt::DirectConnection );
197 connect( mReply, &QNetworkReply::downloadProgress,
this, &QgsBlockingNetworkRequest::replyProgress, Qt::DirectConnection );
198 connect( mReply, &QNetworkReply::uploadProgress,
this, &QgsBlockingNetworkRequest::replyProgress, Qt::DirectConnection );
200 if ( request.hasRawHeader(
"Range" ) )
201 connect( mReply, &QNetworkReply::metaDataChanged,
this, &QgsBlockingNetworkRequest::abortIfNotPartialContentReturned, Qt::DirectConnection );
203 auto resumeMainThread = [&waitConditionMutex, &authRequestBufferNotEmpty ]()
207 waitConditionMutex.lock();
208 authRequestBufferNotEmpty.wakeAll();
209 waitConditionMutex.unlock();
214 if ( requestMadeFromMainThread )
227 connect( qApp, &QCoreApplication::aboutToQuit, &loop, &QEventLoop::quit, Qt::DirectConnection );
232 if ( requestMadeFromMainThread )
234 waitConditionMutex.lock();
235 threadFinished =
true;
236 authRequestBufferNotEmpty.wakeAll();
237 waitConditionMutex.unlock();
241 if ( requestMadeFromMainThread )
243 std::unique_ptr<DownloaderThread> downloaderThread = std::make_unique<DownloaderThread>( downloaderFunction );
244 downloaderThread->start();
248 waitConditionMutex.lock();
249 if ( threadFinished )
251 waitConditionMutex.unlock();
254 authRequestBufferNotEmpty.wait( &waitConditionMutex );
260 if ( !threadFinished )
262 waitConditionMutex.unlock();
264 QgsApplication::processEvents();
270 waitConditionMutex.unlock();
274 downloaderThread->wait();
278 downloaderFunction();
288 mReply->deleteLater();
293void QgsBlockingNetworkRequest::replyProgress( qint64 bytesReceived, qint64 bytesTotal )
295 QgsDebugMsgLevel( QStringLiteral(
"%1 of %2 bytes downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QStringLiteral(
"unknown number of" ) : QString::number( bytesTotal ) ), 2 );
297 if ( bytesReceived != 0 )
298 mGotNonEmptyResponse =
true;
300 if ( !mIsAborted && mReply && ( !mFeedback || !mFeedback->isCanceled() ) )
302 if ( mReply->error() == QNetworkReply::NoError )
304 const QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
313 if ( mMethod == Put || mMethod == Post )
319void QgsBlockingNetworkRequest::replyFinished()
321 if ( !mIsAborted && mReply )
324 if ( mReply->error() == QNetworkReply::NoError && ( !mFeedback || !mFeedback->isCanceled() ) )
327 const QVariant redirect = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
332 const QUrl &toUrl = redirect.toUrl();
334 if ( toUrl == mReply->url() )
336 mErrorMessage = tr(
"Redirect loop detected: %1" ).arg( toUrl.toString() );
338 mReplyContent.
clear();
342 QNetworkRequest request( toUrl );
346 mReplyContent.
clear();
347 mErrorMessage = errorMessageFailedAuth();
357 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, mForceRefresh ? QNetworkRequest::AlwaysNetwork : QNetworkRequest::PreferCache );
358 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute,
true );
361 if ( mReply->request().hasRawHeader(
"Range" ) )
362 request.setRawHeader(
"Range", mReply->request().rawHeader(
"Range" ) );
364 mReply->deleteLater();
367 QgsDebugMsgLevel( QStringLiteral(
"redirected: %1 forceRefresh=%2" ).arg( redirect.toString() ).arg( mForceRefresh ), 2 );
369 sendRequestToNetworkAccessManager( request );
376 mReplyContent.
clear();
377 mErrorMessage = errorMessageFailedAuth();
387 connect( mReply, &QNetworkReply::finished,
this, &QgsBlockingNetworkRequest::replyFinished, Qt::DirectConnection );
388 connect( mReply, &QNetworkReply::downloadProgress,
this, &QgsBlockingNetworkRequest::replyProgress, Qt::DirectConnection );
389 connect( mReply, &QNetworkReply::uploadProgress,
this, &QgsBlockingNetworkRequest::replyProgress, Qt::DirectConnection );
391 if ( request.hasRawHeader(
"Range" ) )
392 connect( mReply, &QNetworkReply::metaDataChanged,
this, &QgsBlockingNetworkRequest::abortIfNotPartialContentReturned, Qt::DirectConnection );
403 QNetworkCacheMetaData cmd = nam->cache()->metaData( mReply->request().url() );
405 QNetworkCacheMetaData::RawHeaderList hl;
406 const auto constRawHeaders = cmd.rawHeaders();
407 for (
const QNetworkCacheMetaData::RawHeader &h : constRawHeaders )
409 if ( h.first !=
"Cache-Control" )
412 cmd.setRawHeaders( hl );
414 QgsDebugMsgLevel( QStringLiteral(
"expirationDate:%1" ).arg( cmd.expirationDate().toString() ), 2 );
415 if ( cmd.expirationDate().isNull() )
417 cmd.setExpirationDate( QDateTime::currentDateTime().addSecs( mExpirationSec ) );
420 nam->cache()->updateMetaData( cmd );
428 const bool fromCache = mReply->attribute( QNetworkRequest::SourceIsFromCacheAttribute ).toBool();
429 QgsDebugMsgLevel( QStringLiteral(
"Reply was cached: %1" ).arg( fromCache ), 2 );
433 const QByteArray content = mReply->readAll();
434 if ( content.isEmpty() && !mGotNonEmptyResponse && mMethod == Get )
436 mErrorMessage = tr(
"empty response: %1" ).arg( mReply->errorString() );
445 if ( mReply->error() != QNetworkReply::OperationCanceledError )
447 mErrorMessage = mReply->errorString();
452 mReplyContent.
setContent( mReply->readAll() );
460 mReply->deleteLater();
470QString QgsBlockingNetworkRequest::errorMessageFailedAuth()
472 return tr(
"network request update failed for authentication config" );
475void QgsBlockingNetworkRequest::abortIfNotPartialContentReturned()
477 if ( mReply && mReply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() == 200 )
482 mErrorMessage = tr(
"The server does not support range requests" );
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
bool updateNetworkRequest(QNetworkRequest &request, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkRequest with an authentication config.
bool updateNetworkReply(QNetworkReply *reply, const QString &authcfg, const QString &dataprovider=QString())
Provider call to update a QNetworkReply with an authentication config (used to skip known SSL errors,...
void downloadProgress(qint64, qint64)
Emitted when when data arrives during a request.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
QgsBlockingNetworkRequest()
Constructor for QgsBlockingNetworkRequest.
ErrorCode put(QNetworkRequest &request, QIODevice *data, QgsFeedback *feedback=nullptr)
Performs a "put" operation on the specified request, using the given data.
~QgsBlockingNetworkRequest() override
ErrorCode head(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "head" operation on the specified request.
void abort()
Aborts the network request immediately.
Q_DECL_DEPRECATED void downloadFinished()
Emitted once a request has finished downloading.
ErrorCode post(QNetworkRequest &request, QIODevice *data, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "post" operation on the specified request, using the given data.
ErrorCode deleteResource(QNetworkRequest &request, QgsFeedback *feedback=nullptr)
Performs a "delete" operation on the specified request.
void finished()
Emitted once a request has finished.
void setAuthCfg(const QString &authCfg)
Sets the authentication config id which should be used during the request.
QString authCfg() const
Returns the authentication config id which will be used during the request.
void uploadProgress(qint64, qint64)
Emitted when when data are sent during a request.
@ NetworkError
A network error occurred.
@ ServerExceptionError
An exception was raised by the server.
@ NoError
No error was encountered.
@ TimeoutError
Timeout was reached before a reply was received.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Base class for feedback objects to be used for cancellation of something running in a worker thread.
void canceled()
Internal routines can connect to this signal if they use event loop.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
network access manager for QGIS
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
void requestTimedOut(QgsNetworkRequestParameters request)
Emitted when a network request has timed out.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
void setContent(const QByteArray &content)
Sets the reply content.
void clear()
Clears the reply, resetting it back to a default, empty reply.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
#define QgsDebugMsgLevel(str, level)