mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
450 lines
15 KiB
C++
450 lines
15 KiB
C++
/***************************************************************************
|
|
qgsserver.cpp
|
|
A server application supporting WMS / WFS / WCS
|
|
-------------------
|
|
begin : July 04, 2006
|
|
copyright : (C) 2006 by Marco Hugentobler & Ionut Iosifescu Enescu
|
|
: (C) 2015 by Alessandro Pasotti
|
|
email : marco dot hugentobler at karto dot baug dot ethz dot ch
|
|
: elpaso at itopen dot it
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
//for CMAKE_INSTALL_PREFIX
|
|
#include "qgsconfig.h"
|
|
#include "qgsserver.h"
|
|
#include "qgsauthmanager.h"
|
|
#include "qgscapabilitiescache.h"
|
|
#include "qgsfontutils.h"
|
|
#include "qgsrequesthandler.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsproviderregistry.h"
|
|
#include "qgslogger.h"
|
|
#include "qgsmapserviceexception.h"
|
|
#include "qgsnetworkaccessmanager.h"
|
|
#include "qgsserverlogger.h"
|
|
#include "qgsserverrequest.h"
|
|
#include "qgsfilterresponsedecorator.h"
|
|
#include "qgsservice.h"
|
|
#include "qgsserverapi.h"
|
|
#include "qgsserverapicontext.h"
|
|
#include "qgsserverparameters.h"
|
|
#include "qgsapplication.h"
|
|
|
|
#include <QDomDocument>
|
|
#include <QNetworkDiskCache>
|
|
#include <QSettings>
|
|
#include <QDateTime>
|
|
|
|
// TODO: remove, it's only needed by a single debug message
|
|
#include <fcgi_stdio.h>
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
// Server status static initializers.
|
|
// Default values are for C++, SIP bindings will override their
|
|
// options in in init()
|
|
|
|
QString *QgsServer::sConfigFilePath = nullptr;
|
|
QgsCapabilitiesCache *QgsServer::sCapabilitiesCache = nullptr;
|
|
QgsServerInterfaceImpl *QgsServer::sServerInterface = nullptr;
|
|
// Initialization must run once for all servers
|
|
bool QgsServer::sInitialized = false;
|
|
QgsServerSettings QgsServer::sSettings;
|
|
|
|
QgsServiceRegistry *QgsServer::sServiceRegistry = nullptr;
|
|
|
|
QgsServer::QgsServer()
|
|
{
|
|
// QgsApplication must exist
|
|
if ( qobject_cast<QgsApplication *>( qApp ) == nullptr )
|
|
{
|
|
qFatal( "A QgsApplication must exist before a QgsServer instance can be created." );
|
|
abort();
|
|
}
|
|
init();
|
|
mConfigCache = QgsConfigCache::instance();
|
|
}
|
|
|
|
QString &QgsServer::serverName()
|
|
{
|
|
static QString *name = new QString( QStringLiteral( "qgis_server" ) );
|
|
return *name;
|
|
}
|
|
|
|
|
|
QFileInfo QgsServer::defaultAdminSLD()
|
|
{
|
|
return QFileInfo( QStringLiteral( "admin.sld" ) );
|
|
}
|
|
|
|
void QgsServer::setupNetworkAccessManager()
|
|
{
|
|
QSettings settings;
|
|
QgsNetworkAccessManager *nam = QgsNetworkAccessManager::instance();
|
|
QNetworkDiskCache *cache = new QNetworkDiskCache( nullptr );
|
|
qint64 cacheSize = sSettings.cacheSize();
|
|
QString cacheDirectory = sSettings.cacheDirectory();
|
|
cache->setCacheDirectory( cacheDirectory );
|
|
cache->setMaximumCacheSize( cacheSize );
|
|
QgsMessageLog::logMessage( QStringLiteral( "cacheDirectory: %1" ).arg( cache->cacheDirectory() ), QStringLiteral( "Server" ), Qgis::Info );
|
|
QgsMessageLog::logMessage( QStringLiteral( "maximumCacheSize: %1" ).arg( cache->maximumCacheSize() ), QStringLiteral( "Server" ), Qgis::Info );
|
|
nam->setCache( cache );
|
|
}
|
|
|
|
QFileInfo QgsServer::defaultProjectFile()
|
|
{
|
|
QDir currentDir;
|
|
fprintf( FCGI_stderr, "current directory: %s\n", currentDir.absolutePath().toUtf8().constData() );
|
|
QStringList nameFilterList;
|
|
nameFilterList << QStringLiteral( "*.qgs" )
|
|
<< QStringLiteral( "*.qgz" );
|
|
QFileInfoList projectFiles = currentDir.entryInfoList( nameFilterList, QDir::Files, QDir::Name );
|
|
for ( int x = 0; x < projectFiles.size(); x++ )
|
|
{
|
|
QgsMessageLog::logMessage( projectFiles.at( x ).absoluteFilePath(), QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
if ( projectFiles.isEmpty() )
|
|
{
|
|
return QFileInfo();
|
|
}
|
|
return projectFiles.at( 0 );
|
|
}
|
|
|
|
void QgsServer::printRequestParameters( const QMap< QString, QString> ¶meterMap, Qgis::MessageLevel logLevel )
|
|
{
|
|
if ( logLevel > Qgis::Info )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QMap< QString, QString>::const_iterator pIt = parameterMap.constBegin();
|
|
for ( ; pIt != parameterMap.constEnd(); ++pIt )
|
|
{
|
|
QgsMessageLog::logMessage( pIt.key() + ":" + pIt.value(), QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
}
|
|
|
|
QString QgsServer::configPath( const QString &defaultConfigPath, const QString &configPath )
|
|
{
|
|
QString cfPath( defaultConfigPath );
|
|
QString projectFile = sSettings.projectFile();
|
|
if ( !projectFile.isEmpty() )
|
|
{
|
|
cfPath = projectFile;
|
|
QgsDebugMsg( QStringLiteral( "QGIS_PROJECT_FILE:%1" ).arg( cfPath ) );
|
|
}
|
|
else
|
|
{
|
|
if ( configPath.isEmpty() )
|
|
{
|
|
// Read it from the environment, because a rewrite rule may have rewritten it
|
|
if ( getenv( "QGIS_PROJECT_FILE" ) )
|
|
{
|
|
cfPath = getenv( "QGIS_PROJECT_FILE" );
|
|
QgsMessageLog::logMessage( QStringLiteral( "Using configuration file path from environment: %1" ).arg( cfPath ), QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
else if ( ! defaultConfigPath.isEmpty() )
|
|
{
|
|
QgsMessageLog::logMessage( QStringLiteral( "Using default configuration file path: %1" ).arg( defaultConfigPath ), QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cfPath = configPath;
|
|
QgsDebugMsg( QStringLiteral( "MAP:%1" ).arg( cfPath ) );
|
|
}
|
|
}
|
|
return cfPath;
|
|
}
|
|
|
|
void QgsServer::initLocale()
|
|
{
|
|
// System locale override
|
|
if ( ! sSettings.overrideSystemLocale().isEmpty() )
|
|
{
|
|
QLocale::setDefault( QLocale( sSettings.overrideSystemLocale() ) );
|
|
}
|
|
// Number group separator settings
|
|
QLocale currentLocale;
|
|
if ( sSettings.showGroupSeparator() )
|
|
{
|
|
currentLocale.setNumberOptions( currentLocale.numberOptions() &= ~QLocale::NumberOption::OmitGroupSeparator );
|
|
}
|
|
else
|
|
{
|
|
currentLocale.setNumberOptions( currentLocale.numberOptions() |= QLocale::NumberOption::OmitGroupSeparator );
|
|
}
|
|
QLocale::setDefault( currentLocale );
|
|
}
|
|
|
|
bool QgsServer::init()
|
|
{
|
|
if ( sInitialized )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QCoreApplication::setOrganizationName( QgsApplication::QGIS_ORGANIZATION_NAME );
|
|
QCoreApplication::setOrganizationDomain( QgsApplication::QGIS_ORGANIZATION_DOMAIN );
|
|
QCoreApplication::setApplicationName( QgsApplication::QGIS_APPLICATION_NAME );
|
|
|
|
QgsApplication::init();
|
|
|
|
#if defined(SERVER_SKIP_ECW)
|
|
QgsMessageLog::logMessage( "Skipping GDAL ECW drivers in server.", "Server", Qgis::Info );
|
|
QgsApplication::skipGdalDriver( "ECW" );
|
|
QgsApplication::skipGdalDriver( "JP2ECW" );
|
|
#endif
|
|
|
|
// reload settings to take into account QCoreApplication and QgsApplication
|
|
// configuration
|
|
sSettings.load();
|
|
|
|
// init and configure logger
|
|
QgsServerLogger::instance();
|
|
QgsServerLogger::instance()->setLogLevel( sSettings.logLevel() );
|
|
if ( ! sSettings.logFile().isEmpty() )
|
|
{
|
|
QgsServerLogger::instance()->setLogFile( sSettings.logFile() );
|
|
}
|
|
else if ( sSettings.logStderr() )
|
|
{
|
|
QgsServerLogger::instance()->setLogStderr();
|
|
}
|
|
|
|
// Configure locale
|
|
initLocale();
|
|
|
|
// log settings currently used
|
|
sSettings.logSummary();
|
|
|
|
setupNetworkAccessManager();
|
|
QDomImplementation::setInvalidDataPolicy( QDomImplementation::DropInvalidChars );
|
|
|
|
// Instantiate the plugin directory so that providers are loaded
|
|
QgsProviderRegistry::instance( QgsApplication::pluginPath() );
|
|
QgsMessageLog::logMessage( "Prefix PATH: " + QgsApplication::prefixPath(), QStringLiteral( "Server" ), Qgis::Info );
|
|
QgsMessageLog::logMessage( "Plugin PATH: " + QgsApplication::pluginPath(), QStringLiteral( "Server" ), Qgis::Info );
|
|
QgsMessageLog::logMessage( "PkgData PATH: " + QgsApplication::pkgDataPath(), QStringLiteral( "Server" ), Qgis::Info );
|
|
QgsMessageLog::logMessage( "User DB PATH: " + QgsApplication::qgisUserDatabaseFilePath(), QStringLiteral( "Server" ), Qgis::Info );
|
|
QgsMessageLog::logMessage( "Auth DB PATH: " + QgsApplication::qgisAuthDatabaseFilePath(), QStringLiteral( "Server" ), Qgis::Info );
|
|
QgsMessageLog::logMessage( "SVG PATHS: " + QgsApplication::svgPaths().join( QDir::listSeparator() ), QStringLiteral( "Server" ), Qgis::Info );
|
|
|
|
QgsApplication::createDatabase(); //init qgis.db (e.g. necessary for user crs)
|
|
|
|
// Initialize the authentication system
|
|
// creates or uses qgis-auth.db in ~/.qgis3/ or directory defined by QGIS_AUTH_DB_DIR_PATH env variable
|
|
// set the master password as first line of file defined by QGIS_AUTH_PASSWORD_FILE env variable
|
|
// (QGIS_AUTH_PASSWORD_FILE variable removed from environment after accessing)
|
|
QgsApplication::authManager()->init( QgsApplication::pluginPath(), QgsApplication::qgisAuthDatabaseFilePath() );
|
|
|
|
QString defaultConfigFilePath;
|
|
QFileInfo projectFileInfo = defaultProjectFile(); //try to find a .qgs/.qgz file in the server directory
|
|
if ( projectFileInfo.exists() )
|
|
{
|
|
defaultConfigFilePath = projectFileInfo.absoluteFilePath();
|
|
QgsMessageLog::logMessage( "Using default project file: " + defaultConfigFilePath, QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
else
|
|
{
|
|
QFileInfo adminSLDFileInfo = defaultAdminSLD();
|
|
if ( adminSLDFileInfo.exists() )
|
|
{
|
|
defaultConfigFilePath = adminSLDFileInfo.absoluteFilePath();
|
|
}
|
|
}
|
|
// Store the config file path
|
|
sConfigFilePath = new QString( defaultConfigFilePath );
|
|
|
|
//create cache for capabilities XML
|
|
sCapabilitiesCache = new QgsCapabilitiesCache();
|
|
|
|
QgsFontUtils::loadStandardTestFonts( QStringList() << QStringLiteral( "Roman" ) << QStringLiteral( "Bold" ) );
|
|
|
|
sServiceRegistry = new QgsServiceRegistry();
|
|
|
|
sServerInterface = new QgsServerInterfaceImpl( sCapabilitiesCache, sServiceRegistry, &sSettings );
|
|
|
|
// Load service module
|
|
QString modulePath = QgsApplication::libexecPath() + "server";
|
|
qDebug() << "Initializing server modules from " << modulePath << endl;
|
|
sServiceRegistry->init( modulePath, sServerInterface );
|
|
|
|
sInitialized = true;
|
|
QgsMessageLog::logMessage( QStringLiteral( "Server initialized" ), QStringLiteral( "Server" ), Qgis::Info );
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
void QgsServer::putenv( const QString &var, const QString &val )
|
|
{
|
|
#ifdef _MSC_VER
|
|
_putenv_s( var.toStdString().c_str(), val.toStdString().c_str() );
|
|
#else
|
|
setenv( var.toStdString().c_str(), val.toStdString().c_str(), 1 );
|
|
#endif
|
|
sSettings.load( var );
|
|
}
|
|
|
|
void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project )
|
|
{
|
|
Qgis::MessageLevel logLevel = QgsServerLogger::instance()->logLevel();
|
|
QTime time; //used for measuring request time if loglevel < 1
|
|
|
|
qApp->processEvents();
|
|
|
|
if ( logLevel == Qgis::Info )
|
|
{
|
|
time.start();
|
|
}
|
|
|
|
// Pass the filters to the requestHandler, this is needed for the following reasons:
|
|
// Allow server request to call sendResponse plugin hook if enabled
|
|
QgsFilterResponseDecorator responseDecorator( sServerInterface->filters(), response );
|
|
|
|
//Request handler
|
|
QgsRequestHandler requestHandler( request, response );
|
|
|
|
try
|
|
{
|
|
// TODO: split parse input into plain parse and processing from specific services
|
|
requestHandler.parseInput();
|
|
}
|
|
catch ( QgsMapServiceException &e )
|
|
{
|
|
QgsMessageLog::logMessage( "Parse input exception: " + e.message(), QStringLiteral( "Server" ), Qgis::Critical );
|
|
requestHandler.setServiceException( e );
|
|
}
|
|
|
|
// Set the request handler into the interface for plugins to manipulate it
|
|
sServerInterface->setRequestHandler( &requestHandler );
|
|
|
|
// Initialize configfilepath so that is is available
|
|
// before calling plugin methods
|
|
// Note that plugins may still change that value using
|
|
// setConfigFilePath() interface method
|
|
if ( ! project )
|
|
{
|
|
QString configFilePath = configPath( *sConfigFilePath, request.serverParameters().map() );
|
|
sServerInterface->setConfigFilePath( configFilePath );
|
|
}
|
|
else
|
|
{
|
|
sServerInterface->setConfigFilePath( project->fileName() );
|
|
}
|
|
|
|
// Call requestReady() method (if enabled)
|
|
responseDecorator.start();
|
|
|
|
// Plugins may have set exceptions
|
|
if ( !requestHandler.exceptionRaised() )
|
|
{
|
|
try
|
|
{
|
|
const QgsServerParameters params = request.serverParameters();
|
|
printRequestParameters( params.toMap(), logLevel );
|
|
|
|
// Setup project (config file path)
|
|
if ( ! project )
|
|
{
|
|
QString configFilePath = configPath( *sConfigFilePath, params.map() );
|
|
|
|
// load the project if needed and not empty
|
|
project = mConfigCache->project( configFilePath );
|
|
}
|
|
|
|
if ( project )
|
|
{
|
|
sServerInterface->setConfigFilePath( project->fileName() );
|
|
}
|
|
|
|
// Dispatcher: if SERVICE is set, we assume a OWS service, if not, let's try an API
|
|
// TODO: QGIS 4 fix the OWS services and treat them as APIs
|
|
QgsServerApi *api = nullptr;
|
|
if ( params.service().isEmpty() && ( api = sServiceRegistry->apiForRequest( request ) ) )
|
|
{
|
|
QgsServerApiContext context { api->rootPath(), &request, &responseDecorator, project, sServerInterface };
|
|
api->executeRequest( context );
|
|
}
|
|
else
|
|
{
|
|
|
|
// Project is mandatory for OWS at this point
|
|
if ( ! project )
|
|
{
|
|
throw QgsServerException( QStringLiteral( "Project file error" ) );
|
|
}
|
|
|
|
if ( ! params.fileName().isEmpty() )
|
|
{
|
|
const QString value = QString( "attachment; filename=\"%1\"" ).arg( params.fileName() );
|
|
requestHandler.setResponseHeader( QStringLiteral( "Content-Disposition" ), value );
|
|
}
|
|
|
|
// Lookup for service
|
|
QgsService *service = sServiceRegistry->getService( params.service(), params.version() );
|
|
if ( service )
|
|
{
|
|
service->executeRequest( request, responseDecorator, project );
|
|
}
|
|
else
|
|
{
|
|
throw QgsOgcServiceException( QStringLiteral( "Service configuration error" ),
|
|
QStringLiteral( "Service unknown or unsupported" ) );
|
|
}
|
|
}
|
|
}
|
|
catch ( QgsServerException &ex )
|
|
{
|
|
responseDecorator.write( ex );
|
|
QString format;
|
|
QgsMessageLog::logMessage( ex.formatResponse( format ), QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
catch ( QgsException &ex )
|
|
{
|
|
// Internal server error
|
|
response.sendError( 500, QStringLiteral( "Internal Server Error" ) );
|
|
QgsMessageLog::logMessage( ex.what(), QStringLiteral( "Server" ), Qgis::Critical );
|
|
}
|
|
}
|
|
// Terminate the response
|
|
responseDecorator.finish();
|
|
|
|
// We are done using requestHandler in plugins, make sure we don't access
|
|
// to a deleted request handler from Python bindings
|
|
sServerInterface->clearRequestHandler();
|
|
|
|
if ( logLevel == Qgis::Info )
|
|
{
|
|
QgsMessageLog::logMessage( "Request finished in " + QString::number( time.elapsed() ) + " ms", QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef HAVE_SERVER_PYTHON_PLUGINS
|
|
void QgsServer::initPython()
|
|
{
|
|
// Init plugins
|
|
if ( ! QgsServerPlugins::initPlugins( sServerInterface ) )
|
|
{
|
|
QgsMessageLog::logMessage( QStringLiteral( "No server python plugins are available" ), QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
else
|
|
{
|
|
QgsMessageLog::logMessage( QStringLiteral( "Server python plugins loaded" ), QStringLiteral( "Server" ), Qgis::Info );
|
|
}
|
|
}
|
|
#endif
|
|
|