wsfactory - spyne application manager

wsfactory - это django-приложение, которое предоставляет гибкое управление сервисами spyne в проекте django.

Contents:

Installation and QuickStart guide

Установка

Чтобы установить приложение достаточно выполнить в окружении:

pip install wsfactory -i http://pypi.bars-open.ru/simple

Вместе с приложением будут установлены следующие зависимости:

  • lxml
  • spyne

Добавление приложения в проект

Как и все обычные django приложения, нужно просто добавить wsfactory в список установленных приложений и расширить urlpatterns:

# где-то в settings.py

INSTALLED_APPS += "wsfactory",

# где-то в urls.py

import wsfactory.urls

urlpatterns += wsfactory.urls.urlpatterns

Теперь необходимо с конфигурировать wsfactory. Сначала скопируем болванку для конфига, выполним менедж-комманду:

cd path/to/directory/you/place/config/
django_admin.py wsfactory_default_config

После чего в директории, в которой выполнялась команда появится файл wsfactory_config.xml примерно следующего содержания:

<WSConfig xmlns="http://bars-open.ru/schema/wsfactory"
    ApplicationClass="spyne.application.Application"
    WsgiClass="spyne.server.django.DjangoApplication"
    ServiceClass="spyne.service.ServiceBase"
    ApiHandler="wsfactory.views.api_handler">
  <Protocols>
    <!-- Протоколы -->
    <Protocol code="soap11" direction="BOTH" module="spyne.protocol.soap.soap11.Soap11" name="SOAP 1.1"/>
    <Protocol code="json" direction="BOTH" module="spyne.protocol.json.JsonDocument" name="JSON Protocol"/>
    <Protocol code="http-rpc" direction="IN" module="spyne.protocol.http.HttpRpc" name="HTTP RPC"/>

    <!-- установите модуль spyne-smev и раскомментируйте следующие строки, чтобы добавить поддежку протоколв СМЭВ -->
    <Protocol code="soap11wsse" direction="BOTH" module="spyne_smev.wsse.Soap11WSSE" name="SOAP 1.1 WSSE"/>
    <Protocol code="smev256" direction="BOTH" module="spyne_smev.smev256.Smev256" name="СМЭВ 2.5.6"/>

  </Protocols>
  <ApiRegistry>
    <!-- API-методы -->
    <Api code="Code" module="some.module.ApiFunction" name="Do some action"/>
  </ApiRegistry>
  <Services>
    <!-- Сервисы, они же услуги - наборы методов-API -->
    <Service code="Service" name="Sample service">
      <Api code="Code"/>
    </Service>
  </Services>
  <SecurityProfile>
    <!-- Профили безопасности WS-Security. Требует установки модуля spyne-smev -->
    <Modules>
      <!-- Описание модулей -->
      <Module code="x509token" path="spyne_smev.wsse.protocols.X509TokenProfile" name="X509 Token Profile"/>
    </Modules>
    <Security module="x509token" code="security" name="Default security profile">
      <!-- Декларация объектов профилей безопасности -->
      <Param key="certificate" valueType="string">path_to_certificate_file</Param>
      <Param key="private_key" valueType="string">path_to_pkey_file</Param>
      <Param key="private_key_pass" valueType="string">pkey_password</Param>
    </Security>
  </SecurityProfile>
  <Applications>
    <!-- Реестр веб-сервисов (Соответствия протоколов-сервисов) -->
    <Application name="SampleApp" service="Service">
      <InProtocol code="soap11"/>
      <OutProtocol code="soap11"/>
    </Application>
  </Applications>
</WSConfig>

Пишем код

Подробно файл конфигурации, разберем позже, а пока создадим в нашем проекте один простой сервис:

# гдето в проекте файл project/service.py
from itertools import repeat
from spyne import srpc, Integer, Unicode, Iterable

@srpc(Unicode, Integer, _returns=Iterable(Unicode))
def SayHello(name, times):
    return repeat(u"Hello, {0}!".format(name), times)

Декларируем веб-сервис

После чего отредактируем файл wsfactory_config.xml. Сперва добавим api-метод в соответствующую секцию - в теге ApiRegistry:

