Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #35738 from elpaso/server-badlayer-ignore
Server: add QGIS_SERVER_IGNORE_BAD_LAYERS config option
  • Loading branch information
elpaso committed Apr 14, 2020
2 parents 5f91b06 + b94988a commit 00000a0
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 17 deletions.
9 changes: 7 additions & 2 deletions python/server/auto_generated/qgsconfigcache.sip.in
Expand Up @@ -36,12 +36,17 @@ Removes an entry from cache.
:param path: The path of the project
%End

const QgsProject *project( const QString &path );
const QgsProject *project( const QString &path, QgsServerSettings *settings = 0 );
%Docstring
If the project is not cached yet, then the project is read thanks to the
If the project is not cached yet, then the project is read from the
path. If the project is not available, then ``None`` is returned.
If the project contains any bad layer it is considered unavailable
unless the server configuration variable QGIS_SERVER_IGNORE_BAD_LAYERS
passed in the optional settings argument is set to ``True`` (the default
value is ``False``).

:param path: the filename of the QGIS project
:param settings: QGIS server settings

:return: the project or ``None`` if an error happened

Expand Down
11 changes: 11 additions & 0 deletions python/server/auto_generated/qgsserversettings.sip.in
Expand Up @@ -173,6 +173,17 @@ The default value is 10000, this value can be changed by setting the environment
variable QGIS_SERVER_API_WFS3_MAX_LIMIT.

.. versionadded:: 3.10
%End

bool ignoreBadLayers() const;
%Docstring
Returns ``True`` if the bad layers are ignored and ``False`` when the presence of a
bad layers invalidates the whole project making it unavailable.

The default value is ``True``, this value can be changed by setting the environment
variable QGIS_SERVER_IGNORE_BAD_LAYERS.

.. versionadded:: 3.10.5
%End

};
Expand Down
22 changes: 17 additions & 5 deletions src/server/qgsconfigcache.cpp
Expand Up @@ -38,7 +38,8 @@ QgsConfigCache::QgsConfigCache()
QObject::connect( &mFileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &QgsConfigCache::removeChangedEntry );
}

const QgsProject *QgsConfigCache::project( const QString &path )

const QgsProject *QgsConfigCache::project( const QString &path, QgsServerSettings *settings )
{
if ( ! mProjectCache[ path ] )
{
Expand Down Expand Up @@ -68,10 +69,20 @@ const QgsProject *QgsConfigCache::project( const QString &path )
}
if ( !unrestrictedBadLayers.isEmpty() )
{
QgsMessageLog::logMessage(
QStringLiteral( "Error, Layer(s) %1 not valid in project %2" ).arg( unrestrictedBadLayers.join( QStringLiteral( ", " ) ), path ),
QStringLiteral( "Server" ), Qgis::Critical );
throw QgsServerException( QStringLiteral( "Layer(s) not valid" ) );
// This is a critical error unless QGIS_SERVER_IGNORE_BAD_LAYERS is set to TRUE
if ( ! settings || ! settings->ignoreBadLayers() )
{
QgsMessageLog::logMessage(
QStringLiteral( "Error, Layer(s) %1 not valid in project %2" ).arg( unrestrictedBadLayers.join( QStringLiteral( ", " ) ), path ),
QStringLiteral( "Server" ), Qgis::Critical );
throw QgsServerException( QStringLiteral( "Layer(s) not valid" ) );
}
else
{
QgsMessageLog::logMessage(
QStringLiteral( "Warning, Layer(s) %1 not valid in project %2" ).arg( unrestrictedBadLayers.join( QStringLiteral( ", " ) ), path ),
QStringLiteral( "Server" ), Qgis::Warning );
}
}
}
mProjectCache.insert( path, prj.release() );
Expand All @@ -86,6 +97,7 @@ const QgsProject *QgsConfigCache::project( const QString &path )
}
QgsProject::setInstance( mProjectCache[ path ] );
return mProjectCache[ path ];

}

QDomDocument *QgsConfigCache::xmlDocument( const QString &filePath )
Expand Down
10 changes: 8 additions & 2 deletions src/server/qgsconfigcache.h
Expand Up @@ -28,6 +28,7 @@
#include "qgis_server.h"
#include "qgis_sip.h"
#include "qgsproject.h"
#include "qgsserversettings.h"

/**
* \ingroup server
Expand All @@ -51,13 +52,18 @@ class SERVER_EXPORT QgsConfigCache : public QObject
void removeEntry( const QString &path );

/**
* If the project is not cached yet, then the project is read thanks to the
* If the project is not cached yet, then the project is read from the
* path. If the project is not available, then NULLPTR is returned.
* If the project contains any bad layer it is considered unavailable
* unless the server configuration variable QGIS_SERVER_IGNORE_BAD_LAYERS
* passed in the optional settings argument is set to TRUE (the default
* value is FALSE).
* \param path the filename of the QGIS project
* \param settings QGIS server settings
* \returns the project or NULLPTR if an error happened
* \since QGIS 3.0
*/
const QgsProject *project( const QString &path );
const QgsProject *project( const QString &path, QgsServerSettings *settings = nullptr );

