Валентин Колесов
разработчик красноярского отделения санкт-петербургской компании "Астрософт", сертифицированный специалист Microsoft (MCSD, MCSE, MCDBA)
Valentin_K@astrosoft-yenisei.krs.ru

Демонстрация работы SOAP на примере написания Web-сервера

Что такое SOAP

Широко распространенные в настоящее время технологии удаленного вызова методов (DCOM, CORBA/IIOP и RMI) довольно сложны в настройке и организации взаимодействия. Это влечет за собой трудности в эксплуатации и функционировании распределенных систем (проблемы безопасности, неудобства транспорта через брандмауэры и т. д.). Многие проблемы удалось решить благодаря созданию SOAP (Simple Object Access Protocol, простой протокол доступа к объектам), простого протокола, основанного на XML и служащего для обмена сообщениями в распределенных средах (WWW). Протокол предназначен для создания Web-сервисов и удаленного вызова методов. SOAP можно использовать в сочетании с разными транспортными протоколами, включая HTTP, SMTP и др.

Что такое Web-сервисы

Web-сервисами мы называем активный контент, реализующий некоторую функциональность, и данные, расположенные на Web-серверах и предоставляемые для использования внешним приложениям. Web-сервисы полностью независимы от языка и платформы реализации. Внешние приложения работают с сервисами посредством стандартных протоколов и форматов данных. Технология Web-сервисов является краеугольным камнем программной модели Microsoft .NET.

Для демонстрации возможностей SOAP в статье использована недавно вышедшая реализация SOAP Toolkit версии 2.0 производства Microsoft. Следует заметить, что текущая версия Toolkit заметно отличается от предыдущей (Microsoft SOAP Toolkit for Visual Studio 6.0) и от бета-версии SOAP Toolkit 2.0.

Объектная модель SOAP Toolkit предоставляет как низкоуровневые, так и высокоуровневые интерфейсы (SOAPClient, SOAPServer), скрывающие от программиста всю "внутреннюю кухню" - разбор и формирование пакетов, вызов методов и т. п. С помощью этих интерфейсов можно весьма элегантным образом использовать Web-сервисы в разрабатываемых приложениях. Объект SOAPClient выступает в роли посредника (proxy), предоставляющего интерфейс Web-сервиса и позволяющего работать с ним как с обычным COM-объектом (рис. 1).

Fig.1
Рис. 1. Механизм взаимодействия клиента и сервера SOAP.

  1. Клиентское приложение создает экземпляр объекта SOAPClient.
  2. SOAPClient читает файлы описания методов Web-сервиса (на языках WSDL и Web Services Meta Language, WSML). Эти файлы могут храниться и на стороне клиента.
  3. Клиентское приложение, используя возможности позднего связывания методов объекта SOAPClient, вызывает метод сервиса. SOAPClient формирует пакет запроса (SOAP Envelope) и отправляет его на сервер. Можно применить любой транспортный протокол, но, как правило, используется HTTP.
  4. Серверное приложение Listener (это может быть ISAPI-приложение или ASP-страница) принимает пакет, создает объект SOAPServer и передает ему пакет запроса. Помимо этого Listener обрабатывает HTTP-пакеты от клиента, отправляет клиенту пакеты с результатом работы сервиса, обрабатывает ошибки и использует функциональность SOAP-объектов.
  5. SOAPServer читает описание Web-сервиса, загружает описание и пакет запроса в деревья XML DOM.
  6. SOAPServer вызывает метод объекта или приложения, реализующего сервис.
  7. Результаты выполнения метода или описание ошибки конвертируются объектом SOAPServer в пакет ответа и отправляются клиенту.
  8. Объект SOAPClient проводит разбор принятого пакета и возвращает клиентскому приложению результаты работы сервиса или описание возникшей ошибки.

WSDL-файл - это документ в формате XML, описывающий методы, предоставляемые Web-сервисом, а также параметры методов, их типы, названия и местонахождение сервиса Listener. Мастер SOAP Toolkit автоматически генерирует этот документ, фрагмент которого приведен ниже:

...
<message name=" SOAPClass.SubtractNumbers">
  <part name="NumberOne" type="xsd:double" /> 
  <part name="NumberTwo" type="xsd:double" /> 
  </message>
<message name=" SOAPClass.SubtractNumbersResponse">
  <part name="Result" type="xsd:double" /> 
  </message>
<message name=" SOAPClass.AddNumbers">
  <part name="NumberOne" type="xsd:double" /> 
  <part name="NumberTwo" type="xsd:double" /> 
  </message>
<message name=" SOAPClass.AddNumbersResponse">
  <part name="Result" type="xsd:double" /> 
  </message>
...

SOAP Envelope (Пакет) - это XML-документ, который содержит в себе запрос на выполнение метода или ответ на него. Удобнее всего рассматривать пакет как почтовый конверт, в который вложена информация (рис. 2).

Fig.2
Рис. 2. Структура SOAP-пакета.

Тег Envelope должен быть корневым элементом пакета. Элемент Header не обязателен, а Body должен присутствовать и быть прямым потомком элемента Envelope. В случае ошибки выполнения метода сервер формирует пакет, содержащий в теге Body элемент Fault, который содержит подробное описание ошибки.

Если вы пользуетесь высокоуровневыми интерфейсами SOAPClient, SOAPServer, то вам не придется вдаваться в тонкости формата пакета, но при желании можно воспользоваться низкоуровневыми интерфейсами или же вообще создать пакет "вручную", используя любой язык программирования.

Объектная модель SOAP Toolkit дает возможность работать с объектами низкоуровневого API:

  • SoapConnector - обеспечивает работу с транспортным протоколом для обмена SOAP-пакетами.
  • SoapConnectorFactory - обеспечивает метод создания коннектора для транспортного протокола, указанного в WSDL-файле (тег ).
  • SoapReader - читает SOAP-сообщения и строит деревья XML DOM.·
  • SoapSerializer - содержит методы создания SOAP-сообщения.·
  • IsoapTypeMapper, SoapTypeMapperFactory - интерфейсы, позволяющие работать со сложными типами данных.

С помощью объектов высокоуровневого API можно передавать данные только простых типов (int, string, float и т. п.), но спецификация SOAP 1.1 допускает работу с более сложными типами данных, например с массивами, структурами, списками и их комбинациями. Для работы с такими типами приходится использовать интерфейсы IsoapTypeMapper и SoapTypeMapperFactory.

Пример

Чтобы продемонстрировать работу SOAP, напишем несложный Web-сервис, который будет уметь складывать и вычитать числа. Для работы серверного приложения потребуется пакет IIS 5 в среде Windows 2000 или IIS4 на Windows NT 4.0 Service Pack 6, а также установленный SOAP Toolkit 2.0.

Требования клиентского приложения - ОС Microsoft Windows 98/Me или Windows NT 4.0 Service Pack 6/2000 Service Pack 1, а также установленный SOAP Toolkit 2.0.

Создание сервера

Откройте в VB новый проект ActiveX DLL. Измените название класса на SOAPClass, а имя проекта - на SOAPProj.

В классе создайте следующие методы:

Public Function AddNumbers(ByVal NumberOne As Double, _
                           ByVal NumberTwo As Double) _
                           As Double
    AddNumbers = NumberOne + NumberTwo
End Function

Public Function SubtractNumbers(ByVal NumberOne As Double, _
                                ByVal NumberTwo As Double) _
                                As Double
    SubtractNumbers = NumberOne - NumberTwo
End Function

Откомпилируйте DLL. Затем запустите приложение WSDL Generator (wsdlgen.exe). Сообщите Мастеру, как вы хотите назвать сервис, (например, SOAPService), затем укажите путь к только что созданному объекту (рис. 3).

Fig.3 Рис. 3. Первое окно Мастера SOAP Toolkit 2.0.