<ApiRegistry>
  <Api code="SayHello" module="project.service.SayHello" name="Приветствие"/>
</ApiRegistry>

Где атрибут code - это уникальный идентификатор метода, module - путь, по которому можно импортировать этот метод, name - человечное название метода. Все атрибуты являются обязательными.

Tip

Для редактирования xml лучше использовать IDE или редактор с поддержкой xsd-схем, т.к. в данном случае, будет автокомплит и проверка типов. xsd-схема конфигурации лежит в пакете wsfactory, и если файл конфигурации был создан менедж-коммандой, то она будет подключена к файлу конфигурации через xsi:schemaLocation.

Дальше задекларируем сервис - то есть набор api-методов:

<Services>
  <Service code="SayHelloService" name="Say Hello Service">
    <Api code="SayHello"/>
  </Service>
</Services

Здесь атрибут code также является идентификатором, а name человечным названием. В элементе Service обязательно должен содержаться хотя бы один элемент Api, у которого в атрибуте code указывается ссылка на api-метод из элемента ApiRegistry.

Теперь осталось задекларировать сам веб-сервис:

<Applications>
  <Application name="SayHelloService" service="SayHelloService" tns="http://company.domain/tns">
    <InProtocol code="http-rpc"/>
    <OutProtocol code="json"/>
  </Application>
</Applications>

Здесь атрибут name является идентификатором веб-сервиса, атрибут service ссылкой на сервис описанный в елементе Services, a tns - это неймспейс, который будет использоваться для wsdl-документа.

В элементах InProtocol и OutProtocol в атрибутах code указывается ссылка на протоколы, описанные в элементе Protocols. В нашем примере мы выбрали spyne-протоколы HttpRpc на входе и JsonDocument на выходе.

Note

Тут же можно указать параметры инициализации протоколов, а так же профиль безопасности ws-security, но об этом будет сказано дальше.

В итоге должен получиться такой xml-документ:

  <WSConfig xmlns="http://bars-open.ru/schema/wsfactory"
    ApplicationClass="spyne.application.Application"
    WsgiClass="spyne.server.django.DjangoApplication"
    ServiceClass="spyne.service.ServiceBase"
    ApiHandler="wsfactory.views.api_handler">
  <Protocols>
    <!-- Протоколы -->
    <Protocol code="soap11" direction="BOTH" module="spyne.protocol.soap.soap11.Soap11" name="SOAP 1.1"/>
    <Protocol code="json" direction="BOTH" module="spyne.protocol.json.JsonDocument" name="JSON Protocol"/>
    <Protocol code="http-rpc" direction="IN" module="spyne.protocol.http.HttpRpc" name="HTTP RPC"/>

    <!-- установите модуль spyne-smev и раскомментируйте следующие строки, чтобы добавить поддежку протоколв СМЭВ -->
    <Protocol code="soap11wsse" direction="BOTH" module="spyne_smev.wsse.Soap11WSSE" name="SOAP 1.1 WSSE"/>
    <Protocol code="smev256" direction="BOTH" module="spyne_smev.smev256.Smev256" name="СМЭВ 2.5.6"/>

  </Protocols>
  <ApiRegistry>
    <!-- API-методы -->
    <Api code="SayHello" module="project.service.SayHello" name="Приветствие"/>
  </ApiRegistry>
  <Services>
    <!-- Сервисы, они же услуги - наборы методов-API -->
    <Service code="SayHelloService" name="Say Hello Service">
      <Api code="SayHello"/>
    </Service>
  </Services>
  <SecurityProfile>
    <!-- Профили безопасности WS-Security. Требует установки модуля spyne-smev -->
    <Modules>
      <!-- Описание модулей -->
      <Module code="x509token" path="spyne_smev.wsse.protocols.X509TokenProfile" name="X509 Token Profile"/>
    </Modules>
    <Security module="x509token" code="security" name="Default security profile">
      <!-- Декларация объектов профилей безопасности -->
      <Param key="certificate" valueType="string">path_to_certificate_file</Param>
      <Param key="private_key" valueType="string">path_to_pkey_file</Param>
      <Param key="private_key_pass" valueType="string">pkey_password</Param>
    </Security>
  </SecurityProfile>
  <Applications>
    <!-- Реестр веб-сервисов (Соответствия протоколов-сервисов) -->
    <Application name="SayHelloService" service="SayHelloService">
      <InProtocol code="http-rpc"/>
      <OutProtocol code="json"/>
    </Application>
