QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 "qgsauthmanager.h"
25 #include "qgscapabilitiescache.h"
26 #include "qgsfontutils.h"
27 #include "qgsrequesthandler.h"
28 #include "qgsproject.h"
29 #include "qgsproviderregistry.h"
30 #include "qgslogger.h"
31 #include "qgsmapserviceexception.h"
33 #include "qgsserverlogger.h"
34 #include "qgsserverrequest.h"
36 #include "qgsservice.h"
37 #include "qgsserverapi.h"
38 #include "qgsserverapicontext.h"
39 #include "qgsserverparameters.h"
40 #include "qgsapplication.h"
41 
42 #include <QDomDocument>
43 #include <QNetworkDiskCache>
44 #include <QSettings>
45 #include <QElapsedTimer>
46 
47 // TODO: remove, it's only needed by a single debug message
48 #include <fcgi_stdio.h>
49 #include <cstdlib>
50 
51 
52 
53 // Server status static initializers.
54 // Default values are for C++, SIP bindings will override their
55 // options in in init()
56 
57 QString *QgsServer::sConfigFilePath = nullptr;
58 QgsCapabilitiesCache *QgsServer::sCapabilitiesCache = nullptr;
59 QgsServerInterfaceImpl *QgsServer::sServerInterface = nullptr;
60 // Initialization must run once for all servers
61 bool QgsServer::sInitialized = false;
62 
63 QgsServiceRegistry *QgsServer::sServiceRegistry = nullptr;
64 
66 
68 {
69  // QgsApplication must exist
70  if ( qobject_cast<QgsApplication *>( qApp ) == nullptr )
71  {
72  qFatal( "A QgsApplication must exist before a QgsServer instance can be created." );
73  abort();
74  }
75  init();
76  mConfigCache = QgsConfigCache::instance();
77 }
78 
79 QString &QgsServer::serverName()
80 {
81  static QString *name = new QString( QStringLiteral( "qgis_server" ) );
82  return *name;
83 }
84 
85 
86 QFileInfo QgsServer::defaultAdminSLD()
87 {
88  return QFileInfo( QStringLiteral( "admin.sld" ) );
89 }
90 
91 void QgsServer::setupNetworkAccessManager()
92 {
93  QSettings settings;
95  QNetworkDiskCache *cache = new QNetworkDiskCache( nullptr );
96  qint64 cacheSize = sSettings()->cacheSize();
97  QString cacheDirectory = sSettings()->cacheDirectory();
98  cache->setCacheDirectory( cacheDirectory );
99  cache->setMaximumCacheSize( cacheSize );
100  QgsMessageLog::logMessage( QStringLiteral( "cacheDirectory: %1" ).arg( cache->cacheDirectory() ), QStringLiteral( "Server" ), Qgis::Info );
101  QgsMessageLog::logMessage( QStringLiteral( "maximumCacheSize: %1" ).arg( cache->maximumCacheSize() ), QStringLiteral( "Server" ), Qgis::Info );
102  nam->setCache( cache );
103 }
104 
105 QFileInfo QgsServer::defaultProjectFile()
106 {
107  QDir currentDir;
108  fprintf( FCGI_stderr, "current directory: %s\n", currentDir.absolutePath().toUtf8().constData() );
109  QStringList nameFilterList;
110  nameFilterList << QStringLiteral( "*.qgs" )
111  << QStringLiteral( "*.qgz" );
112  QFileInfoList projectFiles = currentDir.entryInfoList( nameFilterList, QDir::Files, QDir::Name );
113  for ( int x = 0; x < projectFiles.size(); x++ )
114  {
115  QgsMessageLog::logMessage( projectFiles.at( x ).absoluteFilePath(), QStringLiteral( "Server" ), Qgis::Info );
116  }
117  if ( projectFiles.isEmpty() )
118  {
119  return QFileInfo();
120  }
121  return projectFiles.at( 0 );
122 }
123 
124 void QgsServer::printRequestParameters( const QMap< QString, QString> &parameterMap, Qgis::MessageLevel logLevel )
125 {
126  if ( logLevel > Qgis::Info )
127  {
128  return;
129  }
130 
131  QMap< QString, QString>::const_iterator pIt = parameterMap.constBegin();
132  for ( ; pIt != parameterMap.constEnd(); ++pIt )
133  {
134  QgsMessageLog::logMessage( pIt.key() + ":" + pIt.value(), QStringLiteral( "Server" ), Qgis::Info );
135  }
136 }
137 
138 QString QgsServer::configPath( const QString &defaultConfigPath, const QString &configPath )
139 {
140  QString cfPath( defaultConfigPath );
141  QString projectFile = sSettings()->projectFile();
142  if ( !projectFile.isEmpty() )
143  {
144  cfPath = projectFile;
145  QgsDebugMsg( QStringLiteral( "QGIS_PROJECT_FILE:%1" ).arg( cfPath ) );
146  }
147  else
148  {
149  if ( configPath.isEmpty() )
150  {
151  // Read it from the environment, because a rewrite rule may have rewritten it
152  if ( getenv( "QGIS_PROJECT_FILE" ) )
153  {
154  cfPath = getenv( "QGIS_PROJECT_FILE" );
155  QgsMessageLog::logMessage( QStringLiteral( "Using configuration file path from environment: %1" ).arg( cfPath ), QStringLiteral( "Server" ), Qgis::Info );
156  }
157  else if ( ! defaultConfigPath.isEmpty() )
158  {
159  QgsMessageLog::logMessage( QStringLiteral( "Using default configuration file path: %1" ).arg( defaultConfigPath ), QStringLiteral( "Server" ), Qgis::Info );
160  }
161  }
162  else
163  {
164  cfPath = configPath;
165  QgsDebugMsg( QStringLiteral( "MAP:%1" ).arg( cfPath ) );
166  }
167  }
168  return cfPath;
169 }
170 
171 void QgsServer::initLocale()
172 {
173  // System locale override
174  if ( ! sSettings()->overrideSystemLocale().isEmpty() )
175  {
176  QLocale::setDefault( QLocale( sSettings()->overrideSystemLocale() ) );
177  }
178  // Number group separator settings
179  QLocale currentLocale;
180  if ( sSettings()->showGroupSeparator() )
181  {
182  currentLocale.setNumberOptions( currentLocale.numberOptions() &= ~QLocale::NumberOption::OmitGroupSeparator );
183  }
184  else
185  {
186  currentLocale.setNumberOptions( currentLocale.numberOptions() |= QLocale::NumberOption::OmitGroupSeparator );
187  }
188  QLocale::setDefault( currentLocale );
189 }
190 
191 bool QgsServer::init()
192 {
193  if ( sInitialized )
194  {
195  return false;
196  }
197 
198  QCoreApplication::setOrganizationName( QgsApplication::QGIS_ORGANIZATION_NAME );
199  QCoreApplication::setOrganizationDomain( QgsApplication::QGIS_ORGANIZATION_DOMAIN );
200  QCoreApplication::setApplicationName( QgsApplication::QGIS_APPLICATION_NAME );
201 
203 
204 #if defined(SERVER_SKIP_ECW)
205  QgsMessageLog::logMessage( "Skipping GDAL ECW drivers in server.", "Server", Qgis::Info );
207  QgsApplication::skipGdalDriver( "JP2ECW" );
208 #endif
209 
210  // reload settings to take into account QCoreApplication and QgsApplication
211  // configuration
212  sSettings()->load();
213 
214  // init and configure logger
216  QgsServerLogger::instance()->setLogLevel( sSettings()->logLevel() );
217  if ( ! sSettings()->logFile().isEmpty() )
218  {
219  QgsServerLogger::instance()->setLogFile( sSettings()->logFile() );
220  }
221  else if ( sSettings()->logStderr() )
222  {
224  }
225 
226  // Configure locale
227  initLocale();
228 
229  // log settings currently used
230  sSettings()->logSummary();
231 
232  setupNetworkAccessManager();
233  QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
234 
235  // Instantiate the plugin directory so that providers are loaded
237  QgsMessageLog::logMessage( "Prefix PATH: " + QgsApplication::prefixPath(), QStringLiteral( "Server" ), Qgis::Info );
238  QgsMessageLog::logMessage( "Plugin PATH: " + QgsApplication::pluginPath(), QStringLiteral( "Server" ), Qgis::Info );
239  QgsMessageLog::logMessage( "PkgData PATH: " + QgsApplication::pkgDataPath(), QStringLiteral( "Server" ), Qgis::Info );
240  QgsMessageLog::logMessage( "User DB PATH: " + QgsApplication::qgisUserDatabaseFilePath(), QStringLiteral( "Server" ), Qgis::Info );
241  QgsMessageLog::logMessage( "Auth DB PATH: " + QgsApplication::qgisAuthDatabaseFilePath(), QStringLiteral( "Server" ), Qgis::Info );
242  QgsMessageLog::logMessage( "SVG PATHS: " + QgsApplication::svgPaths().join( QDir::listSeparator() ), QStringLiteral( "Server" ), Qgis::Info );
243 
244  QgsApplication::createDatabase(); //init qgis.db (e.g. necessary for user crs)
245 
246  // Initialize the authentication system
247  // creates or uses qgis-auth.db in ~/.qgis3/ or directory defined by QGIS_AUTH_DB_DIR_PATH env variable
248  // set the master password as first line of file defined by QGIS_AUTH_PASSWORD_FILE env variable
249  // (QGIS_AUTH_PASSWORD_FILE variable removed from environment after accessing)
251 
252  QString defaultConfigFilePath;
253  QFileInfo projectFileInfo = defaultProjectFile(); //try to find a .qgs/.qgz file in the server directory
254  if ( projectFileInfo.exists() )
255  {
256  defaultConfigFilePath = projectFileInfo.absoluteFilePath();
257  QgsMessageLog::logMessage( "Using default project file: " + defaultConfigFilePath, QStringLiteral( "Server" ), Qgis::Info );
258  }
259  else
260  {
261  QFileInfo adminSLDFileInfo = defaultAdminSLD();
262  if ( adminSLDFileInfo.exists() )
263  {
264  defaultConfigFilePath = adminSLDFileInfo.absoluteFilePath();
265  }
266  }
267  // Store the config file path
268  sConfigFilePath = new QString( defaultConfigFilePath );
269 
270  //create cache for capabilities XML
271  sCapabilitiesCache = new QgsCapabilitiesCache();
272 
273  QgsFontUtils::loadStandardTestFonts( QStringList() << QStringLiteral( "Roman" ) << QStringLiteral( "Bold" ) );
274 
275  sServiceRegistry = new QgsServiceRegistry();
276 
277  sServerInterface = new QgsServerInterfaceImpl( sCapabilitiesCache, sServiceRegistry, sSettings() );
278 
279  // Load service module
280  QString modulePath = QgsApplication::libexecPath() + "server";
281  // qDebug() << QStringLiteral( "Initializing server modules from: %1" ).arg( modulePath );
282  sServiceRegistry->init( modulePath, sServerInterface );
283 
284  sInitialized = true;
285  QgsMessageLog::logMessage( QStringLiteral( "Server initialized" ), QStringLiteral( "Server" ), Qgis::Info );
286  return true;
287 }
288 
289 
290 
291 void QgsServer::putenv( const QString &var, const QString &val )
292 {
293  if ( val.isEmpty() )
294  {
295  qunsetenv( var.toUtf8().data() );
296  }
297  else
298  {
299  qputenv( var.toUtf8().data(), val.toUtf8() );
300  }
301  sSettings()->load( var );
302 }
303 
304 void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project )
305 {
307  QElapsedTimer time; //used for measuring request time if loglevel < 1
308 
309  qApp->processEvents();
310 
311  if ( logLevel == Qgis::Info )
312  {
313  time.start();
314  }
315 
316  // Pass the filters to the requestHandler, this is needed for the following reasons:
317  // Allow server request to call sendResponse plugin hook if enabled
318  QgsFilterResponseDecorator responseDecorator( sServerInterface->filters(), response );
319 
320  //Request handler
321  QgsRequestHandler requestHandler( request, response );
322 
323  try
324  {
325  // TODO: split parse input into plain parse and processing from specific services
326  requestHandler.parseInput();
327  }
328  catch ( QgsMapServiceException &e )
329  {
330  QgsMessageLog::logMessage( "Parse input exception: " + e.message(), QStringLiteral( "Server" ), Qgis::Critical );
331  requestHandler.setServiceException( e );
332  }
333 
334  // Set the request handler into the interface for plugins to manipulate it
335  sServerInterface->setRequestHandler( &requestHandler );
336 
337  // Initialize configfilepath so that is is available
338  // before calling plugin methods
339  // Note that plugins may still change that value using
340  // setConfigFilePath() interface method
341  if ( ! project )
342  {
343  QString configFilePath = configPath( *sConfigFilePath, request.serverParameters().map() );
344  sServerInterface->setConfigFilePath( configFilePath );
345  }
346  else
347  {
348  sServerInterface->setConfigFilePath( project->fileName() );
349  }
350 
351  // Call requestReady() method (if enabled)
352  // This may also throw exceptions if there are errors in python plugins code
353  try
354  {
355  responseDecorator.start();
356  }
357  catch ( QgsException &ex )
358  {
359  // Internal server error
360  response.sendError( 500, QStringLiteral( "Internal Server Error" ) );
361  QgsMessageLog::logMessage( ex.what(), QStringLiteral( "Server" ), Qgis::Critical );
362  }
363 
364  // Plugins may have set exceptions
365  if ( !requestHandler.exceptionRaised() )
366  {
367  try
368  {
369  const QgsServerParameters params = request.serverParameters();
370  printRequestParameters( params.toMap(), logLevel );
371 
372  // Setup project (config file path)
373  if ( ! project )
374  {
375  QString configFilePath = configPath( *sConfigFilePath, params.map() );
376 
377  // load the project if needed and not empty
378  project = mConfigCache->project( configFilePath );
379  }
380 
381  if ( project )
382  {
383  sServerInterface->setConfigFilePath( project->fileName() );
384  }
385 
386  // Dispatcher: if SERVICE is set, we assume a OWS service, if not, let's try an API
387  // TODO: QGIS 4 fix the OWS services and treat them as APIs
388  QgsServerApi *api = nullptr;
389  if ( params.service().isEmpty() && ( api = sServiceRegistry->apiForRequest( request ) ) )
390  {
391  QgsServerApiContext context { api->rootPath(), &request, &responseDecorator, project, sServerInterface };
392  api->executeRequest( context );
393  }
394  else
395  {
396 
397  // Project is mandatory for OWS at this point
398  if ( ! project )
399  {
400  throw QgsServerException( QStringLiteral( "Project file error" ) );
401  }
402 
403  if ( ! params.fileName().isEmpty() )
404  {
405  const QString value = QString( "attachment; filename=\"%1\"" ).arg( params.fileName() );
406  requestHandler.setResponseHeader( QStringLiteral( "Content-Disposition" ), value );
407  }
408 
409  // Lookup for service
410  QgsService *service = sServiceRegistry->getService( params.service(), params.version() );
411  if ( service )
412  {
413  service->executeRequest( request, responseDecorator, project );
414  }
415  else
416  {
417  throw QgsOgcServiceException( QStringLiteral( "Service configuration error" ),
418  QStringLiteral( "Service unknown or unsupported" ) );
419  }
420  }
421  }
422  catch ( QgsServerException &ex )
423  {
424  responseDecorator.write( ex );
425  QString format;
426  QgsMessageLog::logMessage( ex.formatResponse( format ), QStringLiteral( "Server" ), Qgis::Warning );
427  }
428  catch ( QgsException &ex )
429  {
430  // Internal server error
431  response.sendError( 500, QStringLiteral( "Internal Server Error" ) );
432  QgsMessageLog::logMessage( ex.what(), QStringLiteral( "Server" ), Qgis::Critical );
433  }
434  }
435 
436  // Terminate the response
437  // This may also throw exceptions if there are errors in python plugins code
438  try
439  {
440  responseDecorator.finish();
441  }
442  catch ( QgsException &ex )
443  {
444  // Internal server error
445  response.sendError( 500, QStringLiteral( "Internal Server Error" ) );
446  QgsMessageLog::logMessage( ex.what(), QStringLiteral( "Server" ), Qgis::Critical );
447  }
448 
449 
450  // We are done using requestHandler in plugins, make sure we don't access
451  // to a deleted request handler from Python bindings
452  sServerInterface->clearRequestHandler();
453 
454  if ( logLevel == Qgis::Info )
455  {
456  QgsMessageLog::logMessage( "Request finished in " + QString::number( time.elapsed() ) + " ms", QStringLiteral( "Server" ), Qgis::Info );
457  }
458 }
459 
460 
461 #ifdef HAVE_SERVER_PYTHON_PLUGINS
462 void QgsServer::initPython()
463 {
464  // Init plugins
465  if ( ! QgsServerPlugins::initPlugins( sServerInterface ) )
466  {
467  QgsMessageLog::logMessage( QStringLiteral( "No server python plugins are available" ), QStringLiteral( "Server" ), Qgis::Info );
468  }
469  else
470  {
471  QgsMessageLog::logMessage( QStringLiteral( "Server python plugins loaded" ), QStringLiteral( "Server" ), Qgis::Info );
472  }
473 }
474 #endif
475 
Server generic API endpoint abstract base class.
Definition: qgsserverapi.h:80
QMap< QString, QString > toMap() const
Returns all parameters in a map.
virtual const QString rootPath() const =0
Returns the root path for the API.
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.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Exception base class for service exceptions.
static bool initPlugins(QgsServerInterface *interface)
Initializes the Python plugins.
Qgis::MessageLevel logLevel() const
Gets the current log level.
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:88
QString message() const
Returns the exception message.
void putenv(const QString &var, const QString &val)
Set environment variable.
Definition: qgsserver.cpp:291
QString what() const
Definition: qgsexception.h:48
QString map() const
Returns MAP parameter as a string or an empty string if not defined.
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.
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).
QString version() const
Returns VERSION parameter as a string or an empty string if not defined.
void init(const QString &nativeModulepath, QgsServerInterface *serverIface=nullptr)
Initialize registry, load modules and auto register services.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:91
QgsServerFiltersMap filters() override
Returns the list of current QgsServerFilter.
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
Q_GLOBAL_STATIC(QgsServerSettings, sSettings)
const QgsProject * project(const QString &path)
If the project is not cached yet, then the project is read thanks to the path.
Exception base class for server exceptions.
static QString pkgDataPath()
Returns the common root path of all application data directories.
The QgsServerApiContext class encapsulates the resources for a particular client request: the request...
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...
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 service() const
Returns SERVICE parameter as a string or an empty string if not defined.
virtual QByteArray formatResponse(QString &responseFormat) const
Formats the exception for sending to client.
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 ...
virtual void executeRequest(const QgsServerApiContext &context) const =0
Executes a request by passing the given context to the API handlers.
QgsServerParameters serverParameters() const
Returns parameters.
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...
QgsServerApi * apiForRequest(const QgsServerRequest &request) const
Searches the API register for an API matching the request and returns a (possibly NULL) pointer to it...
static QString qgisAuthDatabaseFilePath()
Returns the path to the user authentication database file: qgis-auth.db.
static QgsConfigCache * instance()
Returns the current instance.
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 fileName
Definition: qgsproject.h:95
QgsServer()
Creates the server instance.
Definition: qgsserver.cpp:67
void setRequestHandler(QgsRequestHandler *requestHandler) override
Set the request handler.
QString fileName() const
Returns FILE_NAME parameter as a string or an empty string if not defined.
void handleRequest(QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project=nullptr)
Handles the request.
Definition: qgsserver.cpp:304