Tag Archives: soap

PHP клиент для WCF сервиса

В предыдущей статье я подробно рассматривал вопросы создания клиент-серверного .NET приложения поддерживающего обмен данными через WCF сервисы и реализующего защиту передаваемых данных на транспортном уровне. Обе части приложения (клиентская и серверная) были реализованы при помощи .NET. Однако, бывают ситуации, когда существуют уже готовые WCF сервисы и необходимо реализовать их поддержку сторонним клиентом. В данной статье будут рассмотрен вопрос создания PHP клиента для уже существующего WCF сервиса. В качестве окружения будут использованы Windows 7, Apache 2.2.17 с поддержкой PHP 5.2.9-2 и созданный ранее WCF сервис.

Конфигурирование PHP

Для начала надо проверить, что PHP сконфигурирован правильно и включена поддержка всех необходимых библиотек. Для этого достаточно написать простейший скрипт вида:

<?php phpinfo(); ?>

и посмотреть на результат его исполнения. Если все сконфигурировано верно, то в результирующей таблице будет следующая информация:
soap

Soap Client enabled
Soap Server enabled

openssl

OpenSSL support enabled
OpenSSL Version OpenSSL 0.9.8k 25 Mar 2009

Секция soap отвечает за возможность создания класса SoapClient. Если ее нет, то при запуске клиентского кода вы увидите вот эту ошибку:

 Fatal error: Class 'SoapClient' not found in D:\wcfclient.php on line 10

В этом случае в файле php.ini необходимо найти строку:

;extension=php_soap.dll

и раскомментировать ее путем стирания символа “;” стоящего в начале строки.
Секция openssl отвечает за использование SSL при обмене данными. Если ее нет, то при попытке создать клиента к сервису, url которого начинается с “https://…”, вы увидите вот такую ошибку:

Notice: SoapClient::SoapClient() [soapclient.soapclient]: Unable to find the wrapper "https" - did you forget to enable it when you configured PHP? in D:\wcfclient.php on line 10

В этом случае в файле php.ini необходимо найти строку:

;extension=php_openssl.dll

и раскомментировать ее путем стирания символа “;” стоящего в начале строки. После внесения изменений в php.ini необходимо перезагрузить Apache.

Создание PHP клиента

При создании php клиента к WCF сервису следует обратить особое внимание на тип привязки, используемой в сервисе. В данной статье я рассмотрю два типа привязок: basicHttpBinding и wsHttpBinding. Основные различия между этими двумя типами привязок хорошо описаны здесь. Особенно важным пунктом является разница в версиях SOAP.

WCF сервис

Как я уже указывал выше, в качестве сервера будет выступать созданный ранее WCF сервис. Из соображений экономии места здесь я приведу только его конфигурацию:

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
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="SecurityBehavior">
          <serviceMetadata httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceCredentials>
            <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="Hosting.Service1"
               behaviorConfiguration="SecurityBehavior">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="TransportSecurityBinding" contract="Hosting.IService1">
        </endpoint>
        <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="https://localhost:7586/Service1/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="TransportSecurityBinding">
          <security mode="Transport">
            <transport clientCredentialType="None"/>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

По сути, важными здесь являются только 18-я и 23-я строки: в первой задается тип привязки, а во второй указывается url сервиса. Кроме того, перед началом работ по созданию клиента надо убедиться, что серверная часть скомпилирована и запущенна. Если все готово, то поместив в адресную строку интернет-браузера следующий ulr “https://localhost:7586/Service1/?wsdl” (url сервера + параметр “wsdl”) мы должны увидеть wsdl-описание сервиса, сгенерированное сервером.

PHP клиент для basicHttpBinding

basicHttpBinding использует в своей работе SOAP 1.1 который поддерживается классом SoapClient по умолчанию. В данном случае создание клиента не займет у нас много времени:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
header('Content-Type: text/plain; charset=utf-8');
echo "PHP client for WCF service using 'basicHttpBinding'\r\n\r\n";
$accountService = new SoapClient('https://localhost:7586/Service1/?wsdl', array("trace" => 1, "exceptions" => 0));
try { 
	$obj->name = "qwerty";
	$result = $accountService->SayHello($obj);
	echo $result->SayHelloResult;
}
catch (SoapFault $fault) { 
	var_dump($fault->faultcode);
	var_dump($fault->faultstring);
	die;
}
?>

Все самое интересное происходит в строках 4 и 6-8. Фактически, всю техническую работу берет на себя класс SoapClient, который мы создаем в 4-ой строке. Нам необходимо только указать url сервиса (см. 23-ю строку конфигурации сервера) и несколько дополнительных параметров. Вызов сервиса происходит в строке 7, печать результата в строке 8.

PHP клиент для wsHttpBinding

В силу того, что wsHttpBinding основывается на WS*-спецификациях и использует в своей работе SOAP 1.2 – клиентская часть будет чуть обширнее. Для начала изменим конфигурацию сервера:

1
2
3
4
5
6
7
8
9
10
11
12
13
    ...
    <endpoint address="" binding="wsHttpBinding" bindingConfiguration="TransportSecurityBinding" contract="Hosting.IService1"/>
    ...
    <bindings>
      <wsHttpBinding>
        <binding name="TransportSecurityBinding">
          <security mode="Transport">
            <transport clientCredentialType="None"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    ...

скомпилируем и перезапустим серверную часть.
Теперь, на стороне клиента, надо принудительно указать классу SoapClient использовать SOAP 1.2. Кроме того, в силу особенностей SOAP 1.2 потребуется указать несколько дополнительных заголовков:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
header('Content-Type: text/plain; charset=utf-8');
echo "PHP client for WCF service using 'wsHttpBinding'\r\n\r\n";
$connect = array("soap_version" => SOAP_1_2, "trace" => 1, "exceptions" => 0);
$accountService = new SoapClient('https://localhost:7586/Service1/?wsdl', $connect);
$actionHeaders[] = new SoapHeader("http://www.w3.org/2005/08/addressing", "Action", "http://tempuri.org/IService1/SayHello", true);
$actionHeaders[] = new SoapHeader("http://www.w3.org/2005/08/addressing", "To", "https://localhost:7586/Service1/",  true);
$accountService->__setSoapHeaders($actionHeaders);
try { 
	$obj->name = "qwerty";
	$result = $accountService->SayHello($obj);
	echo $result->SayHelloResult;
} catch (SoapFault $fault) { 
	var_dump($fault->faultcode);
	var_dump($fault->faultstring);
	die;
}
?>

Обратите внимание на 4-ю строку. В ней указывается версия SOAP, которую необходимо использовать. Дополнительные заголовки задаются в 6-8 строках. Если эти строки опустить, то вы увидите вот эту:

1
The SOAP action specified on the message, '', does not match the HTTP SOAP Action, 'http://tempuri.org/IService1/SayHello'.

или вот эту ошибку:

1
The message with To '' cannot be processed at the receiver, due to an AddressFilter mismatch at the EndpointDispatcher.  Check that the sender and receiver's EndpointAddresses agree.

URL “http://tempuri.org/IService1/SayHello” в 6-ой строке берется из wsdl, предоставляемого сервером.
Все, клиентская часть готова, можно запускать 🙂 Отдельное спасибо разработчикам php, все работает практически из коробки и без использования сторонних библиотек.