</WSConfig>

Подключение файла конфигурации

Теперь осталось подключить файл конфигурации, для этого гдето в settings.py добавим:

WSFACTORY_CONFIG_FILE = "/path/to/config/file/wsfactory_config.xml"

Проверяем

Запустим dev-сервер и наш сервис станет доступен по урлу:

http://localhost:8000/wsfactory/api/SayHelloService

Выполним запрос:

curl "http://localhost:8000/wsfactory/api/SayHelloService/SayHello?name=Tim&times=4 | python -m json.tool

В результате получим:

[
    "Hello, Tim!",
    "Hello, Tim!",
    "Hello, Tim!",
    "Hello, Tim!"
]

Допустим нам понадобилось чтобы, этот же сервис отдавал данные по спецификации Soap 1.1. Все что нам нужно - это просто добавить новый Application в конфигурацию:

<Applications>
  <!-- Реестр веб-сервисов (Соответствия протоколов-сервисов) -->
  <Application name="SayHelloService" service="SayHelloService">
    <InProtocol code="http-rpc"/>
    <OutProtocol code="json"/>
  </Application>
  <Application name="SayHelloSoap" service="SayHelloSercice">
    <InProtocol code="http-rpc"/>
    <OutProtocol code="soap11"/>
  </Application>
</Applications>

Чтобы новая версия конфигурации применилась необходимо либо перезапустить сервер, либо выполнить менедж-комманду:

django_admin.py wsfactory_reload

Ещё раз повторим запрос, немного изменив урл:

curl "http://localhost:8000/wsfactory/api/SayHelloSoap/SayHello?name=Tim&times=4 | python -m json.tool

И получим результат:

<senv:Envelope xmlns:tns="http://company.domain/tns" xmlns:senv="http://schemas.xmlsoap.org/soap/envelope/">
  <senv:Body>
    <tns:SayHelloResponse>
      <tns:SayHelloResult>
        <tns:string>Hello, Tim!</tns:string>
        <tns:string>Hello, Tim!</tns:string>
        <tns:string>Hello, Tim!</tns:string>
        <tns:string>Hello, Tim!</tns:string>
      </tns:SayHelloResult>
    </tns:SayHelloResponse>
  </senv:Body>
</senv:Envelope>

Немного модифицируем наш api-метод:

@srpc(Unicode, Integer,
      _returns=Iterable(Unicode, member_name="Greeting"),
      _out_variable_name="Greetings")
def SayHello(name, times):
    return repeat(u"Hello, {0}!".format(name), times)

Перезапустим сервер, поновой выполним запрос и получим результат:

<senv:Envelope xmlns:tns="http://company.domain/tns" xmlns:senv="http://schemas.xmlsoap.org/soap/envelope/">
  <senv:Body>
    <tns:SayHelloResponse>
      <tns:Greetings>
        <tns:Greeting>Hello, Tim!</tns:Greeting>
        <tns:Greeting>Hello, Tim!</tns:Greeting>
        <tns:Greeting>Hello, Tim!</tns:Greeting>
        <tns:Greeting>Hello, Tim!</tns:Greeting>
      </tns:Greetings>
    </tns:SayHelloResponse>
  </senv:Body>
</senv:Envelope>

Таким образом, мы написали в коде api-метод, а сам веб-сервис описали декларативно в конфигурации.

Spyne

Как обычно, пишутся веб-сервисы на spyne?

Мы создаем сервис, наследуясь от класса ServiceBase, и реализуем в нём методы нашего api. Потом нужные нам методы заворачиваем в декораторы rpc или srpc:

class Service(ServiceBase):

    @srpc(_returns=Datetime):
    def GetNow():
        return datetime.datetime.now()

    @srpc(Unicode, _returns=Unicode)
    def Echo(string):
        return string

