Importante

La traducción es un esfuerzo comunitario al que puedes unirte. Esta página está actualmente traducida en 100.00%.

20. Servidor QGIS y Python

20.1. Introducción

Para obtener más información sobre QGIS Server, lea Guía/Manual de Servidor QGIS.

QGIS Server es tres cosas diferentes:

  1. Biblioteca de QGIS Server: una biblioteca que proporciona una API para crear servicios web OGC

  2. QGIS Server FCGI: una aplicación binaria FCGI qgis_mapserv.fcgi que junto con un servidor web implementa un conjunto de servicios OGC (WMS, WFS, WCS etc.) y APIs OGC (WFS3/OAPIF)

  3. QGIS Development Server: una aplicación binaria de servidor de desarrollo qgis_mapserver que implementa un conjunto de servicios OGC (WMS, WFS, WCS, etc.) y API de OGC (WFS3 / OAPIF)

Este capítulo del libro de cocina se centra en el primer tema y, al explicar el uso de la API de QGIS Server, muestra cómo es posible utilizar Python para ampliar, mejorar o personalizar el comportamiento del servidor o cómo utilizar la API de QGIS Server para incrustar el servidor QGIS en otra aplicación.

Hay algunas formas diferentes en las que puede alterar el comportamiento de QGIS Server o ampliar sus capacidades para ofrecer nuevos servicios personalizados o API, estos son los principales escenarios a los que puede enfrentarse:

  • EMBEDDING → Utilice la API de QGIS Server desde otra aplicación de Python

  • STANDALONE → Ejecutar QGIS Server como servicio autónomo WSGI/HTTP

  • FILTERS → Mejorar/personalizar QGIS Server con complementos de filtro

  • SERVICES → Añadir un nuevo SERVICE

  • OGC APIs → Añadir una nueva OGC API

Las aplicaciones integradas e independientes requieren el uso de la API de Python de QGIS Server directamente desde otro script o aplicación de Python. Las opciones restantes son más adecuadas para cuando desee agregar funciones personalizadas a una aplicación binaria estándar de QGIS Server (FCGI o servidor de desarrollo): en este caso, deberá escribir un complemento de Python para la aplicación del servidor y registrar sus filtros personalizados, servicios o API.

20.2. Conceptos básicos de la API del servidor

Las clases fundamentales involucradas en una aplicación típica de QGIS Server son:

El flujo de trabajo del servidor de desarrollo o FCGI de QGIS Server se puede resumir de la siguiente manera:

1initialize the QgsApplication
2create the QgsServer
3the main server loop waits forever for client requests:
4    for each incoming request:
5        create a QgsServerRequest request
6        create a QgsServerResponse response
7        call QgsServer.handleRequest(request, response)
8            filter plugins may be executed
9        send the output to the client

Dentro del métodod QgsServer.handleRequest(request, response) las devoluciones de llamada de los complementos de filtro se llaman y QgsServerRequest y QgsServerResponse están disponibles para los complementos a través de la clase QgsServerInterface

Advertencia

Las clases de servidor de QGIS no son seguras para subprocesos, siempre debe usar un modelo o contenedores de multiprocesamiento cuando cree aplicaciones escalables basadas en la API de QGIS Server.

20.3. Independiente o incrustado

Para aplicaciones de servidor independientes o incrustaciones, deberá usar las clases de servidor mencionadas anteriormente directamente, envolviéndolas en una implementación de servidor web que gestiona todas las interacciones del protocolo HTTP con el cliente.

A continuación, se muestra un ejemplo mínimo del uso de la API de QGIS Server (sin la parte HTTP):

 1from qgis.core import QgsApplication
 2from qgis.server import *
 3app = QgsApplication([], False)
 4
 5# Create the server instance, it may be a single one that
 6# is reused on multiple requests
 7server = QgsServer()
 8
 9# Create the request by specifying the full URL and an optional body