private:
QgsConfigCache() SIP_FORCE;
Expand Down
3 changes: 2 additions & 1 deletion src/server/qgsfcgiserverrequest.cpp
Expand Up @@ -210,7 +210,8 @@ void QgsFcgiServerRequest::printRequestInfos( const QUrl &url )
QStringLiteral( "HTTP_PROXY" ),
QStringLiteral( "NO_PROXY" ),
QStringLiteral( "HTTP_AUTHORIZATION" ),
QStringLiteral( "QGIS_PROJECT_FILE" )
QStringLiteral( "QGIS_PROJECT_FILE" ),
QStringLiteral( "QGIS_SERVER_IGNORE_BAD_LAYERS" )
};

QgsMessageLog::logMessage( QStringLiteral( "Request URL: %2" ).arg( url.url() ), QStringLiteral( "Server" ), Qgis::Info );
Expand Down
2 changes: 1 addition & 1 deletion src/server/qgsserver.cpp
Expand Up @@ -375,7 +375,7 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
QString configFilePath = configPath( *sConfigFilePath, params.map() );

// load the project if needed and not empty
project = mConfigCache->project( configFilePath );
project = mConfigCache->project( configFilePath, sServerInterface->serverSettings() );
}

if ( project )
Expand Down
16 changes: 16 additions & 0 deletions src/server/qgsserversettings.cpp
Expand Up @@ -152,6 +152,17 @@ void QgsServerSettings::initSettings()
};
mSettings[ sOverrideSystemLocale.envVar ] = sOverrideSystemLocale;

// bad layers handling
const Setting sIgnoreBadLayers = { QgsServerSettingsEnv::QGIS_SERVER_IGNORE_BAD_LAYERS,
QgsServerSettingsEnv::DEFAULT_VALUE,
QStringLiteral( "Ignore bad layers" ),
QString(),
QVariant::Bool,
QVariant( false ),
QVariant()
};
mSettings[ sIgnoreBadLayers.envVar ] = sIgnoreBadLayers;

// show group separator
const Setting sShowGroupSeparator = { QgsServerSettingsEnv::QGIS_SERVER_SHOW_GROUP_SEPARATOR,
QgsServerSettingsEnv::DEFAULT_VALUE,
Expand Down Expand Up @@ -418,3 +429,8 @@ qlonglong QgsServerSettings::apiWfs3MaxLimit() const
{
return value( QgsServerSettingsEnv::QGIS_SERVER_API_WFS3_MAX_LIMIT ).toLongLong();
}

bool QgsServerSettings::ignoreBadLayers() const
{
return value( QgsServerSettingsEnv::QGIS_SERVER_IGNORE_BAD_LAYERS ).toBool();
}
24 changes: 18 additions & 6 deletions src/server/qgsserversettings.h
Expand Up @@ -59,14 +59,15 @@ class SERVER_EXPORT QgsServerSettingsEnv : public QObject
QGIS_SERVER_LOG_STDERR,
QGIS_PROJECT_FILE,
MAX_CACHE_LAYERS,
QGIS_SERVER_IGNORE_BAD_LAYERS, //!< Do not consider the whole project unavailable if it contains bad layers
QGIS_SERVER_CACHE_DIRECTORY,
QGIS_SERVER_CACHE_SIZE,
QGIS_SERVER_SHOW_GROUP_SEPARATOR, //! Show group (thousands) separator when formatting numeric values, defaults to FALSE (since QGIS 3.8)
QGIS_SERVER_OVERRIDE_SYSTEM_LOCALE, //! Override system locale (since QGIS 3.8)
QGIS_SERVER_WMS_MAX_HEIGHT, //! Maximum height for a WMS request. The most conservative between this and the project one is used (since QGIS 3.6.2)
QGIS_SERVER_WMS_MAX_WIDTH, //! Maximum width for a WMS request. The most conservative between this and the project one is used (since QGIS 3.6.2)
QGIS_SERVER_API_RESOURCES_DIRECTORY, //! Base directory where HTML templates and static assets (e.g. images, js and css files) are searched for (since QGIS 3.10).
QGIS_SERVER_API_WFS3_MAX_LIMIT //! Maximum value for "limit" in a features request, defaults to 10000 (since QGIS 3.10).
QGIS_SERVER_SHOW_GROUP_SEPARATOR, //!< Show group (thousands) separator when formatting numeric values, defaults to FALSE (since QGIS 3.8)
QGIS_SERVER_OVERRIDE_SYSTEM_LOCALE, //!< Override system locale (since QGIS 3.8)
QGIS_SERVER_WMS_MAX_HEIGHT, //!< Maximum height for a WMS request. The most conservative between this and the project one is used (since QGIS 3.6.2)
QGIS_SERVER_WMS_MAX_WIDTH, //!< Maximum width for a WMS request. The most conservative between this and the project one is used (since QGIS 3.6.2)
QGIS_SERVER_API_RESOURCES_DIRECTORY, //!< Base directory where HTML templates and static assets (e.g. images, js and css files) are searched for (since QGIS 3.10).
QGIS_SERVER_API_WFS3_MAX_LIMIT //!< Maximum value for "limit" in a features request, defaults to 10000 (since QGIS 3.10).
};
Q_ENUM( EnvVar )
};
Expand Down Expand Up @@ -222,6 +223,17 @@ class SERVER_EXPORT QgsServerSettings
*/
qlonglong apiWfs3MaxLimit() const;

