Quantum GIS API Documentation
1.8
|
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