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