Plugin-urile Python pot rula și pe QGIS Server (a se vedea: QGIS ca și Server de Date OGC): prin utilizarea interfeței server-ului (QgsServerInterface), un plugin scris în Python, care rulează pe server, poate modifica comportamentul serviciilor de bază existente (WMS, WFS etc.).
Cu ajutorul interfeței de filtrare a server-ului (QgsServerFilter) putem schimba parametrii de intrare, rezultatul generat, sau putem furniza noi servicii.
Cu ajutorul interfeței de control al accesului (QgsAccessControlFilter) putem aplica unele restricții de acces asupra cererilor.
Plugin-urile Python de pe Server sunt încărcate o dată ce pornește aplicația FCGI. Acestea înregistrează una sau mai multe clase QgsServerFilter (din acest punct, ar fi util să aruncați o privire rapidă la Documentația API a plugin-urilor pentru server). Fiecare filtru ar trebui să implementeze cel puțin una dintre cele trei funcții de reapelare:
Toate filtrele au acces la obiectul cerere/răspuns (QgsRequestHandler), putându-i manipula toate proprietățile (de intrare/ieșire) și tratându-i excepțiile (deși într-un mod cu totul particular, după cum vom vedea mai jos).
Mai jos se află un pseudocod care prezintă o sesiune tipică de server și reapelarea filtrelor:
se creează o rutină de tratare a cererilor GET/POST/SOAP
se transmite cererea către o instanță a clasei QgsServerInterface
se apelează filtrele requestReady() ale plugin-urillor
se apelează funcția executeRequest() a serverului și, eventual, a funcției sendResponse() a filtrelor plugin-ului, atunci când are loc generarea rezultatului, sau stocarea fluxului de ieșire cu octeți și a tipului conținutului din rutina de tratare a cererii
se apelează filtrele responseComplete() ale plugin-urillor
se apelează filtrele sendResponse() ale plugin-urillor
request handler output the response
Următoarele paragrafe descriu, în detaliu, funcțiile de reapelare disponibile.
Este apelată atunci când cererea este pregătită: adresa și datele primite au fost analizate și, înainte de a intra în comutatorul serviciilor de bază (WMS, WFS, etc), acesta este punctul în care se poate interveni asupra datelor de intrare, putându-se efectua acțiuni de genul:
autentificare/autorizare
redirectări
adăugarea/eliminarea anumitor parametri (denumirile tipurilor, de exemplu)
tratarea excepțiilor
Ați putea chiar să substituiți în întregime un serviciu de bază, prin schimbarea parametrului SERVICE, astfel, ocolindu-se complet serviciul de bază (deși, acest lucru nu ar avea prea mult sens).
Este apelată de fiecare dată când ieșirea este dirijată către FCGI stdout (și de acolo, înspre client), acest lucru făcându-se, în mod normal, după ce serviciile de bază și-au finalizat procesarea și după ce a fost apelată funcția de interceptare responseComplete; totuși, în anumite cazuri, fișierul XML poate deveni extrem de mare, de aceea a fost nevoie de implementarea unei funcțiuni de transfer continuu a fluxului XML (WFS GetFeature este una dintre ele), în acest caz, funcția sendResponse`() fiind apelată de mai multe ori înainte de încheierea răspunsului (și înainte de a fi apelată responseComplete()). În consecință, sendResponse() este, în mod normal, apelată doar o dată, însă, în mod excepțional, apelarea poate avea loc de mai multe ori, iar în acest caz (și numai în acest caz) va fi apelată înaintea funcției responseComplete().
sendResponse() reprezintă cel mai bun loc pentru manipularea directă a rezultatului serviciilor de bază și, în timp ce responseComplete() constă, de asemenea, într-o opțiune, sendResponse() este singura opțiune viabilă în cazul serviciilor de redare continuă a fluxului.
Este apelată o singură dată, atunci când procesele serviciilor de bază (în cazul în care sunt implicate) s-au încheiat, iar cererea este gata de a fi transmisă clientului. Așa cum s-a discutat mai sus, este apelată, de obicei, înaintea funcției sendResponse(), cu excepția serviciilor de redare continuă a fluxului (sau a altor filtre ale plugin-urilor), care ar putea apela sendResponse() mai devreme.
responseComplete() reprezintă locul ideal pentru implementarea unor noi servicii (WPS sau servicii personalizate), precum și pentru a efectua intervenții directe asupra rezultatului care provine de la serviciile de bază (de exemplu, pentru a adăuga un filigran pe o imagine WMS).
Mai sunt ceva acțiuni de efectuat în acest capitol: implementarea curentă poate distinge între excepțiile tratate și cele netratate, prin setarea proprietății QgsRequestHandler pentru o instanță a clasei QgsMapServiceException; în acest fel, codul C++ principal poate să intercepteze excepțiile Python tratate și să le ignore pe cele netratate (sau mai bine: să le jurnalizeze).
Această abordare funcționează în principiu, dar nu este în spiritul limbajului “python”: o abordare mai bună ar fi de a face vizibile excepțiile din codul python la nivelul buclei C++, pentru a fi manipulată acolo.
Un server de plugin-uri reprezintă doar un plugin Python standard, pentru QGIS, așa cum este descris în Dezvoltarea plugin-urilor Python, care oferă o interfață suplimentară (sau alternativă): un plugin tipic pentru QGIS desktop are acces la aplicație prin instanța QgisInterface, pe când un plugin pentru server are acces, în plus, la clasa QgsServerInterface.
Pentru a spune Serverului QGIS că un plugin are o interfață de server, este necesară o intrare de metadate specială (în metadata.txt)
server=True
Exemplul de plugin discutat aici (cu mult mai multe exemple de filtre), este disponibil pe github: QGIS HelloServer Example Plugin
Iată structura de directoare a exemplului nostru de plugin pentru server
PYTHON_PLUGINS_PATH/
HelloServer/
__init__.py --> *required*
HelloServer.py --> *required*
metadata.txt --> *required*
Acest fișier este cerut de sistemul de import al Python. De asemenea, serverul QGIS necesită ca acest fișier să conțină o funcție serverClassFactory(), care este apelată atunci când plugin-ul se încarcă în QGIS Server, la pornirea serverului. Acesta primește referința către o instanță a QgsServerInterface și trebuie să returneze instanța clasei plugin-ului dvs. Iată cum arată fișierul __init__.py al exemplului de plugin:
# -*- coding: utf-8 -*-
def serverClassFactory(serverIface):
from HelloServer import HelloServerServer
return HelloServerServer(serverIface)
Aici este locul în care se întâmplă magia, și iată rezultatul acesteia: (de exemplu HelloServer.py)
Un plug-in de server este format, de obicei, dintr-una sau mai multe funcții Callback, ambalate în obiecte denumite QgsServerFilter.
Fiecare QgsServerFilter implementează una sau mai multe dintre următoarele funcții callback:
Exemplul următor implementează un filtru minimal, care generează textul HelloServer! atunci când parametrul SERVICE este egal cu “HELLO”:
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!')
Filtrele trebuie să fie înregistrate în serverIface ca în exemplul următor:
class HelloServerServer:
def __init__(self, serverIface):
# Save reference to the QGIS server interface
self.serverIface = serverIface
serverIface.registerFilter( HelloFilter, 100 )
Al doilea parametru al funcției registerFilter() permite stabilirea unei priorități, care definește ordinea pentru funcțiile callback cu același nume (prioritatea inferioară este invocată mai întâi).
Prin utilizarea celor trei funcții callback, plugin-urile pot manipula intrarea și/sau ieșirea serverului în mai multe moduri diferite. În orice moment, instanța plugin-ului are acces la QgsRequestHandler prin intermediul clasei QgsServerInterface. Clasa QgsRequestHandler dispune de o mulțime de metode care pot fi utilizate pentru modificarea parametrilor de intrare, înainte de a intra în nucleul de prelucrare al serverului (prin utilizarea requestReady()) sau în urma procesării cererii de către serviciile de bază (prin utilizarea sendResponse()).
Următorul exemplu demonstrează câteva cazuri de utilizare obișnuită:
Exemplul de plugin conține un test care modifică parametrii de intrare provenind din șirul de interogare, în acest exemplu un nou parametru fiind injectat în parameterMap (deja analizat), parametrul fiind apoi vizibil tuturor serviciilor de bază (WMS etc.); la încheierea procesării, efectuate de către serviciile de bază, vom verifica dacă parametrul este încă acolo:
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)
Acesta este un extras a ceea ce vom vedea în fișierul jurnal:
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
În linia 13, șirul “SUCCESS” indică faptul că plugin-ul a trecut testul.
Aceeași tehnică poate fi exploatată pentru a utiliza un serviciu personalizat in locul unuia de bază: de exemplu, ați putea sări peste o cerere WFS SERVICE, sau peste oricare altă cerere de bază, doar prin schimbarea parametrului SERVICE în ceva diferit, iar serviciul de bază va fi omis; în acel caz, veți puteți injecta datele dvs. în interiorul rezultatului, trimițându-le clientului (acest lucru este explicat în continuare).
Exemplul filtrului cu filigran vă arată cum să înlocuiți rezultatul WMS cu o nouă imagine, obținută prin adăugarea unei imagini filigran în partea de sus a imaginii WMS, generată de serviciul de bază 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)
În cadrul acestui exemplu, este verificată valoarea parametrului SERVICE, iar în cazul în care cererea de intrare este un WMS GETMAP și nici un fel de excepții nu au fost stabilite de către un plugin executat anterior, sau de către serviciul de bază (WMS în acest caz), imaginea generată de către WMS este preluată din zona tampon de ieșire, adăugându-i-se imaginea filigran. Pasul final este de a goli tamponul de ieșire și de-l înlocui cu imaginea nou generată. Rețineți că într-o situație reală, ar trebui, de asemenea, să verificați tipul imaginii solicitate în loc de a returna, în toate cazurile, PNG-ul.
Iată structura de directoare a exemplului nostru de plugin pentru server:
PYTHON_PLUGINS_PATH/
MyAccessControl/
__init__.py --> *required*
AccessControl.py --> *required*
metadata.txt --> *required*
Acest fișier este cerut de sistemul de import al Python. Similar tuturor plugin-urilor pentru serverul QGIS, acest fișier trebuie să conțină o funcție serverClassFactory(), care este apelată atunci când plugin-ul se încarcă în QGIS Server, la pornirea serverului. Acesta primește referința către o instanță a QgsServerInterface și trebuie să returneze instanța clasei plugin-ului dvs. Iată cum arată fișierul __init__.py al exemplului de plugin:
# -*- coding: utf-8 -*-
def serverClassFactory(serverIface):
from MyAccessControl.AccessControl import AccessControl
return AccessControl(serverIface)
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()
Acest exemplu oferă un acces deplin pentru oricine.
Este de datoria plugin-ului să știe cine este conectat.
Toate aceste metode au ca argument stratul, pentru a putea personaliza restricțiile pentru fiecare strat.
Se folosește pentru a adăuga o Expresie de limitare a rezultatelor, ex.:
def layerFilterExpression(self, layer):
return "$role = 'user'"
Pentru restrângerea la entitățile pentru care atributul “rol” are valoarea “user”.
La fel ca și precedenta, dar folosește SubsetString (executată în baza de date)
def layerFilterSubsetString(self, layer):
return "role = 'user'"
Pentru restrângerea la entitățile pentru care atributul “rol” are valoarea “user”.
Limitează accesul la strat.
Returnează un obiect de tip QgsAccessControlFilter.LayerPermissions, care are proprietățile:
canRead pentru a-l vedea în GetCapabilities și pentru a avea acces de citire.
canInsert pentru a putea insera o nouă entitate.
canUpdate pentru a putea actualiza o entitate.
candelete pentru a fi putea șterge o entitate.
Exemplu:
def layerPermissions(self, layer):
rights = QgsAccessControlFilter.LayerPermissions()
rights.canRead = True
rights.canRead = rights.canInsert = rights.canUpdate = rights.canDelete = False
return rights
Pentru a permite tuturor accesul numai pentru citire.
Folosit pentru a reduce vizibilitatea unui subset specific de atribute.
Atributul argument returnează setul actual de atribute vizibile.
Exemplu:
def authorizedLayerAttributes(self, layer, attributes):
return [a for a in attributes if a != "role"]
Pentru a ascunde atributul ‘role’.
Se folosește pentru a limita editarea unui subset specific de entități.
Este folosit în protocolul WFS-Transaction.
Exemplu:
def allowToEdit(self, layer, feature):
return feature.attribute('role') == 'user'
Pentru a putea modifica numai entitatea pentru care atributul “rol” are valoarea de “utilizator”.