Outdated version of the documentation. Find the latest one here.

QGISサーバーのPythonプラグイン

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) we can change the input parameters, change the generated output or even by providing new services.

With the access control interface (QgsAccessControlFilter) we 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:

  • requestReady()
  • responseComplete()
  • sendResponse()

すべてのフィルタは、リクエスト/レスポンスオブジェクトへのアクセス権を持っており( QgsRequestHandler )、そのすべてのプロパティ(入力/出力)を操作し、(以下で見るように非常に特定の方法で)例外を発生させることができます。

ここでは、フィルタのコールバックが呼ばれ、一般的なサーバーのセッションとを示す擬似コードは次のとおりです。

  • 着信要求を取得
    • GET/POST/SOAPリクエストハンドラを作成

    • QgsServerInterface のインスタンスにリクエストを渡す

    • プラグインを呼び出す requestReady() フィルタ

    • 応答がない場合
      • サービスがWMS/WFS/WCSであれば
        • WMS/WFS/WCSサーバーを作成
          • サーバーの executeRequest() を呼び出しpossibily sendResponse() プラグインフィルタを呼び出す、出力をストリーミングするとき、または要求ハンドラのバイトストリーム出力およびコンテンツタイプを保存します

      • プラグインを呼び出す responseComplete() フィルタ

    • sendResponse() フィルタプラグインを呼び出します

    • 要求ハンドラの出力応答

次の段落では、利用可能なコールバックを詳細に説明します。

requestReady

要求の準備ができたときに呼び出されます。受信URLとデータが解析され、コアサービス(WMS、WFSなど)スイッチに入る前に、これは入力を操作するなどのアクションを実行できるポイントです。

  • 認証/認可

  • リダイレクト

  • 特定のパラメーター(例えば、型名)を追加/除去

  • 例外を発生させる

SERVICE パラメーターを変更することでコアサービスを完全に置き換え、それによりコアサービスを完全にバイパスすることさえできるかもしれません(とはいえ、これはあまり意味がないということ)。

sendResponse

これは出力が FCGI stdout へ(そしてそこから、クライアントへ)送られるたびに呼び出され、通常はコアサービスがそのプロセスを終了した後、responseCompleteフックが呼ばれた後に行われますが、たまにXMLがとても巨大になってストリーミングXMLの実装が必要とされることがあり(WFS GetFeatureはそのうちの一つです) 、この場合、sendResponse() は応答が完了する前に(そして responseComplete() が呼び出される前に)複数回呼び出されます。明白な結果はつまり sendResponse() は通常は一度呼び出されますが、例外的に複数回呼び出されることもあり、その場合は(そしてそのような場合だけ)それは responseComplete() の前にも呼び出されます。

sendResponse() は、コアサービスの出力を直接操作するために最高の場所であり、 responseComplete() も一般的には選択肢ですが、ストリーミングサービスの場合は sendResponse() が唯一の実行可能な選択肢です。

responseComplete

(ヒットした場合)コアサービスは、そのプロセスを終了するときに一度だけ呼び出され、要求がクライアントに送信する準備ができています。上述したように、これは通常、 sendResponse() をより早くに呼び出しているかもしれないストリーミングサービス(または他のプラグインフィルタ)を除いては、 sendResponse() の前に呼ばれます。

responseComplete() は、新しいサービスの実装(WPSまたはカスタムサービス)を提供し、コアサービスからの出力を直接操作を実行するために理想的な場所である(例えばWMSの画像に透かしを追加するため)。

プラグインから例外を発生させる

まだこのトピックについていくらか作業する必要があります:現在の実装では、 QgsRequestHandler プロパティを QgsMapServiceException のインスタンスへ設定することによって処理済と未処理の例外を区別できます、このようにメインC ++のコードでは処理済のPythonの例外をキャッチし、未処理の例外を無視できます(あるいはさらに良くは:それらをログに記録)。

このアプローチは、基本的に動作しますが、それは非常に「パイソン的」ではありません:より良いアプローチは、Pythonコードから例外を発生し、それらがそこで処理されるためにC ++ループに湧き上がるのを見ることでしょう。

サーバー・プラグインを書く

A server plugins is just 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

プラグインファイル

私たちの例のサーバー・プラグインのディレクトリ構造はこちらです

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と呼ばれるオブジェクトに詰め込まれる一回の以上のコールバックで構成されています。

QgsServerFilter は、次のコールバックの一つ以上を実装します。

  • requestReady()
  • responseComplete()
  • sendResponse()

次の例では、 サービス パラメーターが「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 )

registerFilter() の第2パラメーターでは、同じ名前のコールバックについての順序を決める優先順位を設定できます(低い優先順位が最初に呼び出されます)。

3つのコールバックを使用することにより、プラグインは、さまざまな方法でサーバーの入力および/または出力を操作できます。すべての瞬間に、プラグインのインスタンスは、 QgsServerInterface を通じて QgsRequestHandler へのアクセスを有します、 QgsRequestHandler は、サーバーのコア処理に入る前に( requestReady() を使用して)または要求がコアサービスによって処理された後に( 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 PyQt4.QtCore import *
from PyQt4.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 %s" % 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 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 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

レイヤーへのアクセスを制限します。

タイプ QgsAccessControlFilter.LayerPermissions のオブジェクトを返します。このオブジェクトのプロパティは:

  • canRead は、 GetCapabilities 中でそれを見て、読み取りアクセス権があります。

  • canInsert は、新しい地物を挿入できるようにします。

  • canUpdate は、地物を更新できるようにします。

  • candelete は、地物を削除できるようにします。

例:

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」を持つ地物だけを編集できます。

cacheKey

QGISサーバーは、このメソッド中に役割を返すことができる役割ごとにキャッシュを持っている能力のキャッシュを維持します。または None を返し、完全にキャッシュを無効にします。