Потом созданный сервис связывается с протоколами через Application, который далее передается в wsgi-приложение:

app = Application(
    [Service], "tns", "name",
    in_protocol=HttpRpc(),
    out_protocol=JsonDocument())

wsgi_app = DjangoApplication(app)

Разберем, что тут означает каждая переменную:

  • class Service - spyne-сервис, набор api-методов. Для удобства далее будем называть их просто сервисами или “услуга”
  • def GetNow - метод сервиса, или api-метод
  • app - spyne-приложение, это некий клей, который связывает между собой, сервисы и протоколы
  • wsgi_app - wsgi-приложение, непосредственно обработчик запросов

Связку wsgi-приложения и spyne-приложения, для простоты, будет называть - “веб-сервис”.

Используя wsfactory, вам необходимо только описать каждый api-метод отдельно главное, чтобы их можно было импортировать через importlib.import_module.:

# где-то в проекте, например в project/api.py

@srpc(_returns=Datetime):
def GetNow():
    return datetime.datetime.now()

@srpc(Unicode, _returns=Unicode)
def Echo(string):
    return string

Далее нужно перечислить их в файле конфигурации. Протоколы, услуги и веб-сервисы декларируются в файле конфигурации.

Configuration file

WSConfig

Корневой элемент конфигурации - WSConfig, с неймспейсом http://bars-open.ru/schema/wsfactory. Этот элемент может содержать необязательные атрибуты, в которых можно перегрузить классы spyne:

Attribute Description Default value
ApplicationClass Класс spyne-приложения spyne.application.Application
WsgiClass Класс wsgi-приложения spyne.server.django.WsgiApplication
ServiceClass Класс сервиса spyne.service.ServiceBase
ApiHandler django view для обработки запросов wsfactory.views.api_handler

Таким образом, если в проекте требуется перегрузить spyne-классы, то подключить их можно в этих атрибутах.

ApiRegistry

Todo

добавить текст сюда

Protocols

Все протоколы, которые планируется использовать в проекте, необходимо перечислить в конфигурации в элементе Protocols. Каждому протоколу соответствует один элемент Protocol, с обязательными атрибутами code, name, module.

  • code - уникальный идентификатор (обычно, просто строка, но допускаются и числа)
  • name - человечное название протокола (любой юникод)
  • module - путь к модулю + имя класса протокола с точкой в качестве разделителя (например, “spyne.protocol.http.HttpRpc”)

Протоколы - это обычные python классы, и они принимают аргументы в конструкторе. wsfactory позволяет задать параметры для протокола, которые будут переданы как keyword-аргументы в конструктор. Для этого нужно добавить в элемент Protocol элемент Param, с обязательными аргументами key, valueType:

  • key - имя параметра, (например validator)
  • valueType - тип параметра, допустимые значения: “string”, “int”, “float”, “bool”, “password”, “text”

Так же тут можно указать два необязательных аргумента name и required, которые используются в приложении m3_wsfatory, где:

  • name - человечное имя параметра
  • required - “true” или “false”, указываем является-ли атрибут обязательным

Значение параметра записывает в тексте в нутри элемента Param.

Attention

Параметры описанные внутри элемента Protocol, будут применены ко всем веб-сервисам, которые используют этот протокол.

Пример:

<Protocols>
  <!-- Протоколы -->

  <Protocol code="soap11" direction="BOTH" module="spyne.protocol.soap.soap11.Soap11" name="SOAP 1.1">
    <Param key="validator" valueType="string">lxml</Param>
    <Param key="pretty_print" valueType="bool">true</Param>
  </Protocol>
  <Protocol code="json" direction="BOTH" module="spyne.protocol.json.JsonDocument" name="JSON Protocol"/>
  <Protocol code="http-rpc" direction="IN" module="spyne.protocol.http.HttpRpc" name="HTTP RPC"/>

</Protocols>

Services

Todo

добавить текст сюда

Soap WS Security

Note

Данный функционал требует установки модуля spyne-smev

В элементе SecurityProfile настраиваются профили безопасности SOAP WS Security. Впервую очередь необходимо описать модули безопасности в элементах Module:

