QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 "qgsserverparameters.h"
38 #include "qgsapplication.h"
39 
40 #include <QDomDocument>
41 #include <QNetworkDiskCache>
42 #include <QSettings>
43 #include <QDateTime>
44 
45 // TODO: remove, it's only needed by a single debug message
46 #include <fcgi_stdio.h>
47 #include <cstdlib>
48 
49 
50 
51 // Server status static initializers.
52 // Default values are for C++, SIP bindings will override their
53 // options in in init()
54 
55 QString *QgsServer::sConfigFilePath = nullptr;
56 QgsCapabilitiesCache *QgsServer::sCapabilitiesCache = nullptr;
57 QgsServerInterfaceImpl *QgsServer::sServerInterface = nullptr;
58 // Initialization must run once for all servers
59 bool QgsServer::sInitialized = false;
60 QgsServerSettings QgsServer::sSettings;
61 
62 QgsServiceRegistry *QgsServer::sServiceRegistry = nullptr;
63 
65 {
66  // QgsApplication must exist
67  if ( qobject_cast<QgsApplication *>( qApp ) == nullptr )
68  {
69  qFatal( "A QgsApplication must exist before a QgsServer instance can be created." );
70  abort();
71  }
72  init();
73  mConfigCache = QgsConfigCache::instance();
74 }
75 
76 QString &QgsServer::serverName()
77 {
78  static QString *name = new QString( QStringLiteral( "qgis_server" ) );
79  return *name;
80 }
81 
82 
83 QFileInfo QgsServer::defaultAdminSLD()
84 {
85  return QFileInfo( QStringLiteral( "admin.sld" ) );
86 }
87 
88 void QgsServer::setupNetworkAccessManager()
89 {
90  QSettings settings;
92  QNetworkDiskCache *cache = new QNetworkDiskCache( nullptr );
93  qint64 cacheSize = sSettings.cacheSize();
94  QString cacheDirectory = sSettings.cacheDirectory();
95  cache->setCacheDirectory( cacheDirectory );
96  cache->setMaximumCacheSize( cacheSize );
97  QgsMessageLog::logMessage( QStringLiteral( "cacheDirectory: %1" ).arg( cache->cacheDirectory() ), QStringLiteral( "Server" ), Qgis::Info );
98  QgsMessageLog::logMessage( QStringLiteral( "maximumCacheSize: %1" ).arg( cache->maximumCacheSize() ), QStringLiteral( "Server" ), Qgis::Info );
99  nam->setCache( cache );
100 }
101 
102 QFileInfo QgsServer::defaultProjectFile()
103 {
104  QDir currentDir;
105  fprintf( FCGI_stderr, "current directory: %s\n", currentDir.absolutePath().toUtf8().constData() );
106  QStringList nameFilterList;
107  nameFilterList << QStringLiteral( "*.qgs" )
108  << QStringLiteral( "*.qgz" );
109  QFileInfoList projectFiles = currentDir.entryInfoList( nameFilterList, QDir::Files, QDir::Name );
110  for ( int x = 0; x < projectFiles.size(); x++ )
111  {
112  QgsMessageLog::logMessage( projectFiles.at( x ).absoluteFilePath(), QStringLiteral( "Server" ), Qgis::Info );
113  }
114  if ( projectFiles.isEmpty() )
115  {
116  return QFileInfo();
117  }
118  return projectFiles.at( 0 );
119 }
120 
121 void QgsServer::printRequestParameters( const QMap< QString, QString> &parameterMap, Qgis::MessageLevel logLevel )
122 {
123  if ( logLevel > Qgis::Info )
124  {
125  return;
126  }
127 
128  QMap< QString, QString>::const_iterator pIt = parameterMap.constBegin();
129  for ( ; pIt != parameterMap.constEnd(); ++pIt )
130  {
131  QgsMessageLog::logMessage( pIt.key() + ":" + pIt.value(), QStringLiteral( "Server" ), Qgis::Info );
132  }
133 }
134 
135 QString QgsServer::configPath( const QString &defaultConfigPath, const QString &configPath )
136 {
137  QString cfPath( defaultConfigPath );
138  QString projectFile = sSettings.projectFile();
139  if ( !projectFile.isEmpty() )
140  {
141  cfPath = projectFile;
142  QgsDebugMsg( QStringLiteral( "QGIS_PROJECT_FILE:%1" ).arg( cfPath ) );
143  }
144  else
145  {
146  if ( configPath.isEmpty() )
147  {
148  QgsMessageLog::logMessage( QStringLiteral( "Using default configuration file path: %1" ).arg( defaultConfigPath ), QStringLiteral( "Server" ), Qgis::Info );
149  }
150  else
151  {
152  cfPath = configPath;
153  QgsDebugMsg( QStringLiteral( "MAP:%1" ).arg( cfPath ) );
154  }
155  }
156  return cfPath;
157 }
158 
159 void QgsServer::initLocale()
160 {
161  // System locale override
162  if ( ! sSettings.overrideSystemLocale().isEmpty() )
163  {
164  QLocale::setDefault( QLocale( sSettings.overrideSystemLocale() ) );
165  }
166  // Number group separator settings
167  QLocale currentLocale;
168  if ( sSettings.showGroupSeparator() )
169  {
170  currentLocale.setNumberOptions( currentLocale.numberOptions() &= ~QLocale::NumberOption::OmitGroupSeparator );
171  }
172  else
173  {
174  currentLocale.setNumberOptions( currentLocale.numberOptions() |= QLocale::NumberOption::OmitGroupSeparator );
175  }
176  QLocale::setDefault( currentLocale );
177 }
178 
179 bool QgsServer::init()
180 {
181  if ( sInitialized )
182  {
183  return false;
184  }
185 
186  QCoreApplication::setOrganizationName( QgsApplication::QGIS_ORGANIZATION_NAME );
187  QCoreApplication::setOrganizationDomain( QgsApplication::QGIS_ORGANIZATION_DOMAIN );
188  QCoreApplication::setApplicationName( QgsApplication::QGIS_APPLICATION_NAME );
189 
191 
192 #if defined(SERVER_SKIP_ECW)
193  QgsMessageLog::logMessage( "Skipping GDAL ECW drivers in server.", "Server", Qgis::Info );
195  QgsApplication::skipGdalDriver( "JP2ECW" );
196 #endif
197 
198  // reload settings to take into account QCoreApplication and QgsApplication
199  // configuration
200  sSettings.load();
201 
202  // init and configure logger
205  if ( ! sSettings.logFile().isEmpty() )
206  {
207  QgsServerLogger::instance()->setLogFile( sSettings.logFile() );
208  }
209  else if ( sSettings.logStderr() )
210  {
212  }
213 
214  // Configure locale
215  initLocale();
216 
217  // log settings currently used
218  sSettings.logSummary();
219 
220  setupNetworkAccessManager();
221  QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
222 
223  // Instantiate the plugin directory so that providers are loaded
225  QgsMessageLog::logMessage( "Prefix PATH: " + QgsApplication::prefixPath(), QStringLiteral( "Server" ), Qgis::Info );
226  QgsMessageLog::logMessage( "Plugin PATH: " + QgsApplication::pluginPath(), QStringLiteral( "Server" ), Qgis::Info );
227  QgsMessageLog::logMessage( "PkgData PATH: " + QgsApplication::pkgDataPath(), QStringLiteral( "Server" ), Qgis::Info );
228  QgsMessageLog::logMessage( "User DB PATH: " + QgsApplication::qgisUserDatabaseFilePath(), QStringLiteral( "Server" ), Qgis::Info );
229  QgsMessageLog::logMessage( "Auth DB PATH: " + QgsApplication::qgisAuthDatabaseFilePath(), QStringLiteral( "Server" ), Qgis::Info );
230  QgsMessageLog::logMessage( "SVG PATHS: " + QgsApplication::svgPaths().join( QDir::listSeparator() ), QStringLiteral( "Server" ), Qgis::Info );
231 
232  QgsApplication::createDatabase(); //init qgis.db (e.g. necessary for user crs)
233 
234  // Initialize the authentication system
235  // creates or uses qgis-auth.db in ~/.qgis3/ or directory defined by QGIS_AUTH_DB_DIR_PATH env variable
236  // set the master password as first line of file defined by QGIS_AUTH_PASSWORD_FILE env variable
237  // (QGIS_AUTH_PASSWORD_FILE variable removed from environment after accessing)
239 
240  QString defaultConfigFilePath;
241  QFileInfo projectFileInfo = defaultProjectFile(); //try to find a .qgs/.qgz file in the server directory
242  if ( projectFileInfo.exists() )
243  {
244  defaultConfigFilePath = projectFileInfo.absoluteFilePath();
245  QgsMessageLog::logMessage( "Using default project file: " + defaultConfigFilePath, QStringLiteral( "Server" ), Qgis::Info );
246  }
247  else
248  {
249  QFileInfo adminSLDFileInfo = defaultAdminSLD();
250  if ( adminSLDFileInfo.exists() )
251  {
252  defaultConfigFilePath = adminSLDFileInfo.absoluteFilePath();
253  }
254  }
255  // Store the config file path
256  sConfigFilePath = new QString( defaultConfigFilePath );
257 
258  //create cache for capabilities XML
259  sCapabilitiesCache = new QgsCapabilitiesCache();
260 
261  QgsFontUtils::loadStandardTestFonts( QStringList() << QStringLiteral( "Roman" ) << QStringLiteral( "Bold" ) );
262 
263  sServiceRegistry = new QgsServiceRegistry();
264 
265  sServerInterface = new QgsServerInterfaceImpl( sCapabilitiesCache, sServiceRegistry, &sSettings );
266 
267  // Load service module
268  QString modulePath = QgsApplication::libexecPath() + "server";
269  qDebug() << "Initializing server modules from " << modulePath << endl;
270  sServiceRegistry->init( modulePath, sServerInterface );
271 
272  sInitialized = true;
273  QgsMessageLog::logMessage( QStringLiteral( "Server initialized" ), QStringLiteral( "Server" ), Qgis::Info );
274  return true;
275 }
276 
277 
278 
279 void QgsServer::putenv( const QString &var, const QString &val )
280 {
281 #ifdef _MSC_VER
282  _putenv_s( var.toStdString().c_str(), val.toStdString().c_str() );
283 #else
284  setenv( var.toStdString().c_str(), val.toStdString().c_str(), 1 );
285 #endif
286  sSettings.load( var );
287 }
288 
289 void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project )
290 {
292  QTime time; //used for measuring request time if loglevel < 1
293 
294  qApp->processEvents();
295 
296  if ( logLevel == Qgis::Info )
297  {
298  time.start();
299  }
300 
301  // Pass the filters to the requestHandler, this is needed for the following reasons:
302  // Allow server request to call sendResponse plugin hook if enabled
303  QgsFilterResponseDecorator responseDecorator( sServerInterface->filters(), response );
304 
305  //Request handler
306  QgsRequestHandler requestHandler( request, response );
307 
308  try
309  {
310  // TODO: split parse input into plain parse and processing from specific services
311  requestHandler.parseInput();
312  }
313  catch ( QgsMapServiceException &e )
314  {
315  QgsMessageLog::logMessage( "Parse input exception: " + e.message(), QStringLiteral( "Server" ), Qgis::Critical );
316  requestHandler.setServiceException( e );
317  }
318 
319  // Set the request handler into the interface for plugins to manipulate it
320  sServerInterface->setRequestHandler( &requestHandler );
321 
322  // Initialize configfilepath so that is is available
323  // before calling plugin methods
324  // Note that plugins may still change that value using
325  // setConfigFilePath() interface method
326  if ( ! project )
327  {
328  QString configFilePath = configPath( *sConfigFilePath, request.serverParameters().map() );
329  sServerInterface->setConfigFilePath( configFilePath );
330  }
331  else
332  {
333  sServerInterface->setConfigFilePath( project->fileName() );
334  }
335 
336  // Call requestReady() method (if enabled)
337  responseDecorator.start();
338 
339  // Plugins may have set exceptions
340  if ( !requestHandler.exceptionRaised() )
341  {
342  try
343  {
344  const QgsServerParameters params = request.serverParameters();
345  printRequestParameters( params.toMap(), logLevel );
346 
347  //Config file path
348  if ( ! project )
349  {
350  // load the project if needed and not empty
351  project = mConfigCache->project( sServerInterface->configFilePath() );
352  if ( ! project )
353  {
354  throw QgsServerException( QStringLiteral( "Project file error" ) );
355  }
356  }
357 
358  if ( ! params.fileName().isEmpty() )
359  {
360  const QString value = QString( "attachment; filename=\"%1\"" ).arg( params.fileName() );
361  requestHandler.setResponseHeader( QStringLiteral( "Content-Disposition" ), value );
362  }
363 
364  // Lookup for service
365  QgsService *service = sServiceRegistry->getService( params.service(), params.version() );
366  if ( service )
367  {
368  service->executeRequest( request, responseDecorator, project );
369  }
370  else
371  {
372  throw QgsOgcServiceException( QStringLiteral( "Service configuration error" ),
373  QStringLiteral( "Service unknown or unsupported" ) );
374  }
375  }
376  catch ( QgsServerException &ex )
377  {
378  responseDecorator.write( ex );
379  QString format;
380  QgsMessageLog::logMessage( ex.formatResponse( format ), QStringLiteral( "Server" ), Qgis::Info );
381  }
382  catch ( QgsException &ex )
383  {
384  // Internal server error
385  response.sendError( 500, QStringLiteral( "Internal Server Error" ) );
386  QgsMessageLog::logMessage( ex.what(), QStringLiteral( "Server" ), Qgis::Critical );
387  }
388  }
389  // Terminate the response
390  responseDecorator.finish();
391 
392  // We are done using requestHandler in plugins, make sure we don't access
393  // to a deleted request handler from Python bindings
394  sServerInterface->clearRequestHandler();
395 
396  if ( logLevel == Qgis::Info )
397  {
398  QgsMessageLog::logMessage( "Request finished in " + QString::number( time.elapsed() ) + " ms", QStringLiteral( "Server" ), Qgis::Info );
399  }
400 }
401 
402 
403 #ifdef HAVE_SERVER_PYTHON_PLUGINS
404 void QgsServer::initPython()
405 {
406  // Init plugins
407  if ( ! QgsServerPlugins::initPlugins( sServerInterface ) )
408  {
409  QgsMessageLog::logMessage( QStringLiteral( "No server python plugins are available" ), QStringLiteral( "Server" ), Qgis::Info );
410  }
411  else
412  {
413  QgsMessageLog::logMessage( QStringLiteral( "Server python plugins loaded" ), QStringLiteral( "Server" ), Qgis::Info );
414  }
415 }
416 #endif
417 
QMap< QString, QString > toMap() const
Returns all parameters in a map.
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.
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:66
QString message() const
Returns the exception message.
void putenv(const QString &var, const QString &val)
Set environment variable.
Definition: qgsserver.cpp:279
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.
Reads and writes project states.
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.
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 ...
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...
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:64
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:289