×

Битрикс: проблема с модулем Веб-cервисы (SOAP)

Есть у нас несколько сайтов под управлением всеми любимой CMS Битрикс (так получилось). Работали они себе, никому не мешали. И тут понадобилось добавить в кратчайшие сроки небольшое взаимодействие с внешними сервисами, написанными на Си++.

Чтобы не возиться с осваиванием новых технологий мы решили воспользоваться модулем Веб-Сервисы (SOAP от Битрикса), входящим в нашу дорогущую редакцию за несколько сотен тысяч рублей (Бизнес Веб Кластер с несколькими сайтами, активно продвигаемый во всех тематических СМИ некоторое время назад) По нему и документация вроде бы была, и пара примеров в интернете.

Прочитали, скопировали пример, поправили код под наши нужды — заработало. Проблема решена — начальство довольно.

Что же случилось?

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

Вот что написано в документации по поводу используемых типов данных:

Методы веб-сервиса Методы веб-сервисов всегда: принадлежат классу, реализующему веб-сервис; полностью описывают свои исходящие и входящие параметры; строго соблюдают типы входящих\исходящих данных согласно их описанию. Типы данных Типы данных используемые методами веб-сервиса могут быть двух типов: Простые типы — string, bool, boolean, int, integer, double, float, number; Сложные типы — массивы, структуры (сериализуются в ассоциативные массивы), классы (сериализуются в экземпляры классов); Описываются с помощью structTypes, classTypes. Типы данных описываются в трёх местах в описателе веб-сервиса CWebServiceDesc:

Для нашей задачи подходил либо массив/структура, либо строка с текстом ответной XML(если с массивом не получилось).

Пробуем вернуть массив

Для того чтобы вернуть массив, нужно описать его методом structTypes(), опишем.

