QGIS API Documentation  2.11.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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  QString proxyHost,
40  int proxyPort,
41  QString proxyUser,
42  QString proxyPass,
43  QNetworkProxy::ProxyType proxyType,
44  QString userName,
45  QString password )
46  : http( NULL )
47  , httpid( 0 )
48  , httpactive( false )
49  , httpurl( uri )
50  , httphost( proxyHost )
51  , httpredirections( 0 )
52  , mWatchdogTimer( NULL )
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", "20000" ).toInt();
62 }
63 
64 QgsHttpTransaction::QgsHttpTransaction()
65  : http( NULL )
66  , httpid( 0 )
67  , httpactive( false )
68  , httpredirections( 0 )
69  , mWatchdogTimer( NULL )
70 {
71  QSettings s;
72  mNetworkTimeoutMsec = s.value( "/qgis/networkAndProxy/networkTimeout", "20000" ).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 = 0;
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  QgsDebugMsg( "entered." );
396 
397 #if 0
398  // The signal that this slot is connected to, QHttp::requestFinished,
399  // appears to get called at the destruction of the QHttp if it is
400  // still working at the time of the destruction.
401  //
402  // This situation may occur when we've detected a timeout and
403  // we already set httpactive = false.
404  //
405  // We have to detect this special case so that the last known error string is
406  // not overwritten (it should rightfully refer to the timeout event).
407  if ( !httpactive )
408  {
409 // QgsDebugMsg("http activity loop already false.");
410  return;
411  }
412 #endif
413 
414  if ( error )
415  {
416  QgsDebugMsg( "however there was an error." );
417  QgsDebugMsg( "error: " + http->errorString() );
418 
419  mError = tr( "HTTP transaction completed, however there was an error: %1" ).arg( http->errorString() );
420  }
421  else
422  {
423  QgsDebugMsg( "no error." );
424  }
425 #else
426  Q_UNUSED( error );
427 #endif
428 
429  // TODO
430  httpresponse = http->readAll();
431 
432  QgsDebugMsg( "Setting httpactive = false" );
433  httpactive = false;
434 }
435 
436 
438 {
439  QgsDebugMsg( "state " + QString::number( state ) + "." );
440 
441  // We saw something come back, therefore restart the watchdog timer
442  mWatchdogTimer->start( mNetworkTimeoutMsec );
443 
444  switch ( state )
445  {
446  case QHttp::Unconnected:
447  QgsDebugMsg( "There is no connection to the host." );
448  emit statusChanged( tr( "Not connected" ) );
449  break;
450 
451  case QHttp::HostLookup:
452  QgsDebugMsg( "A host name lookup is in progress." );
453 
454  emit statusChanged( tr( "Looking up '%1'" ).arg( httphost ) );
455  break;
456 
457  case QHttp::Connecting:
458  QgsDebugMsg( "An attempt to connect to the host is in progress." );
459 
460  emit statusChanged( tr( "Connecting to '%1'" ).arg( httphost ) );
461  break;
462 
463  case QHttp::Sending:
464  QgsDebugMsg( "The client is sending its request to the server." );
465 
466  emit statusChanged( tr( "Sending request '%1'" ).arg( httpurl ) );
467  break;
468 
469  case QHttp::Reading:
470  QgsDebugMsg( "The client's request has been sent and the client is reading the server's response." );
471 
472  emit statusChanged( tr( "Receiving reply" ) );
473  break;
474 
475  case QHttp::Connected:
476  QgsDebugMsg( "The connection to the host is open, but the client is neither sending a request, nor waiting for a response." );
477 
478  emit statusChanged( tr( "Response is complete" ) );
479  break;
480 
481  case QHttp::Closing:
482  QgsDebugMsg( "The connection is closing down, but is not yet closed. (The state will be Unconnected when the connection is closed.)" );
483 
484  emit statusChanged( tr( "Closing down connection" ) );
485  break;
486  }
487 }
488 
489 
491 {
492  QgsDebugMsg( "entering." );
493 
494  mError = tr( "Network timed out after %n second(s) of inactivity.\n"
495  "This may be a problem in your network connection or at the WMS server.", "inactivity timeout", mNetworkTimeoutMsec / 1000 );
496 
497  QgsDebugMsg( "Setting httpactive = false" );
498  httpactive = false;
499  QgsDebugMsg( "exiting." );
500 }
501 
502 
504 {
505  return mError;
506 }
507 
509 {
510  QSettings settings;
511  //check if proxy is enabled
512  bool proxyEnabled = settings.value( "proxy/proxyEnabled", false ).toBool();
513  if ( !proxyEnabled )
514  {
515  return false;
516  }
517 
518  //check if the url should go through proxy
519  QString proxyExcludedURLs = settings.value( "proxy/proxyExcludedUrls", "" ).toString();
520  if ( !proxyExcludedURLs.isEmpty() )
521  {
522  QStringList excludedURLs = proxyExcludedURLs.split( "|" );
523  QStringList::const_iterator exclIt = excludedURLs.constBegin();
524  for ( ; exclIt != excludedURLs.constEnd(); ++exclIt )
525  {
526  if ( url.startsWith( *exclIt ) )
527  {
528  return false; //url does not go through proxy
529  }
530  }
531  }
532 
533  //read type, host, port, user, passw from settings
534  QString proxyHost = settings.value( "proxy/proxyHost", "" ).toString();
535  int proxyPort = settings.value( "proxy/proxyPort", "" ).toString().toInt();
536  QString proxyUser = settings.value( "proxy/proxyUser", "" ).toString();
537  QString proxyPassword = settings.value( "proxy/proxyPassword", "" ).toString();
538 
539  QString proxyTypeString = settings.value( "proxy/proxyType", "" ).toString();
540  QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
541  if ( proxyTypeString == "DefaultProxy" )
542  {
543  proxyType = QNetworkProxy::DefaultProxy;
544  }
545  else if ( proxyTypeString == "Socks5Proxy" )
546  {
547  proxyType = QNetworkProxy::Socks5Proxy;
548  }
549  else if ( proxyTypeString == "HttpProxy" )
550  {
551  proxyType = QNetworkProxy::HttpProxy;
552  }
553  else if ( proxyTypeString == "HttpCachingProxy" )
554  {
555  proxyType = QNetworkProxy::HttpCachingProxy;
556  }
557  else if ( proxyTypeString == "FtpCachingProxy" )
558  {
559  proxyType = QNetworkProxy::FtpCachingProxy;
560  }
561  http.setProxy( QNetworkProxy( proxyType, proxyHost, proxyPort, proxyUser, proxyPassword ) );
562  return true;
563 }
564 
566 {
567  if ( http )
568  {
569  http->abort();
570  }
571 }
572 
573 // ENDS
QString value(const QString &key) const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
QString errorString() const
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)
QgsHttpTransaction(QString uri, QString proxyHost=QString(), int proxyPort=80, QString proxyUser=QString(), QString proxyPass=QString(), QNetworkProxy::ProxyType proxyType=QNetworkProxy::NoProxy, QString userName=QString(), QString password=QString())
Constructor.
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(QString theStatusQString)
emit a signal to be caught by qgisapp and display a msg on status bar
bool getSynchronously(QByteArray &respondedContent, int redirections=0, const QByteArray *postData=0)
Gets the response synchronously.
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)