Quantum GIS API Documentation  1.7.4
src/core/qgshttptransaction.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002   qgshttptransaction.cpp  -  Tracks a HTTP request with its response,
00003                              with particular attention to tracking
00004                              HTTP redirect responses
00005                              -------------------
00006     begin                : 17 Mar, 2005
00007     copyright            : (C) 2005 by Brendan Morley
00008     email                : morb at ozemail dot com dot au
00009  ***************************************************************************/
00010 
00011 /***************************************************************************
00012  *                                                                         *
00013  *   This program is free software; you can redistribute it and/or modify  *
00014  *   it under the terms of the GNU General Public License as published by  *
00015  *   the Free Software Foundation; either version 2 of the License, or     *
00016  *   (at your option) any later version.                                   *
00017  *                                                                         *
00018  ***************************************************************************/
00019 
00020 /* $Id: qgshttptransaction.cpp 5697 2006-08-15 10:29:46Z morb_au $ */
00021 
00022 #include <fstream>
00023 
00024 #include "qgshttptransaction.h"
00025 #include "qgslogger.h"
00026 #include "qgsconfig.h"
00027 
00028 #include <QApplication>
00029 #include <QUrl>
00030 #include <QSettings>
00031 #include <QTimer>
00032 
00033 static int HTTP_PORT_DEFAULT = 80;
00034 
00035 //XXX Set the connection name when creating the provider instance
00036 //XXX in qgswmsprovider. When creating a QgsHttpTransaction, pass
00037 //XXX the user/pass combination to the constructor. Then set the
00038 //XXX username and password using QHttp::setUser.
00039 QgsHttpTransaction::QgsHttpTransaction( QString uri,
00040                                         QString proxyHost,
00041                                         int     proxyPort,
00042                                         QString proxyUser,
00043                                         QString proxyPass,
00044                                         QNetworkProxy::ProxyType proxyType,
00045                                         QString userName,
00046                                         QString password )
00047     : httpresponsecontenttype( "" )
00048     , httpurl( uri )
00049     , httphost( proxyHost )
00050     , mError( "" )
00051 {
00052   QSettings s;
00053   mNetworkTimeoutMsec = s.value( "/qgis/networkAndProxy/networkTimeout", "20000" ).toInt();
00054 }
00055 
00056 QgsHttpTransaction::QgsHttpTransaction()
00057 {
00058 
00059 }
00060 
00061 QgsHttpTransaction::~QgsHttpTransaction()
00062 {
00063   QgsDebugMsg( "deconstructing." );
00064 }
00065 
00066 
00067 void QgsHttpTransaction::setCredentials( const QString& username, const QString& password )
00068 {
00069   mUserName = username;
00070   mPassword = password;
00071 }
00072 void QgsHttpTransaction::getAsynchronously()
00073 {
00074 
00075   //TODO
00076 
00077 }
00078 
00079 bool QgsHttpTransaction::getSynchronously( QByteArray &respondedContent, int redirections, const QByteArray* postData )
00080 {
00081 
00082   httpredirections = redirections;
00083 
00084   QgsDebugMsg( "Entered." );
00085   QgsDebugMsg( "Using '" + httpurl + "'." );
00086   QgsDebugMsg( "Creds: " + mUserName + "/" + mPassword );
00087 
00088   int httpport;
00089 
00090   QUrl qurl( httpurl );
00091 
00092   http = new QHttp( );
00093   // Create a header so we can set the user agent (Per WMS RFC).
00094   QHttpRequestHeader header( "GET", qurl.host() );
00095   // Set host in the header
00096   if ( qurl.port( HTTP_PORT_DEFAULT ) == HTTP_PORT_DEFAULT )
00097   {
00098     header.setValue( "Host", qurl.host() );
00099   }
00100   else
00101   {
00102     header.setValue( "Host", QString( "%1:%2" ).arg( qurl.host() ).arg( qurl.port() ) );
00103   }
00104   // Set the user agent to Quantum GIS plus the version name
00105   header.setValue( "User-agent", QString( "Quantum GIS - " ) + VERSION );
00106   // Set the host in the QHttp object
00107   http->setHost( qurl.host(), qurl.port( HTTP_PORT_DEFAULT ) );
00108   // Set the username and password if supplied for this connection
00109   // If we have username and password set in header
00110   if ( !mUserName.isEmpty() && !mPassword.isEmpty() )
00111   {
00112     http->setUser( mUserName, mPassword );
00113   }
00114 
00115   if ( !QgsHttpTransaction::applyProxySettings( *http, httpurl ) )
00116   {
00117     httphost = qurl.host();
00118     httpport = qurl.port( HTTP_PORT_DEFAULT );
00119   }
00120   else
00121   {
00122     //proxy enabled, read httphost and httpport from settings
00123     QSettings settings;
00124     httphost = settings.value( "proxy/proxyHost", "" ).toString();
00125     httpport = settings.value( "proxy/proxyPort", "" ).toString().toInt();
00126   }
00127 
00128 //  int httpid1 = http->setHost( qurl.host(), qurl.port() );
00129 
00130   mWatchdogTimer = new QTimer( this );
00131 
00132   QgsDebugMsg( "qurl.host() is '" + qurl.host() + "'." );
00133 
00134   httpresponse.truncate( 0 );
00135 
00136   // Some WMS servers don't like receiving a http request that
00137   // includes the scheme, host and port (the
00138   // http://www.address.bit:80), so remove that from the url before
00139   // executing an http GET.
00140 
00141   //Path could be just '/' so we remove the 'http://' first
00142   QString pathAndQuery = httpurl.remove( 0, httpurl.indexOf( qurl.host() ) );
00143   pathAndQuery = httpurl.remove( 0, pathAndQuery.indexOf( qurl.path() ) );
00144   if ( !postData ) //do request with HTTP GET
00145   {
00146     header.setRequest( "GET", pathAndQuery );
00147     // do GET using header containing user-agent
00148     httpid = http->request( header );
00149   }
00150   else //do request with HTTP POST
00151   {
00152     header.setRequest( "POST", pathAndQuery );
00153     // do POST using header containing user-agent
00154     httpid = http->request( header, *postData );
00155   }
00156 
00157   connect( http, SIGNAL( requestStarted( int ) ),
00158            this,      SLOT( dataStarted( int ) ) );
00159 
00160   connect( http, SIGNAL( responseHeaderReceived( const QHttpResponseHeader& ) ),
00161            this,       SLOT( dataHeaderReceived( const QHttpResponseHeader& ) ) );
00162 
00163   connect( http,  SIGNAL( readyRead( const QHttpResponseHeader& ) ),
00164            this, SLOT( dataReceived( const QHttpResponseHeader& ) ) );
00165 
00166   connect( http, SIGNAL( dataReadProgress( int, int ) ),
00167            this,       SLOT( dataProgress( int, int ) ) );
00168 
00169   connect( http, SIGNAL( requestFinished( int, bool ) ),
00170            this,      SLOT( dataFinished( int, bool ) ) );
00171 
00172   connect( http, SIGNAL( done( bool ) ),
00173            this, SLOT( transactionFinished( bool ) ) );
00174 
00175   connect( http,   SIGNAL( stateChanged( int ) ),
00176            this, SLOT( dataStateChanged( int ) ) );
00177 
00178   // Set up the watchdog timer
00179   connect( mWatchdogTimer, SIGNAL( timeout() ),
00180            this,     SLOT( networkTimedOut() ) );
00181 
00182   mWatchdogTimer->setSingleShot( true );
00183   mWatchdogTimer->start( mNetworkTimeoutMsec );
00184 
00185   QgsDebugMsg( "Starting get with id " + QString::number( httpid ) + "." );
00186   QgsDebugMsg( "Setting httpactive = true" );
00187 
00188   httpactive = true;
00189 
00190   // A little trick to make this function blocking
00191   while ( httpactive )
00192   {
00193     // Do something else, maybe even network processing events
00194     qApp->processEvents();
00195   }
00196 
00197   QgsDebugMsg( "Response received." );
00198 
00199 #ifdef QGISDEBUG
00200 //  QString httpresponsestring(httpresponse);
00201 //  QgsDebugMsg("Response received; being '" + httpresponsestring + "'.");
00202 #endif
00203 
00204   delete http;
00205   http = 0;
00206 
00207   // Did we get an error? If so, bail early
00208   if ( !mError.isEmpty() )
00209   {
00210     QgsDebugMsg( "Processing an error '" + mError + "'." );
00211     return false;
00212   }
00213 
00214   // Do one level of redirection
00215   // TODO make this recursable
00216   // TODO detect any redirection loops
00217   if ( !httpredirecturl.isEmpty() )
00218   {
00219     QgsDebugMsg( "Starting get of '" +  httpredirecturl + "'." );
00220 
00221     QgsHttpTransaction httprecurse( httpredirecturl, httphost, httpport );
00222     httprecurse.setCredentials( mUserName, mPassword );
00223 
00224     // Do a passthrough for the status bar text
00225     connect(
00226       &httprecurse, SIGNAL( statusChanged( QString ) ),
00227       this,        SIGNAL( statusChanged( QString ) )
00228     );
00229 
00230     httprecurse.getSynchronously( respondedContent, ( redirections + 1 ) );
00231     return true;
00232 
00233   }
00234 
00235   respondedContent = httpresponse;
00236   return true;
00237 
00238 }
00239 
00240 
00241 QString QgsHttpTransaction::responseContentType()
00242 {
00243   return httpresponsecontenttype;
00244 }
00245 
00246 
00247 void QgsHttpTransaction::dataStarted( int id )
00248 {
00249   QgsDebugMsg( "ID=" + QString::number( id ) + "." );
00250 }
00251 
00252 
00253 void QgsHttpTransaction::dataHeaderReceived( const QHttpResponseHeader& resp )
00254 {
00255   QgsDebugMsg( "statuscode " +
00256                QString::number( resp.statusCode() ) + ", reason '" + resp.reasonPhrase() + "', content type: '" +
00257                resp.value( "Content-Type" ) + "'." );
00258 
00259   // We saw something come back, therefore restart the watchdog timer
00260   mWatchdogTimer->start( mNetworkTimeoutMsec );
00261 
00262   if ( resp.statusCode() == 302 ) // Redirect
00263   {
00264     // Grab the alternative URL
00265     // (ref: "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html")
00266     httpredirecturl = resp.value( "Location" );
00267   }
00268   else if ( resp.statusCode() == 200 ) // OK
00269   {
00270     // NOOP
00271   }
00272   else
00273   {
00274     mError = tr( "WMS Server responded unexpectedly with HTTP Status Code %1 (%2)" )
00275              .arg( resp.statusCode() )
00276              .arg( resp.reasonPhrase() );
00277   }
00278 
00279   httpresponsecontenttype = resp.value( "Content-Type" );
00280 
00281 }
00282 
00283 
00284 void QgsHttpTransaction::dataReceived( const QHttpResponseHeader& resp )
00285 {
00286   // TODO: Match 'resp' with 'http' if we move to multiple http connections
00287 
00288 #if 0
00289   // Comment this out for now - leave the coding of progressive rendering to another day.
00290   char* temp;
00291 
00292   if ( 0 < http->readBlock( temp, http->bytesAvailable() ) )
00293   {
00294     httpresponse.append( temp );
00295   }
00296 #endif
00297 
00298 //  QgsDebugMsg("received '" + data + "'.");
00299 }
00300 
00301 
00302 void QgsHttpTransaction::dataProgress( int done, int total )
00303 {
00304 //  QgsDebugMsg("got " + QString::number(done) + " of " + QString::number(total));
00305 
00306   // We saw something come back, therefore restart the watchdog timer
00307   mWatchdogTimer->start( mNetworkTimeoutMsec );
00308 
00309   emit dataReadProgress( done );
00310   emit totalSteps( total );
00311 
00312   QString status;
00313 
00314   if ( total )
00315   {
00316     status = tr( "Received %1 of %2 bytes" ).arg( done ).arg( total );
00317   }
00318   else
00319   {
00320     status = tr( "Received %1 bytes (total unknown)" ).arg( done );
00321   }
00322 
00323   emit statusChanged( status );
00324 }
00325 
00326 
00327 void QgsHttpTransaction::dataFinished( int id, bool error )
00328 {
00329 
00330 #ifdef QGISDEBUG
00331   QgsDebugMsg( "ID=" + QString::number( id ) + "." );
00332 
00333   // The signal that this slot is connected to, QHttp::requestFinished,
00334   // appears to get called at the destruction of the QHttp if it is
00335   // still working at the time of the destruction.
00336   //
00337   // This situation may occur when we've detected a timeout and
00338   // we already set httpactive = false.
00339   //
00340   // We have to detect this special case so that the last known error string is
00341   // not overwritten (it should rightfully refer to the timeout event).
00342   if ( !httpactive )
00343   {
00344     QgsDebugMsg( "http activity loop already false." );
00345     return;
00346   }
00347 
00348   if ( error )
00349   {
00350     QgsDebugMsg( "however there was an error." );
00351     QgsDebugMsg( "error: " + http->errorString() );
00352 
00353     mError = tr( "HTTP response completed, however there was an error: %1" ).arg( http->errorString() );
00354   }
00355   else
00356   {
00357     QgsDebugMsg( "no error." );
00358   }
00359 #endif
00360 
00361 // Don't do this here as the request could have simply been
00362 // to set the hostname - see transactionFinished() instead
00363 
00364 #if 0
00365   // TODO
00366   httpresponse = http->readAll();
00367 
00368 // QgsDebugMsg("Setting httpactive = false");
00369   httpactive = false;
00370 #endif
00371 }
00372 
00373 
00374 void QgsHttpTransaction::transactionFinished( bool error )
00375 {
00376 
00377 #ifdef QGISDEBUG
00378   QgsDebugMsg( "entered." );
00379 
00380 #if 0
00381   // The signal that this slot is connected to, QHttp::requestFinished,
00382   // appears to get called at the destruction of the QHttp if it is
00383   // still working at the time of the destruction.
00384   //
00385   // This situation may occur when we've detected a timeout and
00386   // we already set httpactive = false.
00387   //
00388   // We have to detect this special case so that the last known error string is
00389   // not overwritten (it should rightfully refer to the timeout event).
00390   if ( !httpactive )
00391   {
00392 // QgsDebugMsg("http activity loop already false.");
00393     return;
00394   }
00395 #endif
00396 
00397   if ( error )
00398   {
00399     QgsDebugMsg( "however there was an error." );
00400     QgsDebugMsg( "error: " + http->errorString() );
00401 
00402     mError = tr( "HTTP transaction completed, however there was an error: %1" ).arg( http->errorString() );
00403   }
00404   else
00405   {
00406     QgsDebugMsg( "no error." );
00407   }
00408 #endif
00409 
00410   // TODO
00411   httpresponse = http->readAll();
00412 
00413   QgsDebugMsg( "Setting httpactive = false" );
00414   httpactive = false;
00415 }
00416 
00417 
00418 void QgsHttpTransaction::dataStateChanged( int state )
00419 {
00420   QgsDebugMsg( "state " + QString::number( state ) + "." );
00421 
00422   // We saw something come back, therefore restart the watchdog timer
00423   mWatchdogTimer->start( mNetworkTimeoutMsec );
00424 
00425   switch ( state )
00426   {
00427     case QHttp::Unconnected:
00428       QgsDebugMsg( "There is no connection to the host." );
00429       emit statusChanged( tr( "Not connected" ) );
00430       break;
00431 
00432     case QHttp::HostLookup:
00433       QgsDebugMsg( "A host name lookup is in progress." );
00434 
00435       emit statusChanged( tr( "Looking up '%1'" ).arg( httphost ) );
00436       break;
00437 
00438     case QHttp::Connecting:
00439       QgsDebugMsg( "An attempt to connect to the host is in progress." );
00440 
00441       emit statusChanged( tr( "Connecting to '%1'" ).arg( httphost ) );
00442       break;
00443 
00444     case QHttp::Sending:
00445       QgsDebugMsg( "The client is sending its request to the server." );
00446 
00447       emit statusChanged( tr( "Sending request '%1'" ).arg( httpurl ) );
00448       break;
00449 
00450     case QHttp::Reading:
00451       QgsDebugMsg( "The client's request has been sent and the client is reading the server's response." );
00452 
00453       emit statusChanged( tr( "Receiving reply" ) );
00454       break;
00455 
00456     case QHttp::Connected:
00457       QgsDebugMsg( "The connection to the host is open, but the client is neither sending a request, nor waiting for a response." );
00458 
00459       emit statusChanged( tr( "Response is complete" ) );
00460       break;
00461 
00462     case QHttp::Closing:
00463       QgsDebugMsg( "The connection is closing down, but is not yet closed. (The state will be Unconnected when the connection is closed.)" );
00464 
00465       emit statusChanged( tr( "Closing down connection" ) );
00466       break;
00467   }
00468 }
00469 
00470 
00471 void QgsHttpTransaction::networkTimedOut()
00472 {
00473   QgsDebugMsg( "entering." );
00474 
00475   mError = tr( "Network timed out after %n second(s) of inactivity.\n"
00476                "This may be a problem in your network connection or at the WMS server.", "inactivity timeout", mNetworkTimeoutMsec / 1000 );
00477 
00478   QgsDebugMsg( "Setting httpactive = false" );
00479   httpactive = false;
00480   QgsDebugMsg( "exiting." );
00481 }
00482 
00483 
00484 QString QgsHttpTransaction::errorString()
00485 {
00486   return mError;
00487 }
00488 
00489 bool QgsHttpTransaction::applyProxySettings( QHttp& http, const QString& url )
00490 {
00491   QSettings settings;
00492   //check if proxy is enabled
00493   bool proxyEnabled = settings.value( "proxy/proxyEnabled", false ).toBool();
00494   if ( !proxyEnabled )
00495   {
00496     return false;
00497   }
00498 
00499   //check if the url should go through proxy
00500   QString  proxyExcludedURLs = settings.value( "proxy/proxyExcludedUrls", "" ).toString();
00501   if ( !proxyExcludedURLs.isEmpty() )
00502   {
00503     QStringList excludedURLs = proxyExcludedURLs.split( "|" );
00504     QStringList::const_iterator exclIt = excludedURLs.constBegin();
00505     for ( ; exclIt != excludedURLs.constEnd(); ++exclIt )
00506     {
00507       if ( url.startsWith( *exclIt ) )
00508       {
00509         return false; //url does not go through proxy
00510       }
00511     }
00512   }
00513 
00514   //read type, host, port, user, passw from settings
00515   QString proxyHost = settings.value( "proxy/proxyHost", "" ).toString();
00516   int proxyPort = settings.value( "proxy/proxyPort", "" ).toString().toInt();
00517   QString proxyUser = settings.value( "proxy/proxyUser", "" ).toString();
00518   QString proxyPassword = settings.value( "proxy/proxyPassword", "" ).toString();
00519 
00520   QString proxyTypeString =  settings.value( "proxy/proxyType", "" ).toString();
00521   QNetworkProxy::ProxyType proxyType = QNetworkProxy::NoProxy;
00522   if ( proxyTypeString == "DefaultProxy" )
00523   {
00524     proxyType = QNetworkProxy::DefaultProxy;
00525   }
00526   else if ( proxyTypeString == "Socks5Proxy" )
00527   {
00528     proxyType = QNetworkProxy::Socks5Proxy;
00529   }
00530   else if ( proxyTypeString == "HttpProxy" )
00531   {
00532     proxyType = QNetworkProxy::HttpProxy;
00533   }
00534   else if ( proxyTypeString == "HttpCachingProxy" )
00535   {
00536     proxyType = QNetworkProxy::HttpCachingProxy;
00537   }
00538   else if ( proxyTypeString == "FtpCachingProxy" )
00539   {
00540     proxyType = QNetworkProxy::FtpCachingProxy;
00541   }
00542   http.setProxy( QNetworkProxy( proxyType, proxyHost, proxyPort, proxyUser, proxyPassword ) );
00543   return true;
00544 }
00545 
00546 void QgsHttpTransaction::abort()
00547 {
00548   if ( http )
00549   {
00550     http->abort();
00551   }
00552 }
00553 
00554 // ENDS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines