중요

번역은 여러분이 참여할 수 있는 커뮤니티 활동입니다. 이 페이지는 현재 100.00% 번역되었습니다.

20. QGIS 서버와 파이썬

20.1. 소개

QGIS 서버에 대해 자세히 알고 싶다면, QGIS 서버 지침서 를 읽어보십시오.

QGIS 서버는 서로 다른 세 가지로 이루어져 있습니다:

  1. QGIS 서버 라이브러리: OGC 웹 서비스를 생성하기 위한 API를 제공하는 라이브러리입니다.

  2. QGIS 서버 FCGI: 웹 서버와 함께 일련의 (WMS, WFS, WCS 등등의) OGC 서비스들 및 OGC API들(WFS3/OAPIF)을 구현하는 FCGI 바이너리 응용 프로그램 qgis_mapserv.fcgi 파일

  3. QGIS 개발 서버: 일련의 (WMS, WFS, WCS 등등의) OGC 서비스들 및 OGC API들(WFS3/OAPIF)을 구현하는 개발 서버 바이너리 응용 프로그램 qgis_mapserver 파일

PyQGIS 쿡북의 이 장에서는 첫 번째 항목에 집중할 것입니다. QGIS 서버 API의 사용 사례를 들어 파이썬을 사용해서 어떻게 서버의 습성을 확장하거나 향상시키거나 사용자 정의할 수 있는지를, 또는 QGIS 서버 API를 사용해서 어떻게 또다른 응용 프로그램 안에 QGIS 서버를 내장시킬 수 있는지를 보여줄 것입니다.

QGIS 서버의 습성을 변경하거나, 새로운 사용자 정의 서비스 또는 API를 제공하기 위해 QGIS 서버의 케이퍼빌리티를 확장할 수 있는 서로 다른 방법들이 몇 가지 있습니다. 다음은 여러분이 마주할 수도 있는 주요 시나리오들입니다:

  • EMBEDDING → 또다른 파이썬 응용 프로그램에서 QGIS 서버 API 사용하기

  • STANDALONE → QGIS 서버를 독립형 WSGI/HTTP 서비스로 실행하기

  • FILTERS → QGIS 서버를 필터 플러그인을 사용해서 향상시키기/사용자 정의하기

  • SERVICES → 새 SERVICE 를 추가하기

  • OGC APIs → 새 OGC API 를 추가하기

내장형 및 독립형 응용 프로그램들은 또다른 파이썬 스크립트 또는 응용 프로그램으로부터 QGIS 서버 파이썬 API를 사용해야 합니다. 나머지 옵션들은 표준 QGIS 서버 바이너리 응용 프로그램(FCGI 또는 개발 서버)에 사용자 정의 기능들을 추가하고 싶은 경우 더 어울립니다. 이런 경우 서버 응용 프로그램 용 파이썬 플러그인을 작성한 다음 사용자 정의 필터, 서비스, 또는 API를 등록해야 할 것입니다.

20.2. 서버 API 기본 사항

전형적인 QGIS 서버 응용 프로그램과 관련된 기본 클래스는 다음과 같습니다:

QGIS 서버 FCGI 또는 개발 서버 워크플로는 다음과 같이 요약할 수 있습니다:

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

QgsServer.handleRequest(request, response) 메소드 안에서 필터 플러그인 콜백을 호출하고 QgsServerInterface 클래스를 통해 플러그인이 QgsServerRequestQgsServerResponse 클래스들을 사용할 수 있게 됩니다.

경고

QGIS 서버 클래스들은 스레드 안전(thread safety)하지 않기 때문에, QGIS 서버 API를 기반으로 확장 가능한(scalable) 응용 프로그램을 작성할 때 항상 다중 처리(multiprocessing) 모델 또는 컨테이너를 사용해야 합니다.

20.3. 독립형 또는 내장형

독립형 또는 내장형 서버 응용 프로그램의 경우, 앞에서 언급했던 서버 클래스들을 클라이언트와의 모든 HTTP 프로토콜 상호 작용들을 관리하는 웹 서버 구현으로 래핑(wrapping)해서 직접 사용해야 합니다.