/**
* Returns TRUE if the bad layers are ignored and FALSE when the presence of a
* bad layers invalidates the whole project making it unavailable.
*
* The default value is TRUE, this value can be changed by setting the environment
* variable QGIS_SERVER_IGNORE_BAD_LAYERS.
*
* \since QGIS 3.10.5
*/
bool ignoreBadLayers() const;

private:
void initSettings();
QVariant value( QgsServerSettingsEnv::EnvVar envVar ) const;
Expand Down
1 change: 1 addition & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -341,6 +341,7 @@ IF (WITH_SERVER)
ADD_PYTHON_TEST(PyQgsServerWMSGetMap test_qgsserver_wms_getmap.py)
ADD_PYTHON_TEST(PyQgsServerWMSGetMapSizeProject test_qgsserver_wms_getmap_size_project.py)
ADD_PYTHON_TEST(PyQgsServerWMSGetMapSizeServer test_qgsserver_wms_getmap_size_server.py)
ADD_PYTHON_TEST(PyQgsServerWMSGetMapIgnoreBadLayers test_qgsserver_wms_getmap_ignore_bad_layers.py)
ADD_PYTHON_TEST(PyQgsServerWMSGetFeatureInfo test_qgsserver_wms_getfeatureinfo.py)
ADD_PYTHON_TEST(PyQgsServerWMSGetLegendGraphic test_qgsserver_wms_getlegendgraphic.py)
ADD_PYTHON_TEST(PyQgsServerWMSGetPrint test_qgsserver_wms_getprint.py)
Expand Down
78 changes: 78 additions & 0 deletions tests/src/python/test_qgsserver_wms_getmap_ignore_bad_layers.py
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsServer WMS GetMap with QGIS_SERVER_IGNORE_BAD_LAYERS=true.
From build dir, run: ctest -R PyQgsServerWMSGetMap -V
.. note:: 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.
"""
__author__ = 'Alessandro Pasotti'
__date__ = '13/04/2020'
__copyright__ = 'Copyright 2020, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

import os

# Needed on Qt 5 so that the serialization of XML is consistent among all executions
os.environ['QT_HASH_SEED'] = '1'

import re
import urllib.request
import urllib.parse
import urllib.error

from qgis.testing import unittest
from qgis.PyQt.QtCore import QSize

import osgeo.gdal # NOQA

from test_qgsserver import QgsServerTestBase
from utilities import unitTestDataPath
from qgis.core import QgsProject, QgsApplication

# Strip path and content length because path may vary
RE_STRIP_UNCHECKABLE = b'MAP=[^"]+|Content-Length: \d+'
RE_ATTRIBUTES = b'[^>\s]+=[^>\s]+'


class TestQgsServerWMSGetMapIgnoreBadLayers(QgsServerTestBase):
"""QGIS Server WMS Tests for GetMap request with QGIS_SERVER_IGNORE_BAD_LAYERS=true"""

#regenerate_reference = True

def setUp(self):
os.environ['QGIS_SERVER_IGNORE_BAD_LAYERS'] = 'true'
super().setUp()

def test_wms_getmap_datasource_error_ignore(self):
"""Must NOT throw a server exception if datasource if not available and QGIS_SERVER_IGNORE_BAD_LAYERS is set"""

qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(os.path.join(self.testdata_path, 'test_project_wms_invalid_layers.qgs')),
"SERVICE": "WMS",
"VERSION": "1.3.0",
"REQUEST": "GetMap",
"BBOX": "613402.5658687877003,5809005.018114360981,619594.408781287726,5813869.006602735259",
"CRS": "EPSG:25832",
"WIDTH": "429",
"HEIGHT": "337",
"LAYERS": "areas and symbols,osm",
"STYLES": ",",
"FORMAT": "image/png",
"DPI": "200",
"MAP_RESOLUTION": "200",
"FORMAT_OPTIONS": "dpi:200"
}.items())])

r, h = self._result(self._execute_request(qs))

self.assertFalse('ServerException' in str(r))


if __name__ == '__main__':
unittest.main()

0 comments on commit 00000a0

Please sign in to comment.