[Bugfix][Server] Add WMS SLD parameter support

Fixed #19795 QGIS Server 3 / WMS: the SLD parameter support has been removed

To reactivate SLD parameter support, we add a new conversion capability in server parameter `toUrl`. And the capabilty to load the content associted to an URL.

Then if the SLD parameter is defined, the content is loaded and the SLD_BODY is set.
This commit is contained in:
rldhont 2018-09-11 10:23:19 +02:00
parent 5047571c13
commit c15f05b4c9
8 changed files with 279 additions and 26 deletions

View File

@ -138,6 +138,28 @@ Converts the parameter into a color.
:param ok: True if there's no error during the conversion, false otherwise
:return: A color
%End
QUrl toUrl( bool &ok ) const;
%Docstring
Converts the parameter into an url.
:param ok: True if there's no error during the conversion, false otherwise
:return: An url
.. versionadded:: 3.4
%End
QString loadUrl( bool &ok ) const;
%Docstring
Loads the data associated to the parameter converted into an url.
:param ok: True if there's no error during the load, false otherwise
:return: The content loaded
.. versionadded:: 3.4
%End
static void raiseError( const QString &msg );

View File

@ -154,20 +154,6 @@ void QgsRequestHandler::setupParameters()
{
const QgsServerRequest::Parameters parameters = mRequest.parameters();
// SLD
QString value = parameters.value( QStringLiteral( "SLD" ) );
if ( !value.isEmpty() )
{
QgsMessageLog::logMessage( QStringLiteral( "http and ftp methods not supported with Qt5." ) );
}
// SLD_BODY
value = parameters.value( QStringLiteral( "SLD_BODY" ) );
if ( ! value.isEmpty() )
{
mRequest.setParameter( QStringLiteral( "SLD" ), value );
}
//feature info format?
QString infoFormat = parameters.value( QStringLiteral( "INFO_FORMAT" ) );
if ( !infoFormat.isEmpty() )

View File

@ -17,6 +17,12 @@
#include "qgsserverparameters.h"
#include "qgsserverexception.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsmessagelog.h"
#include <QUrl>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTextCodec>
//
// QgsServerParameterDefinition
@ -200,6 +206,167 @@ QgsRectangle QgsServerParameterDefinition::toRectangle( bool &ok ) const
return extent;
}
QString QgsServerParameterDefinition::loadUrl( bool &ok ) const
{
ok = true;
// Get URL
QUrl url = toUrl( ok );
if ( !ok )
{
return QString();
}
// Do the request
QNetworkReply *reply = nullptr;
// The following code blocks until the file is downloaded...
// with max redirections fixed to 5
int countRedirections = 0;
QDateTime start = QDateTime::currentDateTime();
while ( 1 )
{
QgsMessageLog::logMessage( QStringLiteral( "Request started [url: %2]" ).arg( url.toString() ) );
QNetworkRequest request( url );
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
reply = QgsNetworkAccessManager::instance()->get( request );
// wait until the SLD download finished
while ( !reply->isFinished() )
{
if ( start.secsTo( QDateTime::currentDateTime() ) >= 5 * 60 )
break;
QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents, 500 );
}
if ( !reply->isFinished() )
{
ok = false;
break;
}
if ( reply->error() != QNetworkReply::NoError )
{
ok = false;
QgsMessageLog::logMessage( QStringLiteral( "Request failed [error: %1 - url: %2]" ).arg( reply->errorString(), reply->url().toString() ) );
break;
}
QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
if ( redirect.isNull() )
{
break;
}
// max redirections
countRedirections++;
if ( countRedirections >= 5 )
{
ok = false;
break;
}
// do a new request to the redirect url
url = redirect.toUrl();
reply->deleteLater();
}
if ( !reply->isFinished() )
{
ok = false;
QgsMessageLog::logMessage( QStringLiteral( "Request timeout [redirections: %1 - url: %2]" ).arg( countRedirections ).arg( mValue.toString() ) );
reply->abort();
reply->deleteLater();
return QString();
}
if ( countRedirections >= 5 )
{
ok = false;
QgsMessageLog::logMessage( QStringLiteral( "Request failed [max redirection raised - url: %1]" ).arg( mValue.toString() ) );
reply->deleteLater();
return QString();
}
QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
if ( !status.isNull() && status.toInt() >= 400 )
{
ok = false;
QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
QgsMessageLog::logMessage( QStringLiteral( "Request error [status: %1 - reason phrase: %2] for %3" ).arg( status.toInt() ).arg( phrase.toString(), reply->url().toString() ) );
reply->deleteLater();
return QString();
}
if ( reply->error() != QNetworkReply::NoError )
{
ok = false;
reply->deleteLater();
return QString();
}
// read content
QByteArray ba = reply->readAll();
reply->deleteLater();
ok = ( !ba.isEmpty() );
//QTextCodec::codecForHtml fails to detect "<meta charset="utf-8"/>" type tags
//see https://bugreports.qt.io/browse/QTBUG-41011
//so test for that ourselves
//basic check
QTextCodec *codec = QTextCodec::codecForUtfText( ba, nullptr );
if ( !codec )
{
//check for meta charset tag
QByteArray header = ba.left( 1024 ).toLower();
int pos = header.indexOf( "meta charset=" );
if ( pos != -1 )
{
pos += int( strlen( "meta charset=" ) ) + 1;
int pos2 = header.indexOf( '\"', pos );
QByteArray cs = header.mid( pos, pos2 - pos );
codec = QTextCodec::codecForName( cs );
}
}
if ( !codec )
{
//fallback to QTextCodec::codecForHtml
codec = QTextCodec::codecForHtml( ba, codec );
}
if ( !codec )
{
//no luck, default to utf-8
codec = QTextCodec::codecForName( "UTF-8" );
}
return codec->toUnicode( ba );
}
QUrl QgsServerParameterDefinition::toUrl( bool &ok ) const
{
ok = true;
QUrl val;
if ( !mValue.toString().isEmpty() )
{
val = mValue.toUrl();
}
ok = ( !val.isEmpty() && val.isValid() );
return val;
}
int QgsServerParameterDefinition::toInt( bool &ok ) const
{
ok = true;

View File

@ -137,6 +137,22 @@ class SERVER_EXPORT QgsServerParameterDefinition
*/
QColor toColor( bool &ok ) const;
/**
* Converts the parameter into an url.
* \param ok True if there's no error during the conversion, false otherwise
* \returns An url
* \since QGIS 3.4
*/
QUrl toUrl( bool &ok ) const;
/**
* Loads the data associated to the parameter converted into an url.
* \param ok True if there's no error during the load, false otherwise
* \returns The content loaded
* \since QGIS 3.4
*/
QString loadUrl( bool &ok ) const;
/**
* Raises an exception in case of an invalid parameters.
* \param msg The message describing the exception

View File

@ -19,6 +19,7 @@
#include "qgsdatasourceuri.h"
#include "qgsmessagelog.h"
#include <iostream>
#include <QUrl>
namespace QgsWms
{
@ -85,6 +86,37 @@ namespace QgsWms
return val;
}
QString QgsWmsParameter::loadUrl() const
{
// Check URL -- it will be used in error messages
const QUrl url = toUrl();
bool ok = false;
const QString content = QgsServerParameterDefinition::loadUrl( ok );
if ( !ok )
{
const QString msg = QString( "%1 request error for %2" ).arg( name( mName ), url.toString() );
QgsMessageLog::logMessage( msg );
QgsServerParameterDefinition::raiseError( msg );
}
return content;
}
QUrl QgsWmsParameter::toUrl() const
{
bool ok = false;
const QUrl url = QgsServerParameterDefinition::toUrl( ok );
if ( !ok )
{
raiseError();
}
return url;
}
QColor QgsWmsParameter::toColor() const
{
bool ok = false;
@ -336,6 +368,9 @@ namespace QgsWms
const QgsWmsParameter pSld( QgsWmsParameter::SLD );
save( pSld );
const QgsWmsParameter pSldBody( QgsWmsParameter::SLD_BODY );
save( pSldBody );
const QgsWmsParameter pLayer( QgsWmsParameter::LAYER );
save( pLayer );
@ -470,6 +505,16 @@ namespace QgsWms
: QgsWmsParameters()
{
load( parameters.urlQuery() );
const QString sld = mWmsParameters[ QgsWmsParameter::SLD ].toString();
if ( !sld.isEmpty() )
{
const QString sldBody = mWmsParameters[ QgsWmsParameter::SLD ].loadUrl();
if ( !sldBody.isEmpty() )
{
loadParameter( QgsWmsParameter::name( QgsWmsParameter::SLD_BODY ), sldBody );
}
}
}
bool QgsWmsParameters::loadParameter( const QString &key, const QString &value )
@ -1159,9 +1204,9 @@ namespace QgsWms
return mWmsParameters[ QgsWmsParameter::WMS_PRECISION ].toInt();
}
QString QgsWmsParameters::sld() const
QString QgsWmsParameters::sldBody() const
{
return mWmsParameters[ QgsWmsParameter::SLD ].toString();
return mWmsParameters[ QgsWmsParameter::SLD_BODY ].toString();
}
QStringList QgsWmsParameters::filters() const

View File

@ -132,6 +132,7 @@ namespace QgsWms
SYMBOLWIDTH,
OPACITIES,
SLD,
SLD_BODY,
FI_POLYGON_TOLERANCE,
FI_LINE_TOLERANCE,
FI_POINT_TOLERANCE,
@ -251,6 +252,22 @@ namespace QgsWms
*/
QColor toColor() const;
/**
* Converts the parameter into an url.
* \returns An url
* \throws QgsBadRequestException Invalid parameter exception
* \since QGIS 3.4
*/
QUrl toUrl() const;
/**
* Loads the data associated to the parameter converted into an url.
* \returns The content loaded
* \throws QgsBadRequestException Invalid parameter exception
* \since QGIS 3.4
*/
QString loadUrl() const;
/**
* Raises an error in case of an invalid conversion.
* \throws QgsBadRequestException Invalid parameter exception
@ -377,10 +394,10 @@ namespace QgsWms
QgsRectangle bboxAsRectangle() const;
/**
* Returns SLD if defined or an empty string.
* \returns sld
* Returns SLD_body if defined or an empty string.
* \returns sld body
*/
QString sld() const;
QString sldBody() const;
/**
* Returns the list of feature selection found in SELECTION parameter.

View File

@ -170,7 +170,7 @@ namespace QgsWms
QList<QgsMapLayer *> layers;
QList<QgsWmsParametersLayer> params = mWmsParameters.layersParameters();
QString sld = mWmsParameters.sld();
QString sld = mWmsParameters.sldBody();
if ( !sld.isEmpty() )
layers = sldStylizedLayers( sld );
else
@ -312,7 +312,7 @@ namespace QgsWms
restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
// init stylized layers according to LAYERS/STYLES or SLD
QString sld = mWmsParameters.sld();
QString sld = mWmsParameters.sldBody();
if ( !sld.isEmpty() )
{
layers = sldStylizedLayers( sld );
@ -661,7 +661,7 @@ namespace QgsWms
restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
// init stylized layers according to LAYERS/STYLES or SLD
QString sld = mWmsParameters.sld();
QString sld = mWmsParameters.sldBody();
if ( !sld.isEmpty() )
{
layers = sldStylizedLayers( sld );
@ -749,7 +749,7 @@ namespace QgsWms
restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
// init stylized layers according to LAYERS/STYLES or SLD
QString sld = mWmsParameters.sld();
QString sld = mWmsParameters.sldBody();
if ( !sld.isEmpty() )
{
layers = sldStylizedLayers( sld );
@ -903,7 +903,7 @@ namespace QgsWms
restorer.reset( new QgsLayerRestorer( mNicknameLayers.values() ) );
// init stylized layers according to LAYERS/STYLES or SLD
QString sld = mWmsParameters.sld();
QString sld = mWmsParameters.sldBody();
if ( !sld.isEmpty() )
layers = sldStylizedLayers( sld );
else

View File

@ -1131,7 +1131,7 @@ class TestQgsServerWMSGetMap(QgsServerTestBase):
"REQUEST": "GetMap",
"VERSION": "1.1.1",
"SERVICE": "WMS",
"SLD": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ogc=\"http://www.opengis.net/ogc\" xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd\" version=\"1.1.0\" xmlns:se=\"http://www.opengis.net/se\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <NamedLayer> <se:Name>db_point</se:Name> <UserStyle> <se:Name>db_point_style</se:Name> <se:FeatureTypeStyle> <se:Rule> <se:Name>Single symbol</se:Name> <ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\"> <ogc:PropertyIsEqualTo> <ogc:PropertyName>gid</ogc:PropertyName> <ogc:Literal>1</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <se:PointSymbolizer uom=\"http://www.opengeospatial.org/se/units/metre\"> <se:Graphic> <se:Mark> <se:WellKnownName>square</se:WellKnownName> <se:Fill> <se:SvgParameter name=\"fill\">5e86a1</se:SvgParameter> </se:Fill> <se:Stroke> <se:SvgParameter name=\"stroke\">000000</se:SvgParameter> </se:Stroke> </se:Mark> <se:Size>0.007</se:Size> </se:Graphic> </se:PointSymbolizer> </se:Rule> </se:FeatureTypeStyle> </UserStyle> </NamedLayer> </StyledLayerDescriptor>",
"SLD_BODY": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ogc=\"http://www.opengis.net/ogc\" xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd\" version=\"1.1.0\" xmlns:se=\"http://www.opengis.net/se\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <NamedLayer> <se:Name>db_point</se:Name> <UserStyle> <se:Name>db_point_style</se:Name> <se:FeatureTypeStyle> <se:Rule> <se:Name>Single symbol</se:Name> <ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\"> <ogc:PropertyIsEqualTo> <ogc:PropertyName>gid</ogc:PropertyName> <ogc:Literal>1</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <se:PointSymbolizer uom=\"http://www.opengeospatial.org/se/units/metre\"> <se:Graphic> <se:Mark> <se:WellKnownName>square</se:WellKnownName> <se:Fill> <se:SvgParameter name=\"fill\">5e86a1</se:SvgParameter> </se:Fill> <se:Stroke> <se:SvgParameter name=\"stroke\">000000</se:SvgParameter> </se:Stroke> </se:Mark> <se:Size>0.007</se:Size> </se:Graphic> </se:PointSymbolizer> </se:Rule> </se:FeatureTypeStyle> </UserStyle> </NamedLayer> </StyledLayerDescriptor>",
"BBOX": "-16817707,-4710778,5696513,14587125",
"WIDTH": "500",
"HEIGHT": "500",
@ -1231,7 +1231,7 @@ class TestQgsServerWMSGetMap(QgsServerTestBase):
"SERVICE": "WMS",
"VERSION": "1.1.1",
"REQUEST": "GetMap",
"SLD": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ogc=\"http://www.opengis.net/ogc\" xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd\" version=\"1.1.0\" xmlns:se=\"http://www.opengis.net/se\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <NamedLayer> <se:Name>CountryGroup</se:Name></NamedLayer> </StyledLayerDescriptor>",
"SLD_BODY": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ogc=\"http://www.opengis.net/ogc\" xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd\" version=\"1.1.0\" xmlns:se=\"http://www.opengis.net/se\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <NamedLayer> <se:Name>CountryGroup</se:Name></NamedLayer> </StyledLayerDescriptor>",
"STYLES": "",
"FORMAT": "image/png",
"BBOX": "-16817707,-4710778,5696513,14587125",