QGIS 서버 API 사용 사례의 (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()

다음은 QGIS 소스 코드 저장소 상에서 CI(continuous integration)를 테스트하기 위해 개발된 완전한 독립형 응용 프로그램의 예시로써, 서로 다른 플러그인 필터들과 인증 스키마들의 광범위한 집합을 보여줍니다(테스트 목적만으로 개발되었기 때문에 생산 용은 아니지만, 학습 용으로도 흥미롭습니다): qgis_wrapped_server.py

20.4. 서버 플러그인

QGIS 서버 응용 프로그램을 구동하고 필터, 서비스, 또는 API를 등록하기 위해 사용할 수 있게 되면, 서버 파이썬 플러그인을 불러옵니다.

서버 플러그인의 구조는 대응하는 데스크탑 플러그인과 매우 유사합니다. 플러그인이 QgsServerInterface 객체를 사용할 수 있게 해주고, 플러그인이 대응하는 레지스트리에 하나 이상의 사용자 정의 필터, 서비스, 또는 API를 서버 인터페이스에서 볼 수 있는 메소드들 가운데 하나를 사용해서 등록할 수 있습니다.

20.4.1. 서버 필터 플러그인

필터는 세 가지 종류가 있으며, 다음 클래스 가운데 하나를 하위 클래스화하고 QgsServerInterface 클래스의 대응하는 메소드를 호출해서 인스턴스화할 수 있습니다:

필터 유형

기반 클래스

QgsServerInterface 등록

I/O

QgsServerFilter

registerFilter()

접근 제어

QgsAccessControlFilter

registerAccessControl()

캐시

QgsServerCacheFilter

registerServerCache()

20.4.1.1. I/O 필터

I/O 필터는 (WMS, WFS 등등의) 핵심 서비스들의 서버 입력 및 출력을 (요청 및 응답을) 조정할 수 있기 때문에 서비스 워크플로를 어떤 유형으로든 조작할 수 있게 해줍니다. 예를 들면 선택한 레이어에의 접근을 제한할 수도 있고, XML 응답에 XSL 스타일시트를 주입(inject)할 수도 있으며, 생성한 WMS 이미지에 워터마크를 추가할 수도 있습니다.

이 시점에서 서버 플러그인 API 문서 를 한번 훑어보는 편이 유용할 수도 있습니다.

각 필터는 다음 콜백 3개 가운데 적어도 하나를 구현해야 합니다:

모든 필터는 요청/응답 객체(QgsRequestHandler)에 접근할 수 있고 이 객체의 모든 속성(입력/출력)을 조작할 수 있으며 (다음에서 보게 될 아주 특수한 방식이긴 하지만) 예외를 발생시킬 수 있습니다.

이 메소드들은 모두 호출을 후속 필터들까지 전파해야 하는지 여부를 나타내는 불(boolean) 값을 반환합니다. 이 메소드들 가운데 하나가 False 를 반환하면 연쇄 전파가 중단되고, 그렇지 않으면 호출을 다음 필터로 전파할 것입니다.

다음은 서버가 전형적인 요청을 어떻게 처리하는지 그리고 언제 필터의 콜백을 호출하는지를 보여주는 의사(pseudo) 코드입니다:

 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

다음 문단들에서 사용할 수 있는 콜백들을 자세히 설명합니다.

20.4.1.1.1. onRequestReady

요청이 준비됐을 때 이 콜백을 호출합니다. 들어오는 URL과 데이터를 파싱한 다음 핵심 서비스 (WMS, WFS 등등) 스위치를 입력하기 전, 이 때가 여러분이 입력을 조작하고 다음과 같은 액션들을 수행할 수 있는 시점입니다:

  • 인증/승인

  • 리다이렉트

  • 특정 파라미터 (예를 들면 typename) 추가/제거

  • 예외 발생

SERVICE 파라미터를 변경, 핵심 서비스를 완전히 우회(bypass)해서 핵심 서비스조차도 완전히 대체할 수 있습니다. (물론 큰 의미가 있는 방법은 아닙니다.)

20.4.1.1.2. onSendResponse

어떤 부분적인 출력이라도 응답 버퍼에서 (예를 들어 FCGI 서버를 사용하는 경우 FCGI stdout 으로) 플러시(flush)될 때마다 이 콜백을 호출하고, 그 다음 클라이언트로 전달합니다. 대용량 콘텐츠가 스트리밍 될 때 (예: WFS GetFeature) 이런 일이 발생합니다. 이 경우 onSendResponse() 를 여러 번 호출할 수도 있습니다.

응답을 스트리밍하지 않는 경우 onSendResponse() 메소드를 전혀 호출하지 않을 것이란 점을 기억하십시오.

모든 경우에, onResponseComplete() 메소드를 호출한 다음 클라이언트에 마지막 (또는 유일한) 덩어리(chunk)를 전달할 것입니다.

False 를 반환하면 클라이언트에 데이터를 플러시하지 않을 것입니다. 플러그인이 응답에서 나오는 모든 덩어리들을 모은 다음 검사하거나 또는 onResponseComplete() 에서 응답을 변경하고자 하는 경우 유용합니다.

20.4.1.1.3. onResponseComplete

핵심 서비스가 (적중한 경우) 처리 과정을 완료하고 클라이언트에 응답을 전달할 준비가 됐을 때 이 콜백을 한 번 호출합니다. 앞에서 설명한 대로, 클라이언트에 마지막 (또는 유일한) 데이터 덩어리를 전달하기 전에 이 메소드를 호출할 것입니다. 스트리밍 서비스의 경우, onSendResponse() 메소드를 여러 번 호출할 수도 있습니다.

onResponseComplete() 가 새로운 서비스를 (WPS 또는 사용자 정의 서비스를) 구현하고 핵심 서비스에서 나오는 출력을 직접 조작할 수 있는 (예를 들면 WMS 이미지 위에 워터마크를 추가할 수 있는) 이상적인 위치입니다.

False 를 반환하면 다음 플러그인들이 onResponseComplete() 메소드를 실행하지 않을 것이지만, 어떤 경우에라도 클라이언트에 응답을 전달하지 않을 것이라는 사실을 기억하십시오.

20.4.1.1.4. 플러그인에서 예외를 발생시키기

이 주제에 대해서는 아직 좀 더 작업을 해야 합니다. 현재 구현된 바로는 QgsMapServiceException 클래스의 인스턴스에 QgsRequestHandler 속성을 설정해서 처리된 예외와 처리되지 않은 예외를 구별할 수 있습니다. 이런 방식은 주 C++ 코드가 처리된 파이썬 예외를 캐치하고 처리되지 않은 예외는 무시(또는 더 나은 방법으로는 로그를 작성)할 수 있게 합니다.

이 접근법은 기본적으로 작동하지만 그다지 “파이썬스럽지” 않습니다. 더 나은 접근법은 파이썬 코드로부터 예외를 발생시킨 다음 처리를 위해 C++ 루프로 버블링(bubbling)시키는 것일 겁니다.

20.4.1.1.5. 서버 플러그인 작성하기

서버 플러그인은 파이썬 플러그인 개발하기 에서 설명한 대로 추가적인 (또는 대체하는) 인터페이스만 제공하는 표준 QGIS 파이썬 플러그인입니다. 일반적인 QGIS 데스크탑 플러그인은 QgisInterface 인스턴스를 통해 QGIS 응용 프로그램에 접근할 수 있지만, 서버 플러그인은 QGIS 서버 응용 프로그램 맥락 안에서 실행될 때에만 QgsServerInterface 클래스에 접근할 수 있습니다.

QGIS 서버가 플러그인이 서버 인터페이스를 가지고 있다는 사실을 알게 하려면, (metadata.txt 파일에) 특별한 메타데이터 항목이 필요합니다:

server=True

중요

QGIS 서버는 server=True 메타데이터를 가진 플러그인들만 불러오고 실행할 것입니다.

여기에서 설명하는 (더 많은 플러그인을 가진) qgis3-server-vagrant 예제 플러그인은 깃허브에서 사용할 수 있으며, 몇몇 서버 플러그인은 공식 QGIS 플러그인 저장소 에도 공개되어 있습니다.

20.4.1.1.5.1. 플러그인 파일들

다음은 예제 서버 플러그인의 디렉터리 구조입니다.

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

파이썬의 가져오기 시스템이 이 파일을 필요로 합니다. 또 QGIS 서버도 이 파일이 serverClassFactory() 함수를 담고 있을 것을 요구합니다. 이 함수는 서버 구동 시 QGIS 서버에 플러그인을 불러왔을 때 호출되어, QgisServerInterface 클래스의 인스턴스를 가리키는 참조를 받아서 플러그인 클래스의 인스턴스를 반환해야만 합니다. __init__.py 의 내용은 다음과 같이 보여야 합니다:

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

이 파일에서 마법이 벌어지는데, 이 마법은 다음과 같이 보일 것입니다: (HelloServer.py 파일의 예시)

서버 플러그인은 일반적으로 QgsServerFilter 클래스의 인스턴스에 패키징된 하나 이상의 콜백으로 이루어져 있습니다.

각각의 QgsServerFilter 클래스는 다음과 같은 하나 이상의 콜백을 구현합니다:

다음은 SERVICE 파라미터가 “HELLO”와 동등한 경우 HelloServer! 를 출력하는 최소한의 필터를 구현하는 예시입니다:

 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

이 필터들은 다음 예시에서처럼 반드시 serverIface 에 등록되어 있어야만 합니다:

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

registerFilter() 메소드의 두 번째 파라미터는 같은 이름을 가진 콜백들에 대한 순서를 정의하는 우선 순위를 설정합니다(우선 순위 값이 낮을수록 먼저 호출됩니다):

플러그인은 이 3개의 콜백을 사용해서 서버의 입력 그리고/또는 출력을 서로 다른 많은 방식으로 조작할 수 있습니다. 모든 경우에서, 플러그인 인스턴스는 QgsServerInterface 클래스를 통해 QgsRequestHandler 클래스에 접근합니다. QgsRequestHandler 클래스는 (requestReady() 함수를 사용해서) 서버의 핵심 처리 과정에 들어가기 전에 또는 (sendResponse() 함수를 사용해서) 핵심 서비스가 요청을 처리한 후에 입력 파라미터들을 변경하는 데 쓰일 수 있는 메소드들을 많이 가지고 있습니다.

다음은 흔한 사용 사례 몇 개를 설명하는 예시들입니다:

20.4.1.1.5.2. 입력을 수정하기

다음 플러그인은 쿼리 문자열에서 나오는 입력 파라미터들을 변경하는 테스트 예제로, 이 예시에서는 (이미 파싱된) parameterMap 에 새 파라미터를 주입합니다. 그러면 (WMS 등등의) 핵심 서비스가 이 파라미터를 인식해서 핵심 서비스 처리 과정 마지막에 해당 파라미터가 그대로 있는지 확인합니다:

 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

다음은 로그 파일에 보이는 내용을 추출한 것입니다:

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

강조된 줄에 있는 “SUCCESS” 문자열이 플러그인이 테스트를 통과했다는 사실을 나타냅니다.

동일한 기술을 사용해서 핵심 서비스 대신 사용자 정의 서비스를 사용할 수 있습니다. 예를 들어 SERVICE 파라미터를 다른 무언가로 변경하는 것만으로도 WFS SERVICE 요청 또는 다른 모든 핵심 요청을 뛰어넘을 수 있으며, 그러면 핵심 서비스를 뛰어넘을 것입니다. 그 다음 출력에 사용자 정의 결과를 주입해서 클라이언트에 전달할 수 있습니다. (이에 대해서는 다음 부분에서 설명할 것입니다.)

여러분이 정말로 사용자 정의 서비스를 구현하고자 하는 경우, QgsService 클래스의 하위 클래스를 생성한 다음 registerService(service) 메소드를 호출해서 registerFilter() 메소드 상에서 사용자 정의 서비스를 등록하는 방식을 권장합니다.

20.4.1.1.5.3. 출력을 수정 또는 대체하기

다음 워터마크 필터 예시는 WMS 출력을 WMS 핵심 서비스가 생성한 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

이 예시에서는 SERVICE 파라미터 값을 확인해서 들어오는 요청이 WMS GETMAP 이며 이전에 실행된 플러그인이 또는 핵심 서비스가 (이 경우 WMS가) 어떤 예외도 설정하지 않은 경우, 출력 버퍼에서 WMS가 생성한 이미지를 검색해서 워터마크 이미지를 추가합니다. 마지막 단계는 출력 버퍼를 지운 다음 새로 생성한 이미지로 대체하는 것입니다. 실제 상황에서는 PNG 또는 JPG만 지원하는 것이 아니라 요청된 이미지 유형도 확인해야 한다는 사실을 기억하십시오.

20.4.1.2. 접근 제어 필터

접근 제어 필터들은 개발자가 어떤 레이어, 필터, 그리고 속성에 접근할 수 있는지에 대해 세밀하게 제어할 수 있게 해줍니다. 접근 제어 필터에 다음 콜백들을 구현할 수 있습니다:

20.4.1.2.1. 플러그인 파일들

다음은 예제 플러그인의 디렉터리 구조입니다.

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

파이썬의 가져오기 시스템이 이 파일을 필요로 합니다. 모든 QGIS 서버 플러그인과 마찬가지로, 이 파일이 serverClassFactory() 함수를 담고 있습니다. 이 함수는 서버 구동 시 QGIS 서버에 플러그인을 불러왔을 때 호출되어, QgisServerInterface 클래스의 인스턴스를 가리키는 참조를 받아서 플러그인 클래스의 인스턴스를 반환해야만 합니다. __init__.py 의 내용은 다음과 같이 보여야 합니다:

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)

이 예시는 모두에게 완전한 접근 권한을 부여합니다.

누가 로그인했는지 확인하는 것이 플러그인의 역할입니다.

이런 모든 메소드에는 제약 조건을 레이어 별로 사용자 정의할 수 있는 레이어 인자가 있습니다.

20.4.1.2.2. layerFilterExpression

결과를 제한하기 위한 표현식을 추가하는 데 쓰입니다.

예를 들면 role 속성이 user 인 경우로 피처를 제한할 수 있습니다.

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

바로 앞의 함수와 동일하지만 (데이터베이스에서 실행되는) SubsetString 을 사용합니다.

예를 들면 role 속성이 user 인 경우로 피처를 제한할 수 있습니다.

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

레이어에의 접근을 제한합니다.

다음 속성을 가진 LayerPermissions() 유형의 객체를 반환합니다:

  • canRead: GetCapabilities 에서 피처를 볼 수 있고 읽기 접근 권한을 가집니다.

  • canInsert: 새 피처를 삽입할 수 있습니다.

  • canUpdate: 피처를 업데이트할 수 있습니다.

  • canDelete: 피처를 삭제할 수 있습니다.

예를 들어 읽기 전용 접근에서 모든 권한을 제한하려면:

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

속성의 특정 하위 집합의 가시성을 제한하는 데 쓰입니다.

이 인자 속성은 현재 가시 속성 집합을 반환합니다.

예를 들어 role 속성을 숨기려면:

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

피처들의 하위 집합에 대한 편집 작업을 제한하는 데 쓰입니다.

이 함수는 WFS-Transaction 프로토콜에 사용됩니다.

예를 들면 role 속성이 user 인 피처만 편집할 수 있게 하려면:

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

QGSI 서버가 케이퍼빌리티의 캐시를 유지·관리하는 경우 역할(role) 별로 캐시하려면 이 메소드에서 역할을 반환하면 됩니다. 또는 None 을 반환해서 캐시를 완전히 비활성화시킬 수도 있습니다.

20.4.2. 사용자 정의 서비스

QGIS 서버에서 WMS, WFS 및 WCS 같은 핵심 서비스들은 QgsService 클래스의 하위 클래스로 구현됩니다.

SERVICE 쿼리 문자열 파라미터가 서비스 이름과 일치할 때 실행될 새 서비스를 구현하려면, 여러분 고유의 QgsService 클래스를 구현한 다음 registerService(service) 메소드를 호출해서 serviceRegistry() 메소드 상에서 여러분의 서비스를 등록하면 됩니다.

다음은 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. 사용자 정의 API

QGIS 서버에서, OAPIF (다른 이름으로는 WFS3) 같은 OGC API들은 QgsServerOgcApi 클래스의 인스턴스에 등록된 QgsServerOgcApiHandler 하위 클래스들의 집합으로 (또는 그 상위 클래스인 QgsServerApi 로) 구현됩니다.

URL 경로가 특정 URL과 일치할 때 실행될 새 API를 구현하려면, 여러분 고유의 QgsServerOgcApiHandler 클래스 인스턴스들을 구현한 다음 QgsServerOgcApi 클래스에 추가하고 registerApi(api) 메소드를 호출해서 serviceRegistry() 메소드 상에서 API를 등록하면 됩니다.

다음은 URL이 /customapi 를 담고 있는 경우 실행될 사용자 정의 API의 예시입니다:

 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)