10# (for example for POST requests)
11request = QgsBufferServerRequest(
12    'http://localhost:8081/?MAP=/qgis-server/projects/helloworld.qgs' +
13    '&SERVICE=WMS&REQUEST=GetCapabilities')
14
15# Create a response objects
16response = QgsBufferServerResponse()
17
18# Handle the request
19server.handleRequest(request, response)
20
21print(response.headers())
22print(response.body().data().decode('utf8'))
23
24app.exitQgis()

A continuación se muestra un ejemplo completo de aplicación independiente desarrollada para las pruebas de integraciones continuas en el repositorio de código fuente de QGIS, que muestra un amplio conjunto de diferentes filtros de complementos y esquemas de autenticación (no significa para la producción, ya que se desarrollaron sólo para fines de prueba, pero sigue siendo interesante para el aprendizaje): qgis_wrapped_server.py

20.4. Complementos del servidor

Los complementos de Python del servidor se cargan una vez cuando se inicia la aplicación QGIS Server y se pueden usar para registrar filtros, servicios o API.

La estructura de un complemento de servidor es muy similar a su contraparte de escritorio, un objeto QgsServerInterface está disponible para los complementos y los complementos pueden registrar uno o más filtros personalizados, servicios o API en el registro correspondiente mediante uno de los métodos expuestos por la interfaz del servidor.

20.4.1. Complementos de filtro de servidor

Los filtros vienen en tres sabores diferentes y se pueden instanciar subclasificando una de las clases a continuación y llamando al método correspondiente de QgsServerInterface:

Tipo de filtro

Clase Base

Registro de QgsServerInterface

E/S

QgsServerFilter

registerFilter()

Control de acceso

QgsAccessControlFilter

registerAccessControl()

Cache

QgsServerCacheFilter

registerServerCache()

20.4.1.1. Filtros de E/S

Los filtros de E/S pueden modificar la entrada y salida del servidor (la solicitud y la respuesta) de los servicios centrales (WMS, WFS, etc.) permitiendo realizar cualquier tipo de manipulación del flujo de trabajo de los servicios. Es posible, por ejemplo, restringir el acceso a las capas seleccionadas, inyectar una hoja de estilo XSL a la respuesta XML, agregar una marca de agua a una imagen WMS generada, etc.

A partir de este punto, encontrará útil un vistazo rápido a server plugins API docs.

Cada filtro debe implementar al menos una de las tres devoluciones de llamada:

Todos los filtros tienen acceso al objeto de solicitud/respuesta (QgsRequestHandler) y puede manipular todas sus propiedades (entrada/salida) y generar excepciones (aunque de una manera bastante particular como veremos a continuación).

Todos estos métodos devuelven un valor booleano que indica si la llamada debe propagarse a los filtros siguientes. Si uno de estos métodos devuelve False entonces la cadena se detiene, de lo contrario la llamada se propagará al siguiente filtro.

Aquí está el pseudocódigo que muestra cómo el servidor maneja una solicitud típica y cuándo se llaman las devoluciones de llamada del filtro:

 1for each incoming request:
 2    create GET/POST request handler
 3    pass request to an instance of QgsServerInterface
 4    call onRequestReady filters
 5
 6    if there is not a response:
 7        if SERVICE is WMS/WFS/WCS:
 8            create WMS/WFS/WCS service
 9            call service’s executeRequest
10                possibly call onSendResponse for each chunk of bytes
11                sent to the client by a streaming services (WFS)
12        call onResponseComplete
13    request handler sends the response to the client

Los siguientes párrafos describen las devoluciones de llamada disponibles en detalle.

20.4.1.1.1. onRequestReady

Esto se llama cuando la solicitud está lista: la URL entrante y los datos se han analizado y antes de ingresar al conmutador de servicios centrales (WMS, WFS, etc.), este es el punto donde puede manipular la entrada y realizar acciones como:

  • autenticación/autorización

  • redirije

  • añadir/borrar ciertos parámetros (nombres de tipos, por ejemplo)

  • plantear excepciones

Incluso podría sustituir un servicio central por completo cambiando el parámetro SERVICE y, por lo tanto, omitiendo el servicio central por completo (aunque esto no tiene mucho sentido).

20.4.1.1.2. onSendResponse

Esta llamada se realiza siempre que cualquier salida parcial se descarga desde el buffer de respuesta (es decir, a FCGI stdout si se utiliza el servidor fcgi) y desde allí, al cliente. Esto ocurre cuando se transmite un contenido enorme (como WFS GetFeature). En este caso onSendResponse() puede ser llamado varias veces.

Tenga en cuenta que si la respuesta no se transmite, entonces onSendResponse() no se llamará en absoluto.

En todos los casos, el último (o único) trozo se enviará al cliente tras una llamada a onResponseComplete().

Devolver False prevendrá la descarga de datos al cliente. Esto es deseable cuando un complemento quiere recoger todos los trozos de una respuesta y examinar o cambiar la respuesta en onResponseComplete().

20.4.1.1.3. onResponseComplete

Se llama una vez cuando los servicios centrales (si se han alcanzado) terminan su proceso y la petición está lista para ser enviada al cliente. Como se ha comentado anteriormente, este método se llamará antes de que el último (o único) trozo de datos se envíe al cliente. Para servicios de streaming, multiples llamadas a onSendResponse() pueden haber sido llamadas.

onResponseComplete() es el lugar ideal para proporcionar la implementación de nuevos servicios (WPS o servicios personalizados) y para realizar la manipulación directa de la salida procedente de los servicios centrales (por ejemplo para añadir una marca de agua sobre una imagen WMS).

Tenga en cuenta que devolver False evitará que los siguientes complementos ejecuten onResponseComplete() pero, en cualquier caso, evitará que se envíe la respuesta al cliente.

20.4.1.1.4. Generación de excepciones de un complemento

Aún queda trabajo por hacer en este tema: la implementación actual puede distinguir entre excepciones manejadas y no manejadas estableciendo una propiedad QgsRequestHandler a una instancia de QgsMapServiceException, de esta manera el código principal de C ++ puede detectar excepciones de Python controladas e ignorar las excepciones no controladas (o mejor: registrarlas).

Este enfoque básicamente funciona, pero no es muy «pitónico»: un mejor enfoque sería generar excepciones del código de Python y verlas burbujear en el bucle C ++ para que se manejen allí.

20.4.1.1.5. Escribiendo un complemento del servidor

Un complemento de servidor es un complemento estándar de QGIS Python como se describe en Desarrollando Plugins Python, que solo proporciona una interfaz adicional (o alternativa): un complemento de escritorio QGIS típico tiene acceso a la aplicación QGIS a través de: class:` QgisInterface <qgis. gui.QgisInterface> , un complemento de servidor solo tiene acceso a :class:`QgsServerInterface <qgis.server.QgsServerInterface> cuando se ejecuta dentro del contexto de la aplicación QGIS Server.

Para que QGIS Server sepa que un complemento tiene una interfaz de servidor, se necesita una entrada de metadatos especial (en metadata.txt):

server=True

Importante

QGIS Server solo cargará y ejecutará los complementos que tengan el conjunto de metadatos server=True.

El complemento de ejemplo qgis3-server-vagrant discutido aquí (con muchos más) está disponible en github, algunos complementos de servidor también están publicados en el repositorio oficial de complementos QGIS.

20.4.1.1.5.1. Archivos de complementos

Aquí está la estructura de directorios de nuestro complemento de servidor de ejemplo.

1PYTHON_PLUGINS_PATH/
2  HelloServer/
3    __init__.py    --> *required*
4    HelloServer.py  --> *required*
5    metadata.txt   --> *required*
20.4.1.1.5.1.1. __init__.py

Este archivo es requerido por el sistema de importación de Python. Además, QGIS Server requiere que este archivo contenga una función serverClassFactory(), que se llama cuando el complemento se carga en QGIS Server cuando se inicia el servidor. Recibe una referencia a la instancia de QgsServerInterface y debe devolver la instancia de la clase de su complemento. Así es como se ve el complemento de ejemplo __init __. Py:

def serverClassFactory(serverIface):
    from .HelloServer import HelloServerServer
    return HelloServerServer(serverIface)
20.4.1.1.5.1.2. HelloServer.py

Aquí es donde ocurre la magia y así es como se ve la magia: (por ejemplo HelloServer.py)

Un complemento de servidor generalmente consiste en una o más devoluciones de llamada empaquetadas en instancias de QgsServerFilter.

Cada QgsServerFilter implementa una o más de las siguientes devoluciones de llamada:

El siguiente ejemplo implementa un filtro mínimo que imprime HelloServer! En caso de que el parámetro SERVICE sea igual a «HELLO»:

 1class HelloFilter(QgsServerFilter):
 2
 3    def __init__(self, serverIface):
 4        super().__init__(serverIface)
 5
 6    def onRequestReady(self) -> bool:
 7        QgsMessageLog.logMessage("HelloFilter.onRequestReady")
 8        return True
 9
10    def onSendResponse(self) -> bool:
11        QgsMessageLog.logMessage("HelloFilter.onSendResponse")
12        return True
13
14    def onResponseComplete(self) -> bool:
15        QgsMessageLog.logMessage("HelloFilter.onResponseComplete")
16        request = self.serverInterface().requestHandler()
17        params = request.parameterMap()
18        if params.get('SERVICE', '').upper() == 'HELLO':
19            request.clear()
20            request.setResponseHeader('Content-type', 'text/plain')
21            # Note that the content is of type "bytes"
22            request.appendBody(b'HelloServer!')
23        return True

Los filtros deben registrarse en serverIface como en el siguiente ejemplo:

class HelloServerServer:
    def __init__(self, serverIface):
        serverIface.registerFilter(HelloFilter(serverIface), 100)

El segundo parámetro del método registerFilter() establece una prioridad que define el orden de las devoluciones de llamada con el mismo nombre (la prioridad más baja se invoca primero).

Al utilizar las tres devoluciones de llamada, los complementos pueden manipular la entrada y / o la salida del servidor de muchas formas diferentes. En todo momento, la instancia del complemento tiene acceso a QgsRequestHandler a través de QgsServerInterface. La clase QgsRequestHandler tiene muchos métodos que se pueden usar para modificar los parámetros de entrada antes de ingresar al procesamiento central del servidor (usando requestReady()) o después de la solicitud ha sido procesado por los servicios centrales (usando sendResponse()).

Los siguientes ejemplos cubren algunos casos comunes de uso:

20.4.1.1.5.2. Modificando la entrada

El complemento de ejemplo contiene un ejemplo de prueba que cambia los parámetros de entrada provenientes de la cadena de consulta, en este ejemplo se inyecta un nuevo parámetro en el (ya analizado) parameterMap, este parámetro es visible luego por los servicios centrales (WMS, etc.) , al final del procesamiento de los servicios centrales, verificamos que el parámetro todavía esté allí:

 1class ParamsFilter(QgsServerFilter):
 2
 3    def __init__(self, serverIface):
 4        super(ParamsFilter, self).__init__(serverIface)
 5
 6    def onRequestReady(self) -> bool:
 7        request = self.serverInterface().requestHandler()
 8        params = request.parameterMap( )
 9        request.setParameter('TEST_NEW_PARAM', 'ParamsFilter')
10        return True
11
12    def onResponseComplete(self) -> bool:
13        request = self.serverInterface().requestHandler()
14        params = request.parameterMap( )
15        if params.get('TEST_NEW_PARAM') == 'ParamsFilter':
16            QgsMessageLog.logMessage("SUCCESS - ParamsFilter.onResponseComplete")
17        else:
18            QgsMessageLog.logMessage("FAIL    - ParamsFilter.onResponseComplete")
19        return True

Esto es un extracto de lo que puede ver en el archivo de log:

1 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] HelloServerServer - loading filter ParamsFilter
2 src/core/qgsmessagelog.cpp: 45: (logMessage) [1ms] 2014-12-12T12:39:29 Server[0] Server plugin HelloServer loaded!
3 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 Server[0] Server python plugins loaded
4 src/mapserver/qgshttprequesthandler.cpp: 547: (requestStringToParameterMap) [1ms] inserting pair SERVICE // HELLO into the parameter map
5 src/mapserver/qgsserverfilter.cpp: 42: (onRequestReady) [0ms] QgsServerFilter plugin default onRequestReady called
6 src/core/qgsmessagelog.cpp: 45: (logMessage) [0ms] 2014-12-12T12:39:29 plugin[0] SUCCESS - ParamsFilter.onResponseComplete

En la línea resaltada, la cadena «SUCCESS» indica que el complemento pasó la prueba.

La misma técnica se puede aprovechar para usar un servicio personalizado en lugar de uno central: por ejemplo, podría omitir una solicitud SERVICE WFS o cualquier otra solicitud principal simplemente cambiando el parámetro SERVICE a algo diferente y se omitirá el servicio principal. Luego, puede inyectar sus resultados personalizados en la salida y enviarlos al cliente (esto se explica a continuación).

Truco

Si realmente desea implementar un servicio personalizado, se recomienda crear una subclase QgsService y registre su servicio en registerFilter() llamando a su método registerService(service)

20.4.1.1.5.3. Modificar o reemplazar la salida

El ejemplo del filtro de marca de agua muestra cómo reemplazar la salida de WMS con una nueva imagen obtenida agregando una imagen de marca de agua en la parte superior de la imagen de WMS generada por el servicio principal de WMS:

 1from qgis.server import *
 2from qgis.PyQt.QtCore import *
 3from qgis.PyQt.QtGui import *
 4
 5class WatermarkFilter(QgsServerFilter):
 6
 7    def __init__(self, serverIface):
 8        super().__init__(serverIface)
 9
10    def onResponseComplete(self) -> bool:
11        request = self.serverInterface().requestHandler()
12        params = request.parameterMap( )
13        # Do some checks
14        if (params.get('SERVICE').upper() == 'WMS' \
15                and params.get('REQUEST').upper() == 'GETMAP' \
16                and not request.exceptionRaised() ):
17            QgsMessageLog.logMessage("WatermarkFilter.onResponseComplete: image ready %s" % request.parameter("FORMAT"))
18            # Get the image
19            img = QImage()
20            img.loadFromData(request.body())
21            # Adds the watermark
22            watermark = QImage(os.path.join(os.path.dirname(__file__), 'media/watermark.png'))
23            p = QPainter(img)
24            p.drawImage(QRect( 20, 20, 40, 40), watermark)
25            p.end()
26            ba = QByteArray()
27            buffer = QBuffer(ba)
28            buffer.open(QIODevice.WriteOnly)
29            img.save(buffer, "PNG" if "png" in request.parameter("FORMAT") else "JPG")
30            # Set the body
31            request.clearBody()
32            request.appendBody(ba)
33        return True

En este ejemplo, el valor del parámetro SERVICE se verifica y si la solicitud entrante es un WMS GETMAP y no se han establecido excepciones por un complemento ejecutado previamente o por el servicio central (WMS en este caso), la imagen generada por WMS se recupera del búfer de salida y se agrega la imagen de marca de agua. El último paso es borrar el búfer de salida y reemplazarlo con la imagen recién generada. Tenga en cuenta que, en una situación del mundo real, también deberíamos comprobar el tipo de imagen solicitada en lugar de admitir solo PNG o JPG.

20.4.1.2. Filtros de control de acceso

Los filtros de control de acceso brindan al desarrollador un control detallado sobre las capas, características y atributos a los que se puede acceder; las siguientes devoluciones de llamada se pueden implementar en un filtro de control de acceso:

20.4.1.2.1. Archivos de complementos

Aquí está la estructura de directorios de nuestro complemento de ejemplo:

1PYTHON_PLUGINS_PATH/
2  MyAccessControl/
3    __init__.py    --> *required*
4    AccessControl.py  --> *required*
5    metadata.txt   --> *required*
20.4.1.2.1.1. __init__.py

Este archivo es requerido por el sistema de importación de Python. Como para todos los complementos del servidor QGIS, este archivo contiene una función serverClassFactory (), que se llama cuando el complemento se carga en el servidor QGIS al inicio. Recibe una referencia a una instancia de QgsServerInterface y debe devolver una instancia de la clase de su complemento. Así es como el complemento de ejemplo __init__.py aparece:

def serverClassFactory(serverIface):
    from MyAccessControl.AccessControl import AccessControlServer
    return AccessControlServer(serverIface)
20.4.1.2.1.2. AccessControl.py
 1class AccessControlFilter(QgsAccessControlFilter):
 2
 3    def __init__(self, server_iface):
 4        super().__init__(server_iface)
 5
 6    def layerFilterExpression(self, layer):
 7        """ Return an additional expression filter """
 8        return super().layerFilterExpression(layer)
 9
10    def layerFilterSubsetString(self, layer):
11        """ Return an additional subset string (typically SQL) filter """
12        return super().layerFilterSubsetString(layer)
13
14    def layerPermissions(self, layer):
15        """ Return the layer rights """
16        return super().layerPermissions(layer)
17
18    def authorizedLayerAttributes(self, layer, attributes):
19        """ Return the authorised layer attributes """
20        return super().authorizedLayerAttributes(layer, attributes)
21
22    def allowToEdit(self, layer, feature):
23        """ Are we authorised to modify the following geometry """
24        return super().allowToEdit(layer, feature)
25
26    def cacheKey(self):
27        return super().cacheKey()
28
29class AccessControlServer:
30
31   def __init__(self, serverIface):
32      """ Register AccessControlFilter """
33      serverIface.registerAccessControl(AccessControlFilter(serverIface), 100)

Este ejemplo otorga acceso total para todos.

Es rol del complemento saber quién ha ingresado.

En todos esos métodos tenemos el argumento layer on para poder personalizar la restricción por capa.

20.4.1.2.2. layerFilterExpression

Permite añadir una expresión para limitar los resultados.

Por ejemplo, para limitar a características donde el atributo role es igual a user.

def layerFilterExpression(self, layer):
    return "$role = 'user'"
20.4.1.2.3. layerFilterSubsetString

Igual que el anterior pero usa el SubsetString (ejecutado en la base de datos)

Por ejemplo, para limitar a características donde el atributo role es igual a user.

def layerFilterSubsetString(self, layer):
    return "role = 'user'"
20.4.1.2.4. layerPermissions

Limitar el acceso a la capa.

Devuelve un objeto de tipo LayerPermissions(), el cuál tiene las propiedades:

  • canRead para verlo en GetCapabilities y tiene acceso de lectura.

  • canInsert para poder insertar una nueva característica.

  • canUpdate para ser capaz de actualizar una característica.

  • canDelete para ser capaz de borrar una característica.

Por ejemplo, para limitar todo al acceso de sólo lectura:

1def layerPermissions(self, layer):
2    rights = QgsAccessControlFilter.LayerPermissions()
3    rights.canRead = True
4    rights.canInsert = rights.canUpdate = rights.canDelete = False
5    return rights
20.4.1.2.5. authorizedLayerAttributes

Usado para limitar la visibilidad de un subconjunto específico de atributo.

El atributo del argumento devuelve el conjunto actual de atributos visibles.

Por ejemplo, para ocultar el atributo role:

def authorizedLayerAttributes(self, layer, attributes):
    return [a for a in attributes if a != "role"]
20.4.1.2.6. allowToEdit

Esto es usado para limitar la edición de un subconjunto de objetos espaciales.

Se utiliza en el protocolo WFS-Transaction.

Por ejemplo, para poder editar sólo el objeto espacial que tiene el atributo role con el valor user:

def allowToEdit(self, layer, feature):
    return feature.attribute('role') == 'user'
20.4.1.2.7. cacheKey

QGIS Server mantiene una caché de las capacidades entonces para tener una caché por rol puedes retornar el rol en este método. O devolver None para desactivar completamente la caché.

20.4.2. Servicios personalizados

En QGIS Server, los servicios centrales como WMS, WFS y WCS se implementan como subclases de QgsService.

Para implementar un nuevo servicio que se ejecutará cuando el parámetro de cadena de consulta SERVICE coincida con el nombre del servicio, puedes implementar tu propio QgsService y registrar tu servicio en el serviceRegistry() llamando a su registerService(service).

