Веб сервис wsdl. Язык WSDL. Структура WSDL-документа. Способы создания. Режимы операции WSDL. Создаем интерфейс и класс сервиса

Когда-то поставили передо мной задачу начать разработку Web-сервисов и дали мне сорцы простейшего проекта без каких-либо объяснений. Проект, конечно же, не запускался. Что такое Spring и как он работает, я тоже представления не имел. Адекватных статей по разработке Web-сервисов средствами Spring ни русскоязычных, ни англоязычных я тоже не смог найти. Пришлось разбираться во всем самому, оказалось все не так страшно.
И вот недавно я решил посмотреть, какие новые возможности добавились в Spring с тех пор, и обновить старые сервисы, что в результате и сподвигло меня на написание данной статьи.

Данная статья является руководством по разработке простейшего Web-сервиса, использующего SOAP -протокол, средствами Spring-WS.

И так, писать будем простейший сервис, принимающий имя пользователя и отправляющий приветствие и текущее время на сервере.

Что же нам потребуется?
  • IDE. Я использую Eclipse .
Подготовка к работе
Создаем новый проект Web-приложения. В Eclipse это: «File => New => Dynamic Web Project».
Я назвал проект: HelloService.
Далее копируем библиотеки из Spring, XMLBean, wsdl4j, commons-logging в каталог проекта WEB-INF/lib.
При желании можете добавить их к библиотекам сервера, чтобы не таскать их с каждым приложением.
Создание WSDL-схемы
По сути WSDL-схема предназначена для описания сервиса.
Вручную создавать её мы, конечно же, не будем. Схема будет сгенерирована автоматически средствами Spring"а, но об этом позднее.
Определяем входные и выходные данные
Входные данные:
  • String имя.
Выходные данные:
  • String приветствие;
  • Time текущее время.
Создаем описание входных и выходных данных
В каталоге WEB-INF создаем файл HelloService.xsd. Данный файл нужен будет для генерации WSDL-схемы и создания соответствующих Java-классов.
Текст файла:

Атрибут targetNamespace – используемое пространство имен. Т.е. все созданные объекты будут располагаться в пакете org.example.helloService.
Элементы ServiceRequest и ServiceResponse описывают соответственно входные и выходные данные (запрос/ответ).
Атрибуты minOccurs и maxOccurs определяют количество повторений данного компонента в пределах одного элемента. Если эти параметры не указывать, то по умолчанию они считаются равными 1. Для необязательного компонента необходимо указать minOccurs=0. При неограниченном количестве компонент: maxOccurs=unbounded.
Подробнее о XML-схемах можно прочитать .
Создаем JavaBeans
На основании созданной схемы будем создавать Java классы. Для этого создаем файл build.xml:

Параметр WS_HOME должен указывать на каталог, где располагается XMLBeans.
HelloService.xsd – путь к созданной схеме.
lib\helloservice.jar – создаваемая java-библиотека.

Далее запускаем Ant-build (надеюсь, вы его уже установили).
В Eclipse можно запустить так: ПКМ по файлу build.xml=> Run As => Ant Build.
Если через командную строку:
ant -buildfile build.xml
Ну и ждем завершения построения. После чего, можем проверить каталог проекта WEB-INF\lib на наличие соответствующей библиотеки (helloservice.jar).

Реализация сервиса
Создаем интерфейс и класс сервиса
Интерфейс сервиса: HelloService.java:
package org.example; import java.util.Calendar; public interface HelloService { public String getHello(String name) throws Exception; public Calendar getCurrentTime(); }
Реализация сервиса: HelloServiceImpl.java:
package org.example; import java.util.Calendar; import org.springframework.stereotype.Service; @Service public class HelloServiceImpl implements HelloService { public String getHello(String name) throws Exception { return "Hello, " + name + "!"; } public Calendar getCurrentTime() { return Calendar.getInstance(); } }
Данный код, я думаю, не нуждается в комментариях. Единственное, что у людей, не сталкивающихся ранее со Spring"ом, может вызвать вопросы, так это аннотация @ Service. Но об этом же расскажу чуть позже.
Endpoint
Endpoint – класс, который будет отвечать за обработку входящих запросов (своего рода точка входа).

Создаем файл HelloServiceEndpoint.java:
package org.example; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ws.server.endpoint.annotation.Endpoint; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; import org.example.helloService.ServiceRequestDocument; import org.example.helloService.ServiceRequestDocument.ServiceRequest; import org.example.helloService.ServiceResponseDocument; import org.example.helloService.ServiceResponseDocument.ServiceResponse; @Endpoint public class HelloServiceEndpoint{ private static final String namespaceUri = "http://www.example.org/HelloService"; private HelloService helloService; @Autowired public void HelloService (HelloService helloService) { this.helloService = helloService; } @PayloadRoot(localPart = "ServiceRequest", namespace = namespaceUri) public ServiceResponseDocument getService(ServiceRequestDocument request) throws Exception { ServiceRequestDocument reqDoc = request; ServiceRequest req = reqDoc.getServiceRequest(); ServiceResponseDocument respDoc = ServiceResponseDocument.Factory.newInstance(); ServiceResponse resp = respDoc.addNewServiceResponse(); String userName = req.getName(); String helloMessage = testNewService.getHello(userName); Calendar currentTime = testNewService.getCurrentTime(); resp.setHello(helloMessage); resp.setCurrentTime(currentTime); return respDoc; } }
Что же здесь сделано?
Аннотация @Endpoint как раз и определяет, что данный класс будет обрабатывать входящие запросы.
namespaceUri – то же пространство имен, что и указывалось при создании xml-схемы.

Теперь вернемся немного назад и вспомним про аннотацию @ Service . Если не вдаваться в подробности, чтобы не перегружать читателя лишней информацией, то эта аннотацию говорит Spring"у создать соответствующий объект. А аннотация @Autowired служит для инъекции (автоматической подстановки) соответствующего объекта. Конечно же при построении простых приложений в использовании данных аннотаций отсутствует смысл, но я решил все-такие не исключать их в данном примере.

В остальном опять же все должно быть ясно. Обратите внимание, что ServiceRequest, ServiceResponse и т.д. – это как раз те классы, которые были созданы на основе нашей xml-схемы.

Spring-конфигурация сервиса
Вот и близится уже завершение.
Создаем файл service-ws-servlet.xml.

sws:annotation-driven – говорит как раз о том, что в данном проекте используются аннотации.
А context:component-scan указывает на пакет, в котором будет производится поиск аннотаций, при этом поиск производится и в подпакетах.

Два последующих бина всегда будут неизменны. Суть их заключается в приеме и преобразовании запроса из Xml в Java-объект и дальнейшего обратного преобразования.

sws:dynamic-wsdl отвечает за автоматическую генерацию WSDL-документа на основе созданной Xml-схемы.
location указывает на путь к схеме.
locationUri – адрес (относительно контейнера), по которому будет доступна WSDL-схема.
В моем случае WSDL доступен по следующему адресу:
localhost/HelloService/HelloService.wsdl

