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