Este es un ejemplo de un servicio personalizado llamado CUSTOM:

 1from qgis.server import QgsService
 2from qgis.core import QgsMessageLog
 3
 4class CustomServiceService(QgsService):
 5
 6    def __init__(self):
 7        QgsService.__init__(self)
 8
 9    def name(self):
10        return "CUSTOM"
11
12    def version(self):
13        return "1.0.0"
14
15    def executeRequest(self, request, response, project):
16        response.setStatusCode(200)
17        QgsMessageLog.logMessage('Custom service executeRequest')
18        response.write("Custom service executeRequest")
19
20
21class CustomService():
22
23    def __init__(self, serverIface):
24        serverIface.serviceRegistry().registerService(CustomServiceService())

20.4.3. APIs personalizadas

En QGIS Server, las API principales de OGC como OAPIF (también conocido como WFS3) se implementan como colecciones de QgsServerOgcApiHandler subclases que están registradas en una instancia de QgsServerOgcApi).

Para implementar una nueva API que se ejecute cuando la ruta url coincida con una determinada URL, puede implementar sus propias instancias QgsServerOgcApiHandler, añadirlas a una QgsServerOgcApi y registrar la API en el serviceRegistry() llamando a su registerApi(api).

A continuación, se muestra un ejemplo de una API personalizada que se ejecutará cuando la URL contenga /customapi:

 1import json
 2import os
 3
 4from qgis.PyQt.QtCore import QBuffer, QIODevice, QTextStream, QRegularExpression
 5from qgis.server import (
 6    QgsServiceRegistry,
 7    QgsService,
 8    QgsServerFilter,
 9    QgsServerOgcApi,
10    QgsServerQueryStringParameter,
11    QgsServerOgcApiHandler,
12)
13
14from qgis.core import (
15    QgsMessageLog,
16    QgsJsonExporter,
17    QgsCircle,
18    QgsFeature,
19    QgsPoint,
20    QgsGeometry,
21)
22
23
24class CustomApiHandler(QgsServerOgcApiHandler):
25
26    def __init__(self):
27        super(CustomApiHandler, self).__init__()
28        self.setContentTypes([QgsServerOgcApi.HTML, QgsServerOgcApi.JSON])
29
30    def path(self):
31        return QRegularExpression("/customapi")
32
33    def operationId(self):
34        return "CustomApiXYCircle"
35
36    def summary(self):
37        return "Creates a circle around a point"
38
39    def description(self):
40        return "Creates a circle around a point"
41
42    def linkTitle(self):
43        return "Custom Api XY Circle"
44
45    def linkType(self):
46        return QgsServerOgcApi.data
47
48    def handleRequest(self, context):
49        """Simple Circle"""
50
51        values = self.values(context)
52        x = values['x']
53        y = values['y']
54        r = values['r']
55        f = QgsFeature()
56        f.setAttributes([x, y, r])
57        f.setGeometry(QgsCircle(QgsPoint(x, y), r).toCircularString())
58        exporter = QgsJsonExporter()
59        self.write(json.loads(exporter.exportFeature(f)), context)
60
61    def templatePath(self, context):
62        # The template path is used to serve HTML content
63        return os.path.join(os.path.dirname(__file__), 'circle.html')
64
65    def parameters(self, context):
66        return [QgsServerQueryStringParameter('x', True, QgsServerQueryStringParameter.Type.Double, 'X coordinate'),
67                QgsServerQueryStringParameter(
68                    'y', True, QgsServerQueryStringParameter.Type.Double, 'Y coordinate'),
69                QgsServerQueryStringParameter('r', True, QgsServerQueryStringParameter.Type.Double, 'radius')]
70
71
72class CustomApi():
73
74    def __init__(self, serverIface):
75        api = QgsServerOgcApi(serverIface, '/customapi',
76                            'custom api', 'a custom api', '1.1')
77        handler = CustomApiHandler()
78        api.registerHandler(handler)
79        serverIface.serviceRegistry().registerApi(api)