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