...
function GetWebServiceDesc(){
 
	...
 
	$wsdesc->structTypes['TestList'] = Array(
		'FIRST_ARG' => array('varType' => 'string'),
		'SECOND_ARG' => array('varType' => 'string')
	);
 
	...
 
	$wsdesc->classes = array(
		'AddPayments' => array(
			'type'      => 'public',
			'input'      => array(
				'USERLOGIN' => array('fakeArg' => 'string')
			),
			'output'   => array(
				'info' => array('varType' => 'TestList')
			)
		)
	);
...

Далее напишем пустой метод с одним входящим параметром, возвращающий ассоциативный массив описанной ранее структуры.

class cTestSOAP extends IWebService{
	function test($fakeArg){	
		$i =0;
 
		$arResult[($i++).':ELEMENT_NAME'] = Array(
			'FIRST_ARG' => 'bla-bla-bla',
			'SECOND_ARG' => 'qwerty'
		);
 
		$arResult[($i++).':ELEMENT_NAME'] = Array(
			'FIRST_ARG' => 'bla-bla-bla',
			'SECOND_ARG' => 'qwerty'
		);
 
		return $arResult;
	}
 
...

И сразу натыкаемся на костыль: оказывается, чтобы добавить в корневой элемент XML несколько дочерних элементов с одинаковым именем, нужно сформировать массив следующим образом:

$arResult[($i++).':ELEMENT_NAME'] = Array(
	'FIRST_ARG' => 'bla-bla-bla',
	'SECOND_ARG' => 'qwerty'
);

Где $i — счетчик элементов, а ELEMENT_NAME — имя элемента. Нигде в документации об этом не написано. Мало того, на форуме также никакой информации.

Ну да ладно, это можно пережить. Написали мы, значит, такой тестовый метод и пробуем его протестировать средствами, которые нам предоставляет битрикс. Выглядят они примерно вот так:

Пример с сайта Битрикса

Пример с сайта Битрикса

Вводим наш фиктивный параметр и в ответ получаем xml нужного нам формата. Казалось бы, всё хорошо.

Результат выполнения

Результат выполнения

Но разработчики приложения на C++ почему-то говорят что ничего не приходит. Не беда, попробуем обратиться сами к себе написав простенькую страничку для теста:

<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
 
CModule::IncludeModule('webservice');
 
function TestComponent() {
	$client = new CSOAPClient('адрес сайта', 'страница с сервисом' );
	$request = new CSOAPRequest( "test", "Пространство имён" );
	$request->addParameter("fakeArg", "alskdaskd");
	$response = $client->send($request);
 
	if($response->isFault()){
	    echo($response->faultCode(). " - " . $response->faultString() . "<br />" );
	}else{
	    echo "[OK]: ".mydump($response->Value);
	}
}
 
if($_REQUEST['test'] == 'Test'){
	TestComponent();
}
 
?>
<form>
	<input type="submit" name="test" value="Test" />
</form><?
 
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_after.php");?>

Здесь используется битриксовая функция mydump, которая является аналогом var_dump, только возвращает строку.

Нажимаем на кнопочку тест и получаем следующее:

[OK]: array(1) => [info] => NULL()

Т.е. реально ничего не пришло. Пробуем воспользоваться fiddler’ом на стороне разработчиков клиента на C++ и получаем подтверждение нашим догадкам. Вот ответная XML от веб сервиса:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<testResponse xmlns="адрес сервиса">
			<info></info>
		</testResponse>
	</soap:Body>
</soap:Envelope>

Ладно, может у нас где-то ошибка. попробуем начать с простого — вернём строку.

Пробуем вернуть строку

Для возврата строки упростим всё до максимума.

...
function GetWebServiceDesc(){
 
	...
 
	$wsdesc->structTypes = Array();
 
	...
 
	$wsdesc->classes = array(
		'AddPayments' => array(
			'type'      => 'public',
			'input'      => array(
				'USERLOGIN' => array('fakeArg' => 'string'),
			),
			'output'   => array(
				'info' => array('varType' => 'string')
			)
		)
	);
...

ну и метод

class cTestSOAP extends IWebService{
	function test($fakeArg){	
		return 'test';
	}
 
...

При тестировании через средства битрикса опять всё хорошо, но разработчики клиента опять жалуются на проблемы — теперь им приходит только первый символ из строки.

Пробуем обратиться сами к себе (код тестовой странички менять не нужно) Получаем:

[OK]: array(1) => [info] => string(1) => "t"

Т.е. проблема подтверждается и мы реально можем получить только первый символ из строки.

Обращаемся в техподдержку

Пробуем написать в техподдержку. Ведь может так получиться, что в моём коде ошибки (integer и bool же возвращаются без проблем). Сначала спрашиваем: «реально ли вернуть массив?» Получаем ответ:

Добрый день! У нас в продукте это делается так: Цитата function UsersOnline() { if (($r = CStatisticWS::CheckAuth()) !== False) return $r; $dbresult = CUserOnline::GetList($guest_count, $session_count, Array(«s_session_time»=>»desc»)); $result = Array(«GUEST_COUNT»=>$guest_count, «SESSIONS»=>Array()); $i=0; while ($ar = $dbresult->Fetch()) { $strTmp = «»; $rsUser = CUser::GetByID($ar[«LAST_USER_ID»]); if ($ar1 = $rsUser->Fetch()) $strTmp = «[«.$ar1[«ID»].»] «.$ar1[«NAME»].» «.$ar1[«LAST_NAME»].» («.$ar1[«LOGIN»].») «; else $strTmp = «[«.$ar[«LAST_USER_ID»].»]»; $ar[«USER_NAME»] = $strTmp; $result[«SESSIONS»][($i++).’:SESSION’] = $ar; } return $result; } Полный пример можно найти в файле: /bitrix/components/bitrix/webservice.statistic/component.php

Можно, это хорошо. Далее идёт долгое обсуждение того же что я написал вверху (только код был от рабочего метода). В итоге после моего сообщения:

Попробовал обратиться сам к себе через битриксовый клиент SOAP и получил ту же проблему. Если поменять тип возвращаемого значения на string, то можно получить только один первый символ из всей строки. При этом integer, передать удаётся. p.s. Ошибки получаются нормально, текст и коды читаются.

Получаем следующий ответ:

Добрый день! Обсуждали вопрос с разработчиками. Модуль представляет из себя набор классов, которые были разработаны для расширения внутренних механизмов продукта. Они обособлены в виде модуля чтобы разработчики могли сами расширять текущую реализацию. Что теперь и происходит. Фактически, мы можем оказывать поддержку на уровне стандартного функционала продукта, где используется этот модуль. Ваша задача является частной разработкой и, к сожалению, мы не сможем более погружаться в этот вопрос. Вам необходимо продолжить разработку собственными силами используя наш модуль, библиотеку php_soap или альтернативные решения.

Немного обалдев, я уточнил:

Т.е. модуль веб сервисов от битрикса не подразумевает, что методы могут возвращать текстовые данные? Так? Проблема как раз в том, как работает ваш модуль SOAP. В том коде, что я предоставил, нет ничего выходящего за рамки изложенного в документации примера

Ответ был удручающим:

Как мы говорили выше, мы не поддерживаем решения, которые выходят за рамки работы стандартного функционала нашего модуля. Рекомендации по дальнейшим действиям мы привели в предыдущем сообщении, с сожалению больше ничего предложить в рамах ТП не можем.

После последней попытки задать вопрос:

Покажите мне участок кода, в котором не используется стандартный функционал? Вы не можете объяснить почему ваш SOAP не может вернуть строку?

Был получен еще более печальный ответ:

Вы разрабатываете частное решение, мы не можем его поддерживать. Как мы говорили, модуль сделан для решения внутренних потребностей продукта, других его модулей. Почему он работает так или иначе могут ответить только разработчики, а они говорят, что это корректное поведение.

Что в итоге

Раз уж это поведение является нормальным, я попросил следующее:

Тогда поправьте документацию. В ней написано, что простые типы могут быть возвращены методами класса.

После чего был получен вот такой ответ:

Автоматическое сообщение о создании заявки в разработку На основе информации из данного обращения была создана заявка в отдел разработок. Категория: Ошибки. Критичность: Нормальная. Номер обращения в разработку: 25208.

Заявка повисела недельку и была закрыта без изменений

Автоматическое сообщение о закрытии заявки в разработке Зарегистрированное в разработке обращение было закрыто. Модуль: documentation, версия: 11.0.2 Решение: изменения не требуются Номер обращения в разработку: 25208.

Т.е. долбайтесь сами с нашим SOAP, мы вам ничем не поможем.

Сейчас я всё переписываю на php soap. А про разработчиков битрикса могу только нехорошее сказать.

logo