26
2011
Zend_Soap_AutoDiscover и eAccelerator
Сегодня хочу рассказать об одной проблеме, с которой я столкнулся совсем недавно. Надеюсь, что мой опыт, описанный в данной статье, поможет сэкономить часы и нервные клетки тем людям, кто занимается разработкой SOAP сервисов с использованием Zend Framework, и в частности класса Zend_Soap_AutoDiscover.
Проблема заключается в том, что Zend_Soap_AutoDiscover отрабатывает некорректно вкупе с использованием известного оптимизатора кода eAccelerator. А именно, если быть точным некорректно работает метод ReflectionClass::getDocComment(). Но…обо всем по порядку.
При разработке SOAP сервисов в 99% случаев перед программистом встает задача автоматической генерации здоровенных WSDL документов, которые используются в качестве описания сервисов. Такие WSDL содержат описания типов данных и методов, которые предоставляются сервисами. Насколько мне известно, многие программисты для решения этой задачи используют средства, предоставляемые Zend Framework, а именно Zend_Soap_AutoDiscover (ходят слухи, что для решения подобной задачи можно использовать библиотеку PEAR SOAP, но в данном случае не могу сказать ничего конкретного, т.к. не использовал её).
Не буду подробно останавливаться на описании как же использовать Zend_Soap, все это отлично описано в официальной документации или в статьях на хабрахабре — например вот тут.
В чем основное преимущество использования Zend_Soap_AutoDiscover? При помощи этого класса мы можем автоматически строить WSDL, на основе классов, которые укажем в качестве параметров. Соотвественно WSDL будет динамически перестраиваться в зависимости от изменения классов (на основе которых он строится). Но есть один важный момент — Zend_Soap_AutoDiscover использует комментарии, оформленные в стиле PHPDoc, для определения типов данных параметров методов. Поэтому код должен быть хорошо задокументирован.
Далее следует пример простого client-server взаимодействия.
Пусть у нас есть 3 скрипта:
soap-client.php — клиент SOAP сервиса
soap-server.php — реализация SOAP сервиса
soap-server-model.php — класс, на основе которого строится SOAP сервис
Листинг кода каждого скрипта прост и незатейлив и представлен ниже:
soap-client.php:
require_once 'Zend/Soap/Client.php'; $wsdlUri = 'http://localhost/soap-server.php?wsdl'; try { $client = new Zend_Soap_Client($wsdlUri); echo $client->showSomething('test', 46); echo '<br />'; echo 'the end'; } catch (Exception $e) { echo 'Error: ' . $e->getMessage(); }
soap-server.php:
require_once 'soap-server-model.php'; $wsdlUri = 'http://localhost/soap-server.php?wsdl'; if(isset($_GET['wsdl'])) { require_once 'Zend/Soap/AutoDiscover.php'; $autodiscover = new Zend_Soap_AutoDiscover(); $autodiscover->setClass('SoapModel'); $autodiscover->handle(); } else { require_once 'Zend/Soap/Server.php'; $soap = new Zend_Soap_Server($wsdlUri); $soap->setClass('SoapModel'); $soap->handle(); }
soap-server-model.php:
class SoapModel { /** * Method for testing SOAP server with Zend_Soap_AutoDiscover * * @param string $word * @param int $num * @return string */ public function showSomething($word, $num) { return 'Server said: ' . $word . ' ' . $num; } }
В php.ini настройки SOAP (на стороне клиента) имеют следующий вид:
[soap] soap.wsdl_cache_enabled=0 soap.wsdl_cache_dir="/tmp" soap.wsdl_cache_ttl=18000 soap.wsdl_cache_limit = 0
то есть в целях тестирования отключаем кэширование WSDL
Казалось бы все круто, запускаем скрипт клиента и вместо ожидаемого «Server said: test 46» видим лишь «the end». Смотрим возвращаемый WSDL (в примере он доступен по URL: localhost/soap-server.php?wsdl&a=1), и что же мы видим — вместо ожидаемого:
<?xml version="1.0"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://localhost/soap-server.php" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" name="SoapModel" targetNamespace="http://localhost/soap-server.php"> <types> <xsd:schema targetNamespace="http://localhost/soap-server.php"/> </types> <portType name="SoapModelPort"> <operation name="showSomething"> <documentation>Method for testing SOAP server with Zend_Soap_AutoDiscover</documentation> <input message="tns:showSomethingIn"/> <output message="tns:showSomethingOut"/> </operation> </portType> <binding name="SoapModelBinding" type="tns:SoapModelPort"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="showSomething"><soap:operation soapAction="http://localhost/soap-server.php#showSomething"/> <input> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/soap-server.php"/> </input> <output> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/soap-server.php"/> </output> </operation> </binding> <service name="SoapModelService"> <port name="SoapModelPort" binding="tns:SoapModelBinding"> <soap:address location="http://localhost/soap-server.php"/> </port> </service> <message name="showSomethingIn"> <part name="word" type="xsd:string"/> <part name="num" type="xsd:int"/> </message> <message name="showSomethingOut"> <part name="return" type="xsd:string"/> </message> </definitions>
выдается следующий код:
<?xml version="1.0"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://localhost/soap-server.php" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" name="SoapModel" targetNamespace="http://localhost/soap-server.php"> <types> <xsd:schema targetNamespace="http://localhost/soap-server.php"/> </types> <portType name="SoapModelPort"> <operation name="showSomething"> <documentation>showSomething</documentation> <input message="tns:showSomethingIn"/> </operation> </portType> <binding name="SoapModelBinding" type="tns:SoapModelPort"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="showSomething"> <soap:operation soapAction="http://localhost/soap-server.php#showSomething"/> <input> <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="http://localhost/soap-server.php"/> </input> </operation> </binding> <service name="SoapModelService"> <port name="SoapModelPort" binding="tns:SoapModelBinding"> <soap:address location="http://localhost/soap-server.php"/> </port> </service> <message name="showSomethingIn"> <part name="word" type="xsd:anyType"/> <part name="num" type="xsd:anyType"/> </message> </definitions>
Вообщем, после долгих поисков неисправности, изучения мануалов и криков WTF o.O оказалось, что все прекрасно работает после отключения eAccelerator. Почему же так происходит? Zend_Soap_AutoDiscover в своей внутренней реализации использует механизм Reflections (если быть точным за это отвечает класс Zend_Server_Reflection), для получения параметров из комментариев к методам. eAccelerator же в свою очередь после первого обращения к скрипту во время создания байткода удаляет все комментарии, в результате чего мы имеем следующую вышеописанную трабблу. Логично, но достаточно нетривиально.
Какие же пути решения?
1. Отказаться от eAccelerator. И использовать например APC (лично проверял — с APC подобных проблем не наблюдалось).
2. Использовать фильтры eAccelerator-а, для того чтобы не оптимизировать файлы, в которых важны комментарии. Делается это примерно так:
ini_set('eaccelerator.filter', '!soap-server-model.php');
3. Решение с хабрахабра: при установке сконфигурять eAccelerator с ключем with-eaccelerator-doc-comment-inclusion, то есть:
./configure --with-eaccelerator-doc-comment-inclusion
P.S. Вообще выше обозначенная проблема несколько глубже, чем просто «какой-то там класс Zend-а не работает с eAccelerator». Пусть это не часто, но тем не менее механизмы Reflections (ReflectionClass::getDocComment()) используются при разработке, и зачастую комментарии к классам могут играть роль в разрабатываемом функционале.
P.S.S. Как это часто бывает, тулзы типо eAccelerator используются на production-серверах, и очень редко, когда кто-либо устанавливает их у себя на локали. В итоге, идеально работающий на локали код, может совершенно нетривиально не работать на production.
P.S.S.S. Zend_Soap, как и большинство других компонент Zend можно использовать отдельно от самого Framework-а. Так, например, в текущем разрабатываемом проекте мы используем лишь 3 компонента ZF, а именно Zend_Db, Zend_Form и Zend_Soap. И если, например, кто-то захочет заюзать у себя Zend_Soap_AutoDiscover, то вовсе не обязательно чтобы весь проект был на ZF. Достаточно перенести все необходимые классы ZF. По правде говоря, кроме использования супер-полезного Zend_Soap_AutoDiscover, в остальном Zend_Soap слабо расширяет встроенную Soap функциональность. Так что если вы не озадачены созданием Soap-сервисов, то вряд ли будет сильно полезно менять нативный SoapClient на Zend_Soap_Client.
P.S.S.S.S Опубликовал аналогичный пост на хабре. Вот линка.
Пн | Вт | Ср | Чт | Пт | Сб | Вс |
---|---|---|---|---|---|---|
« Сен | ||||||
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
Метки
Рубрики
- Apache (1)
- Highload (4)
- JavaScript (1)
- Linux (3)
- MongoDB (1)
- MySQL (1)
- Perl (1)
- PHP (5)
- Python (5)
- Web-разработка (5)
- Алгоритмы (1)
- За жизнь (2)
- Конференции (6)