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