Skip to content

Commit

Permalink
Merge pull request #31299 from elpaso/server-python-catch-plugins-exc…
Browse files Browse the repository at this point in the history
…eptions

Server: catch Python exceptions from plugins
  • Loading branch information
elpaso committed Aug 20, 2019
2 parents 07841c4 + c9c2d3e commit 0897b4e
Show file tree
Hide file tree
Showing 15 changed files with 239 additions and 45 deletions.
23 changes: 13 additions & 10 deletions python/server/auto_generated/qgsserverogcapihandler.sip.in
Expand Up @@ -99,16 +99,12 @@ Tags
%Docstring
Returns the default response content type in case the client did not specifically
ask for any particular content type.
The default implementation returns the first content type returned by
contentTypes() or JSON if that list is empty.
%End

virtual QList<QgsServerOgcApi::ContentType> contentTypes() const;
%Docstring
Returns the list of content types this handler can serve, default to JSON and HTML.
In case a specialized type (such as GEOJSON) is supported,
the generic type (such as JSON) should not be listed.
%End

virtual void handleRequest( const QgsServerApiContext &context ) const = 0;
virtual void handleRequest( const QgsServerApiContext &context ) const throw( QgsServerApiBadRequestException ) /VirtualErrorHandler=serverapi_badrequest_exception_handler/;
%Docstring
Handles the request within its ``context``

Expand Down Expand Up @@ -149,7 +145,7 @@ returns an empty string if there are not matches.



void write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata = QVariantMap() ) const;
void write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata = QVariantMap() ) const throw( QgsServerApiBadRequestException );
%Docstring
Writes ``data`` to the ``context`` response stream, content-type is calculated from the ``context`` request,
optional ``htmlMetadata`` for the HTML templates can be specified and will be added as "metadata" to
Expand Down Expand Up @@ -179,7 +175,7 @@ Returns an URL to self, to be used for links to the current resources and as a b
:param extension: optional file extension to add (the dot will be added automatically).
%End

const QString templatePath( const QgsServerApiContext &context ) const;
virtual const QString templatePath( const QgsServerApiContext &context ) const;
%Docstring
Returns the HTML template path for the handler in the given ``context``

Expand All @@ -189,7 +185,7 @@ e.g. for an API with root path "/wfs3" and an handler with operationId "collecti
will be apiResourcesDirectory() + "/ogc/templates/wfs3/collectionItems.html"
%End

const QString staticPath( const QgsServerApiContext &context ) const;
virtual const QString staticPath( const QgsServerApiContext &context ) const;
%Docstring
Returns the absolute path to the base directory where static resources for
this handler are stored in the given ``context``.
Expand Down Expand Up @@ -218,6 +214,13 @@ Returns a vector layer from the ``collectionId`` in the given ``context``



protected:

void setContentTypesInt( const QList<int> &contentTypes ) /PyName=setContentTypes/;
%Docstring
Set the content types to ``contentTypes``
%End


};

Expand Down
7 changes: 4 additions & 3 deletions python/server/auto_generated/qgsserverresponse.sip.in
Expand Up @@ -109,17 +109,18 @@ Write server exception
Returns the underlying QIODevice
%End

virtual void finish() = 0;
virtual void finish() throw( QgsServerException ) /VirtualErrorHandler=server_exception_handler/;
%Docstring
Finish the response, ending the transaction
Finish the response, ending the transaction. The default implementation does nothing.
%End

virtual void flush() = 0;
virtual void flush() throw( QgsServerException ) /VirtualErrorHandler=server_exception_handler/;
%Docstring
Flushes the current output buffer to the network

'flush()' may be called multiple times. For HTTP transactions
headers will be written on the first call to 'flush()'.
The default implementation does nothing.
%End

virtual void clear() = 0;
Expand Down
14 changes: 14 additions & 0 deletions python/server/server.sip.in
Expand Up @@ -27,3 +27,17 @@ ${DEFAULTDOCSTRINGSIGNATURE}
}
throw QgsServerApiBadRequestException( strVal );
%End


