QGISサーバーのPythonプラグイン¶
警告
Despite our constant efforts, information beyond this line may not be updated for QGIS 3. Refer to https://qgis.org/pyqgis/master for the python API documentation or, give a hand to update the chapters you know about. Thanks.
Python plugins can also run on QGIS Server (see OGCデータサーバーとしてのQGIS):
By using the server interface (
QgsServerInterface
) a Python plugin running on the server can alter the behavior of existing core services (WMS, WFS etc.).With the server filter interface (
QgsServerFilter
) you can change the input parameters, change the generated output or even provide new services.With the access control interface (
QgsAccessControlFilter
) you can apply some access restriction per requests.
サーバーフィルタプラグインアーキテクチャ¶
Server python plugins are loaded once when the FCGI application starts. They
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:
All filters have access to the request/response object (QgsRequestHandler
)
and can manipulate all its properties (input/output) and
raise exceptions (while in a quite particular way as we’ll see below).
ここでは、フィルタのコールバックが呼ばれ、一般的なサーバーのセッションとを示す擬似コードは次のとおりです。
- 着信要求を取得
GET/POST/SOAPリクエストハンドラを作成
pass request to an instance of
QgsServerInterface
call plugins
requestReady
filters- 応答がない場合
- サービスがWMS/WFS/WCSであれば
- WMS/WFS/WCSサーバーを作成
call server’s
executeRequest
and possibly callsendResponse
plugin filters when streaming output or store the byte stream output and content type in the request handler
call plugins
responseComplete
filters
call plugins
sendResponse
filters要求ハンドラの出力応答
次の段落では、利用可能なコールバックを詳細に説明します。
requestReady¶
要求の準備ができたときに呼び出されます。受信URLとデータが解析され、コアサービス(WMS、WFSなど)スイッチに入る前に、これは入力を操作するなどのアクションを実行できるポイントです。
認証/認可
リダイレクト
特定のパラメーター(例えば、型名)を追加/除去
例外を発生させる
SERVICE パラメーターを変更することでコアサービスを完全に置き換え、それによりコアサービスを完全にバイパスすることさえできるかもしれません(とはいえ、これはあまり意味がないということ)。
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).
プラグインから例外を発生させる¶
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).
このアプローチは、基本的に動作しますが、それは非常に「パイソン的」ではありません:より良いアプローチは、Pythonコードから例外を発生し、それらがそこで処理されるためにC ++ループに湧き上がるのを見ることでしょう。
サーバー・プラグインを書く¶
A server plugin is a standard QGIS Python plugin as described in
Pythonプラグインを開発する, that just provides an additional (or alternative)
interface: a typical QGIS desktop plugin has access to QGIS application
through the QgisInterface
instance, a server
plugin has also
access to a QgsServerInterface
.
プラグインがサーバー・インターフェイスを持つことをQGISサーバーに知らせるには、特別なメタデータエントリが( metadata.txt 中に)必要とされます
server=True
The example plugin discussed here (with many more example filters) is available on github: QGIS HelloServer Example Plugin. You could also find more examples at https://github.com/elpaso/qgis3-server-vagrant/tree/master/resources/web/plugins or browsing the QGIS plugins repository.
プラグインファイル¶
私たちの例のサーバー・プラグインのディレクトリ構造はこちらです
PYTHON_PLUGINS_PATH/
HelloServer/
__init__.py --> *required*
HelloServer.py --> *required*
metadata.txt --> *required*
__init__.py¶
This file is required by Python's import system. Also, QGIS Server requires that this
file contains a serverClassFactory()
function, which is called when the
plugin gets loaded into QGIS Server when the server starts. It receives reference to instance of
QgsServerInterface
and must return instance
of your plugin's class.
This is how the example plugin __init__.py
looks like:
# -*- coding: utf-8 -*-
def serverClassFactory(serverIface):
from HelloServer import HelloServerServer
return HelloServerServer(serverIface)
HelloServer.py¶
魔法が起こると、これは魔法がどのように見えるかであるところである:(例 HelloServer.py
)
サーバー・プラグインは通常、QgsServerFilterと呼ばれるオブジェクトに詰め込まれる一回の以上のコールバックで構成されています。
Each QgsServerFilter
implements one or more
of the following callbacks:
次の例では、 サービス パラメーターが「HELLO」に等しい場合に HelloServer! を印刷する、最小限のフィルタを実装します:
from qgis.server import *
from qgis.core import *
class HelloFilter(QgsServerFilter):
def __init__(self, serverIface):
super(HelloFilter, self).__init__(serverIface)
def responseComplete(self):
request = self.serverInterface().requestHandler()
params = request.parameterMap()
if params.get('SERVICE', '').upper() == 'HELLO':
request.clearHeaders()
request.setHeader('Content-type', 'text/plain')
request.clearBody()
request.appendBody('HelloServer!')
フィルタは、次の例のように serverIface に登録する必要があります:
class HelloServerServer:
def __init__(self, serverIface):
# Save reference to the QGIS server interface
self.serverIface = serverIface
serverIface.registerFilter( HelloFilter, 100 )
The second parameter of
registerFilter
sets a priority which
defines the order for the callbacks with the same name (the lower priority is
invoked first).
By using the three callbacks, plugins can manipulate the input and/or the output
of the server in many different ways. In every moment, the plugin instance has
access to the QgsRequestHandler
through
the QgsServerInterface
.
The QgsRequestHandler
class has plenty of
methods that can be used to alter
the input parameters before entering the core processing of the server (by using
requestReady()
) or after the request has been processed by the core services
(by using sendResponse()
).
次の例は、いくつかの一般的なユースケースをカバーします:
入力を変更する¶
例のプラグインにはクエリ文字列からの入力パラメーターを変更する試験例を含んでいます、この例では新しいパラメーターが(既に解析された) parameterMap
中に注入され、するとこのパラメータはコアサービス(WMSなど)によって表示され、コアサービス処理の終わりではパラメーターがまだあることを確認します
from qgis.server import *
from qgis.core import *
class ParamsFilter(QgsServerFilter):
def __init__(self, serverIface):
super(ParamsFilter, self).__init__(serverIface)
def requestReady(self):
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
request.setParameter('TEST_NEW_PARAM', 'ParamsFilter')
def responseComplete(self):
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
if params.get('TEST_NEW_PARAM') == 'ParamsFilter':
QgsMessageLog.logMessage("SUCCESS - ParamsFilter.responseComplete", 'plugin', QgsMessageLog.INFO)
else:
QgsMessageLog.logMessage("FAIL - ParamsFilter.responseComplete", 'plugin', QgsMessageLog.CRITICAL)
これは、ログファイルに見るものの抽出物である:
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] HelloServerServer - loading filter ParamsFilter
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/qgsgetrequesthandler.cpp: 35: (parseInput) [0ms] query string is: SERVICE=HELLO&request=GetOutput
src/mapserver/qgshttprequesthandler.cpp: 547: (requestStringToParameterMap) [1ms] inserting pair SERVICE // HELLO into the parameter map
src/mapserver/qgshttprequesthandler.cpp: 547: (requestStringToParameterMap) [0ms] inserting pair REQUEST // GetOutput 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] HelloFilter.requestReady
src/mapserver/qgis_map_serv.cpp: 235: (configPath) [0ms] Using default configuration file path: /home/xxx/apps/bin/admin.sld
src/mapserver/qgshttprequesthandler.cpp: 49: (setHttpResponse) [0ms] Checking byte array is ok to set...
src/mapserver/qgshttprequesthandler.cpp: 59: (setHttpResponse) [0ms] Byte array looks good, setting response...
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] HelloFilter.responseComplete
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] SUCCESS - ParamsFilter.responseComplete
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] RemoteConsoleFilter.responseComplete
src/mapserver/qgshttprequesthandler.cpp: 158: (sendResponse) [0ms] Sending HTTP response
src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] HelloFilter.sendResponse
強調表示された行の「SUCCESS」の文字列は、プラグインがテストに合格したことを示しています。
同じ手法が、コアのサービスでなくカスタムサービスを利用するために利用できます:たとえば WFS SERVICE 要求または任意の他のコア要求を SERVICE パラメーターを別の何かに変更するだけでスキップできます、そしてコアサービスはスキップされ、それからカスタム結果を出力に注入してそれらをクライアントに送信できます(これはここで以下に説明される)。
出力を変更または置き換えする¶
透かしフィルタの例は、WMSコアサービスによって作成されたWMS画像の上に透かし画像を加算した新たな画像でWMS出力を置き換える方法を示しています:
import os
from qgis.server import *
from qgis.core import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
class WatermarkFilter(QgsServerFilter):
def __init__(self, serverIface):
super(WatermarkFilter, self).__init__(serverIface)
def responseComplete(self):
request = self.serverInterface().requestHandler()
params = request.parameterMap( )
# Do some checks
if (request.parameter('SERVICE').upper() == 'WMS' \
and request.parameter('REQUEST').upper() == 'GETMAP' \
and not request.exceptionRaised() ):
QgsMessageLog.logMessage("WatermarkFilter.responseComplete: image ready {}".format(request.infoFormat()), 'plugin', QgsMessageLog.INFO)
# Get the image
img = QImage()
img.loadFromData(request.body())
# Adds the watermark
watermark = QImage(os.path.join(os.path.dirname(__file__), 'media/watermark.png'))
p = QPainter(img)
p.drawImage(QRect( 20, 20, 40, 40), watermark)
p.end()
ba = QByteArray()
buffer = QBuffer(ba)
buffer.open(QIODevice.WriteOnly)
img.save(buffer, "PNG")
# Set the body
request.clearBody()
request.appendBody(ba)
この例では SERVICE パラメーター値がチェックされます。そして着信要求が WMS GetMap であり、例外が以前に実行されたプラグインまたはコアサービス(この場合WMS)によって設定されていない場合、WMSで生成された画像が出力バッファから取得され、透かし画像が追加されます。最後のステップは、出力バッファをクリアして、新たに作成された画像で置き換えることです。実世界の状況では、どのような場合にもPNGを返すのではなく、要求された画像の種類を確認する必要もあることに注意してください。
アクセス制御プラグイン¶
プラグインファイル¶
私たちの例のサーバー・プラグインのディレクトリ構造がこちらです:
PYTHON_PLUGINS_PATH/
MyAccessControl/
__init__.py --> *required*
AccessControl.py --> *required*
metadata.txt --> *required*
__init__.py¶
This file is required by Python's import system. As for all QGIS server plugins, this
file contains a serverClassFactory()
function, which is called when the
plugin gets loaded into QGIS Server at startup. It receives a reference to an instance of
QgsServerInterface
and must return an instance
of your plugin's class.
This is how the example plugin __init__.py
looks like:
# -*- coding: utf-8 -*-
def serverClassFactory(serverIface):
from MyAccessControl.AccessControl import AccessControl
return AccessControl(serverIface)
AccessControl.py¶
class AccessControl(QgsAccessControlFilter):
def __init__(self, server_iface):
super(QgsAccessControlFilter, self).__init__(server_iface)
def layerFilterExpression(self, layer):
""" Return an additional expression filter """
return super(QgsAccessControlFilter, self).layerFilterExpression(layer)
def layerFilterSubsetString(self, layer):
""" Return an additional subset string (typically SQL) filter """
return super(QgsAccessControlFilter, self).layerFilterSubsetString(layer)
def layerPermissions(self, layer):
""" Return the layer rights """
return super(QgsAccessControlFilter, self).layerPermissions(layer)
def authorizedLayerAttributes(self, layer, attributes):
""" Return the authorised layer attributes """
return super(QgsAccessControlFilter, self).authorizedLayerAttributes(layer, attributes)
def allowToEdit(self, layer, feature):
""" Are we authorise to modify the following geometry """
return super(QgsAccessControlFilter, self).allowToEdit(layer, feature)
def cacheKey(self):
return super(QgsAccessControlFilter, self).cacheKey()
この例では全員に完全なアクセス権を与えています。
誰がログオンしているかを知るのはこのプラグインの役割です。
これらすべての方法で私達は、レイヤーごとの制限をカスタマイズできるようにするには、引数のレイヤーを持っています。
layerFilterExpression¶
結果を制限するために式を追加するために使用し、例えば:
def layerFilterExpression(self, layer):
return "$role = 'user'"
属性の役割が「ユーザー」に等しい地物に制限するため。
layerFilterSubsetString¶
以前よりも同じですが、(データベース内で実行) SubsetString
を使用
def layerFilterSubsetString(self, layer):
return "role = 'user'"
属性の役割が「ユーザー」に等しい地物に制限するため。
layerPermissions¶
レイヤーへのアクセスを制限します。
Return an object of type LayerPermissions
, which has the properties:
canRead
to see it in theGetCapabilities
and have read access.canInsert
to be able to insert a new feature.canUpdate
to be able to update a feature.canDelete
to be able to delete a feature.
例:
def layerPermissions(self, layer):
rights = QgsAccessControlFilter.LayerPermissions()
rights.canRead = True
rights.canRead = rights.canInsert = rights.canUpdate = rights.canDelete = False
return rights
読み取り専用のアクセスのすべてを制限します。
authorizedLayerAttributes¶
属性の特定のサブセットの可視性を制限するために使用します。
引数の属性が表示属性の現在のセットを返します。
例:
def authorizedLayerAttributes(self, layer, attributes):
return [a for a in attributes if a != "role"]
「role」属性を非表示にします。
allowToEdit¶
これは、地物のサブセットに編集を制限するために使用されます。
これは、 WFS-Transaction
プロトコルで使用されています。
例:
def allowToEdit(self, layer, feature):
return feature.attribute('role') == 'user'
値「user」の属性「role」を持つ地物だけを編集できます。