В следующем окне Мастера можно выбрать методы, которые войдут в Web-сервис. В данном случае выберите все методы. Затем укажите, где будет находиться Web-приложение (например, http://wsd010/soap/), задайте тип приложения Listener (ASP или ISAPI), выберите ASP, формат схемы (по умолчанию). Укажите путь, где будут находиться файлы описания Web-сервиса, и кодировку. После окончания работы Мастера в Web-каталоге появятся файлы ASP, WSDL и WSML - это Listener для ASP и описания сервиса (см. листинги 1-3).

Осталось только настроить права доступа к Web-приложению - желательно установить NT Challenge/Response authentication. На этом работа по созданию сервера закончена.

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

Откройте в VB новый проект Standard EXE. В меню Project/References сделайте ссылку на библиотеку Microsoft SOAP type library. Создайте на форме кнопку, в обработчике нажатия кнопки напишите следующий код:

    Dim SoapClient As New SoapClient
    SoapClient.mssoapinit "http://wsd010/soap/SOAPService.wsdl"
    MsgBox SoapClient.AddNumbers(4, 3)
    MsgBox SoapClient.SubtractNumbers(3, 2) 

Не забудьте изменить адрес Web-сервера и путь к WSDL-файлу во второй строке на адрес и путь к вашему Web-сервису.

После создания объекта SOAPClient его надо инициализировать - указать путь к документу описания Web-сервиса. После инициализации у объекта появятся методы Web-сервиса. С ними можно работать как с обычным COM-объектом.

С помощью замечательной утилиты MsSoapT.exe (Trace Utility), входящей в Toolkit, можно просматривать в режиме реального времени пакеты, идущие от клиента к серверу и обратно. Для этого надо в WSDL-файле найти тег и вместо строки типа http://wsd010/soap/SOAPService.ASP написать http://wsd010:8080/soap/SOAPService.ASP, то есть указать порт 8080. После этого нужно запустить утилиту трассирования и принять настройки по умолчанию, а затем создать новый объект Formatted Trace. Теперь все SOAP-пакеты работы с Web-сервисом доступны для оперативного просмотра. Если трассировщик не загружен, то в нужно убрать указание порта 8080. На рис. 4 приведено содержимое пакета запроса на выполнение метода SubstractNumbers.

Fig.4 Рис. 4. Приложение Trace Utility - содержимое пакета запроса на выполнение метода SubstractNumbers.

А пакет ответа выглядит следующим образом:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?> 
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle=
 "http://schemas.xmlsoap.org/soap/encoding/" 
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAPSDK1:SubtractNumbersResponse 
 xmlns:SOAPSDK1="http://tempuri.org/message/">
  <Result>1</Result> 
  </SOAPSDK1:SubtractNumbersResponse>
  </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>

Так выглядит пакет сервера, пришедший в ответ на запрос некорректного формата:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?> 
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle=
 "http://schemas.xmlsoap.org/soap/encoding/" 
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
  <faultcode>SOAP-ENV:Server</faultcode> 
  <faultstring>Connector - Bad request to the 
   server.</faultstring> 
<detail>
<mserror:errorInfo xmlns:mserror=
 "http://schemas.microsoft.com/soap-toolkit/faultdetail/error/">
  <mserror:returnCode>-2146823238</mserror:returnCode> 
<mserror:callStack>
<mserror:callElement>
<mserror:description><HRESULT>800a13ba</HRESULT>
 </mserror:description> 
  </mserror:callElement>
  </mserror:callStack>
  </mserror:errorInfo>
  </detail>
  </SOAP-ENV:Fault>
  </SOAP-ENV:Body>
  </SOAP-ENV:Envelope>

Дополнительная информация по теме

http://msdn.microsoft.com/webservices/ и http://msdn.microsoft.com/soap/ - последние новости о SOAP и Web-сервисах от Microsoft. Там же можно найти свежие версии ПО.

http://www.vbxml.com/soap/ - много полезной информации для разработчиков. Есть презентации и учебники по SOAP.

http://www.w3.org/TR/SOAP/ - спецификация SOAP от W3C.

http://www.w3.org/TR/wsdl - сведения о стандарте Web Services Definition Language (WSDL) 1.1/

http://microsoft.public.xml.soap - в этой конференции специалисты помогут решить сложные проблемы программирования SOAP.

Листинг 1. Код Listener для ASP

<%@ LANGUAGE=VBScript %>
<%
Option Explicit
On Error Resume Next
Response.ContentType = "text/xml"
Dim SoapServer
If Not Application("SoapServerInitialized") Then
  Application.Lock
  If Not Application("SoapServerInitialized") Then
    Dim WSDLFilePath
    Dim WSMLFilePath
    WSDLFilePath = Server.MapPath("SOAPService.wsdl")
    WSMLFilePath = Server.MapPath("SOAPService.wsml")
    Set SoapServer = Server.CreateObject("MSSOAP.SoapServer")
    If Err Then SendFault "Cannot create SoapServer object. " 
    	& Err.Description
    SoapServer.Init WSDLFilePath, WSMLFilePath
    If Err Then SendFault "SoapServer.Init failed. " 
    	& Err.Description
    Set Application("SOAPServiceServer") = SoapServer
    Application("SoapServerInitialized") = True
  End If
  Application.UnLock
End If
Set SoapServer = Application("SOAPServiceServer")
SoapServer.SoapInvoke Request, Response, ""
If Err Then SendFault "SoapServer.SoapInvoke failed. " 
    	& Err.Description
Sub SendFault(ByVal LogMessage)
  Dim Serializer
  On Error Resume Next
  ' "URI Query" logging must be enabled for AppendToLog to work
  Response.AppendToLog " SOAP ERROR: " & LogMessage
  Set Serializer = Server.CreateObject("MSSOAP.SoapSerializer")
  If Err Then
    Response.AppendToLog "Could not create SoapSerializer object. " 
    	& Err.Description
    Response.Status = "500 Internal Server Error"
  Else
    Serializer.Init Response
    If Err Then
      Response.AppendToLog "SoapSerializer.Init failed. " 
      	& Err.Description
      Response.Status = "500 Internal Server Error"
    Else
      Serializer.startEnvelope
      Serializer.startBody
      Serializer.startFault "Server", "The request could not 
      	be processed due to a problem in the server. Please contact 
      	the system administrator. " & LogMessage
      Serializer.endFault
      Serializer.endBody
      Serializer.endEnvelope
      If Err Then
        Response.AppendToLog "SoapSerializer failed. " 
        	& Err.Description
        Response.Status = "500 Internal Server Error"
      End If
    End If
  End If
  Response.End
End Sub
%>

Листинг 2. Код файла WSDL

<?xml version="1.0" encoding="UTF-8" ?> 
<!--  Generated 08/17/01 by Microsoft SOAP Toolkit WSDL File 
Generator, Version 1.00.623.1   -->
<definitions name="SOAPService" targetNamespace=
	"http://tempuri.org/wsdl/" 
	xmlns:wsdlns="http://tempuri.org/wsdl/" 
xmlns:typens="http://tempuri.org/type" 
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:stk="http://schemas.microsoft.com/soap-toolkit/
	wsdl-extension" xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
  <schema targetNamespace="http://tempuri.org/type" 
xmlns="http://www.w3.org/2001/XMLSchema" 
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
	elementFormDefault="qualified" /> 
  </types>
<message name="Sample1.SubtractNumbers">
  <part name="NumberOne" type="xsd:double" /> 
  <part name="NumberTwo" type="xsd:double" /> 
  </message>
<message name="Sample1.SubtractNumbersResponse">
  <part name="Result" type="xsd:double" /> 
  </message>
<message name="Sample1.AddNumbers">
  <part name="NumberOne" type="xsd:double" /> 
  <part name="NumberTwo" type="xsd:double" /> 
  </message>
<message name="Sample1.AddNumbersResponse">
  <part name="Result" type="xsd:double" /> 
  </message>
<portType name="Sample1SoapPort">
<operation name="SubtractNumbers" 
	parameterOrder="NumberOne NumberTwo">
  <input message="wsdlns:Sample1.SubtractNumbers" /> 
  <output message="wsdlns:Sample1.SubtractNumbersResponse" /> 
  </operation>
<operation name="AddNumbers" parameterOrder="NumberOne NumberTwo">
  <input message="wsdlns:Sample1.AddNumbers" /> 
  <output message="wsdlns:Sample1.AddNumbersResponse" /> 
  </operation>
  </portType>
<binding name="Sample1SoapBinding" type="wsdlns:Sample1SoapPort">
  <stk:binding preferredEncoding="UTF-8" /> 
  <soap:binding style="rpc" 
	transport="http://schemas.xmlsoap.org/soap/http" /> 
<operation name="SubtractNumbers">
  <soap:operation soapAction=
	"http://tempuri.org/action/Sample1.SubtractNumbers" /> 
<input>
  <soap:body use="encoded" 
	namespace="http://tempuri.org/message/" 
	encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> 
  
<output>
  <soap:body use="encoded" namespace="http://tempuri.org/message/" 
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> 
  </output>
  </operation>
<operation name="AddNumbers">
  <soap:operation soapAction=
	"http://tempuri.org/action/Sample1.AddNumbers" /> 
<input>
  <soap:body use="encoded" namespace="http://tempuri.org/message/" 
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> 
  
<output>
  <soap:body use="encoded" namespace="http://tempuri.org/message/" 
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> 
  </output>
  </operation>
  </binding>
<service name="SOAPService">
<port name="Sample1SoapPort" binding="wsdlns:Sample1SoapBinding">
  <soap:address location="http://wsd010/soap/SOAPService.ASP" /> 
  </port>
  </service>
  </definitions>

Листинг 3. Код файла WSDL

<?xml version="1.0" encoding="UTF-8" ?> 
<!-- Generated 08/17/01 by Microsoft SOAP Toolkit WSDL 
	File Generator, Version 1.00.623.1 
  --> 
<servicemapping name="SOAPService">
<service name="SOAPService">
  <using PROGID="SOAPProj.Sample1" cachable="0" 
  	ID="Sample1Object" /> 
<port name="Sample1SoapPort">
<operation name="SubtractNumbers">
<execute uses="Sample1Object" method="SubtractNumbers" 
	dispID="1610809345">
  <parameter callIndex="1" name="NumberOne" 
  	elementName="NumberOne" /> 
  <parameter callIndex="2" name="NumberTwo" 
	elementName="NumberTwo" /> 
  <parameter callIndex="-1" name="retval" elementName="Result" /> 
  </execute>
  </operation>
<operation name="AddNumbers">
<execute uses="Sample1Object" method="AddNumbers" 
	dispID="1610809344">
  <parameter callIndex="1" name="NumberOne" 
  	elementName="NumberOne" /> 
  <parameter callIndex="2" name="NumberTwo" 
  	elementName="NumberTwo" /> 
  <parameter callIndex="-1" name="retval" 
  	elementName="Result" /> 
  </execute>
  </operation>
  </port>
  </service>
  </servicemapping>