QGIS API Documentation  2.17.0-Master (0497e4a)
qgshttptransaction.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgshttptransaction.cpp - Tracks a HTTP request with its response,
3  with particular attention to tracking
4  HTTP redirect responses
5  -------------------
6  begin : 17 Mar, 2005
7  copyright : (C) 2005 by Brendan Morley
8  email : morb at ozemail dot com dot au
9  ***************************************************************************/
10 
11 /***************************************************************************
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * *
18  ***************************************************************************/
19 
20 
21 #include <fstream>
22 
23 #include "qgshttptransaction.h"
24 #include "qgslogger.h"
25 #include "qgsconfig.h"
26 
27 #include <QApplication>
28 #include <QUrl>
29 #include <QSettings>
30 #include <QTimer>
31 
32 static int HTTP_PORT_DEFAULT = 80;
33 
34 //XXX Set the connection name when creating the provider instance
35 //XXX in qgswmsprovider. When creating a QgsHttpTransaction, pass
36 //XXX the user/pass combination to the constructor. Then set the
37 //XXX username and password using QHttp::setUser.
39  const QString& proxyHost,
40  int proxyPort,
41  const QString& proxyUser,
42  const QString& proxyPass,
43  QNetworkProxy::ProxyType proxyType,
44  const QString& userName,
45  const QString& password )
46  : http( nullptr )
47  , httpid( 0 )
48  , httpactive( false )
49  , httpurl( uri )
50  , httphost( proxyHost )
51  , httpredirections( 0 )
52  , mWatchdogTimer( nullptr )
53 {
54  Q_UNUSED( proxyPort );
55  Q_UNUSED( proxyUser );
56  Q_UNUSED( proxyPass );
57  Q_UNUSED( proxyType );
58  Q_UNUSED( userName );
59  Q_UNUSED( password );
60  QSettings s;
61  mNetworkTimeoutMsec = s.value( "/qgis/networkAndProxy/networkTimeout", "60000" ).toInt();
62 }
63 
65  : http( nullptr )
66  , httpid( 0 )
67  , httpactive( false )
68  , httpredirections( 0 )
69  , mWatchdogTimer( nullptr )
70 {
71  QSettings s;
72  mNetworkTimeoutMsec = s.value( "/qgis/networkAndProxy/networkTimeout", "60000" ).toInt();
73 }
74 
76 {
77  QgsDebugMsg( "deconstructing." );
78 }
79 
80 
81 void QgsHttpTransaction::setCredentials( const QString& username, const QString& password )
82 {
83  mUserName = username;
84  mPassword = password;
85 }
87 {
88 
89  //TODO
90 
91 }
92 
93 bool QgsHttpTransaction::getSynchronously( QByteArray &respondedContent, int redirections, const QByteArray* postData )
94 {
95 
96  httpredirections = redirections;
97 
98  QgsDebugMsg( "Entered." );
99  QgsDebugMsg( "Using '" + httpurl + "'." );
100  QgsDebugMsg( "Creds: " + mUserName + '/' + mPassword );
101 
102  int httpport;
103 
104  QUrl qurl( httpurl );
105 
106  http = new QHttp();
107  // Create a header so we can set the user agent (Per WMS RFC).
108  QHttpRequestHeader header( "GET", qurl.host() );
109  // Set host in the header
110  if ( qurl.port( HTTP_PORT_DEFAULT ) == HTTP_PORT_DEFAULT )
111  {
112  header.setValue( "Host", qurl.host() );
113  }
114  else
115  {
116  header.setValue( "Host", QString( "%1:%2" ).arg( qurl.host() ).arg( qurl.port() ) );
117  }
118  // Set the user agent to QGIS plus the version name
119  header.setValue( "User-agent", QString( "QGIS - " ) + VERSION );
120  // Set the host in the QHttp object
121  http->setHost( qurl.host(), qurl.port( HTTP_PORT_DEFAULT ) );
122  // Set the username and password if supplied for this connection
123  // If we have username and password set in header
124  if ( !mUserName.isEmpty() && !mPassword.isEmpty() )
125  {
126  http->setUser( mUserName, mPassword );
127  }
128 
129  if ( !QgsHttpTransaction::applyProxySettings( *http, httpurl ) )
130  {
131  httphost = qurl.host();
132  httpport = qurl.port( HTTP_PORT_DEFAULT );
133  }
134  else
135  {
136  //proxy enabled, read httphost and httpport from settings
137  QSettings settings;
138  httphost = settings.value( "proxy/proxyHost", "" ).toString();
139  httpport = settings.value( "proxy/proxyPort", "" ).toString().toInt();
140  }
141 
142 // int httpid1 = http->setHost( qurl.host(), qurl.port() );
143 
144  mWatchdogTimer = new QTimer( this );
145 
146  QgsDebugMsg( "qurl.host() is '" + qurl.host() + "'." );
147 
148  httpresponse.truncate( 0 );
149 
150  // Some WMS servers don't like receiving a http request that
151  // includes the scheme, host and port (the
152  // http://www.address.bit:80), so remove that from the url before
153  // executing an http GET.
154 
155  //Path could be just '/' so we remove the 'http://' first
156  QString pathAndQuery = httpurl.remove( 0, httpurl.indexOf( qurl.host() ) );
157  pathAndQuery = httpurl.remove( 0, pathAndQuery.indexOf( qurl.path() ) );
158  if ( !postData ) //do request with HTTP GET
159  {
160  header.setRequest( "GET", pathAndQuery );
161  // do GET using header containing user-agent
162  httpid = http->request( header );
163  }
164  else //do request with HTTP POST
165  {
166  header.setRequest( "POST", pathAndQuery );
167  // do POST using header containing user-agent
168  httpid = http->request( header, *postData );
169  }
170 
171  connect( http, SIGNAL( requestStarted( int ) ),
172  this, SLOT( dataStarted( int ) ) );
173 
174  connect( http, SIGNAL( responseHeaderReceived( const QHttpResponseHeader& ) ),
175  this, SLOT( dataHeaderReceived( const QHttpResponseHeader& ) ) );
176 
177  connect( http, SIGNAL( readyRead( const QHttpResponseHeader& ) ),
178  this, SLOT( dataReceived( const QHttpResponseHeader& ) ) );
179 
180  connect( http, SIGNAL( dataReadProgress( int, int ) ),
181  this, SLOT( dataProgress( int, int ) ) );
182 
183  connect( http, SIGNAL( requestFinished( int, bool ) ),
184  this, SLOT( dataFinished( int, bool ) ) );
185 
186  connect( http, SIGNAL( done( bool ) ),
187  this, SLOT( transactionFinished( bool ) ) );
188 
189  connect( http, SIGNAL( stateChanged( int ) ),
190  this, SLOT( dataStateChanged( int ) ) );
191 
192  // Set up the watchdog timer
193  connect( mWatchdogTimer, SIGNAL( timeout() ),
194  this, SLOT( networkTimedOut() ) );
195 
196  mWatchdogTimer->setSingleShot( true );
197  mWatchdogTimer->start( mNetworkTimeoutMsec );
198 
199  QgsDebugMsg( "Starting get with id " + QString::number( httpid ) + '.' );
200  QgsDebugMsg( "Setting httpactive = true" );
201 
202  httpactive = true;
203 
204  // A little trick to make this function blocking
205  while ( httpactive )
206  {
207  // Do something else, maybe even network processing events
208  qApp->processEvents();
209  }
210 
211  QgsDebugMsg( "Response received." );
212 
213 #ifdef QGISDEBUG
214 // QString httpresponsestring(httpresponse);
215 // QgsDebugMsg("Response received; being '" + httpresponsestring + "'.");
216 #endif
217 
218  delete http;
219  http = nullptr;
220 
221  // Did we get an error? If so, bail early
222  if ( !mError.isEmpty() )
223  {
224  QgsDebugMsg( "Processing an error '" + mError + "'." );
225  return false;
226  }
227 
228  // Do one level of redirection
229  // TODO make this recursable
230  // TODO detect any redirection loops
231  if ( !httpredirecturl.isEmpty() )
232  {
233  QgsDebugMsg( "Starting get of '" + httpredirecturl + "'." );
234 
235  QgsHttpTransaction httprecurse( httpredirecturl, httphost, httpport );
236  httprecurse.setCredentials( mUserName, mPassword );
237 
238  // Do a passthrough for the status bar text
239  connect(
240  &httprecurse, SIGNAL( statusChanged( QString ) ),
241  this, SIGNAL( statusChanged( QString ) )
242  );
243 
244  httprecurse.getSynchronously( respondedContent, ( redirections + 1 ) );
245  return true;
246 
247  }
248 
249  respondedContent = httpresponse;
250  return true;
251 
252 }
253 
254 
256 {
257  return httpresponsecontenttype;
258 }
259 
260 
262 {
263  Q_UNUSED( id );
264  QgsDebugMsg( "ID=" + QString::number( id ) + '.' );
265 }
266 
267 
269 {
270  QgsDebugMsg( "statuscode " +
271  QString::number( resp.statusCode() ) + ", reason '" + resp.reasonPhrase() + "', content type: '" +
272  resp.value( "Content-Type" ) + "'." );
273 
274  // We saw something come back, therefore restart the watchdog timer
275  mWatchdogTimer->start( mNetworkTimeoutMsec );
276 
277  if ( resp.statusCode() == 302 ) // Redirect
278  {
279  // Grab the alternative URL
280  // (ref: "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html")
281  httpredirecturl = resp.value( "Location" );
282  }
283  else if ( resp.statusCode() == 200 ) // OK
284  {
285  // NOOP
286  }
287  else
288  {
289  mError = tr( "WMS Server responded unexpectedly with HTTP Status Code %1 (%2)" )
290  .arg( resp.statusCode() )
291  .arg( resp.reasonPhrase() );
292  }
293 
294  httpresponsecontenttype = resp.value( "Content-Type" );
295 
296 }
297 
298 
300 {
301  Q_UNUSED( resp );
302  // TODO: Match 'resp' with 'http' if we move to multiple http connections
303 
304 #if 0
305  // Comment this out for now - leave the coding of progressive rendering to another day.
306  char* temp;
307 
308  if ( 0 < http->readBlock( temp, http->bytesAvailable() ) )
309  {
310  httpresponse.append( temp );
311  }
312 #endif
313 
314 // QgsDebugMsg("received '" + data + "'.");
315 }
316 
317 
318 void QgsHttpTransaction::dataProgress( int done, int total )
319 {
320 // QgsDebugMsg("got " + QString::number(done) + " of " + QString::number(total));
321 
322  // We saw something come back, therefore restart the watchdog timer
323  mWatchdogTimer->start( mNetworkTimeoutMsec );
324 
325  emit dataReadProgress( done );
326  emit totalSteps( total );
327 
328  QString status;
329 
330  if ( total )
331  {
332  status = tr( "Received %1 of %2 bytes" ).arg( done ).arg( total );
333  }
334  else
335  {
336  status = tr( "Received %1 bytes (total unknown)" ).arg( done );
337  }
338 
339  emit statusChanged( status );
340 }
341 
342 
343 void QgsHttpTransaction::dataFinished( int id, bool error )
344 {
345 #ifdef QGISDEBUG
346  QgsDebugMsg( "ID=" + QString::number( id ) + '.' );
347 
348  // The signal that this slot is connected to, QHttp::requestFinished,
349  // appears to get called at the destruction of the QHttp if it is
350  // still working at the time of the destruction.
351  //
352  // This situation may occur when we've detected a timeout and
353  // we already set httpactive = false.
354  //
355  // We have to detect this special case so that the last known error string is
356  // not overwritten (it should rightfully refer to the timeout event).
357  if ( !httpactive )
358  {
359  QgsDebugMsg( "http activity loop already false." );
360  return;
361  }
362 
363  if ( error )
364  {
365  QgsDebugMsg( "however there was an error." );
366  QgsDebugMsg( "error: " + http->errorString() );
367 
368  mError = tr( "HTTP response completed, however there was an error: %1" ).arg( http->errorString() );
369  }
370  else
371  {
372  QgsDebugMsg( "no error." );
373  }
374 #else
375  Q_UNUSED( id );
376  Q_UNUSED( error );
377 #endif
378 
379 // Don't do this here as the request could have simply been
380 // to set the hostname - see transactionFinished() instead
381 
382 #if 0
383  // TODO
384  httpresponse = http->readAll();
385 
386 // QgsDebugMsg("Setting httpactive = false");
387  httpactive = false;
388 #endif
389 }
390 
391 
393 {
394 #ifdef QGISDEBUG
395 
396 #if 0
397  // The signal that this slot is connected to, QHttp::requestFinished,
398  // appears to get called at the destruction of the QHttp if it is
399  // still working at the time of the destruction.
400  //
401  // This situation may occur when we've detected a timeout and
402  // we already set httpactive = false.
403  //
404  // We have to detect this special case so that the last known error string is
405  // not overwritten (it should rightfully refer to the timeout event).
406  if ( !httpactive )
407  {
408 // QgsDebugMsg("http activity loop already false.");
409  return;
410  }
411 #endif
412 
413  if ( error )
414  {
415  QgsDebugMsg( "however there was an error." );
416  QgsDebugMsg( "error: " + http->errorString() );
417 
418  mError = tr( "HTTP transaction completed, however there was an error: %1" ).arg( http->errorString() );
419  }
420  else
421  {
422  QgsDebugMsg( "no error." );
423  }
424 #else
425  Q_UNUSED( error );
426 #endif
427 
428  // TODO
429  httpresponse = http->readAll();
430 
431  QgsDebugMsg( "Setting httpactive = false" );
432  httpactive = false;
433 }
434 
435 
437 {
438  QgsDebugMsg( "state " + QString::number( state ) + '.' );
439 
440  // We saw something come back, therefore restart the watchdog timer
441  mWatchdogTimer->start( mNetworkTimeoutMsec );
442 
443  switch ( state )
444  {
445  case QHttp::Unconnected:
446  QgsDebugMsg( "There is no connection to the host." );
447  emit statusChanged( tr( "Not connected" ) );
448  break;
449 
450  case QHttp::HostLookup:
451  QgsDebugMsg( "A host name lookup is in progress." );
452 
453  emit statusChanged( tr( "Looking up '%1'" ).arg( httphost ) );
454  break;
455 
456  case QHttp::Connecting:
457  QgsDebugMsg( "An attempt to connect to the host is in progress." );
458 
459  emit statusChanged( tr( "Connecting to '%1'" ).arg( httphost ) );
460  break;
461 
462  case QHttp::Sending:
463  QgsDebugMsg( "The client is sending its request to the server." );
464 
465  emit statusChanged( tr( "Sending request '%1'" ).arg( httpurl ) );
466  break;
467 
468  case QHttp::Reading:
469  QgsDebugMsg( "The client's request has been sent and the client is reading the server's response." );
470 
471  emit statusChanged( tr( "Receiving reply" ) );
472  break;
473 
474  case QHttp::Connected:
475  QgsDebugMsg( "The connection to the host is open, but the client is neither sending a request, nor waiting for a response." );
476 
477  emit statusChanged( tr( "Response is complete" ) );
478  break;
479 
480  case QHttp::Closing:
481  QgsDebugMsg( "The connection is closing down, but is not yet closed. (The state will be Unconnected when the connection is closed.)" );
482 
483  emit statusChanged( tr( "Closing down connection" ) );
484  break;
485  }
486 }
487 
488 
490 {
491 
492  mError = tr( "Network timed out after %n second(s) of inactivity.\n"
493  "This may be a problem in your network connection or at the WMS server.", "inactivity timeout", mNetworkTimeoutMsec / 1000 );
494 
495  QgsDebugMsg( "Setting httpactive = false" );
496  httpactive = false;
497  QgsDebugMsg( "exiting." );
498 }
499 
500 
502 {
503  return mError;
504 }
505 
507 {
508  QSettings settings;
509  //check if proxy is enabled
510  bool proxyEnabled = settings.value( "proxy/proxyEnabled", false ).toBool();
511  if ( !proxyEnabled )
512  {
513  return false;
514  }
515 
516  //check if the url should go through proxy
517  QString proxyExcludedURLs = settings.value( "proxy/proxyExcludedUrls", "" ).toString();
518  if ( !proxyExcludedURLs.isEmpty() )
519  {
520  QStringList excludedURLs = proxyExcludedURLs.split( '|' );
521  QStringList::const_iterator exclIt = excludedURLs.constBegin();
522  for ( ; exclIt != excludedURLs.constEnd(); ++exclIt )
523  {
524  if ( url.startsWith( *exclIt ) )
525  {
526  return false; //url does not go through proxy
527  }
528  }
529  }
530 
531  //read type, host, port, user, passw from settings
532  QString proxyHost = settings.value( "proxy/proxyHost", "" ).toString();
533  int proxyPort = settings.value( "proxy/proxyPort", "" ).toString().toInt();
534  QString proxyUser = settings.value( "proxy/proxyUser", "" ).toString();
535  QString proxyPassword = settings.value( "proxy/proxyPassword", "" ).toString();
536 
537  QString proxyTypeString = settings.value( "proxy/proxyType", "" ).toString();
538  QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
539  if ( proxyTypeString == "DefaultProxy" )
540  {
541  proxyType = QNetworkProxy::DefaultProxy;
542  }
543  else if ( proxyTypeString == "Socks5Proxy" )
544  {
545  proxyType = QNetworkProxy::Socks5Proxy;
546  }
547  else if ( proxyTypeString == "HttpProxy" )
548  {
549  proxyType = QNetworkProxy::HttpProxy;
550  }
551  else if ( proxyTypeString == "HttpCachingProxy" )
552  {
553  proxyType = QNetworkProxy::HttpCachingProxy;
554  }
555  else if ( proxyTypeString == "FtpCachingProxy" )
556  {
557  proxyType = QNetworkProxy::FtpCachingProxy;
558  }
559  http.setProxy( QNetworkProxy( proxyType, proxyHost, proxyPort, proxyUser, proxyPassword ) );
560  return true;
561 }
562 
564 {
565  if ( http )
566  {
567  http->abort();
568  }
569 }
570 
571 // ENDS
QString value(const QString &key) const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
QString errorString() const
QgsHttpTransaction(const QString &uri, const QString &proxyHost=QString(), int proxyPort=80, const QString &proxyUser=QString(), const QString &proxyPass=QString(), QNetworkProxy::ProxyType proxyType=QNetworkProxy::NoProxy, const QString &userName=QString(), const QString &password=QString())
Constructor.
bool getSynchronously(QByteArray &respondedContent, int redirections=0, const QByteArray *postData=nullptr)
Gets the response synchronously.
QByteArray readAll()
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
QString errorString()
If an operation returns 0 (e.g.
QString host() const
qint64 readBlock(char *data, quint64 maxlen)
void dataReadProgress(int theProgress)
Signal for progress update.
QString & remove(int position, int n)
int port() const
QString tr(const char *sourceText, const char *disambiguation, int n)
int request(const QHttpRequestHeader &header, QIODevice *data, QIODevice *to)
QString number(int n, int base)
QString reasonPhrase() const
void setCredentials(const QString &username, const QString &password)
Set the credentials (username and password)
int toInt(bool *ok) const
void dataProgress(int done, int total)
int setProxy(const QString &host, int port, const QString &username, const QString &password)
int toInt(bool *ok, int base) const
int setHost(const QString &hostName, quint16 port)
bool isEmpty() const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QString path() const
void totalSteps(int theTotalSteps)
Signal for adjusted number of steps.
int setUser(const QString &userName, const QString &password)
void truncate(int pos)
void abort()
void dataFinished(int id, bool error)
QByteArray & append(char ch)
virtual ~QgsHttpTransaction()
Destructor.
int statusCode() const
void dataStateChanged(int state)
QVariant value(const QString &key, const QVariant &defaultValue) const
HTTP request/response manager that is redirect-aware.
void statusChanged(const QString &theStatusQString)
emit a signal to be caught by qgisapp and display a msg on status bar
bool toBool() const
qint64 bytesAvailable() const
void start(int msec)
const_iterator constEnd() const
void abort()
Aborts the current transaction.
const_iterator constBegin() const
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void dataReceived(const QHttpResponseHeader &resp)
void setValue(const QString &key, const QString &value)
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
void transactionFinished(bool error)
void dataHeaderReceived(const QHttpResponseHeader &resp)
static int HTTP_PORT_DEFAULT
static bool applyProxySettings(QHttp &http, const QString &url)
Apply proxy settings from QSettings to a http object.
void setSingleShot(bool singleShot)