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