Python web services.
Июнь 7, 2008 – 4:52 ппОбсуждений протокола SOAP в сети имеется огромное множество – в теории и в практике. В основном он используется в корпоративных проектах, как следствие, основные и хорошо отлаженные реализации имеются для таких платформ как Java, .Net, PHP. Однако иногда возникают экзотические требования. Например, для реализации клиента выбирается язык PHP, а для реализации сервера – Python. Недавно именно такая задача имела место быть.
Были рассмотрены 3 реализации SOAP протокола для Python: ZSI, SOAPpy, soaplib. Сразу скажу, что писал примеры сервера и клиентов только для двух последних – ZSI не понравился из-за отсутствия WSGI и тяжелых интерфейсов.
Сервер.
Сервер написан с применением библиотеки soaplib. Такое решение было выбрано по нескольким причинам:
- Использование WSGI. Стандартный протокол – это всегда хорошо и надежно. В данном примере был использован сервер CherryPy, но с таким же успехом можно использовать и Paster.
- Наглядный и гибкий способ регистрации SOAP методов. С помощью декоратора soaplib.service.soapmethod можно сообщить библиотеке, что данный метод должен быть включен в WSDL описание сервиса. Кроме того, там же можно задать сигнатуру вызова. Сначала передаются типы формальных параметров, далее, если метод возвращает некоторое значение, используется позиционный параметр _returns.
- Автоматическая генерация 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>";
?>
Комментарии:
Спасибо, статья весьма полезная.
В подробностях пока не разбирался, но насколько я вижу SOAP+WSDL достаточно мутно специфицирована. Спеки SOAP () не содержат никаких рекомендаций относительно использования WSDL. В любом случае, насчет именования wsdl-сервиса автор soaplib не прав (), про передачу параметров лень искать но таж наверняка косяк либы.
By MuTPu4 on Июнь 8, 2008
Передача параметров в soaplib – однозначный косяк, т.к. эксперименты с SOAP сервером на php показывают, что в общем случае complexType не нужен и передача может вестись обычным образом.
By Roinet on Июнь 8, 2008
Ну эксперименты экспериментами а стандарты стандартами, я всетаки предпочитаю разобраться в спеках а потом утвержадать. Область явно довольно темная, зачастую может существовать несколько корректных трактовок одной спецификации.
By MuTPu4 on Июнь 8, 2008
Почитал. :)
В спецификации имеется четкое указание, что можно описывать типы данных в виде simpleType и complexType. simpleType позволяет ограничить базовые типы (string, boolean, integer и др.). complexType позволяет описывать структуры любой сложности.
Указаний какими типами могут быть описаны формальные параметры SOAP методов я не нашел, однако в примерах имеется 3 вполне ожидаемых варианта: использование базовых типов, simpleType и complexType. Таким образом, оборачивание любых формальных параметров в complexType – особенность реализации soaplib. Возможно автор руководствовался простотой, чтобы не писать 3 возможных варианта описания параметров.
By Roinet on Июнь 8, 2008
ok, верю =)
Похоже что так и есть. Лана главное разобрались. При острой необходимости, думаю, можно дописать логику передачи множественных параметров.
By MuTPu4 on Июнь 8, 2008
А никто не пробовал коннектиться к MSSOAP серверу? Если пользоваться библиотекой SOAPpy, то передать многомерный массив или структуру никак не получиться, если в WDSL тип описан как complexType.
By LA on Сен 2, 2009
Нет. К сожалению не пробовал. Знаю тока одно – такого рода проблемы на cpython будут, т.к. нет ни одной нормальной реализации SOAP, кроме ZSI, которая ужасна по своим интерфейсам. В ironpython есть более-менее адекватные решения на основе .Net.
By Roinet on Сен 16, 2009
У меня клиент к 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
К сожалению, не подскажу. Последнее время вообще перешел на SoapUI + ручное формирование xml. :)
Так получается более надежно, хотя и дольше по времени. С soap вообще все очень грустно, если даже java с .net иногда подружится не могут, то что можно вообще говорить про python.
By Roinet on Фев 11, 2010