Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/Update QgsServerFilter cookbook documentation #6949

Merged
merged 3 commits into from
Feb 11, 2022
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 66 additions & 56 deletions docs/pyqgis_developer_cookbook/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,38 +197,43 @@ From this point, you might find useful a quick look to the

Each filter should implement at least one of three callbacks:

* :meth:`requestReady() <qgis.server.QgsServerFilter.requestReady>`
* :meth:`responseComplete() <qgis.server.QgsServerFilter.responseComplete>`
* :meth:`sendResponse() <qgis.server.QgsServerFilter.sendResponse>`
* :meth:`onRequestReady() <qgis.server.QgsServerFilter.onRequestReady>`
* :meth:`onResponseComplete() <qgis.server.QgsServerFilter.onResponseComplete>`
* :meth:`onSendResponse() <qgis.server.QgsServerFilter.onSendResponse>`

All filters have access to the request/response object
(:class:`QgsRequestHandler <qgis.server.QgsRequestHandler>`)
and can manipulate all its properties (input/output) and
raise exceptions (while in a quite particular way as we’ll see below).

All these methods return a boolean value indicating if the call should be propagated the subsequent
DelazJ marked this conversation as resolved.
Show resolved Hide resolved
filters. If one of these method returns :const:`False` then the chain stop, otherwise the call will propagate
to the next filter.

Here is the pseudo code showing how the server handles a typical request and when the
filter’s callbacks are called:


.. code-block:: text

for each incoming request:
create GET/POST request handler
pass request to an instance of QgsServerInterface
call requestReady filters
call onRequestReady filters

if there is not a response:
if SERVICE is WMS/WFS/WCS:
create WMS/WFS/WCS service
call service’s executeRequest
possibly call sendResponse for each chunk of bytes
possibly call onSendResponse for each chunk of bytes
sent to the client by a streaming services (WFS)
call responseComplete
call sendResponse
call onResponseComplete
request handler sends the response to the client

The following paragraphs describe the available callbacks in details.

requestReady
^^^^^^^^^^^^
onRequestReady
^^^^^^^^^^^^^^

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
Expand All @@ -243,41 +248,43 @@ 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
^^^^^^^^^^^^
onSendResponse
^^^^^^^^^^^^^^

This is called whenever any 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 is needed (WFS GetFeature is one of them).
In that case, instead of a single call to
:meth:`sendResponse() <qgis.server.QgsServerFilter.sendResponse>`, the method might
be exceptionally called multiple times before the response is complete,
and in that case (and only in that case) it is also called
before :meth:`responseComplete() <qgis.server.QgsServerFilter.responseComplete>`.

:meth:`sendResponse() <qgis.server.QgsServerFilter.sendResponse>` is the best place
for direct manipulation of core service’s
output and while :meth:`responseComplete() <qgis.server.QgsServerFilter.responseComplete>`
is typically also an option,
:meth:`sendResponse() <qgis.server.QgsServerFilter.sendResponse>` is the only
viable option in case of streaming services.

responseComplete
^^^^^^^^^^^^^^^^
This is called whenever any partial output is flushed from response buffer (i.e to **FCGI** ``stdout``
if the fcgi server is used) and from there, to the client.
This occurs when huge content is streamed (like WFS GetFeature). In this case
:meth:`onSendResponse() <qgis.server.QgsServerFilter.onSendResponse>` may be called multiple times.

Note that if the response is not streamed, then :meth:`onSendResponse() <qgis.server.QgsServerFilter.onSendResponse>` will not be called at all.

In all case, the last (or unique) chunk will be sent to client after a call to
:meth:`onResponseComplete() <qgis.server.QgsServerFilter.onResponseComplete>`.

Returning :const:`False` will prevent flushing of data to the client. This is desirable when a plugin
wants to collect all chunks from a response and examine or change the response in
:meth:`onResponseComplete() <qgis.server.QgsServerFilter.onResponseComplete>`.


onResponseComplete
^^^^^^^^^^^^^^^^^^

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 :meth:`sendResponse() <qgis.server.QgsServerFilter.sendResponse>`
except for streaming services (or other plugin
filters) that might have called
:meth:`sendResponse() <qgis.server.QgsServerFilter.sendResponse>` earlier.
request is ready to be sent to the client.
As discussed above, this method will be called before the last (or unique) chunk of
data is sent to the client.
For streaming services, multiple calls to :meth:`onSendResponse() <qgis.server.QgsServerFilter.onSendResponse>` migth have been called.
DelazJ marked this conversation as resolved.
Show resolved Hide resolved

:meth:`responseComplete() <qgis.server.QgsServerFilter.responseComplete>` is the
:meth:`onResponseComplete() <qgis.server.QgsServerFilter.onResponseComplete>` 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).

Note that returning :const:`False` will prevent the next plugins to execute
:meth:`onResponseComplete() <qgis.server.QgsServerFilter.onResponseComplete>`
but, in any case, prevent response to be sent to the client.


Raising exceptions from a plugin
................................

Expand Down Expand Up @@ -361,9 +368,9 @@ of a :class:`QgsServerFilter <qgis.server.QgsServerFilter>`.
Each :class:`QgsServerFilter <qgis.server.QgsServerFilter>` implements one or more
of the following callbacks:

* :meth:`requestReady() <qgis.server.QgsServerFilter.requestReady>`
* :meth:`responseComplete() <qgis.server.QgsServerFilter.responseComplete>`
* :meth:`sendResponse() <qgis.server.QgsServerFilter.sendResponse>`
* :meth:`onRequestReady() <qgis.server.QgsServerFilter.onRequestReady>`
* :meth:`onResponseComplete() <qgis.server.QgsServerFilter.onResponseComplete>`
* :meth:`onSendResponse() <qgis.server.QgsServerFilter.onSendResponse>`

The following example implements a minimal filter which prints *HelloServer!*
in case the **SERVICE** parameter equals to “HELLO”:
Expand All @@ -375,22 +382,24 @@ in case the **SERVICE** parameter equals to “HELLO”:
def __init__(self, serverIface):
super().__init__(serverIface)

def requestReady(self):
QgsMessageLog.logMessage("HelloFilter.requestReady")
def onRequestReady(self) -> bool:
QgsMessageLog.logMessage("HelloFilter.onRequestReady")
return True

def sendResponse(self):
QgsMessageLog.logMessage("HelloFilter.sendResponse")
def onSendResponse(self) -> bool:
QgsMessageLog.logMessage("HelloFilter.onSendResponse")
return True

def responseComplete(self):
QgsMessageLog.logMessage("HelloFilter.responseComplete")
def onResponseComplete(self) -> bool:
QgsMessageLog.logMessage("HelloFilter.onResponseComplete")
request = self.serverInterface().requestHandler()
params = request.parameterMap()
if params.get('SERVICE', '').upper() == 'HELLO':
request.clear()
request.setResponseHeader('Content-type', 'text/plain')
# Note that the content is of type "bytes"
request.appendBody(b'HelloServer!')

return True

The filters must be registered into the **serverIface** as in the following example:

Expand Down Expand Up @@ -433,19 +442,20 @@ is still there:
def __init__(self, serverIface):
super(ParamsFilter, self).__init__(serverIface)

def requestReady(self):
def onRequestReady(self) -> bool:
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
request.setParameter('TEST_NEW_PARAM', 'ParamsFilter')
return True

def responseComplete(self):
def onResponseComplete(self) -> bool:
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
if params.get('TEST_NEW_PARAM') == 'ParamsFilter':
QgsMessageLog.logMessage("SUCCESS - ParamsFilter.responseComplete")
QgsMessageLog.logMessage("SUCCESS - ParamsFilter.onResponseComplete")
else:
QgsMessageLog.logMessage("FAIL - ParamsFilter.responseComplete")

QgsMessageLog.logMessage("FAIL - ParamsFilter.onResponseComplete")
return True

This is an extract of what you see in the log file:

Expand All @@ -456,8 +466,8 @@ This is an extract of what you see in the log file:
src/core/qgsmessagelog.cpp: 45: (logMessage) [1ms] 2014-12-12T12:39:29 Server[0] Server plugin HelloServer loaded!
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 Server[0] Server python plugins loaded
src/mapserver/qgshttprequesthandler.cpp: 547: (requestStringToParameterMap) [1ms] inserting pair SERVICE // HELLO into the parameter map
src/mapserver/qgsserverfilter.cpp: 42: (requestReady) [0ms] QgsServerFilter plugin default requestReady called
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] SUCCESS - ParamsFilter.responseComplete
src/mapserver/qgsserverfilter.cpp: 42: (onRequestReady) [0ms] QgsServerFilter plugin default onRequestReady called
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] SUCCESS - ParamsFilter.onResponseComplete

On the highlighted line the “SUCCESS” string indicates that the plugin passed the test.

Expand Down Expand Up @@ -492,14 +502,14 @@ by the WMS core service:
def __init__(self, serverIface):
super().__init__(serverIface)

def responseComplete(self):
def onResponseComplete(self) -> bool:
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
# Do some checks
if (params.get('SERVICE').upper() == 'WMS' \
and params.get('REQUEST').upper() == 'GETMAP' \
and not request.exceptionRaised() ):
QgsMessageLog.logMessage("WatermarkFilter.responseComplete: image ready %s" % request.parameter("FORMAT"))
QgsMessageLog.logMessage("WatermarkFilter.onResponseComplete: image ready %s" % request.parameter("FORMAT"))
# Get the image
img = QImage()
img.loadFromData(request.body())
Expand All @@ -515,7 +525,7 @@ by the WMS core service:
# Set the body
request.clearBody()
request.appendBody(ba)

return True

In this example the **SERVICE** parameter value is checked and if the incoming
request is a **WMS** **GETMAP** and no exceptions have been set by a previously
Expand Down