Python web services.

Июнь 7, 2008 – 4:52 пп

Обсуждений протокола SOAP в сети имеется огромное множество – в теории и в практике. В основном он используется в корпоративных проектах, как следствие, основные и хорошо отлаженные реализации имеются для таких платформ как Java, .Net, PHP. Однако иногда возникают экзотические требования. Например, для реализации клиента выбирается язык PHP, а для реализации сервера – Python. Недавно именно такая задача имела место быть.

Были рассмотрены 3 реализации SOAP протокола для Python: ZSI, SOAPpy, soaplib. Сразу скажу, что писал примеры сервера и клиентов только для двух последних – ZSI не понравился из-за отсутствия WSGI и тяжелых интерфейсов.

Сервер.

Сервер написан с применением библиотеки soaplib. Такое решение было выбрано по нескольким причинам:

  1. Использование WSGI. Стандартный протокол – это всегда хорошо и надежно. В данном примере был использован сервер CherryPy, но с таким же успехом можно использовать и Paster.
  2. Наглядный и гибкий способ регистрации SOAP методов. С помощью декоратора soaplib.service.soapmethod можно сообщить библиотеке, что данный метод должен быть включен в WSDL описание сервиса. Кроме того, там же можно задать сигнатуру вызова. Сначала передаются типы формальных параметров, далее, если метод возвращает некоторое значение, используется позиционный параметр _returns.
  3. Автоматическая генерация WSDL.

Пример сервера:

import soaplib
import soaplib.wsgi_soap
import soaplib.service
import soaplib.serializers.clazz

class RequestData ( soaplib.serializers.clazz.ClassSerializer ) :
    '''Данные для приема.'''

    class types :
        login = soaplib.serializers.primitive.String
        password = soaplib.serializers.primitive.String

    def __init__ ( self ) :
        pass

class TestService ( soaplib.wsgi_soap.SimpleWSGISoapApp ) :
    from soaplib.serializers.primitive import String, Integer, Array

    @soaplib.service.soapmethod( String, Integer, _returns = Array( String ) )
    def say_hello ( self, name, times ) :
        results = []
        for i in range( 0, times ) :
            results.append( "Hello, %s" % name )
        return results

    @soaplib.service.soapmethod( RequestData, _returns = String )
    def login ( self, request ) :
        return "Email: %s    Passwd: %s" % ( request.login, request.password )

if __name__ == '__main__' :
    from cherrypy.wsgiserver import CherryPyWSGIServer, WSGIPathInfoDispatcher
    dispatcher = WSGIPathInfoDispatcher( { '/TestService' : TestService() } )
    server = CherryPyWSGIServer( ( 'localhost', 8888 ), dispatcher )
    server.start()

В классе types необходимо прописывать либо полные namespace с названиями классов, либо импортировать классы типов глобально. Если импортировать типы внутри, soaplib сгенерирует некорректный wsdl. Пример некорректного оформления класса types:

class types :
        from soaplib.serializers.primitive import String
        login = String
        password = String

Генерация WSDL.

Метод wsdl(self, url) в классе soaplib.service.SoapServiceBase работает по довольно интересной логике: необходимость вызова метода диспатчером определяется по последним 4 символам, а генерация тега soap:address (непосредственный URL SOAP сервиса) происходит попытка удаления .wsdl из URL. Вот тут и начинаются проблемы. Фактически, если обратится по адресу http://localhost:8888/TestService/wsdl, сервер отдаст невалидный документ, так как в данном URL нет токена .wsdl и он без изменений будет подставлен в качестве значения тега soap:address, а URL сервиса, на самом деле, является http://localhost:8888/TestService/. Кроме того wsdl генерируется только при первом обращении к серверу – если первым был запрошен wsdl-документ по некорректному адресу, дальше поможет только перезапуск.
Пример корректно генерируемого WSDL файла.

Клиенты.

Клиенты были написаны на Python SOAPpy и стандартной SOAP PHP для демонстрации совместимости сервера с различными библиотеками. Здесь тоже не обошлось без неожиданностей. Если указать в сигнатуре SOAP метода, например, два формальных параметра типов String и Integer, то логично ожидать что метод должен вызываться как-то так:

client.say_hello( 'roinet', 5 )

Однако сервер на основе soaplib обладает ещё одной особенностью: любые формальные параметры упаковываются в complexType. Поэтому каждому SOAP методу приходится передавать ассоциативный массив, где ключи – имена формальных параметров, а значения – фактические параметры. Таким образом код на Python и PHP выглядит следующим образом:

