QGIS API Documentation  2.7.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator 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 
61 QgsHttpTransaction::QgsHttpTransaction()
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 
120  if ( !QgsHttpTransaction::applyProxySettings( *http, httpurl ) )
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 );
188  mWatchdogTimer->start( mNetworkTimeoutMsec );
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 {
248  return httpresponsecontenttype;
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
266  mWatchdogTimer->start( mNetworkTimeoutMsec );
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
314  mWatchdogTimer->start( mNetworkTimeoutMsec );
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
433  mWatchdogTimer->start( mNetworkTimeoutMsec );
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
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QString errorString()
If an operation returns 0 (e.g.
void dataReadProgress(int theProgress)
Signal for progress update.
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)
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.
virtual ~QgsHttpTransaction()
Destructor.
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.
void abort()
Aborts the current transaction.
void dataReceived(const QHttpResponseHeader &resp)
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.
#define tr(sourceText)