<SecurityProfile>
  <!-- Профили безопасности WS-Security. Требует установки модуля spyne-smev -->
  <Modules>
    <!-- Описание модулей -->
    <Module code="x509token" path="spyne_smev.wsse.protocols.X509TokenProfile" name="X509 Token Profile"/>
  </Modules>
</SecurityProfile>

Атрибуты Module:

Атрибут Описание Обязательный
code идентификатор модуля безопасности да
path путь, по которому его можно импортировать да
name человечье название да

Модули безопасности - это классы наследники класса spyne_smev.wsse.protocols.BaseWSS.

Внути элемента Module, по аналогии с Protocol, можно указать параметры по умолчанию:

<Module code="x509token" path="spyne_smev.wsse.protocols.X509TokenProfile" name="X509 Token Profile">
  <Param key="private_key_path" valueType="string">/path/to/private_key</Param>
  <Param key="certificate_path" valueType="string">/path/to/certificate</Param>
  <Param key="private_key_pass" valueType="password">P@ssw0rd</Param>
</Module>

Далее декларируем профили в элементах Security:

<SecurityProfile>
  <!-- Профили безопасности WS-Security. Требует установки модуля spyne-smev -->
  <Modules>
    <!-- Описание модулей -->
    <Module code="x509token" path="spyne_smev.wsse.protocols.X509TokenProfile" name="X509 Token Profile"/>
  </Modules>
  <Security module="x509token" code="security" name="Default security profile">
    <Param key="certificate" valueType="string">path_to_certificate_file</Param>
    <Param key="private_key" valueType="string">path_to_pkey_file</Param>
    <Param key="private_key_pass" valueType="string">password</Param>
  </Security>
  <Security module="x509token" code="second-security" name="Second security profile">
    <Param key="certificate" valueType="string">path_to_second_certificate</Params>
    <Param key="private_key" valueType="string">path_to_second_pkey_file</Param>
    <Param key="private_key_pass" valueType="string">second-password</Param>
  </Security>
</SecurityProfile>

Applications

Здесь описываются веб-сервисы в элементах Application:

<Applications>
  <!-- Реестр веб-сервисов (Соответствия протоколов-сервисов) -->
  <Application name="SampleApp" service="Service">
    <InProtocol code="soap11"/>
    <OutProtocol code="soap11"/>
  </Application>
</Application>

Атрибуты Application:

Атрибут Описание Обязательный
name уникальный идентификатор, который также будет использоваться в урле да
service код сервиса, ссылка на сервис описанный в элементах Service да
tns неймспейс для веб-сервиса нет
url regex URL, по умолчанию /wsfactory/api/<Application.name>/ нет

Внутри элемента Application в обязательном порядке должны содержаться элементы InProtocol и OutProtocol.

Атрибуты InProtocol/OutProtocol

Атрибут Описание Обязательный
code код протокола, ссылка на протокол в элементах Protocol да
security код сервиса, ссылка на профиль безопасноти в элементах Service да

Так же внутри каждого из этих протоколов можно задать параметры инициализации, которые могут перекрывать параметры поумолчанию описанные в протоколе:

<Applications>
  <!-- Реестр веб-сервисов (Соответствия протоколов-сервисов) -->
  <Application name="SampleApp" service="Service">
    <InProtocol code="soap11">
      <Param key="pretty_print" valueType="bool">true</Param>
    </InProtocol>
    <OutProtocol code="soap11"/>
  </Application>
</Application>

Валидация, схема, загрузка и request

Схема конфигурации находиться в пакете по пути wsfactory/schema/wsfactory.xsd. В ней описаны все аспекты файла конфигурации и можно использовать её как документацию к файлу конфигурации.

При первом запросе к сервисам, произойдет чтение файла, и выполнится валидация по схеме. Если файл валидный, то запрос продолжит свое выполнение, wsfactory сконструирует на основе файла конфигурации веб-сервис и передаст ему request.

Если вы допустили ошибку и файл не прошел валидацию, то будет возбуждено исключение wsfactory.config.ImproperlyConfigured, с текстом ошибки похожим на формат вывода утилиты xmllint.

Todo

добавить пример ошибки (pull-request приветствуется)