import SOAPpy

class RequestData :
    def __init__ ( self, login, password ) :
        self.login = login
        self.password = password

def main () :
    client = SOAPpy.WSDL.Proxy( r'http://localhost:8888/TestService/.wsdl' )
    print client.say_hello( name = 'roinet', times = 5 )
    print client.login( request = RequestData( "roinet", "123" ) )

if __name__ == '__main__' :
    main()
< ?php
	ini_set( "soap.wsdl_cache_enabled", "0" );
	set_time_limit( 0 );
	$client = new SoapClient( "http://localhost:8888/TestService/.wsdl",
                                   array( 'soap_version' => SOAP_1_1 ) );
	echo "<pre>";
	print_r( $client->say_hello( array( 'name' => 'roinet', 'times' => 5 ) ) );
	echo "</pre>";
?>


  1. Комментарии:

  2. Спасибо, статья весьма полезная.
    В подробностях пока не разбирался, но насколько я вижу SOAP+WSDL достаточно мутно специфицирована. Спеки SOAP (http://www.w3.org/TR/2007/REC-soap12-part1-20070427/) не содержат никаких рекомендаций относительно использования WSDL. В любом случае, насчет именования wsdl-сервиса автор soaplib не прав (http://www.w3.org/TR/wsdl#_document-n), про передачу параметров лень искать но таж наверняка косяк либы.

    By MuTPu4 on Июнь 8, 2008

  3. Передача параметров в soaplib – однозначный косяк, т.к. эксперименты с SOAP сервером на php показывают, что в общем случае complexType не нужен и передача может вестись обычным образом.

    By Roinet on Июнь 8, 2008

  4. Ну эксперименты экспериментами а стандарты стандартами, я всетаки предпочитаю разобраться в спеках а потом утвержадать. Область явно довольно темная, зачастую может существовать несколько корректных трактовок одной спецификации.

    By MuTPu4 on Июнь 8, 2008

  5. Почитал. :)
    В спецификации wsdl имеется четкое указание, что можно описывать типы данных в виде simpleType и complexType. simpleType позволяет ограничить базовые типы (string, boolean, integer и др.). complexType позволяет описывать структуры любой сложности.

    Указаний какими типами могут быть описаны формальные параметры SOAP методов я не нашел, однако в примерах имеется 3 вполне ожидаемых варианта: использование базовых типов, simpleType и complexType. Таким образом, оборачивание любых формальных параметров в complexType – особенность реализации soaplib. Возможно автор руководствовался простотой, чтобы не писать 3 возможных варианта описания параметров.

    By Roinet on Июнь 8, 2008

  6. ok, верю =)
    Похоже что так и есть. Лана главное разобрались. При острой необходимости, думаю, можно дописать логику передачи множественных параметров.

    By MuTPu4 on Июнь 8, 2008

  7. А никто не пробовал коннектиться к MSSOAP серверу? Если пользоваться библиотекой SOAPpy, то передать многомерный массив или структуру никак не получиться, если в WDSL тип описан как complexType.

    By LA on Сен 2, 2009

  8. Нет. К сожалению не пробовал. Знаю тока одно – такого рода проблемы на cpython будут, т.к. нет ни одной нормальной реализации SOAP, кроме ZSI, которая ужасна по своим интерфейсам. В ironpython есть более-менее адекватные решения на основе .Net.

    By Roinet on Сен 16, 2009

  9. У меня клиент к 1С 8 серверу вэбсервисов. Использую ZSI и wsdl2py.
    К сожалению ZSI клиент глючит с soaplib сервером:
    ZSI.EvaluateException: Element NS mismatch (got None wanted _mod_wsgi_55f7d9b09b5963fe9560e888eb918c97.SOAPClass)
    [Element trace: /SOAP-ENV:Envelope/SOAP-ENV:Body/test_BooleanResponse]

    Может кто знает в чем дело? В сети инфы по этой проблеме мало.

    By Andrey on Фев 11, 2010

  10. К сожалению, не подскажу. Последнее время вообще перешел на SoapUI + ручное формирование xml. :)
    Так получается более надежно, хотя и дольше по времени. С soap вообще все очень грустно, если даже java с .net иногда подружится не могут, то что можно вообще говорить про python.

    By Roinet on Фев 11, 2010

Добавить комментарий: