QGIS server python plugins
Today it’s a great day for QGIS Server: Python plugins, the project that took me busy during the past two months, has been merged to master and will be available starting with the next QGIS release.
The project has been discussed and approved in Essen during the last QGIS HF (see my presentation about server plugins), thanks to the input and suggestions coming from Marco Hugentobler and Martin Dobias it is now implemented in the more complete and flexible way.
In this article I will introduce the core concepts and the main features of python plugins for QGIS server.
QGIS server plugins architecture
QGIS server provides some core services: WFS, WMS, WCS. What we wanted to achieve was a system to easily add new services and modify existing services through python plugins.
Mi first experiments were limited to a 404 handler that intercepts unhandled requests and hooks into python plugins capturing every stdout
output, this was indeed not enough flexible for a full fledged plugins implementation.
The main loop
QGIS server is not different from most web services implementations: it listens for incoming requests, parses the URL query string parameters and returns its output accordingly to the incoming request.
The standard loop before introducing python plugins looked like the following:
- Get the request
- create GET/POST/SOAP request handler
- if SERVICE is WMS/WFS/WCS
- create WMS/WFS/WCS server passing in request handler
- call server’s
executeRequest()
- call request handler output method
- call server’s
- create WMS/WFS/WCS server passing in request handler
- else
Exception
Plugins come into play
Server python plugins are loaded once when the FCGI application starts and they should register one or more QgsServerFilter (from this point, you might find useful a quick look to the server plugins API docs). Each filter should implement at least one of three callbacks (aka: hooks):
All filters have access to the request/response object (QgsRequestHandler) and can manipulate all its properties (input/output) and can raise exceptions (while in a quite particular way as we’ll see below).
Here is a pseudo code showing how and when the filter’s callbacks are called:
-
- Get the request
-
- create GET/POST/SOAP request handler
- pass request to
serverIface
- call plugins
requestReady
filters - if there is not a response
- if SERVICE is WMS/WFS/WCS
- create WMS/WFS/WCS server
- call server’s
executeRequest
and possibily callsendResponse
plugin filters when streaming output or store the byte stream output and content type in the request handler
- call server’s
- create WMS/WFS/WCS server
- call plugins
responseComplete
filters
- if SERVICE is WMS/WFS/WCS
-
call plugins
sendResponse
filters - request handler output the response
requestReady
This is called when the request is ready: incoming URL and data have been parsed and before entering the core services (WMS, WFS etc.) switch, this is the point where you can manipulate the input and perform actions like:
- authentication/authorization
- redirects
- add/remove certain parameters (typenames for example)
- raise exceptions
You could even substitute a core service completely by changing SERVICE parameter and hence bypassing the core service completely (not that this make much sense though).
sendResponse
This is called whenever output is sent to FCGI stdout
(and from there, to the client), this is normally done after core services have finished their process and after responseComplete hook was called, but in a few cases XML can become so huge that a streaming XML implementation was needed (WFS GetFeature
is one of them), in this case, sendResponse
is called multiple times before the response is complete (and before responseComplete is called). The obvious consequence is that sendResponse
is normally called once but might be exceptionally called multiple times and in that case (and only in that case) it is also called before responseComplete.
SendResponse is the best place for direct manipulation of core service’s output and while responseComplete is typically also an option, sendResponse is the only viable option in case of streaming services.
responseComplete
This is called once when core services (if hit) finish their process and the request is ready to be sent to the client. As discussed above, this is normally called before sendResponse
except for streaming services (or other plugin filters) that might have called sendResponse
earlier.
responseComplete
is the ideal place to provide new services implementation (WPS or custom services) and to perform direct manipulation of the output coming from core services (for example to add a watermark upon a WMS image).
Raising exception from a plugin
Some work has still to be done on this topic: the current implementation can distinguish between handled and unhandled exceptions by setting a QgsRequestHandler property to an instance of QgsMapServiceException, this way the main C++ code can catch handled python exceptions and ignore unhandled exceptions (or better: log them).
This approach basically works but it does not satisfy my pythonic way of handle exceptions: I would rather prefer to raise exceptions from python code to see them bubbling up into C++ loop for being handled there.
Conclusions
The new plugin system is very flexible and allows for basic input/output (i.e. request/response) manipulation and for new services implementation while it remains unobtrusive and has negligible impact on performances, in the next article I will discuss server plugin implementation in depth.
See also the second part of this article.
See all QGIS Server related posts