%VirtualErrorHandler server_exception_handler
PyObject *exception, *value, *traceback;
PyErr_Fetch(&exception, &value, &traceback);
SIP_RELEASE_GIL( sipGILState );
QString strVal = "Server internal error";
if ( value && PyUnicode_Check(value) )
{
Py_ssize_t size;
strVal = QString::fromUtf8( PyUnicode_AsUTF8AndSize(value, &size) );
}
throw QgsServerException( strVal );
%End
1 change: 1 addition & 0 deletions src/server/qgsfilterresponsedecorator.cpp
Expand Up @@ -19,6 +19,7 @@

#include "qgsconfig.h"
#include "qgsfilterresponsedecorator.h"
#include "qgsserverexception.h"

QgsFilterResponseDecorator::QgsFilterResponseDecorator( QgsServerFiltersMap filters, QgsServerResponse &response )
: mFilters( filters )
Expand Down
3 changes: 2 additions & 1 deletion src/server/qgsfilterresponsedecorator.h
Expand Up @@ -24,6 +24,7 @@

#include "qgsserverresponse.h"
#include "qgsserverfilter.h"
#include "qgsserverexception.h"

/**
* \ingroup server
Expand All @@ -45,7 +46,7 @@ class QgsFilterResponseDecorator: public QgsServerResponse
/**
* Call filters requestReady() method
*/
void start();
void start() SIP_THROW( QgsServerException ) SIP_VIRTUALERRORHANDLER( server_exception_handler );

// QgsServerResponse overrides

Expand Down
26 changes: 24 additions & 2 deletions src/server/qgsserver.cpp
Expand Up @@ -345,7 +345,17 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
}

// Call requestReady() method (if enabled)
responseDecorator.start();
// This may also throw exceptions if there are errors in python plugins code
try
{
responseDecorator.start();
}
catch ( QgsException &ex )
{
// Internal server error
response.sendError( 500, QStringLiteral( "Internal Server Error" ) );
QgsMessageLog::logMessage( ex.what(), QStringLiteral( "Server" ), Qgis::Critical );
}

// Plugins may have set exceptions
if ( !requestHandler.exceptionRaised() )
Expand Down Expand Up @@ -418,8 +428,20 @@ void QgsServer::handleRequest( QgsServerRequest &request, QgsServerResponse &res
QgsMessageLog::logMessage( ex.what(), QStringLiteral( "Server" ), Qgis::Critical );
}
}

// Terminate the response
responseDecorator.finish();
// This may also throw exceptions if there are errors in python plugins code
try
{
responseDecorator.finish();
}
catch ( QgsException &ex )
{
// Internal server error
response.sendError( 500, QStringLiteral( "Internal Server Error" ) );
QgsMessageLog::logMessage( ex.what(), QStringLiteral( "Server" ), Qgis::Critical );
}


// We are done using requestHandler in plugins, make sure we don't access
// to a deleted request handler from Python bindings
Expand Down
1 change: 0 additions & 1 deletion src/server/qgsserverogcapi.cpp
Expand Up @@ -70,7 +70,6 @@ void QgsServerOgcApi::executeRequest( const QgsServerApiContext &context ) const
{
// Get url
auto path { sanitizeUrl( context.request()->url() ).path() };
//path.truncate( context.apiRootPath().length() );
// Find matching handler
auto hasMatch { false };
for ( const auto &h : mHandlers )
Expand Down
30 changes: 30 additions & 0 deletions src/server/qgsserverogcapihandler.cpp
Expand Up @@ -66,6 +66,22 @@ QgsServerOgcApiHandler::~QgsServerOgcApiHandler()
//qDebug() << "handler destroyed";
}

QgsServerOgcApi::ContentType QgsServerOgcApiHandler::defaultContentType() const
{
return contentTypes().size() > 0 ? contentTypes().first() : QgsServerOgcApi::ContentType::JSON;
}

QList<QgsServerOgcApi::ContentType> QgsServerOgcApiHandler::contentTypes() const
{
return mContentTypes;
}

void QgsServerOgcApiHandler::handleRequest( const QgsServerApiContext &context ) const
{
Q_UNUSED( context );
throw QgsServerApiNotImplementedException( QStringLiteral( "Subclasses must implement handleRequest" ) );
}

QString QgsServerOgcApiHandler::contentTypeForAccept( const QString &accept ) const
{

Expand Down Expand Up @@ -496,3 +512,17 @@ json QgsServerOgcApiHandler::jsonTags() const
{
return QgsJsonUtils::jsonFromVariant( tags() );
}

void QgsServerOgcApiHandler::setContentTypesInt( const QList<int> &contentTypes )
{
mContentTypes.clear();
for ( const int &i : qgis::as_const( contentTypes ) )
{
mContentTypes.push_back( static_cast<QgsServerOgcApi::ContentType>( i ) );
}
}

void QgsServerOgcApiHandler::setContentTypes( const QList<QgsServerOgcApi::ContentType> &contentTypes )
{
mContentTypes = contentTypes;
}
33 changes: 27 additions & 6 deletions src/server/qgsserverogcapihandler.h
Expand Up @@ -108,15 +108,18 @@ class SERVER_EXPORT QgsServerOgcApiHandler
/**
* Returns the default response content type in case the client did not specifically
* ask for any particular content type.
* The default implementation returns the first content type returned by
* contentTypes() or JSON if that list is empty.
*/
virtual QgsServerOgcApi::ContentType defaultContentType() const { return QgsServerOgcApi::ContentType::JSON; }
virtual QgsServerOgcApi::ContentType defaultContentType() const;

/**
* Returns the list of content types this handler can serve, default to JSON and HTML.
* In case a specialized type (such as GEOJSON) is supported,
* the generic type (such as JSON) should not be listed.
* \note not available in Python bindings
*/
virtual QList<QgsServerOgcApi::ContentType> contentTypes() const { return { QgsServerOgcApi::ContentType::JSON, QgsServerOgcApi::ContentType::HTML }; }
QList<QgsServerOgcApi::ContentType> contentTypes() const SIP_SKIP;

/**
* Handles the request within its \a context
Expand All @@ -126,7 +129,7 @@ class SERVER_EXPORT QgsServerOgcApiHandler
*
* \throws QgsServerApiBadRequestError if the method encounters any error
*/
virtual void handleRequest( const QgsServerApiContext &context ) const = 0;
virtual void handleRequest( const QgsServerApiContext &context ) const SIP_THROW( QgsServerApiBadRequestException ) SIP_VIRTUALERRORHANDLER( serverapi_badrequest_exception_handler );

/**
* Analyzes the incoming request \a context and returns the validated
Expand Down Expand Up @@ -266,7 +269,7 @@ class SERVER_EXPORT QgsServerOgcApiHandler
* - content_type_name( content_type ): returns a short name from a content type for example "text/html" will return "HTML"
*
*/
void write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata = QVariantMap() ) const;
void write( QVariant &data, const QgsServerApiContext &context, const QVariantMap &htmlMetadata = QVariantMap() ) const SIP_THROW( QgsServerApiBadRequestException );

/**
* Returns an URL to self, to be used for links to the current resources and as a base for constructing links to sub-resources
Expand All @@ -285,14 +288,14 @@ class SERVER_EXPORT QgsServerOgcApiHandler
* e.g. for an API with root path "/wfs3" and an handler with operationId "collectionItems", the path
* will be apiResourcesDirectory() + "/ogc/templates/wfs3/collectionItems.html"
*/
const QString templatePath( const QgsServerApiContext &context ) const;
virtual const QString templatePath( const QgsServerApiContext &context ) const;

/**
* Returns the absolute path to the base directory where static resources for
* this handler are stored in the given \a context.
*
*/
const QString staticPath( const QgsServerApiContext &context ) const;
virtual const QString staticPath( const QgsServerApiContext &context ) const;

/**
* Returns the content type from the \a request.
Expand Down Expand Up @@ -331,6 +334,24 @@ class SERVER_EXPORT QgsServerOgcApiHandler
*/
json jsonTags( ) const SIP_SKIP;

protected:

/**
* Set the content types to \a contentTypes
*/
void setContentTypesInt( const QList<int> &contentTypes ) SIP_PYNAME( setContentTypes );

/**
* Set the content types to \a contentTypes
* \note not available in Python bindings
*/
void setContentTypes( const QList<QgsServerOgcApi::ContentType> &contentTypes ) SIP_SKIP;

private:

//! List of content types this handler can serve, first is the default
QList<QgsServerOgcApi::ContentType> mContentTypes = { QgsServerOgcApi::ContentType::JSON, QgsServerOgcApi::ContentType::HTML };


};

Expand Down
10 changes: 10 additions & 0 deletions src/server/qgsserverresponse.cpp
Expand Up @@ -65,6 +65,16 @@ qint64 QgsServerResponse::write( const char *data )
return 0;
}

void QgsServerResponse::finish()
{

}

void QgsServerResponse::flush()
{

}

qint64 QgsServerResponse::write( const std::string data )
{
return write( data.c_str() );
Expand Down
8 changes: 5 additions & 3 deletions src/server/qgsserverresponse.h
Expand Up @@ -21,6 +21,7 @@

#include "qgis_server.h"
#include "qgis_sip.h"
#include "qgsserverexception.h"

#include <QString>
#include <QIODevice>
Expand Down Expand Up @@ -159,17 +160,18 @@ class SERVER_EXPORT QgsServerResponse
virtual QIODevice *io() = 0;

/**
* Finish the response, ending the transaction
* Finish the response, ending the transaction. The default implementation does nothing.
*/
virtual void finish() = 0;
virtual void finish() SIP_THROW( QgsServerException ) SIP_VIRTUALERRORHANDLER( server_exception_handler );

/**
* Flushes the current output buffer to the network
*
* 'flush()' may be called multiple times. For HTTP transactions
* headers will be written on the first call to 'flush()'.
* The default implementation does nothing.
*/
virtual void flush() = 0;
virtual void flush() SIP_THROW( QgsServerException ) SIP_VIRTUALERRORHANDLER( server_exception_handler );

/**
* Reset all headers and content for this response
Expand Down
5 changes: 4 additions & 1 deletion src/server/services/wfs3/qgswfs3handlers.cpp
Expand Up @@ -35,6 +35,7 @@
QgsWfs3APIHandler::QgsWfs3APIHandler( const QgsServerOgcApi *api ):
mApi( api )
{
setContentTypes( { QgsServerOgcApi::ContentType::OPENAPI3, QgsServerOgcApi::ContentType::HTML } );
}

void QgsWfs3APIHandler::handleRequest( const QgsServerApiContext &context ) const
Expand Down Expand Up @@ -188,7 +189,6 @@ json QgsWfs3APIHandler::schema( const QgsServerApiContext &context ) const

QgsWfs3LandingPageHandler::QgsWfs3LandingPageHandler()
{

}

void QgsWfs3LandingPageHandler::handleRequest( const QgsServerApiContext &context ) const
Expand Down Expand Up @@ -620,6 +620,7 @@ json QgsWfs3DescribeCollectionHandler::schema( const QgsServerApiContext &contex

QgsWfs3CollectionsItemsHandler::QgsWfs3CollectionsItemsHandler()
{
setContentTypes( { QgsServerOgcApi::ContentType::GEOJSON, QgsServerOgcApi::ContentType::HTML } );
}

QList<QgsServerQueryStringParameter> QgsWfs3CollectionsItemsHandler::parameters( const QgsServerApiContext &context ) const
Expand Down Expand Up @@ -1053,6 +1054,7 @@ void QgsWfs3CollectionsItemsHandler::handleRequest( const QgsServerApiContext &c

QgsWfs3CollectionsFeatureHandler::QgsWfs3CollectionsFeatureHandler()
{
setContentTypes( { QgsServerOgcApi::ContentType::GEOJSON, QgsServerOgcApi::ContentType::HTML } );
}

void QgsWfs3CollectionsFeatureHandler::handleRequest( const QgsServerApiContext &context ) const
Expand Down Expand Up @@ -1171,6 +1173,7 @@ json QgsWfs3CollectionsFeatureHandler::schema( const QgsServerApiContext &contex

QgsWfs3StaticHandler::QgsWfs3StaticHandler()
{
setContentTypes( { QgsServerOgcApi::ContentType::HTML } );
}

void QgsWfs3StaticHandler::handleRequest( const QgsServerApiContext &context ) const
Expand Down

0 comments on commit 0897b4e

Please sign in to comment.