QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgsserver.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsserver.cpp
3  A server application supporting WMS / WFS / WCS
4  -------------------
5  begin : July 04, 2006
6  copyright : (C) 2006 by Marco Hugentobler & Ionut Iosifescu Enescu
7  : (C) 2015 by Alessandro Pasotti
8  email : marco dot hugentobler at karto dot baug dot ethz dot ch
9  : elpaso at itopen dot it
10  ***************************************************************************/
11 
12 /***************************************************************************
13  * *
14  * This program is free software; you can redistribute it and/or modify *
15  * it under the terms of the GNU General Public License as published by *
16  * the Free Software Foundation; either version 2 of the License, or *
17  * (at your option) any later version. *
18  * *
19  ***************************************************************************/
20 
21 //for CMAKE_INSTALL_PREFIX
22 #include "qgsconfig.h"
23 #include "qgsserver.h"
24 #include "qgsmapsettings.h"
25 #include "qgsauthmanager.h"
26 #include "qgscapabilitiescache.h"
27 #include "qgsfontutils.h"
28 #include "qgsrequesthandler.h"
29 #include "qgsproject.h"
30 #include "qgsproviderregistry.h"
31 #include "qgslogger.h"
32 #include "qgsmapserviceexception.h"
34 #include "qgsserverlogger.h"
35 #include "qgsserverrequest.h"
37 #include "qgsbufferserverrequest.h"
39 #include "qgsservice.h"
40 #include "qgsserverprojectutils.h"
41 #include "qgsserverparameters.h"
42 
43 #include <QDomDocument>
44 #include <QNetworkDiskCache>
45 #include <QImage>
46 #include <QSettings>
47 #include <QDateTime>
48 
49 // TODO: remove, it's only needed by a single debug message
50 #include <fcgi_stdio.h>
51 #include <cstdlib>
52 
53 
54 
55 // Server status static initializers.
56 // Default values are for C++, SIP bindings will override their
57 // options in in init()
58 
59 QString *QgsServer::sConfigFilePath = nullptr;
60 QgsCapabilitiesCache *QgsServer::sCapabilitiesCache = nullptr;
61 QgsServerInterfaceImpl *QgsServer::sServerInterface = nullptr;
62 // Initialization must run once for all servers
63 bool QgsServer::sInitialized = false;
64 QgsServerSettings QgsServer::sSettings;
65 
66 QgsServiceRegistry *QgsServer::sServiceRegistry = nullptr;
67 
69 {
70  // QgsApplication must exist
71  if ( qobject_cast<QgsApplication *>( qApp ) == nullptr )
72  {
73  qFatal( "A QgsApplication must exist before a QgsServer instance can be created." );
74  abort();
75  }
76  init();
77  mConfigCache = QgsConfigCache::instance();
78 }
79 
80 QString &QgsServer::serverName()
81 {
82  static QString *name = new QString( QStringLiteral( "qgis_server" ) );
83  return *name;
84 }
85 
86 
87 QFileInfo QgsServer::defaultAdminSLD()
88 {
89  return QFileInfo( QStringLiteral( "admin.sld" ) );
90 }
91 
92 void QgsServer::setupNetworkAccessManager()
93 {
94  QSettings settings;
96  QNetworkDiskCache *cache = new QNetworkDiskCache( nullptr );
97  qint64 cacheSize = sSettings.cacheSize();
98  QString cacheDirectory = sSettings.cacheDirectory();
99  cache->setCacheDirectory( cacheDirectory );
100  cache->setMaximumCacheSize( cacheSize );
101  QgsMessageLog::logMessage( QStringLiteral( "cacheDirectory: %1" ).arg( cache->cacheDirectory() ), QStringLiteral( "Server" ), Qgis::Info );
102  QgsMessageLog::logMessage( QStringLiteral( "maximumCacheSize: %1" ).arg( cache->maximumCacheSize() ), QStringLiteral( "Server" ), Qgis::Info );
103  nam->setCache( cache );
104 }
105 
106 QFileInfo QgsServer::defaultProjectFile()
107 {
108  QDir currentDir;
109  fprintf( FCGI_stderr, "current directory: %s\n", currentDir.absolutePath().toUtf8().constData() );
110  QStringList nameFilterList;
111  nameFilterList << QStringLiteral( "*.qgs" )
112  << QStringLiteral( "*.qgz" );
113  QFileInfoList projectFiles = currentDir.entryInfoList( nameFilterList, QDir::Files, QDir::Name );
114  for ( int x = 0; x < projectFiles.size(); x++ )
115  {
116  QgsMessageLog::logMessage( projectFiles.at( x ).absoluteFilePath(), QStringLiteral( "Server" ), Qgis::Info );
117  }
118  if ( projectFiles.isEmpty() )
119  {
120  return QFileInfo();
121  }
122  return projectFiles.at( 0 );
123 }
124 
125 void QgsServer::printRequestParameters( const QMap< QString, QString> &parameterMap, Qgis::MessageLevel logLevel )
126 {
127  if ( logLevel > Qgis::Info )
128  {
129  return;
130  }
131 
132  QMap< QString, QString>::const_iterator pIt = parameterMap.constBegin();
133  for ( ; pIt != parameterMap.constEnd(); ++pIt )
134  {
135  QgsMessageLog::logMessage( pIt.key() + ":" + pIt.value(), QStringLiteral( "Server" ), Qgis::Info );
136  }
137 }
138 
139 QString QgsServer::configPath( const QString &defaultConfigPath, const QString &configPath )
140 {
141  QString cfPath( defaultConfigPath );
142  QString projectFile = sSettings.projectFile();
143  if ( !projectFile.isEmpty() )
144  {
145  cfPath = projectFile;
146  QgsDebugMsg( QStringLiteral( "QGIS_PROJECT_FILE:%1" ).arg( cfPath ) );
147  }
148  else
149  {
150  if ( configPath.isEmpty() )
151  {
152  QgsMessageLog::logMessage( QStringLiteral( "Using default configuration file path: %1" ).arg( defaultConfigPath ), QStringLiteral( "Server" ), Qgis::Info );
153  }
154  else
155  {
156  cfPath = configPath;
157  QgsDebugMsg( QStringLiteral( "MAP:%1" ).arg( cfPath ) );
158  }
159  }
160  return cfPath;
161 }
162 
163 void QgsServer::initLocale()
164 {
165  // System locale override
166  if ( ! sSettings.overrideSystemLocale().isEmpty() )
167  {
168  QLocale::setDefault( QLocale( sSettings.overrideSystemLocale() ) );
169  }
170  // Number group separator settings
171  QLocale currentLocale;
172  if ( sSettings.showGroupSeparator() )
173  {
174  currentLocale.setNumberOptions( currentLocale.numberOptions() &= ~QLocale::NumberOption::OmitGroupSeparator );
175  }
176  else
177  {
178  currentLocale.setNumberOptions( currentLocale.numberOptions() |= QLocale::NumberOption::OmitGroupSeparator );
179  }
180  QLocale::setDefault( currentLocale );
181 }
182 
183 bool QgsServer::init()
184 {
185  if ( sInitialized )
186  {
187  return false;
188  }
189 
190  QCoreApplication::setOrganizationName( QgsApplication::QGIS_ORGANIZATION_NAME );
191  QCoreApplication::setOrganizationDomain( QgsApplication::QGIS_ORGANIZATION_DOMAIN );
192  QCoreApplication::setApplicationName( QgsApplication::QGIS_APPLICATION_NAME );
193 
195 
196 #if defined(SERVER_SKIP_ECW)
197  QgsMessageLog::logMessage( "Skipping GDAL ECW drivers in server.", "Server", Qgis::Info );
199  QgsApplication::skipGdalDriver( "JP2ECW" );
200 #endif
201 
202  // reload settings to take into account QCoreApplication and QgsApplication
203  // configuration
204  sSettings.load();
205 
206  // init and configure logger
209  if ( ! sSettings.logFile().isEmpty() )
210  {
211  QgsServerLogger::instance()->setLogFile( sSettings.logFile() );
212  }
213  else if ( sSettings.logStderr() )
214  {
216  }
217 
218  // Configure locale
219  initLocale();
220 
221  // log settings currently used
222  sSettings.logSummary();
223 
224  setupNetworkAccessManager();
225  QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
226 
227  // Instantiate the plugin directory so that providers are loaded
229  QgsMessageLog::logMessage( "Prefix PATH: " + QgsApplication::prefixPath(), QStringLiteral( "Server" ), Qgis::Info );
230  QgsMessageLog::logMessage( "Plugin PATH: " + QgsApplication::pluginPath(), QStringLiteral( "Server" ), Qgis::Info );
231  QgsMessageLog::logMessage( "PkgData PATH: " + QgsApplication::pkgDataPath(), QStringLiteral( "Server" ), Qgis::Info );
232  QgsMessageLog::logMessage( "User DB PATH: " + QgsApplication::qgisUserDatabaseFilePath(), QStringLiteral( "Server" ), Qgis::Info );
233  QgsMessageLog::logMessage( "Auth DB PATH: " + QgsApplication::qgisAuthDatabaseFilePath(), QStringLiteral( "Server" ), Qgis::Info );
234 #ifdef Q_OS_WIN
235  QgsMessageLog::logMessage( "SVG PATHS: " + QgsApplication::svgPaths().join( ';' ), QStringLiteral( "Server" ), Qgis::Info );
236 #else
237  QgsMessageLog::logMessage( "SVG PATHS: " + QgsApplication::svgPaths().join( ':' ), QStringLiteral( "Server" ), Qgis::Info );
238 #endif
239 
240  QgsApplication::createDatabase(); //init qgis.db (e.g. necessary for user crs)
241 
242  // Initialize the authentication system
243  // creates or uses qgis-auth.db in ~/.qgis3/ or directory defined by QGIS_AUTH_DB_DIR_PATH env variable
244  // set the master password as first line of file defined by QGIS_AUTH_PASSWORD_FILE env variable
245  // (QGIS_AUTH_PASSWORD_FILE variable removed from environment after accessing)
247 
248  QString defaultConfigFilePath;
249  QFileInfo projectFileInfo = defaultProjectFile(); //try to find a .qgs/.qgz file in the server directory
250  if ( projectFileInfo.exists() )
251  {
252  defaultConfigFilePath = projectFileInfo.absoluteFilePath();
253  QgsMessageLog::logMessage( "Using default project file: " + defaultConfigFilePath, QStringLiteral( "Server" ), Qgis::Info );
254  }
255  else
256  {
257  QFileInfo adminSLDFileInfo = defaultAdminSLD();
258  if ( adminSLDFileInfo.exists() )
259  {
260  defaultConfigFilePath = adminSLDFileInfo.absoluteFilePath();
261  }
262  }
263  // Store the config file path
264  sConfigFilePath = new QString( defaultConfigFilePath );
265 
266  //create cache for capabilities XML
267  sCapabilitiesCache = new QgsCapabilitiesCache();
268 
269  QgsFontUtils::loadStandardTestFonts( QStringList() << QStringLiteral( "Roman" ) << QStringLiteral( "Bold" ) );
270 
271  sServiceRegistry = new QgsServiceRegistry();
272 
273  sServerInterface = new QgsServerInterfaceImpl( sCapabilitiesCache, sServiceRegistry, &sSettings );
274 
275  // Load service module
276  QString modulePath = QgsApplication::libexecPath() + "server";
277  qDebug() << "Initializing server modules from " << modulePath << endl;
278  sServiceRegistry->init( modulePath, sServerInterface );
279 
280  sInitialized = true;
281  QgsMessageLog::logMessage( QStringLiteral( "Server initialized" ), QStringLiteral( "Server" ), Qgis::Info );
282  return true;
283 }
284 
285 
286 
287 void QgsServer::putenv( const QString &var, const QString &val )
288 {
289 #ifdef _MSC_VER
290  _putenv_s( var.toStdString().c_str(), val.toStdString().c_str() );
291 #else
292  setenv( var.toStdString().c_str(), val.toStdString().c_str(), 1 );
293 #endif
294  sSettings.load( var );
295 }
296 
297 void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project )
298 {
300  QTime time; //used for measuring request time if loglevel < 1
301 
302  qApp->processEvents();
303 
304  if ( logLevel == Qgis::Info )
305  {
306  time.start();
307  }
308 
309  // Pass the filters to the requestHandler, this is needed for the following reasons:
310  // Allow server request to call sendResponse plugin hook if enabled
311  QgsFilterResponseDecorator responseDecorator( sServerInterface->filters(), response );
312 
313  //Request handler
314  QgsRequestHandler requestHandler( request, response );
315 
316  try
317  {
318  // TODO: split parse input into plain parse and processing from specific services
319  requestHandler.parseInput();
320  }
321  catch ( QgsMapServiceException &e )
322  {
323  QgsMessageLog::logMessage( "Parse input exception: " + e.message(), QStringLiteral( "Server" ), Qgis::Critical );
324  requestHandler.setServiceException( e );
325  }
326 
327  // Set the request handler into the interface for plugins to manipulate it
328  sServerInterface->setRequestHandler( &requestHandler );
329 
330  // Initialize configfilepath so that is is available
331  // before calling plugin methods
332  // Note that plugins may still change that value using
333  // setConfigFilePath() interface method
334  if ( ! project )
335  {
336  QString configFilePath = configPath( *sConfigFilePath, request.serverParameters().map() );
337  sServerInterface->setConfigFilePath( configFilePath );
338  }
339  else
340  {
341  sServerInterface->setConfigFilePath( project->fileName() );
342  }
343 
344  // Call requestReady() method (if enabled)
345  responseDecorator.start();
346 
347  // Plugins may have set exceptions
348  if ( !requestHandler.exceptionRaised() )
349  {
350  try
351  {
352  const QgsServerParameters params = request.serverParameters();
353  printRequestParameters( params.toMap(), logLevel );
354 
355  //Config file path
356  if ( ! project )
357  {
358  // load the project if needed and not empty
359  project = mConfigCache->project( sServerInterface->configFilePath() );
360  if ( ! project )
361  {
362  throw QgsServerException( QStringLiteral( "Project file error" ) );
363  }
364  }
365 
366  if ( ! params.fileName().isEmpty() )
367  {
368  const QString value = QString( "attachment; filename=\"%1\"" ).arg( params.fileName() );
369  requestHandler.setResponseHeader( QStringLiteral( "Content-Disposition" ), value );
370  }
371 
372  // Lookup for service
373  QgsService *service = sServiceRegistry->getService( params.service(), params.version() );
374  if ( service )
375  {
376  service->executeRequest( request, responseDecorator, project );
377  }
378  else
379  {
380  throw QgsOgcServiceException( QStringLiteral( "Service configuration error" ),
381  QStringLiteral( "Service unknown or unsupported" ) );
382  }
383  }
384  catch ( QgsServerException &ex )
385  {
386  responseDecorator.write( ex );
387  }
388  catch ( QgsException &ex )
389  {
390  // Internal server error
391  response.sendError( 500, ex.what() );
392  }
393  }
394  // Terminate the response
395  responseDecorator.finish();
396 
397  // We are done using requestHandler in plugins, make sure we don't access
398  // to a deleted request handler from Python bindings
399  sServerInterface->clearRequestHandler();
400 
401  if ( logLevel == Qgis::Info )
402  {
403  QgsMessageLog::logMessage( "Request finished in " + QString::number( time.elapsed() ) + " ms", QStringLiteral( "Server" ), Qgis::Info );
404  }
405 }
406 
407 
408 #ifdef HAVE_SERVER_PYTHON_PLUGINS
409 void QgsServer::initPython()
410 {
411  // Init plugins
412  if ( ! QgsServerPlugins::initPlugins( sServerInterface ) )
413  {
414  QgsMessageLog::logMessage( QStringLiteral( "No server python plugins are available" ), QStringLiteral( "Server" ), Qgis::Info );
415  }
416  else
417  {
418  QgsMessageLog::logMessage( QStringLiteral( "Server python plugins loaded" ), QStringLiteral( "Server" ), Qgis::Info );
419  }
420 }
421 #endif
422 
QMap< QString, QString > toMap() const
Returns all parameters in a map.
QString logFile() const
Returns the log file.
QString cacheDirectory() const
Returns the cache directory.
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
virtual void sendError(int code, const QString &message)=0
Send error This method delegates error handling at the server level.
void setConfigFilePath(const QString &configFilePath) override
Set the configuration file path.
QString configFilePath() override
Returns the configuration file path.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Exception base class for service exceptions.
static bool initPlugins(QgsServerInterface *interface)
Initializes the Python plugins.
Provides a way to retrieve settings by prioritizing according to environment variables, ini file and default values.
Class defining decorator for calling filter&#39;s hooks.
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition: qgis.h:79
void putenv(const QString &var, const QString &val)
Set environment variable.
Definition: qgsserver.cpp:287
QString fileName() const
Returns FILE_NAME parameter as a string or an empty string if not defined.
bool showGroupSeparator() const
Show group (thousand) separator.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
void setLogLevel(Qgis::MessageLevel level)
Set the current log level.
Interfaces exposed by QGIS Server and made available to plugins.
static QString pluginPath()
Returns the path to the application plugin directory.
QString projectFile() const
Returns the QGS project file to use.
QString version() const
Returns VERSION parameter as a string or an empty string if not defined.
static bool createDatabase(QString *errorMessage=nullptr)
initialize qgis.db
virtual void executeRequest(const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project)=0
Execute the requests and set result in QgsServerRequest.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QgsServerParameters serverParameters() const
Returns parameters.
qint64 cacheSize() const
Returns the cache size.
void init(const QString &nativeModulepath, QgsServerInterface *serverIface=nullptr)
Initialize registry, load modules and auto register services.
Reads and writes project states.
Definition: qgsproject.h:89
QgsServerFiltersMap filters() override
Returns the list of current QgsServerFilter.
void logSummary() const
Log a summary of settings currently loaded.
static bool loadStandardTestFonts(const QStringList &loadstyles)
Loads standard test fonts from filesystem or qrc resource.
void setLogStderr()
Activates logging to stderr.
This class is an interface hiding the details of reading input and writing output from/to a wms reque...
static const char * QGIS_ORGANIZATION_NAME
const QgsProject * project(const QString &path)
If the project is not cached yet, then the project is read thanks to the path.
Qgis::MessageLevel logLevel() const
Gets the current log level.
QString map() const
Returns MAP parameter as a string or an empty string if not defined.
Exception base class for server exceptions.
static QString pkgDataPath()
Returns the common root path of all application data directories.
static void skipGdalDriver(const QString &driver)
Sets the GDAL_SKIP environment variable to include the specified driver and then calls GDALDriverMana...
QgsService Class defining interfaces for QGIS server services.
Definition: qgsservice.h:39
A cache for capabilities xml documents (by configuration file path)
QgsServerRequest Class defining request interface passed to services QgsService::executeRequest() met...
bool logStderr() const
Returns whether logging to stderr is activated.
static QgsAuthManager * authManager()
Returns the application&#39;s authentication manager instance.
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
QString what() const
Definition: qgsexception.h:48
QgsServiceRegistry Class defining the registry manager for QGIS server services.
static const char * QGIS_ORGANIZATION_DOMAIN
static void init(QString profileFolder=QString())
This method initializes paths etc for QGIS.
QgsService * getService(const QString &name, const QString &version=QString())
Retrieve a service from its name.
QgsServerParameters provides an interface to retrieve and manipulate global parameters received from ...
void load()
Load settings according to current environment variables.
static QgsServerLogger * instance()
Gets the singleton instance.
bool init(const QString &pluginPath=QString(), const QString &authDatabasePath=QString())
init initialize QCA, prioritize qca-ossl plugin and optionally set up the authentication database ...
static QStringList svgPaths()
Returns the paths to svg directories.
void clearRequestHandler() override
Clear the request handler.
void setLogFile(const QString &filename=QString())
Set the current log file.
QgsServerResponse Class defining response interface passed to services QgsService::executeRequest() m...
static QString qgisAuthDatabaseFilePath()
Returns the path to the user authentication database file: qgis-auth.db.
static QgsConfigCache * instance()
Returns the current instance.
QString message() const
Returns the exception message.
QString overrideSystemLocale() const
Overrides system locale.
static QString libexecPath()
Returns the path with utility executables (help viewer, crssync, ...)
static QString prefixPath()
Returns the path to the application prefix directory.
static const char * QGIS_APPLICATION_NAME
Exception class for WMS service exceptions (for compatibility only).
Defines a QGIS exception class.
Definition: qgsexception.h:34
network access manager for QGISThis class implements the QGIS network access manager.
QString service() const
Returns SERVICE parameter as a string or an empty string if not defined.
QString fileName
Definition: qgsproject.h:93
Qgis::MessageLevel logLevel() const
Returns the log level.
QgsServer()
Creates the server instance.
Definition: qgsserver.cpp:68
void setRequestHandler(QgsRequestHandler *requestHandler) override
Set the request handler.
void handleRequest(QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project=nullptr)
Handles the request.
Definition: qgsserver.cpp:297