Дескриптор развертывания
Ну и, наконец, последнее.
В каталоге WEB-INF изменяем или создаем файл web.xml.
HelloService HelloService service-ws org.springframework.ws.transport.http.MessageDispatcherServlet transformWsdlLocations true service-ws /*
Данный файл описывать уже не буду, большинство и так должны знать. Для несложных проектов он по сути не должен изменяться. Стоит отметить только, что имя сервлета(servlet-name) должно соответствовать имени файла Spring-конфигурации сервиса service-ws -servlet.xml.
Проверка работоспособности
Самым первым признаком корректной работы является созданная WSDL-схема.
Для проверки просто переходим по адресу этой схемы (http://localhost/HelloService/HelloService.wsdl) и смотрим: там должен отобразиться xml-файл. Если ничего не отобразилось или какая ошибка появилась, перечитываем внимательно всю статью и ищем, что сделали не так.

Для дальнейшей проверки нам потребуется soapUI (у меня версия 3.0.1).
Устанавливаем и запускаем его.
Создаем новый проект: File => New soapUI Project. В поле Initial WSDL/WADL вставляем ссылку на WSDL-схему (http://localhost/HelloService/HelloService.wsdl).
В созданном проекте открываем необходимый запрос.

В поле Name вбиваем имя и жмем на кнопку «Send request»


В результате получаем ответ от сервера с приветствием и текущим временем.


Если что-то пошло не так, то опять перечитываем данную статью.

Что дальше?
Ну а дальше предстоит написание клиента для данного Web-сервиса. Но это уже материал для другой статьи, которая возможно будет написана позже, если данный материал кого-то заинтересует.

Предисловие

Заказчики заказчиков попросили заказчиков xsd файлы для структур, передаваемых реализуемыми Web-сервисами. Заказчики в ответ предложили заказчикам заказчиков сделать WSDL-ки. Т.о. неожиданно «на ровном месте» возникла необходимость сделать не просто xsd-схемы для валидации данных, а «целые WSDL-ки». Обычно WSDL-ки используются для SOAP, а у нас REST…

Ранее я уже писал про

Введение

Термин Web-сервисы обычно ассоциируется с operation- или action-based сервисами, базирующимися на SOAP или WS* стандартах, таких как WS-Addressing или WS-Security. Термин REST Web-сервисы обычно относится к resource-based архитектуре Web-сервисов, передающих XML через HTTP. Каждый из этих архитектурных стилей имеет собственное место, но до недавнего времени, WSDL стандарт не поддерживал оба эти стиля. WSDL 1.1 HTTP binding был неадекватен для описания взаимодействия с помощью XML по HTTP, т.о. не было формального способа описать REST Web-сервисы с помощью WSDL. Публикация стандарта WSDL 2.0 (который был разработан с учётом необходимости описания REST Web-сервисов) в статусе рекомендации World Wide Web Consortium (W3C) дала язык для описания REST Web-сервисов.

REST — архитектурный стиль трактующий Web как resource-centric приложение. Практически, это означает, что каждый URL в RESTful приложении представляет собой ресурс. URL-и просто понимать и запоминать. Например, книжный магазин может определить URL http://www.bookstore.com/books/ для списка продаваемых книг и http://www.bookstore.com/books/0321396855/ для деталей о конкретной книге с ISBN 0321396855. Это контрастирует с action-centric приложениями, обычно имеющими длинные сложношифованные URL-и, описывающими действия, которые необходимо выполнить, например http://www.bookstore.com/action/query?t=b&id=11117645532&qp=0321396855 . Параметры запроса используются для выбора необходимых данных. Используя пример книжного магазина, указание параметра subject ограничивает тематику книги. Например физика или детективы или к примеру URL http://www.bookstore.com/books/?subject=computers/eclipse — запрос возвращающий список книг про платформу Eclipse.

Термин REST придумал Roy Fielding в своей кандидатской диссертации. Он рассматривал гиперссылки как средство для изменения (хранения) состояния приложения. Гиперлинки хранятся в ресурсах приложения и являются методом изменения состояния приложения, редиректа из одного состояния в другое. Обычно гиперлинки в (X)HTML предназначены для использования людьми, они не использовались в XML, который был предназначен для машинной обработки. Также как и (X)HTML, REST Web-сервисы испльзуют гиперлинки в XML.

Традиционные Web-приложения получают доступ к ресурсам посредством HTTP GET или POST операций. RESTfull приложения работают с ресурсами в стиле «create, read, update, and delete (CRUD)» используя полные возможности HTTP протокола (POST, GET, PUT, and DELETE).

Ещё одно важное замечание о REST приложении: RESTful приложения должны быть stateless. Это означает, что REST приложение не сохраняет никакого состояния сессии на стороне сервера. Вся информация, необходимая для выполнения запроса, передается в самом запросе. (Поэтому на повторяющиеся запросы сервер обязан отвечать одинаково прим. переводчика). Соответственно клиент может кешировать полученные ресурсы, что может значительно ускорить скорость работы приложения там, где сервис явно разрешает это. Чтобы узнать больше про REST, см ссылки к статье.

WSDL и REST

WSDL содержит все детали о веб-сервисе, включая:

    URL веб-сервиса
    Механизмы коммуникации, корые понимает веб-сервис
    Операции, которые может выполнять веб-сервис
    Структура сообщений веб-сервиса

Клиенты могут использовать перечисленные детали для взамодействия с сервисом.

WSDL 2.0 был объявлен рекомендацией W3C в июне 2007. Эта версия WSDL стандарта была выпущена для решения проблем стандарта WSDL 1.1, многие из которых были обнаружены организацией Web Services Interoperability (WS-I). Кроме того в WSDL 2.0 улучшена поддержка HTTP bindings.

WSDL сам является XML — подмножеством формально описывающим веб-сервис. Рассматривайте WSDL описание веб-сервиса как его API контракт с клиентом. В WSDL указывается адрес, допустимые способы передачи информации, интерфейса и типы сообщений веб-сервиса. Короче говоря, описание WSDL содержит всю информацию, необходимую клиенту для использования веб-сервиса.

Применимость WSDL, выходит за пределы его использования в качестве контракта API. Являясь формальным определением, WSDL может быть использован софтом упрощающим реализацию веб-сервисов для таких операций как:

  • Генерирование исходного кода клиентского приложения и сервера для веб-сервиса на разных языках программирования
  • Публикация веб-сервиса
  • Динамическое тестирование веб-сервиса

Большинство программных средств для работы с веб-сервисами включают в себя поддержку WSDL 1.1. Последнее время растёт количество средств разработки веб-сервисов, поддерживающих WSDL 2.0. Проект Apache Web services состоит из двух подпроектов, которые в настоящее время поддерживают WSDL 2.0. Woden — парсер-валидатор WSDL 2.0 на базе Java. Apache Web services проект также предоставляет XSL (XSLT) трансформацию WSDL 2.0 под названием WSDL 2.0 pretty printer , обеспечивающую лучшую человекочитаемость документа WSDL. Axis2 популярный engine веб-сервисов, (тоже от Apache) реализующий генерацию клиентского и серверного Java кода из документа WSDL 2.0.

Описание REST веб-сервиса с помощью WSDL 2.0

Вы создаете книжный магазин, который с продвинутым URL: http://www.bookstore.com . Вы уже создали две службы REST Web:

  • book list — сервис получает список продаваемых у вас книг.
  • book details — сервис получает информацию о конкретной книги.

Ответ возвращается в XML-документах.

Элемент interface определяет список операций веб-сервиса, включая описание входных, выходных сообщений и сообщений об ошибках для операций, а также порядка передачи.

Элемент binding определяет, средства коммуникации клиента с веб-сервисом. В случае REST веб-сервисов, в качестве средства коммуникации указывается HTTP.

Элемент service ассоциирует адреса веб-сервиса с конкретными интерфейсами (interface) и средствами коммуникации (binding). (Т.е. задает соответствие URL операции веб-сервиса и элементу binding ).

Привязываем book list к HTTP

Элемент binding задает привязку веб-сервиса к конкретному протоколу передачи данных. Для привязки book list сервиса к HTTP нужно указать значение http://www.w3.org/ns/wsdl/http для атрибута type элемента binding .

Элемент binding может опционально ссылаться на interface . Оставьте атрибут interface пустым. Вы создадите его в следующем разделе. Если interface ассоциирован с binding , тогда binding элемент может опционально определять дочерний элемент operation , являющийся зеркальным для interface operation элемента. Вам нужно создать заглушку элемента operation и заполнить ссылку на operation позже после создания interface .

Существует 4 HTTP communication метода

  • DELETE

Book list сервис читает запрос и соответственно оперирует с помощью HTTP GET. Установите метод GET для элемента operatioin используя атрибут method из WSDL 2.0 HTTP namespace. Для использования этого атрибута, вам нужно сначала определить namespace http://www.w3.org/ns/wsdl/http в элементе description .

Book list сервис binding определен на нижеследующем листинге. Укажите теперь binding в элементе endpoint : tns:BookListHTTPBinding.

The bookstore"s book list service.

Определение book list service operation

So far you’ve learned how to address and communicate with the book list Web service. Next you specify the book list service operation, which describes what the book list service does.

Итак, вы научились задавать address и задавать binding (способ коммуникации) для веб-сервиса. Далее необходимо задать service operation, определяющую что делает book list веб-сервис.

Элемент interface и его дочерний operation элемент используются для определения сервисных операций. В случае с book list, вы определяете одну операцию getBookList, возвращающую список книг.

Затем определите три атрибута для элемента operation:

Pattern

Используется для указания шаблона обмена сообщениями message exchange pattern (MEP) для операции. MEP определяет последовательность сообщений для операции и их направление. В этом случае необходимо указать значение http://www.w3.org/ns/wsdl/in-out чтобы указать, что веб-сервис получает одно входное сообщение просьбой о списке книг, и посылает одно выходное сообщение со списком книг. Для поддержки этого MEP, указажите дочерние элементы input и output для элемента operation . Эти элементы используют элементы описанные в XML schema, определяющие структуры сообщений. Подробности в следующем разделе.

Style

Используется для указания дополнительной информации о работе. Укажите значение http://www.w3.org/ns/wsdl/style/iri , накладывающее ограничения на содержание входных элементов, такие как требование, что это только использовать XML schema элементы.

wsdlx:safe

wsdlx:safe: From the WSDL extensions namespace, this attribute declares that this operation is idempotent. This type of operation doesn’t modify the resource and can therefore be called many times with the same results. To make use of this element, declare the WSDL extensions namespace http://www.w3.org/ns/wsdl-extensions on the description element.

Этот атрибут из WSDL extentions namespace. Он определяет, что операция является idempotent . Эта операция не модифицирует ресурс, поэтому может быть вызвана многократно с одинаковыми результатами. Чтобы использовать этот элемент, нужно объявить namespace WSDL extentions http://www.w3.org/ns/wsdl-extensions в корневом элементе (элементе description).

Вы можете найти предопределенные Message Exchange Patterns, styles и wsdlx:safe определения по ссылке WSDL 2.0 Part 2: Adjuncts

Ниже приведено определение book list сервиса с добавленным описанием interface . После добавления interface теперь можно изменить binding operation элемент чтобы указать ссылки на описанные interface и operation.

The RESTful HTTP binding for the book list service. The bookstore"s book list service.

Определение сообщений сервиса book list operation

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

WSDL 2.0 supports multiple type systems for describing the message content, but XML schema is the only one in use. This section doesn’t cover the details of XML schema. XML schema is used in many other applications, like WSDL 1.1, and there are many good articles about it. This section highlights how to use XML schema for the book list REST Web service and how to use additional attributes defined by WSDL 2.0 to annotate a schema attribute.

WSDL 2.0 поддерживает множество систем определения типов, но на практике используются только XML schema. Эта статья не вдается в детали XML schema. XML schema используется во многих других приложениях, например, WSDL 1.1, и есть много хороших статей о нем. (). В этом разделе демонстрируется, применение XML schema применительно к конкретному примеру REST сервиса book list, а также использование дополнительных атрибутов определенных в WSDL 2.0 для аннотации schema атрибута.

Чтобы описать 2 сообщения для book list, необходимо описать 2 глобальных элемента.

  • getBookList представляет собой входное сообщение. Он содержит последовательность элементов, включая каждый параметр запроса, позволенный для сервиса: uthor, title, publisher, subject и language . Внутри getBookList сообщения могут использоваться только элементы, потому что выбран IRI style для interface operation.
  • bookList представляет собой выходное сообщение. Он содержит последовательность book элементов. Каждый book элемент в свою очередь содержит title и url атрибуты. Атрибут title не требует пояснений. Атрибут url это линк на сервис book details, возвращающий детальную информацию о конкретной книге.

Ваше определение атрибута url использует в свою очередь 2 атрибута из WSDL extensions namespace. Атрибуты wsdlx:interface и wsdlx:binding задают interface и binding для сервиса. Программное обеспечение может использовать эту информацию для автоматического нахождения сервиса. Для использования этих атрибутов, укажите WSDL extentions namespace для элемента schema . Также включите book details namespace из его WSDL 2.0 описания.

XML schema для book list сервиса приведена ниже.

The request element for the book list service. The response element for the book list service.

Для ссылки на input и output элементы, вы должны импортировать схему в ваш WSDL документ. Для импортирования сземы, используйте schema import элемент в разделе types как показано на листинге ниже. Кроме того, необходимо добавить ссылки на getBookList и Booklist элементы в interface operation input и output элементах и добавить пространства имен book list XML schema в корневой элемент WSDL.

Готовый WSDL для book list веб-сервиса.

This is a WSDL 2.0 description of a sample bookstore service listing for obtaining book information. This operation returns a list of books. The RESTful HTTP binding for the book list service. The bookstore"s book list service.

Примечание переводчика

Я позволил себе не переводить summary и ссылки. И то и другое смотреть у автора. в оригинальной статье . Надо сказать язык оригинала весьма тяжёл. Однако, надеюсь, статья окажется полезной.

WSDL (Web Services Description Language ) версии 1.1 был опубликован 15 марта 2001 года. WSDL - это формат, базирующийся на XML и использующийся для описания сетевых cервисов, при помощи сообщений, содержащих информация о том как осуществлять доступ к конкретному веб-сервису. WSDL расширяем, что позволяет описывать услуги (сервисы) и их сообщения независимо от того, какие форматы сообщений или сетевые протоколы используются для транспорта, однако, чаще всего используется WSDL 1.1 вместе с SOAP 1.1, HTTP GET/POST и MIME. Поскольку WSDL был разработан совместно с SOAP, в его разработке участвовали все те же фирмы Microsoft, Ariba и IBM. Если рассматривать документ WSDL интуитивно, то можно сказать, что он позволяет ответить на 4 вопроса :

1) что вы делаете? Ответ на этот вопрос дается в форме пригодной как для восприятия человеком так и форме воспринимаемой машиной. Ответ для чел-ка в тегах: <name />, <documentation />, для машины - <message />, <pointType >

2) на каком языке вы разговариваете? (какие типы вы используете?)Ответ в теге: <types />

3) как я буду с вами общаться? (как клиент будет обращаться к веб-службе?):HTTP или SMTP. Ответ находится в <binding />

4) где мне вас найти? (где я могу найти эту веб-службу или какой у нее URL?). Ответ находится: <service />

Структура:

Каждый документ WSDL можно разбить на три логические части:

1. определение типов данных - определение вида отправляемых и получаемых сервисом XML сообщений

2. абстрактные операции - список операций, которые могут быть выполнены с сообщениями

3. связывание сервисов - способ, которым сообщение будет доставлено

Документы WSDL можно создавать вручную, однако строгая формализация языка WSDL позволяет автоматизировать процесс написания WSDL -документов. Многие интсрументальные средства создания Web-служб содержат утилиты, которые автоматичеки создают WSDL -файлы, описывающие готовые Web-службы. Например средство создания Web-служб Apache Axis содержит в своем составе класс Java2WSDL , создающий WSDL -файл по классу или интерфейсу Java, описывающему Web-службу. Пакет IBM WSTK, в состав которого входит Axis , содержит утилиту java2wsdl , создающую и запускающую объект из этого класса. Она работает из командной строки.

Элементы WSDL-документа

Опишем наиболее часто употребляемые теги WSDL:

Тег – это корневой тег всех WSDL-документов. Он определяет несколько пространств имен:

1)target Name space – это пространство имен нашей веб-службы

2)xmlns – стандартное пространство имен документа WSDL

3)xmlns: SOAP_ENC – пространство имен используемое для описания кодировки SOAP


4)xmlns: impl и intf – пространство имен реализации и определения нашей веб-службы

· Документ для определения веб-службы

· Документ для реализации веб-службы

Для простоты, как правило, используют 1 файл, который содержит всю информацию

Элемент - предоставляет информацию о данных, которые передаются от одной конечной точки к другой.

Для описания вызова RPC необходимо создать входной сообщение и выходное сообщение.

В рамках этого элемента можно указать параметры метода с помощью элемента

Элемент описывает и определяет операции или методы поддерживаемые веб-службой

Операции могут иметь входные сообщения, а также сообщения об ошибках.

Элемент - описывает, как операции определенные в типе порта будут передаваться по сети. Т.к. элемент использует тип порта, то он должен указывать тип определенный где-то ранее в документе.

Элемент - указывает где найти веб службу

Элемент import . Очень часто элемент service выделяется в свой wsdl документ из соображений практичности.

Для того, чтобы позволить собрать из нескольких wsdl документов один используется элемент import. Он позволяет включать один wsdl документ в другой.

Элемент types позволяет указать типы передаваемых данных если они не являются стандартными.

WSDL поддерживает 4 режима операций:

· операции типа one-way или односторонние операции. Сообщение посылается конечной точке службы. В этом случае операция описывается только одним входным сообщением.

· Request-Response – режим запрос-ответ. Этот режим операции является наиболее общим. В этом режиме описание операции содержит входное и выходное сообщение и необязательное сообщение об ошибке.

· Операция типа требование-ответ. В этом режиме конечной точкой является клиент другой конечной точки. Формат операции похож на режим запрос-ответ, но выходные данные перечисляются перед входными данными.

· Операция уведомление. Этот режим – еще одна версия примитива односторонней передачи, в которой конечная точка посылает сообщение а не получает его. Операция содержит только выходное сообщение.

hats 23 июля 2013 в 13:09

Пишем SOAP клиент-серверное приложение на PHP

  • PHP
  • Tutorial

Всем привет!
Так случилось, что в последнее время я стал заниматься разработкой веб-сервисов. Но сегодня топик не обо мне, а о том, как нам написать свой XML Web Service основанный на протоколе SOAP 1.2.

Я надеюсь, что после прочтения топика вы сможете самостоятельно:

  • написать свою собственную серверную реализацию веб-приложения;
  • написать свою собственную клиентскую реализацию веб-приложения;
  • написать свое собственное описание веб-сервиса (WSDL);
  • отправлять клиентом массивы однотипных данных на сервер.
Как вы могли догадаться, вся магия будет твориться с использованием PHP и встроенных классов SoapClient и SoapServer. В качестве кролика у нас будет выступать сервис по отправке sms-сообщений.

1 Постановка задачи

1.1 Границы

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

1.2 Какими данными будем меняться?

Отлично, с границами мы определились! Следующий шаг, который необходимо сделать – решить какими данными мы будем обмениваться между сервером и клиентом. На эту тему предлагаю долго не мудрить и сразу для себя ответить на главные вопросы:
  • Какой минимум данных надо посылать на сервер, чтобы отправить sms-сообщение абоненту?
  • Какой минимум данных надо посылать с сервера, чтобы удовлетворить потребности клиента?
Что-то мне подсказывает, что для этого необходимо посылать следующее:
  • номер мобильного телефона, а также
  • текст sms-сообщения.
В принципе, двух этих характеристик достаточно для отправки, но мне сразу представляется случай, как sms-ка с поздравлением о дне рождения приходит вам в 3 часа утра, или 4! В этот момент я буду всем очень благодарен за то, что про меня не забыли! Поэтому, мы также будем посылать на сервер и
  • дату отправки sms-сообщения.
Следующее, что я бы хотел отправлять на сервер, так это
  • Тип сообщения.
Данный параметр не является обязательным, но он может нам очень сильно пригодиться в случае, если быстро понадобится сказать боссу о том, скольких из наших клиентов мы «обрадовали» своим известием, а также нарисовать какую-нибудь красивую статистику на этот счет.

И все же, я что-то забыл! Если еще немного порефлексировать, то стоит отметить, что клиент за раз может отправить на сервер как одно sms-сообщение, так и некоторое их количество. Другими словами, в одном пакете данных может быть от одного до бесконечности сообщений.

В результате мы получаем, что для отправки sms-сообщения нам необходимы следующие данные:

  • номер мобильного телефона,
  • текст sms-сообщения,
  • время отправки sms-сообщения абоненту,
  • тип сообщения.

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

  • TRUE – пакет успешно дошел до сервера, прошел аутентификацию и встал в очередь для отправки sms-провайдеру
  • FALSE – во всех остальных случаях

На этом мы закончили описание постановки задачи! И наконец-то приступим к самому интересному – будем разбираться что за диковинный зверь этот SOAP!

2 С чем есть SOAP?

Вообще, изначально я не планировал ничего писать о том, что такое SOAP и хотел ограничиться ссылками на сайт w3.org с нужными спецификациями, а также ссылками на Wikipedia. Но в самом конце решил написать коротенькую справочку об этом протоколе.

И начну я свое повествование с того, что данный протокол обмена данными относится к подмножеству протоколов основанных на так называемой парадигме RPC (Remote Procedure Call, удалённый вызов процедур) антиподом которой является REST (Representational State Transfer, передача репрезентативного состояния). Более подробно об этом можно прочесть в Wikipedia, ссылки на статьи находятся в самом конце топика. Из этих статей нам надо уяснить следующее: «Подход RPC позволяет использовать небольшое количество сетевых ресурсов с большим количеством методов и сложным протоколом. При подходе REST количество методов и сложность протокола строго ограничены, из-за чего количество отдельных ресурсов может быть большим». Т.е., применительно к нам это означает, что на сайте в случае RPC подхода будет всегда один вход (ссылка) на сервис и какую процедуру вызывать для обработки поступающих данных мы передаем вместе с данными, в то время как при REST подходе на нашем сайте есть много входов (ссылок), каждая из которых принимает и обрабатывает только определенные данные. Если кто-то из читающих знает, как еще проще объяснить различие в данных подходах, то обязательно пишите в комментариях!

Следующее, что нам надо узнать про SOAP – данный протокол в качестве транспорта использует тот самый XML, что с одной стороны очень хорошо, т.к. сразу же в наш арсенал попадает вся мощь стека технологий основанных на данном языке разметки, а именно XML-Schema – язык описания структуры XML-документа (спасибо Wikipedia!), который позволяет производит автоматическую валидацию поступающих на сервер данных от клиентов.

И так, теперь мы знаем, что SOAP – протокол используемый для реализации удаленного вызова процедур и в качестве транспорта он использует XML! Если почитать статью на Wikipedia, то оттуда можно узнать еще и о том, что он может использоваться поверх любого протокола прикладного уровня, а не только в паре с HTTP (к сожалению, в данном топике мы будем рассматривать только SOAP поверх HTTP). И знаете, что мне во всем этом больше всего нравится? Если нет никаких догадок, то я дам подсказку – SOAP!… Всеравно не появилось догадок?… Вы точно прочли статью на Wikipedia?… В общем, не буду вас дальше мучить. Поэтому, сразу перейду к ответу: «SOAP (от англ. Simple Object Access Protocol - простой протокол доступа к объектам; вплоть до спецификации 1.2 )». Самое примечательное в этой строчке выделено курсивом! Я не знаю какие выводы сделали вы из всего этого, но мне видится следующее – поскольку данный протокол ну никак нельзя назвать «простым» (и видимо с этим согласны даже в w3), то с версии 1.2 он вообще перестал как-то расшифровываться! И стал называться SOAP, просто SOAP и точка.

Ну да ладно, прошу меня извинить, занесло немного в сторону. Как я писал ранее, в качестве транспорта используется XML, а пакеты, которые курсируют между клиентом и сервером называются SOAP-конвертами. Если рассматривать обобщенную структуру конверта, то он вам покажется очень знакомым, т.к. напоминает структуру HTML-страницы. В нем есть основной раздел – Envelop , который включает разделы Header и Body , либо Fault . В Body передаются данные и он является обязательным разделом конверта, в то время как Header является опциональным. В Header может передаваться авторизация, либо какие-либо иные данные, которые на прямую не относятся к входным данным процедур веб-сервиса. Про Fault особо рассказывать нечего, кроме того, что он приходит в клиент с сервера в случае возникновения каких-либо ошибок.

На этом мой обзорный рассказ про протокол SOAP заканчивается (более детально сами конверты и их структуру мы рассмотрим когда наши клиент и сервер наконец-то научатся запускать их друг в друга) и начинается новый – про компаньона SOAP под названием WSDL (Web Services Description Language). Да-да, это та самая штука, которая отпугивает большинство из нас от самой попытки взять и реализовать свое API на данном протоколе. В результате чего, мы обычно изобретаем свой велосипед с JSON в качестве транспорта. И так, что такое WSDL? WSDL – язык описания веб-сервисов и доступа к ним, основанный на языке XML (с) Wikipedia. Если из этого определения вам не становится понятным весь сакральный смысл данной технологии, то я попытаюсь описать его своими словами!

WSDL предназначен для того, чтобы наши клиенты могли нормально общаться с сервером. Для этого в файле с расширением «*.wsdl» описывается следующая информация:

  • Какие пространства имен использовались,
  • Какие схемы данных использовались,
  • Какие типы сообщений веб-сервис ждет от клиентов,
  • Какие данные принадлежат каким процедурам веб-сервиса,
  • Какие процедуры содержит веб-сервис,
  • Каким образом клиент должен вызывать процедуры веб-сервиса,
  • На какой адрес должны отправляться вызовы клиента.
Как видно, данный файл и есть весь веб-сервис. Указав в клиенте адрес WSDL-файла мы будем знать об любом веб-сервисе все! В результате, нам не надо абсолютно ничего знать о том, где расположен сам веб-сервис. Достаточно знать адрес расположения его WSDL-файла! Скоро мы узнаем, что не так страшен SOAP как его малюют (с) русская пословицы.

3 Введение в XML-Schema

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

Основная задачи схемы – описать структуру данных которые мы собираемся обрабатывать. Все данные в XML-схемах делятся на простые (скалярные) и коплексные (структуры) типы. К простым типам относятся такие типы как:

  • строка,
  • число,
  • булево значение,
  • дата.
Что-то очень простое, у чего внутри нет расширений. Их антиподом являются сложные комплексные типы. Самый простой пример комплексного типа, который приходит всем в голову – объекты. Например, книга. Книга состоит из свойств: автор , название , цена , ISBN номер и т.д. И эти свойства, в свою очередь, могут быть как простыми типами, так и комплексными. И задача XML-схемы это описать.

Предлагаю далеко не ходить и написать XML-схему для нашего sms-сообщения! Ниже представлено xml-описание sms-сообщения:

71239876543 Тестовое сообщение 2013-07-20T12:00:00 12
Схема нашего комплексного типа будет выглядеть следующим образом:


Эта запись читается следующим образом: у нас есть переменная «message » типа «Message » и есть комплексный тип с именем «Message », который состоит из последовательного набора элементов «phone » типа string , «text » типа string , «date » типа dateTime , «type » типа decimal . Эти типы простые и уже определены в описании схемы. Поздравляю! Мы только что написали нашу первую XML-схему!

Думаю, что значение элементов «element » и «complexType » вам стало все более-менее понятно, поэтому не будем на них больше заострять внимание и переключимся сразу же на элемент-композитор «sequence ». Когда мы используем элемент-композитор «sequence » мы сообщаем о том, что элементы включенные в него должны всегда располагаться в указанной в схеме последовательности, а также все из них являются обязательными. Но не стоит отчаиваться! В XML-схемах есть еще два элемента-композитора: «choice » и «all ». Композитор «choice » сообщает о том, что должен быть какой-то один из перечисленных в нем элементов, а композитор «all » – любая комбинация перечисленных элементов.

Как вы помните, то в первом разделе топика мы договорились о том, что в пакете может передаваться от одного до бесконечности sms-сообщений. Поэтому предлагаю разобраться как такие данные декларируются в XML-схеме. Общая структура пакета может выглядеть следующим образом:

71239876543 Тестовое сообщение 1 2013-07-20T12:00:00 12 71239876543 Тестовое сообщение N 2013-07-20T12:00:00 12
Схема для такого комплексного типа будет выглядеть так:


В первом блоке идет знакомое нам декларирование комплексного типа «Message ». Если вы заметили, то в каждом простом типе, входящем в «Message », были добавлены новые уточняющие атрибуты «minOccurs » и «maxOccurs ». Как не трудно догадаться из названия, первый (minOccurs ) сообщает о том, что в данной последовательности должно быть минимум по одному элементу типа «phone », «text », «date » и «type », в то время как следующий (maxOccurs ) атрибут нам декларирует, что таких элементов в нашей последовательности максимум по-одному. В результате, когда мы пишем свои схемы для каких-либо данных, нам предоставляется широчайший выбор по их настройке!

Второй блок схемы декларирует элемент «messageList » типа «MessageList ». Видно, что «MessageList » представляет собой комплексный тип, который включает минимум один элемент «message », но максимальное число таких элементов не ограничено!

4 Пишем свой WSDL

Вы помните о том, что WSDL и есть наш веб-сервис? Надеюсь, что помните! Как мы его напишем, так на нем наш маленький веб-сервис и поплывет. Поэтому, предлагаю не халтурить.

Вообще, для того, чтобы у нас все работало правильно нам надо передавать клиенту WSDL-файл с правильным MIME-типом. Для этого необходимо настроить ваш веб-сервер соответствующим образом, а именно – установить для файлов с расширением «*.wsdl» MIME-тип равный следующей строке:

Application/wsdl+xml
Но на практике, я обычно отправлял посредством PHP HTTP-заголовок«text/xml »:

Header("Content-Type: text/xml; charset=utf-8");
и все прекрасно работало!

Хочу сразу предупредить, наш простенький веб-сервис будет иметь довольно внушительное описание, поэтому не пугайтесь, т.к. большая часть текста является обязательной водой и написав ее один раз можно постоянно копировать от одного веб-сервиса к другому!

Поскольку WSDL – это XML, то в самой первой строке необходимо прямо об этом и написать. Корневой элемент файла всегда должен называться «definitions »:


Обычно, WSDL состоит из 4-5 основных блоков. Самый первый блок – определение веб-сервиса или другими словами – точки входа.


Здесь написано, что у нас есть сервис, который называется – «SmsService ». В принципе, все имена в WSDL-файле могут быть вами изменены на какие только пожелаете, т.к. они не играют абсолютно никакой роли.

После этого мы объявляем о том, что в нашем веб-сервисе «SmsService » есть точка входа («port»), которая называется «SmsServicePort ». Именно в эту точку входа и будут отправляться все запросы от клиентов к серверу. И указываем в элементе «address » ссылку на файл-обработчик, который будет принимать запросы.

После того, как мы определили веб-сервис и указали для него точку входа – необходимо привязать к нему поддерживаемые процедуры:


Для этого перечисляется какие операции и в каком виде у будут вызываться. Т.е. для порта «SmsServicePort » определена привязка под именем «SmsServiceBinding », которая имеет тип вызова «rpc » и в качестве протокола передачи (транспорта) используется HTTP. Т.о., мы здесь указали, что будем осуществлять RPC вызов поверх HTTP. После этого мы описываем какие процедуры (operation ) поддерживаются в веб-сервисе. Мы будем поддерживать всего одну процедуру – «sendSms ». Через эту процедуру будут отправляться на сервер наши замечательные сообщения! После того, как была объявлена процедура, необходимо указать в каком виде будут передаваться данные. В данном случае указано, что будут использоваться стандартные SOAP-конверты.

После этого нам необходимо привязать процедуру к сообщениям:


Для этого мы указываем, что наша привязка («binding») имеет тип «SmsServicePortType » и в элементе «portType » с одноименным типу именем указываем привязку процедур к сообщениям. И так, входящее сообщение (от клиента к серверу) будет называться «sendSmsRequest », а исходящее (от сервера к клиенту) «sendSmsResponse ». Как и все имена в WSDL, имена входящих и исходящих сообщения – произвольные.

Теперь нам необходимо описать сами сообщения, т.е. входящие и исходящие:


Для этого мы добавляем элементы «message » с именами «sendSmsRequest » и «sendSmsResponse » соответственно. В них мы указываем, что на вход должен прийти конверт, структура которого соответствует типу данных «Request ». После чего с сервера возвращается конверт содержащий тип данных – «Response ».

Теперь надо сделать самую малость – добавить описание данных типов в наш WSDL-файл! И как вы думаете, как описываются в WSDL входящие и исходящие данные? Думаю, что вы уже все давно поняли и сказали сами себе, что при помощи XML-схем! И вы будете абсолютно правы!


Можно нас поздравить! Наш первый WSDL был написан! И мы еще на один шаг приблизились к достижению поставленной цели.
Далее мы разберемся с тем, что нам предоставляет PHP для разработки собственных распределенных приложений.

5 Наш первый SOAP-сервер

Ранее я писал, что для создания SOAP-сервера на PHP мы будем использовать встроенный класс SoapServer. Для того, чтобы все дальнейшие действия происходили также как и у меня, вам понадобиться немного подкрутить свой PHP. Если быть еще точнее, то необходимо убедиться, что у вас установлено расширение «php-soap». Как его поставить на ваш веб-сервере лучше всего прочитать на официальном сайте PHP (см. список литературы).

После того, как все было установлено и настроено нам необходимо будет создать в корневой папке вашего хостинга файл «smsservice.php » со следующим содержанием:

setClass("SoapSmsGateWay"); //Запускаем сервер $server->handle();
То, что находится выше строчки с функцией «ini_set», надеюсь, что объяснять не надо. Т.к. там определяется какие HTTP-заголовки мы будем отправлять с сервера клиенту и настраивается окружение. В строчке с «ini_set» мы отключаем кеширование WSDL-файла для того, чтобы наши изменения в нем сразу же вступали в действие на клиенте.

Теперь мы подошли к серверу! Как видим, весь SOAP-сервер занимает всего лишь три строки! В первой строке мы создаем новый экземпляр объекта SoapServer и передаем ему в конструктор адрес нашего WSDL-описания веб-сервиса. Теперь мы знаем, что он будет располагаться в корне хостинга в файле с говорящим именем «smsservice.wsdl.php ». Во второй строке мы сообщаем SOAP-серверу какой класс необходимо дергать для того, чтобы обработать поступивший с клиента конверт и вернуть конверт с ответом. Как вы могли догадаться, именно в этом классе будет описан наш единственный метод sendSms . В третьей строке мы запускаем сервер! Все, наш сервер готов! С чем я нас всех и поздравляю!

Теперь нам необходимо создать WSDL-файл. Для этого можно либо просто скопировать его содержимое из предыдущего раздела, либо позволить себе вольности и немного его «шаблонизировать»:

"; ?> /" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" name="SmsWsdl" xmlns="http://schemas.xmlsoap.org/wsdl/"> /"> /smsservice.php" />
На этом этапе получившийся сервер нас должен устроить полностью, т.к. поступающие к нему конверты мы можем логировать и потом спокойно анализировать приходящие данные. Для того, чтобы мы могли что-либо получать на сервер, нам необходим клиент. Поэтому давайте им и займемся!

6 SOAP-клиент на подходе

Прежде всего нам надо создать файл, в котором будем писать клиент. Как обычно, мы его создадим в корне хоста и назовем «client.php », а внутри напишем следующее:

messageList = new MessageList(); $req->messageList->message = new Message(); $req->messageList->message->phone = "79871234567"; $req->messageList->message->text = "Тестовое сообщение 1"; $req->messageList->message->date = "2013-07-21T15:00:00.26"; $req->messageList->message->type = 15; $client = new SoapClient("http://{$_SERVER["HTTP_HOST"]}/smsservice.wsdl.php", array("soap_version" => SOAP_1_2)); var_dump($client->sendSms($req));
Опишем наши объекты. Когда мы писали WSDL в нем для входящего на сервер конверта описывались три сущности: Request , MessageList и Message . Соответственно классы Request , MessageList и Message являются отражениями этих сущностей в нашем PHP-скрипте.

После того, как мы определили объекты, нам необходимо создать объект ($req ), который будем отправлять на сервер. После чего идут две самые заветные для нас строки! Наш SOAP-клиент! Верите или нет, но этого достаточно для того, чтобы на наш сервер начали сыпаться сообщения от клиента, а также для того, чтобы наш сервер успешно их принимал и обрабатывал! В первой из них мы создаем экземпляр класса SoapClient и передаем в его конструктор адрес расположения WSDL-файла, а в параметрах явно указываем, что работать мы будем по протоколу SOAP версии 1.2. В следующей строке мы вызываем метод sendSms объекта $client и сразу же выводим в браузере результат.
Давайте запусти и посмотрим что-же у нас наконец-то получилось!

Мне с сервера вернулся следующий объект:

Object(stdClass) public "status" => boolean true
И это замечательно, т.к. теперь мы точно знаем о том, что наш сервер работает и не просто работает, но еще и может возвращать на клиент какие-то значения!

Теперь посмотрим на лог, который мы предусмотрительно ведем на серверной стороне! В первой его части мы видим необработанные данные, которые поступили на сервер:

79871234567 Тестовое сообщение 1 2013-07-21T15:00:00.26 15
Это и есть конверт. Теперь вы знаете как он выглядит! Но постоянно на него любоваться нам вряд ли будет интересно, поэтому давайте десереализуем объект из лог-файла и посмотрим все ли у нас хорошо:

Object(stdClass) public "messageList" => object(stdClass) public "message" => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 1" (length=37) public "date" => string "2013-07-21T15:00:00.26" (length=22) public "type" => string "15" (length=2)
Как видим, объект десериализовался правильно, с чем я нас всех хочу поздравить! Далее нас ждет что-то более интересно! А именно – мы будем отправлять клиентом на сервер не одно sms-сообщение, а целую пачку (если быть точнее, то целых три)!

7 Отправляем сложные объекты

Давайте подумаем над тем, как же нам передать целую пачку сообщений на сервер в одном пакете? Наверно, самым простым способом будет организация массива внутри элемента messageList! Давайте это сделаем:

// создаем объект для отправки на сервер $req = new Request(); $req->messageList = new MessageList(); $msg1 = new Message(); $msg1->phone = "79871234567"; $msg1->text = "Тестовое сообщение 1"; $msg1->date = "2013-07-21T15:00:00.26"; $msg1->type = 15; $msg2 = new Message(); $msg2->phone = "79871234567"; $msg2->text = "Тестовое сообщение 2"; $msg2->date = "2014-08-22T16:01:10"; $msg2->type = 16; $msg3 = new Message(); $msg3->phone = "79871234567"; $msg3->text = "Тестовое сообщение 3"; $msg3->date = "2014-08-22T16:01:10"; $msg3->type = 17; $req->messageList->message = $msg1; $req->messageList->message = $msg2; $req->messageList->message = $msg3;
В наших логах числится, что пришел следующий пакет от клиента:

79871234567 Тестовое сообщение 1 2013-07-21T15:00:00.26 15 79871234567 Тестовое сообщение 2 2014-08-22T16:01:10 16 79871234567 Тестовое сообщение 3 2014-08-22T16:01:10 17
Что за ерунда, скажете вы? И будете правы в некотором смысле, т.к. только что мы узнали о том, что какой объект ушел от клиента, то абсолютно в том же виде он пришел к нам на сервер в виде конверта. Правда, sms-сообщения сериализовались в XML не так, как нам было необходимо – они должны были быть обернуты в элементы message , а не в Struct . Теперь посмотрим в каком виде приходит такой объект в метод sendSms :

Object(stdClass) public "messageList" => object(stdClass) public "message" => object(stdClass) public "Struct" => array (size=3) 0 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 1" (length=37) public "date" => string "2013-07-21T15:00:00.26" (length=22) public "type" => string "15" (length=2) 1 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 2" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "16" (length=2) 2 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 3" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "17" (length=2)
Что нам дает это знание? Только то, что выбранный нами путь не является верным и мы не получили ответа на вопрос – «Как нам на сервере получить правильную структуру данных?». Но я предлагаю не отчаиваться и попробовать привести наш массив к типу объект :

$req->messageList->message = (object)$req->messageList->message;
В этом случае, нам придет уже другой конверт:

79871234567 Тестовое сообщение 1 2013-07-21T15:00:00.26 15 79871234567 Тестовое сообщение 2 2014-08-22T16:01:10 16 79871234567 Тестовое сообщение 3 2014-08-22T16:01:10 17
Пришедший в метод sendSms объект имеет следующую структуру:

Object(stdClass) public "messageList" => object(stdClass) public "message" => object(stdClass) public "BOGUS" => array (size=3) 0 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 1" (length=37) public "date" => string "2013-07-21T15:00:00.26" (length=22) public "type" => string "15" (length=2) 1 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 2" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "16" (length=2) 2 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 3" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "17" (length=2)
Как по мне, то «от перемены мест слагаемых – сумма не меняется» (с). Что BOGUS , что Struct – цель нами до сих пор не достигнута! А для ее достижения нам необходимо сделать так, чтобы вместо этих непонятных названий отображалось наше родное message . Но как этого добиться, автору пока не известно. Поэтому единственное, что мы можем сделать – избавить от лишнего контейнера. Другими словами, мы сейчас сделаем так, чтобы вместо message стал BOGUS ! Для этого изменим объект следующим образом:

// создаем объект для отправки на сервер $req = new Request(); $msg1 = new Message(); $msg1->phone = "79871234567"; $msg1->text = "Тестовое сообщение 1"; $msg1->date = "2013-07-21T15:00:00.26"; $msg1->type = 15; $msg2 = new Message(); $msg2->phone = "79871234567"; $msg2->text = "Тестовое сообщение 2"; $msg2->date = "2014-08-22T16:01:10"; $msg2->type = 16; $msg3 = new Message(); $msg3->phone = "79871234567"; $msg3->text = "Тестовое сообщение 3"; $msg3->date = "2014-08-22T16:01:10"; $msg3->type = 17; $req->messageList = $msg1; $req->messageList = $msg2; $req->messageList = $msg3; $req->messageList = (object)$req->messageList;
Вдруг нам повезет и из схемы подтянется правильное название? Для этого посмотрим на пришедший конверт:

79871234567 Тестовое сообщение 1 2013-07-21T15:00:00.26 15 79871234567 Тестовое сообщение 2 2014-08-22T16:01:10 16 79871234567 Тестовое сообщение 3 2014-08-22T16:01:10 17
Да, чуда не произошло! BOGUS – не победим! Пришедший в sendSms объект в этом случае будет выглядеть следующим образом:

Object(stdClass) public "messageList" => object(stdClass) public "BOGUS" => array (size=3) 0 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 1" (length=37) public "date" => string "2013-07-21T15:00:00.26" (length=22) public "type" => string "15" (length=2) 1 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 2" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "16" (length=2) 2 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 3" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "17" (length=2)
Как говорится – «Почти»! На этой (немного печальной) ноте предлагаю потихонечку закругляться и сделать некоторые для себя выводы.

8 Заключение

Наконец-то мы добрались сюда! Давайте определимся с тем, что вы теперь умеете делать:
  • вам по силам написать необходимый для вашего веб-сервиса WSDL-файл;
  • вы без всяких проблем можете написать свой собственный клиент способный общаться с сервером по протоколу SOAP;
  • вы можете написать свой собственный сервер общающийся с окружающим миром по SOAP;
  • вы можете отправлять массивы однотипных объектов на сервер со своего клиента (с некоторыми ограничениями).
Также, мы сделали для себя некоторые открытия в ходе нашего небольшого исследования:
  • нативный класс SoapClient не умеет правильно сериализовывать однотипные структуры данных в XML;
  • при сериализации массива в XML он создает лишний элемент с именем Struct ;
  • при сериализации объекта в XML он создает лишний элемент с именем BOGUS ;
  • BOGUS меньшее зло чем Struct из-за того, что конверт получается компактнее (не добавляются лишние namespace"ы в XML заголовке конверта);
  • к сожалению, класс SoapServer автоматически не валидирует данные конверта нашей XML-схемой (возможно, и другие сервера этого не делают).

Язык описания Web-сервисов (WSDL)

В последних нескольких примерах вы могли видеть отдельные фрагменты WSDL-кода. Напомним, что WSDL – это основанная на XML грамматика, предназначенная для описания возможностей взаимодействия внешних клиентов с Web-методами, доступными по данному адресу URL в рамках каждого из поддерживаемых протоколов связи. Во многих отношениях WSDL-документ может рассматриваться, как "контракт" между клиентом Web-сервиса и самим Web-сервисом. Это еще один метаязык. В частности, WSDL используется для описания следующих характеристик любого доступного Web-метода:

Имя Web-метода XML;

Число, тип и порядок следования параметров (если таковые имеются);

Тип возвращаемого значения (если таковое предусмотрено);

Условия вызова HTTP GET, HTTP POST и SOAP.

В большинстве случаев WSDL-документы генерируются автоматически соответствующим Web-сервером. Напомним, что при добавлении суффикса?wsdl к адресу URL, указывающему на файл *.asmx, Web-сервер генерирует WSDL-документ для указанного Web-сервиса XML.

http://locаlhost/SomeWS/theWS.asmx?wsdl

Но если IIS автоматически генерирует WSDL-документ для данного Web-сервиса XML, зачем тогда нужно глубокое понимание синтаксиса генерируемых WSDL-данных? Ответ обычно зависит от того, как ваш сервис будет использоваться внешними приложениями. В случае Web-сервисов XML, предназначенных для "внутреннего" использования, сгенерированного Web-сервером WSDL-кода будет, как правило, достаточно.

Между тем. вполне возможно начать разработку Web-сервиса XML с создания WSDL-документа вручную (об этом уже говорилось выше). Главная идея начала разработки с создания WSDL-документа связана с вопросами совместимости. Вспомните о том, что до появления спецификации WSI различные инструменты построения Web-сервисов нередко генерировали несовместимые WSDL-описания. Если начинать разработку с WSDL-кода, вы можете построить документ так, как требуется.

Как вы можете догадаться, для начала разработки Web-сервиса XML с создания WSDL-документа требуется очень хорошее знание грамматики WSDL, обсуждение которой в контексте этой главы не предусмотрено. Но мы рассмотрим базовую структуру WSDL-документа. Разобравшись в основах, вы сможете оценить пользу утилиты командной строки wsdl.exe.

Замечание. Самую свежую информацию о языке WSDL можно найти на страницах http://www.w3.org/tr/wsdl .

Определение WSDL-документа

Действительный документ WSDL открывается и закрывается корневым элементом ‹definitions›. В открывающем дескрипторе обычно определяются различные атрибуты xmlns. Они задают пространства имен XML, определяющие различные подчиненные элементы. Как минимум, элемент ‹definitions› должен указать пространство имен, где определены сами элементы WSDL (http://schemas.xmlsoap.org/wsdl). Для того чтобы быть полезным, открывающий дескриптор ‹definitions› должен, кроме того, указать пространства имен XML, определяющие простые типы данных WSDL, типы XML-схемы, элементы SOAP, а также целевое пространство имен. Например, вот как выглядит раздел ‹definitions› для нашего Web-сервиса калькулятора.

‹wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"

xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"

xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"

xmlns-mime="http://schemas.xmlsoap.org/wsdl/mime/"

xmlns:tns="http://www.IntertechTraining.com/"

xmlns:s="http://www.w3.org/2001/XMLSchema"

xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"

xmlns:http="http://schemes.xmlsoap.оrg/wsdl/http/"

targetNamespace="http://www.IntertechTraining.com/"

xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"›

‹/wsdl :definitions›

В контексте корневого элемента вы можете найти пять подчиненных элементов. Общий вид WSDL-документа должен быть примерно таким.

‹?xml version="1.0" encoding="utf-8"?›

‹wsdl:definitions …›

‹wsdl:types›

‹!-- Список типов, доступных для данного Web-сервиса - -›

‹wsdl:/types›

‹wsdl:message›

‹!-- Формат сообщений - -›

‹wsdl:/message›

‹wsdl:portType›

‹!-- Информация портов - -›

‹wsdl:/portType›

‹wsdl:binding›

‹!-- Информация связывания - -›

‹wsdl:/binding›

‹wsdl:service›

‹!-– Информация о самом Web-сервисе XML - -›

‹wsdl:/service›

‹wsdl:/definitions›

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

Элемент ‹types›

Сначала мы рассмотрим элемент ‹types›, который содержит описания всех типов данных, предлагаемых Web-сервисом. Вы, возможно, знаете, что язык XML сам определяет ряд "базовых" типов данных, и все они определены в рамках пространства имен XML http://www.w3.org/2001/XMLSchema (которое должно быть указано в контексте корневого элемента ‹definitions›). Возьмем, например, метод Subtract() нашего Web-сервиса калькулятора, имеющий два входных параметра целочисленного типа. В терминах WSDL тип System.Int32 среды CLR описывается в контексте элемента ‹complexType›.

‹s:еlement name= "Subtract"›

‹s:sequence›

‹s:element minOccurs="1 " maxOccurs="1 " name="x " type="s:int " /›

‹s:element minOccurs=""1 " maxOccurs="1 " name="y " type="s:int " /›

‹/ s:sequence›

‹/s:complexType›

‹/s:element›

Целое число, возвращаемое методом Subtract(), также описывается в рамках элемента ‹types›.

‹s:element name= "SubtractResponse "›

‹s:complexType›

‹s:sequence›

‹s:element minOccurs="1 " maxOccurs= "1 " name="SubtractResult " type="s:int "/›

‹/s:sequence›

‹ /s:complexType›

‹/s:element›

Если вы имеете Web-метод, возвращающий или получающий пользовательские типы данных, они также появятся в контексте элемента ‹complexType›. Детали того, как с помощью Web-метода сделать доступными пользовательские типы данных.NET, мы рассмотрим позже. Для примера предположим, что вы определили Web-мeтод, возвращающий структуру с именем Point.

public struct Point {

public string pointName;

WSDL-описание для этой "сложной структуры" будет выглядеть примерно так.

‹s:complexType name="Point "›

‹s:sequence›

‹s:element minOccurs="1 " maxOccurs="1 " name="x " type="s:int " /›

‹s:element minOccurs="1 "" maxOccurs="1 " name="y " type= "s:int " /›

‹s:element minOccurs="0 " maxOccurs="1 " name="рointName " type="s:string " /›

‹/s:sequence›

‹/s:complexType›

Элемент ‹message›

Элемент ‹message› используется для определения формата обмена запросами и ответами данного Web-метода. Поскольку один Web-сервис позволяет передачу множества сообщений между отправителем и получателем, одному WSDL-документу позволяется определять множество элементов ‹message›. Как правило, в этих определениях используются типы, указанные в рамках элемента ‹types›.

Независимо от количества элементов ‹message›, определенных в документе WSDL, они обычно "присутствуют" парами. Первое определение представляет входной формат сообщения, а второе – выходной формат того же сообщения. Например, метод Subtract() Web-сервиса CalculatorWebService определяет следующие элементы ‹message›.

‹wsdl:message name="SubtractSoapIn "›

‹wsdl:part name="parameters" element="tns:Subtract" /›

‹/wsdl:message›

‹wsdl: message name="SubtractSoapOut "›

‹wsdl:part name="parameters" element="tns:SubtractResponse" /›

‹/wsdl:message›

Здесь вы видите только связь SOAP соответствующего сервиса. Как говорилось в начале этой главы, Web-сервисы XML могут вызываться с помощью SOAP или HTTP-методов GET и POST. Но если вы разрешите связь HTTP POST (соответствующие объяснения будут предложены позже), генерируемый WSDL-код должен продемонстрировать следующие данные ‹message›.

‹wsdl: message name="SubtractHttpPostIn "›

‹part name="n1" type="s:string" /›

‹part name="n2" type="s:string" /›

‹wsdl:/message›

‹wsdl:message name="SubtractHttpPostOut "›

‹part name="Body" element="s0:int" /›

‹wsdl:/message›

Элементы ‹message› сами по себе не слишком полезны. Однако на эти определения сообщений ссылаются другие части WSDL-документа.

Замечание. Не все Web-методы требуют и запроса, и ответа. Если Web-метод является "односторонним", для него необходим только элемент ‹message› запроса. Обозначить Web-метод, как односторонний, можно с помощью атрибута .

Элемент ‹portType›

Элемент ‹portType› определяет различные связи, которые могут возникать между клиентом и сервером, и каждая такая связь представляется вложенным элементом ‹operation›. Несложно догадаться, что самыми типичными операциями здесь должны быть SOAP, HTTP GET и HTTP POST. Однако есть и другие операции. Например, односторонняя операция позволяет клиенту отправить сообщение данному Web-серверу, но не получить ответ (это похоже на вызов метода без ожидания возвращаемого значения). Операция "требование-ответ" позволяет серверу отправить, запрос во время ответа клиента (что можно рассматривать, как дополнение операции "запрос-ответ").

Чтобы проиллюстрировать формат необязательного вложенного элемента ‹operation›, рассмотрим WSDL-определение для метода Subtract().

‹wsdl portType name="CalculatorWebServiceSoap "›

‹wsdl:operation name="Subtract "›

‹wsdl:input message="tns:SubtractSoapIn " /›

‹wsdl:output message="tns:SubtractSoapOut " /›

‹ /wsdl:operation›

‹wsdl:/portType›

Обратите внимание на то, как элементы ‹input› и ‹output› ссылаются на соответствующее имя сообщения, определенное в рамках элемента ‹message›. Если бы для метода Subtract() был разрешен HTTP-метод POST, вы бы увидели следующий дополнительный элемент ‹operation›.

‹wsdl:portType name="CalculatorWebServiceHttpPost"›

‹wsdl:input message="s0:SubtractHttpPostIn " /›

‹wsdl:output message= "s0:SubtractHttpPostOut " /›

‹ wsdl:/operation›

‹wsdl:/portType›

Наконец, учтите то, что если данный Web-метод описан с помощью свойства Description, элемент ‹operation› будет содержать вложенный элемент ‹documentation›.

Элемент ‹binding›

Этот элемент указывает точный формат обмена GET, POST и SOAP. Это самый "многословный" из всех элементов, содержащихся в контексте корневого элемента ‹definition›. Вот, например, определение элемента ‹binding› с описанием того, как вызывающая сторона может взаимодействовать с Web-методом MyMethod(). используя SOAP.

‹wsdl:binding name="СаlculatorWebServiceSoap12" type="tns:CalculatorWebServiceSoap "›

‹soap12:binding transport="http://schemas.xmlsoap.org/soap/http " /›

‹wsdl:operation name= "Subtract "›

‹soap12:operation soapAction="http://www.IntertechTraining.com/Subtract " style="document" /›

‹wsdl:input›

‹soap12:body use="literal " /›

‹/wsdl:input›

‹wsdl:output›

‹soap12:body use="literal " /›

‹/wsdl:output›

‹/wsdl:operation›

‹/wsdl:binding›

Элемент ‹service›

Наконец, у нас есть элемент ‹service›, который указывает характеристики самого Web-сервиса (например, его URL). Главной задачей этого элемента является описание множества портов, открытых данным Web-сервером. Для этого элемент ‹services› может использовать любое число вложенных элементов ‹port› (не путайте их с элементом ‹portType›). Вот как выглядит элемент ‹service› для CalculatorWebService.

‹wsdl:service name="CalculatorWebService "›

‹wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"›

Чудесный Web-сервис калькулятора

‹/wsdl:documentation›

‹wsdl:port name="CalculatorWebServiceSoap" binding="tns :CalculatorWebServiceSoap"

‹soap:address location="http://localhost:1109/CalculatorWebService/ Service.asmx" /›

‹/wsdl:port›

‹wsdl:port name="CalculatorWebServiceSoap12" binding= "tns :CalculatorWebServiceSoap12 "›

‹soap12:address location="http://localhost:1109/CalculatorWebService/Service.asmx" /›

‹/wsdl:port›

‹/wsdl:service›

Итак, как видите, WSDL-код, автоматически возвращаемый сервером ITS, не является сверхсложным, но, поскольку WSDL представляет собой грамматику на основе XML, этот код достаточно "многословен". Тем не менее, теперь вы должны лучше понимать роль WSDL, так что давайте рассмотрим немного подробнее протоколы связи Web-сервисов XML.

Замечание. Напомним, что пространство имен System.Web.Services.Description содержит множество типов, которые позволяют программно читать и обрабатывать "сырой" WSDL-код (можете проверить сами, если вас это интересует).