Всем привет!
Так случилось, что в последнее время я стал заниматься разработкой веб-сервисов. Но сегодня топик не обо мне, а о том, как нам написать свой XML Web Service основанный на протоколе SOAP 1.2.
Я надеюсь, что после прочтения топика вы сможете самостоятельно:
И все же, я что-то забыл! Если еще немного порефлексировать, то стоит отметить, что клиент за раз может отправить на сервер как одно sms-сообщение, так и некоторое их количество. Другими словами, в одном пакете данных может быть от одного до бесконечности сообщений.
В результате мы получаем, что для отправки sms-сообщения нам необходимы следующие данные:
На первый вопрос мы ответили, теперь необходимо ответить на второй вопрос. И пожалуй, я позволю себе немного с халтурить. Поэтому, с сервера мы будем присылать только булевы данные, значение которых имеет следующий смысл:
На этом мы закончили описание постановки задачи! И наконец-то приступим к самому интересному – будем разбираться что за диковинный зверь этот SOAP!
И начну я свое повествование с того, что данный протокол обмена данными относится к подмножеству протоколов основанных на так называемой парадигме 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» описывается следующая информация:
Основная задачи схемы – описать структуру данных которые мы собираемся обрабатывать. Все данные в XML-схемах делятся на простые (скалярные) и коплексные (структуры) типы. К простым типам относятся такие типы как:
Предлагаю далеко не ходить и написать XML-схему для нашего sms-сообщения! Ниже представлено xml-описание sms-сообщения:
Схема нашего комплексного типа будет выглядеть следующим образом:
Эта запись читается следующим образом: у нас есть переменная «message
» типа «Message
» и есть комплексный тип с именем «Message
», который состоит из последовательного набора элементов «phone
» типа string
, «text
» типа string
, «date
» типа dateTime
, «type
» типа decimal
. Эти типы простые и уже определены в описании схемы. Поздравляю! Мы только что написали нашу первую XML-схему!
Думаю, что значение элементов «element » и «complexType » вам стало все более-менее понятно, поэтому не будем на них больше заострять внимание и переключимся сразу же на элемент-композитор «sequence ». Когда мы используем элемент-композитор «sequence » мы сообщаем о том, что элементы включенные в него должны всегда располагаться в указанной в схеме последовательности, а также все из них являются обязательными. Но не стоит отчаиваться! В XML-схемах есть еще два элемента-композитора: «choice » и «all ». Композитор «choice » сообщает о том, что должен быть какой-то один из перечисленных в нем элементов, а композитор «all » – любая комбинация перечисленных элементов.
Как вы помните, то в первом разделе топика мы договорились о том, что в пакете может передаваться от одного до бесконечности sms-сообщений. Поэтому предлагаю разобраться как такие данные декларируются в XML-схеме. Общая структура пакета может выглядеть следующим образом:
Схема для такого комплексного типа будет выглядеть так:
В первом блоке идет знакомое нам декларирование комплексного типа «Message
». Если вы заметили, то в каждом простом типе, входящем в «Message
», были добавлены новые уточняющие атрибуты «minOccurs
» и «maxOccurs
». Как не трудно догадаться из названия, первый (minOccurs
) сообщает о том, что в данной последовательности должно быть минимум по одному элементу типа «phone
», «text
», «date
» и «type
», в то время как следующий (maxOccurs
) атрибут нам декларирует, что таких элементов в нашей последовательности максимум по-одному. В результате, когда мы пишем свои схемы для каких-либо данных, нам предоставляется широчайший выбор по их настройке!
Второй блок схемы декларирует элемент «messageList » типа «MessageList ». Видно, что «MessageList » представляет собой комплексный тип, который включает минимум один элемент «message », но максимальное число таких элементов не ограничено!
Вообще, для того, чтобы у нас все работало правильно нам надо передавать клиенту 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 для разработки собственных распределенных приложений.
После того, как все было установлено и настроено нам необходимо будет создать в корневой папке вашего хостинга файл «smsservice.php » со следующим содержанием:
setClass("SoapSmsGateWay");
//Запускаем сервер
$server->handle();
То, что находится выше строчки с функцией «ini_set», надеюсь, что объяснять не надо. Т.к. там определяется какие HTTP-заголовки мы будем отправлять с сервера клиенту и настраивается окружение. В строчке с «ini_set» мы отключаем кеширование WSDL-файла для того, чтобы наши изменения в нем сразу же вступали в действие на клиенте.
Теперь мы подошли к серверу! Как видим, весь SOAP-сервер занимает всего лишь три строки! В первой строке мы создаем новый экземпляр объекта SoapServer и передаем ему в конструктор адрес нашего WSDL-описания веб-сервиса. Теперь мы знаем, что он будет располагаться в корне хостинга в файле с говорящим именем «smsservice.wsdl.php ». Во второй строке мы сообщаем SOAP-серверу какой класс необходимо дергать для того, чтобы обработать поступивший с клиента конверт и вернуть конверт с ответом. Как вы могли догадаться, именно в этом классе будет описан наш единственный метод sendSms . В третьей строке мы запускаем сервер! Все, наш сервер готов! С чем я нас всех и поздравляю!
Теперь нам необходимо создать WSDL-файл. Для этого можно либо просто скопировать его содержимое из предыдущего раздела, либо позволить себе вольности и немного его «шаблонизировать»:
";
?>
На этом этапе получившийся сервер нас должен устроить полностью, т.к. поступающие к нему конверты мы можем логировать и потом спокойно анализировать приходящие данные. Для того, чтобы мы могли что-либо получать на сервер, нам необходим клиент. Поэтому давайте им и займемся!
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
И это замечательно, т.к. теперь мы точно знаем о том, что наш сервер работает и не просто работает, но еще и может возвращать на клиент какие-то значения!
Теперь посмотрим на лог, который мы предусмотрительно ведем на серверной стороне! В первой его части мы видим необработанные данные, которые поступили на сервер:
Это и есть конверт. Теперь вы знаете как он выглядит! Но постоянно на него любоваться нам вряд ли будет интересно, поэтому давайте десереализуем объект из лог-файла и посмотрим все ли у нас хорошо:
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-сообщение, а целую пачку (если быть точнее, то целых три)!
// создаем объект для отправки на сервер
$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;
В наших логах числится, что пришел следующий пакет от клиента:
Что за ерунда, скажете вы? И будете правы в некотором смысле, т.к. только что мы узнали о том, что какой объект ушел от клиента, то абсолютно в том же виде он пришел к нам на сервер в виде конверта. Правда, 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;
В этом случае, нам придет уже другой конверт:
Пришедший в метод 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;
Вдруг нам повезет и из схемы подтянется правильное название? Для этого посмотрим на пришедший конверт:
Да, чуда не произошло! 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)
Как говорится – «Почти»! На этой (немного печальной) ноте предлагаю потихонечку закругляться и сделать некоторые для себя выводы.
Страница 2 из 3
SOAP работает очень хорошо, если о Web-службе все известно. Однако это не всегда так. Средством описания интерфейса для доступа к Web-службе является язык WSDL (Web Services Description Language - язык описания Web-служб). Этот стандарт совместно разработан компаниями IBM, Microsoft и webMethods. У каждой из этих трех компаний был собственный подход к разработке стандарта для описания Web-служб: IBM создала NASSL, Microsoft разработала SCL, а компания webMethods придумала WIDL.
Результатом их сотрудничества стала версия 1.1 языка WSDL, По поводу W3C следует отметить, что так же как и с SOAP, консорциум W3C на основе версии 1.1 разработал версию WSDL 1.2, которая теперь является рекомендацией W3C. WSDL-описание Web-службы содержит всю необходимую для использования этой службы информацию, включая доступные методы и их параметры. Эта информация содержится в следующих пяти элементах:
Вся эта информация хранится в корневом элементе WSDL-описания
Да уж... без стаканА не разберёшся, а ведь это один из самых простеньких(!) WSDL файлов. К сожалению, один из недостатков SOAP-расширения для РНР 5 связан с тем, что в отличие от других реализаций SOAP, оно не позволяет создавать WSDL-описания автоматически (во всяком случае, пока что). Наверняка этот недостаток исправят в будущих версиях РНР.
Кстати!
Для автоматического создания WSDL-описания вы можете использовать альтернативные реализации протокола SOAP в РНР:
Теперь, после того как мы знаем, как получать информацию о Web-службе и как ее запрашивать, нужно научиться находить такую службу. Для этой цели существует нечто похожее на "Желтые страницы", а именно - реестры UBR (Universal Business Registries - универсальные бизнес-реестры) - справочники Web-служб.
Существует несколько таких реестров, среди которых реестры компаний IBM, Microsoft, NTT-Com и SAP. Эти реестры синхронизируют свои данные, поэтому можно пользоваться любым из них. Текущей версией стандарта UDDI является версия UDDI 3.0, хотя большинство реализаций используют версию 2. Среди разработчиков этого стандарта такие компании-гиганты, как HP, Intel, Microsoft и Sun.
Для взаимодействия с UBR существует два типа API-интерфейсов: Inquiry API и Publish API . Интерфейс Inquiry API (Запрос) предназначен для запроса служб в реестрах UBR, а интерфейс Publish API (Публикация) позволяет разработчикам регистрировать свои службы . Похоже, что заполнение содержимого реестров спамом - это только вопрос времени:)
Кстати!
Существуют тестовые реестры, предназначенные для тестирования регистрации служб перед их размещением в "настоящих" реестрах.
В примере выше видно, что UDDI-запрос инкапсулирован в SOAP-сообщение, поэтому выглядит он довольно знакомым. Ответом на запрос является также SOAP-документ, показанный ниже:
Установить SOAP-расширение для PHP5 довольно легко. В Windows этот модуль находится в подкаталоге ext каталога установки РНР. Для его использования необходимо в файл php.ini добавить следующую строку: extension=php_soap.dll Для работы этому модулю требуется, которая включена в РНР 5 по умолчанию, по крайней мере, в Windows-версии.
Стандартизированное описание упрощает понимание и применение. Допустим, что вы нашли сервис, который решает необходимые вам задачи, и хотите его использовать в своих решениях. Самый простой способ получить информацию о чужой разработке и ее возможностях - взглянуть на WSDL-описание. Документы WSDL могут состоять из нескольких модулей или ссылаться на другие документы либо XML-схемы (XSD), которые описывают типы данных, используемые в веб-сервисе. Изначально было предложено несколько вариантов ведения описания, и два крупных игрока - Microsoft и IBM - познакомили со своим видением данной проблемы. Первая разработала и предложила язык SDL (Service Description Language), который был включен в состав первой версии SOAP Toolkit этой компании. IBM явила свое видение проблемы в Network NASSL (Accessible Service Specification Language), которая была реализована в SOAP4J в виде набора NASSL Toolkit . Идеи, предложенные в NASSL, вдохновили Microsoft на продолжение развития языка описания, в результате чего на свет появился SOAP Contract Language (SCL). Это решение оказалось очень эффективным, его доработали с учетом пожеланий сторонних производителей, и 15 марта 2001 года идеи превратились в спецификацию WSDL 1.1. Конечно же столько лет без изменений компьютерная область жить не может, поэтому 27 марта 2006 года появилась версия 2.0, а с 26 июня 2007 года она носит рекомендательный характер.
WSDL 1.0 (Сент. 2000) был разработан IBM , Microsoft и Ariba для описания веб-сервисов для SOAP toolkit .
WSDL 1.1, выпущен в марте 2001. Фактически это формализованный WSDL 1.0. Между этими версиями нет никаких принципиальных отличий.
WSDL 1.2 (Июнь 2003) по прежнему работает под W3C . WSDL 1.2 не поддерживается большинством вендоров SOAP.
WSDL 2.0 получил официальную поддержку W3C в июне 2007. WSDL 1.2 был переименован WSDL 2.0 поскольку имел большие отличия от предыдущей версии.
Употребляемый автором термин Web-сервисов относится исключительно к тому виду технологии, которая состредоточена на взаимодействии. Это означает, что эта технология стандартизирована: гетерогенные системы работают только при наличии отрытых стандартов. В этом случае уместен вопрос: будут ли Web-сервисы решением вашей проблемы? Сегодня одного слова Web-сервисы уже недостаточно, чтобы говорить о солидности компании, их использующей, поэтому будет лучше, если имеются иные веские основания для их использования. Если вы контролируете оба конца канала, не исключено, что существуют более подходящие технологии. Сейчас - только заря эры открытых стандартов распределенной обработки данных. Поэтому цена поддержки Web-сервисов на этом еще несформировавшемся рынке остается высокой - это и снижение производительности, и увеличение затрат на разработку, и ухудшение защищенности. Тезис о том, что Web-сервисы являются "дружественными по отношению к брандмауэру" ("firewall friendly"), обманчив. Действительно, обычные брандмауэры оберегают корпоративные ценности от "злоумышленников", которые используют слабые места в прикладном программном обеспечении, появившиеся в результате открытия портов, на которых исполняются защищенные сервисы. С другой стороны, Web-сервисы через этих порты "выставляют" прикладное программное обеспечение.
Другими словами, они ослабляют безопасность, предоставляя посторонним лицам доступ к приложениям - именно то, чему сетевой брандмауэр должен был бы воспрепятствовать. Поэтому необходимо новое поколение брандмауэров. На рынке уже появилось несколько новых игроков, предлагающих такие продукты. Поставщики обычных брандмауэров также начинают обращать внимание на эту проблему. Но это только начало, и такая технология должна еще оправдать себя. Даже если предполагается применять Web-сервисы, необходимо помнить, что необязательно использовать SOAP . Например, автор статьи видел формы, передаваемые как аргумент запроса на удаленный вызов процедуры SOAP (SOAP-RPC). Ему также попадались формы, которые возвращались как часть ответа удаленного вызова процедуры SOAP . Он так и не смог найти дополнительную пользу от применения SOAP. Если распределенные компоненты могут разрабатываться на различных языках программирования, то для того, чтобы указать, как должны быть задействованы сервисы, необходим Язык описания интерфейсов (Interface Definition Language, IDL), нейтральный по отношению к языку программирования. У CORBA (Общая архитектура посредника запросов к объектам, Common Object Request Broker Architecture), как и у DCOM (Распределенная модель компонентных объектов, Distributed Component Object Model), такой язык есть. IDL - это контракт между инициатором на обслуживание и поставщиком, но он только собирает синтаксис. Семантика остается неосвещенной: IDL оставляет открытым вопрос о том, что делает сервис. WSDL - это IDL Web-сервисов. Он описывает, как вызывать Web-сервисы. Он также определяет ответы, которые могут быть получены как при успешном вызове, так и нет.
Спецификация WSDL жестко регламентирует формат сообщений, используемые протоколы и адрес, по которому находятся сервисы. К сожалению, даже четкое и строгое описании на WSDL не гарантирует высокое качество проектирования. Подобно всем языкам IDL, WSDL силен в синтаксисе и слаб в семантике. Однако, не стоит им пренебрегать - в конечном счете важна именно семантика, синтаксис же используется просто, чтобы ее раскрывать.
Прежде чем приступить к написанию WSDL, необходимо оговорить с заказчиками то, что Web-сервис должен делать. Следует записать случаи использования, четко определив, как этот сервис взаимодействует со своей средой. Предположим, что программа-агент (actor) с какой-либо целью вызывает Web-сервис. В этом случае, нужно создать не только сценарий "солнечного дня", который реализует поставленную задачу, но сценарий "дождливого дня", когда результат отрицательный. Какие гарантии может предложить система, что цель успешно достигнута? А когда нет? Где находится граница ответственности клиентской части и сервера? Трудно переоценить важность четкого определения требований. На этом этапа стоит задуматься о цели проекта. В чем конкретно она заключается? Какие данные необходимо получать от клиента, и что должен поставлять сервер? Совершение ошибки на этом шаге чревато большими затратами. Например, рассмотрим Web-сервис, который в качестве параметров принимает список установленных программ и величину свободного места на диске. Затем этот сервис должен вернуть список приложений, подлежащих обновлению.
В случае если места достаточно, проблем не возникает. Однако, как быть, если его недостаточно? Как выбрать продукты, для которых необходимо установить новые версии? Предполагается ли, что клиент сам удаляет старые редакции программ, чтобы освободить место для новых? Это непростые вопросы, для решения которых потребуется разрабатывать сложные алгоритмы, при реализация которых не исключены ошибки. Разумеется, ни одну систему не следует определять подобным образом. Сервер должен предоставлять список приложений, зависимости между ними и требования, которые они предъявляют к ресурсам. Решение же о том, какой пакет установить, является задачей клиента. Поэтому поручив серверу это задание, клиент ничего не выиграет, он только повысит затраты на разработку серверной части. Таким образом, необходимо проводить анализ затрат на начальном этапе. При этом можно проигнорировать технические детали - сервис можно рассматривать как черный ящик. Но нельзя пренебрегать деталями, касающимися взаимодействия между этим сервисом и его средой. Следующий шаг - проанализировать, как можно реализовать случаи использования.
Будет ли единственный интерфейс (portType в WSDL версии 1.1), обеспечивающий всю функциональность? Или несколько интерфейсов? Каждый интерфейс может быть предложен в нескольких конечных точках, но, как правило, он должен быть неделимым. То есть конечная точка должна предоставлять либо всю функциональность, либо ничего. Аналогично, интерфейс должен быть семантически последовательным. Сказанное носит рекомендательный характер и опирается на здравый смысл, хотя ничто в спецификации WSDL не препятствует иной интерпретации. Требуются ли какие-нибудь поддерживающие интерфейсы? Как и когда они должны вызываться? Какова природа этой зависимости? Пока нет стандартов, а только рекомендации о том, как обращаться с "хореографией сервисов" (service choreographies), как разрешать сервисам выполнять гиперссылки к другим сервисам. Другими словами, это неисследованная проблемная область.
Когда-то поставили передо мной задачу начать разработку Web-сервисов и дали мне сорцы простейшего проекта без каких-либо объяснений. Проект, конечно же, не запускался. Что такое Spring и как он работает, я тоже представления не имел. Адекватных статей по разработке Web-сервисов средствами Spring ни русскоязычных, ни англоязычных я тоже не смог найти. Пришлось разбираться во всем самому, оказалось все не так страшно.
И вот недавно я решил посмотреть, какие новые возможности добавились в Spring с тех пор, и обновить старые сервисы, что в результате и сподвигло меня на написание данной статьи.
Данная статья является руководством по разработке простейшего Web-сервиса, использующего SOAP -протокол, средствами Spring-WS.
И так, писать будем простейший сервис, принимающий имя пользователя и отправляющий приветствие и текущее время на сервере.
Далее запускаем Ant-build (надеюсь, вы его уже установили).
В Eclipse можно запустить так: ПКМ по файлу build.xml=> Run As => Ant Build.
Если через командную строку:
ant -buildfile build.xml
Ну и ждем завершения построения. После чего, можем проверить каталог проекта WEB-INF\lib на наличие соответствующей библиотеки (helloservice.jar).
Создаем файл 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-схемы.
Два последующих бина всегда будут неизменны. Суть их заключается в приеме и преобразовании запроса из Xml в Java-объект и дальнейшего обратного преобразования.
sws:dynamic-wsdl
отвечает за автоматическую генерацию WSDL-документа на основе созданной Xml-схемы.
location
указывает на путь к схеме.
locationUri
– адрес (относительно контейнера), по которому будет доступна WSDL-схема.
В моем случае WSDL доступен по следующему адресу:
localhost/HelloService/HelloService.wsdl
Для дальнейшей проверки нам потребуется soapUI (у меня версия 3.0.1).
Устанавливаем и запускаем его.
Создаем новый проект: File => New soapUI Project. В поле Initial WSDL/WADL вставляем ссылку на WSDL-схему (http://localhost/HelloService/HelloService.wsdl).
В созданном проекте открываем необходимый запрос.
В поле Name вбиваем имя и жмем на кнопку «Send request»
В результате получаем ответ от сервера с приветствием и текущим временем.
Если что-то пошло не так, то опять перечитываем данную статью.
В главе 2 мы говорили о том, что после создания Web-службы на сервере в виде сервлета, страницы JSP, JWS-файла, компонента EJB или другого объекта, следует описать состав и возможности Web-службы на языке, не зависящем от платформы, операционной системы, системы программирования, использованной при создании Web-службы. Это описание регистрируется в общедоступном месте Интернета, например, реестре UDDI или ebXML, или хранится на сервере Web-службы. Описание должно содержать полную и точную информацию обо всех услугах, предоставляемых Web-службой, способы получения услуг, содержимое запроса на получение услуги, формат предоставляемой информации.
Одно из средств точного и единообразного описания Web-услуг - язык WSDL, созданный консорциумом W3C. Этот язык - еще одна реализация XML. Его последняя рекомендованная спецификация всегда публикуется на странице http://www.w3.org/TR/wsdI . Во время написания книги на черновой стадии была версия WSDL 1.2, которую мы и опишем в этой главе.
Состав документа WSDL
Корневым элементом документа XML - описания WSDL - служит элемент
Описания WSDL активно используют различные пространства имен. Кроме собственных имен, язык WSDL часто использует имена типов и элементов языка описания схем XSD (см. главу 1) и имена языка протокола SOAP. Пространство имен языка WSDL часто описывается как пространство имен по умолчанию. Идентификатор пространства имен последней на время написания этих строк версии WSDL 1.2 был равен http://www.w3.org/2002/07/wsdl . Целевое пространство имен, идентификатор которого определяется атрибутом обычно получает префикс tns (target namespace).
В корневой элемент
?
?
?
?
?
? < service > - указывает местоположение Web-службы как один или несколько портов. Каждый порт описывается вложенным элементом
Кроме этих шести основных элементов есть еще два вспомогательных элемента.
?
Комментарий. Его можно включить в любой элемент
описания WSDL.
Можно сказать, что элементы
Элементы
Наконец, элементы
Структура документа WSDL показана в листинге 4.1. Символы в квадратных скобках не содержатся в документе. Они показывают повторяемость элемента или атрибута в описании Web-службы:
Символ [?] означает, что элемент или атрибут может появиться в документе нуль или один раз;
Символ [*] означает, что элемент может появиться нуль или несколько раз;
Символ [+] означает, что элемент может появиться один или несколько раз;
Отсутствие символа в квадратных скобках означает, что атрибут должен появиться ровно один раз.
j Листинг 4.1. Схема WSDL-документа
targetNamespace="nfleH l ra«iij location="URI-aflpec" /> [*] Произвольный комментарий Описания сложных и нестандартных типов.
Абстрактное описание SOAP-послания как набора составляющих его частей.
Абстрактное описание Web-службы как набора операций (услуг). Описание услуги как получения (input) и отправки (output, fault) посланий. Получаемое послание. Отправляемое message="nMH соотв. элемента Отправляемое сообщение об ошибке. type="MMH соотв. элемента Детали конкретного протокола. Они определяются в схеме этого протокола. -> Сюда записываются элементы, описывающие детали конкретной операции. -> Сюда записываются элементы, описывающие детали конкретного получаемого послания. -> Сюда записываются элементы, описывающие детали конкретного отправляемого послания. ->
Сюда записываются элементы, описывающие детали конкретного сообщения об ошибке. ->
serviceType="MMH соотв. элемента Описание интерфейса Web-службы как набора портов. binding="nMH соотв. элемента Сюда записывается обязательный и единственный адрес интерфейса Web-службы, записанный по правилам протокола, указанного в элементе
Каждый конкретный протокол пересылки посланий - SOAP, HTTP, FTP, SMTP - добавляет к шести основным и двум вспомогательным элементам языка WSDL свои дополнительные элементы, описывающие особенности данного протокола.
Приведем простой пример. В листинге 3.14 мы записали в виде класса Java простейшую Web-службу, возвращающую без всякой обработки присланный запрос:
public class EchoService{
public String getEcho (String req) { return req;
В листинге 4.2 приведено описание этой Web-службы на языке WSDL, использующее протокол SOAP.
Листинг 4.2. Описание Web-службы EchoService
version="1.0" encoding="UTF-8" ?>
targetNamespace="http://echoservice.com/echoservice.wsdl" xmlns="http://www.w3.org/2002/07/wsdl" xmlns:tns="http://echoservice.com/echoservice.wsdl" xmlns:soap="http://www.w3.org/2002/07/wsdl/soapl2" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
transport="http://schemas.xmlsoap.org/soap/http" /> "http://schemas.xmlsoap.org/soap/encoding/" namespace= "http: //echoservice. ccm/echcservice .wsdl" use="encoded" /> ^oapKbocy enccdingStyle= "http: //schemas .xmlsoap. org/soap/encoding/" namespace= "http: //echoservice. c^/ech^service .wsdl" use="encoded" /> "http://localhost:8080/axis/EchoService.jws" /> В листинге 4.2 мы в элементе Имена "getEchoRequest" И "getEchoResponse" ИСПОЛЬЗОВаны В следующем элементе txarspcrt^=^"ht:tp^://?cheпas^.>пlscap^.c^rc^/?cap^/ht:tp^" /> Если применяется документный стиль SOAP, то в атрибуте style записывается значение "document". Наконец, в элементе В листинге 4.2 имена с префиксом soap конкретизировали описание послания и способы его пересылки. Посмотрим, какие конкретные протоколы предлагает спецификация WSDL 1.2. Литература:
Хабибуллин И. Ш. Разработка Web-служб средствами Java. - СПб.: БХВ-Петербург, 2003. - 400 с: ил.