Заголовок топика – это действительно вопрос, т.к. я сам не знаю, что это и впервые попробую поработать с этим в рамках настоящей статьи. Единственное, что могу гарантировать, что код, представленный ниже, будет работать, однако мои фразы будут лишь предположениями и догадками о том, как я сам все это понимаю. Итак, поехали…
Введение
Начать надо с того, для чего создавалась концепция веб-сервисов. К моменту появления этого понятия в мире уже существовали технологии, позволяющие приложениям взаимодействовать на расстоянии, где одна программа могла вызвать какой-нибудь метод в другой программе, которая при этом могла быть запущена на компьютере, расположенном в другом городе или даже стране. Все этого сокращенно называется RPC (Remote Procedure Calling – удаленный вызов процедур). В качестве примеров можно привести технологии CORBA, а для Java – RMI (Remote Method Invoking – удаленный вызов методов). И все вроде в них хорошо, особенно в CORBA, т.к. с ней можно работать на любом языке программирования, но чего-то все же не хватало. Полагаю, что минусом CORBA является то, что она работает через какие-то свои сетевые протоколы вместо простого HTTP, который пролезет через любой firewall. Идея веб-сервиса заключалась в создании такого RPC, который будет засовываться в HTTP пакеты. Так началась разработка стандарта. Какие у этого стандарта базовые понятия:- SOAP . Прежде чем вызвать удаленную процедуру, нужно этот вызов описать в XML файле формата SOAP. SOAP – это просто одна из многочисленных XML разметок, которая используется в веб-сервисах. Все, что мы хотим куда-то отправить через HTTP, сначала превращается в XML описание SOAP, потом засовывается в HTTP пакет и посылается на другой компьютер в сети по TCP/IP.
- WSDL . Есть веб-сервис, т.е. программа, методы которой можно удаленно вызывать. Но стандарт требует, чтобы к этой программе прилагалось описание, в котором сказано, что «да, вы не ошиблись – это действительно веб-сервис и можно у него вызвать такие-то такие-то методы». Такое описание представляется еще одним файлом XML, который имеет другой формат, а именно WSDL. Т.е. WSDL – это просто XML файл описания веб-сервиса и больше ничего.
Общий подход
В веб-сервисах всегда есть клиент и сервер. Сервер – это и есть наш веб-сервис и иногда его называют endpoint (типа как, конечная точка, куда доходят SOAP сообщения от клиента). Нам нужно сделать следующее:- Описать интерфейс нашего веб-сервиса
- Реализовать этот интерфейс
- Запустить наш веб-сервис
- Написать клиента и удаленно вызвать нужный метод веб-сервиса
Сервер
Запустим IDEA и создадим новый проект Create New Project . Укажем имя HelloWebService и нажмем кнопку Next , далее кнопку Finish . В папке src создадим пакет ru.javarush.ws . В этом пакете создадим интерфейс HelloWebService: package ru. javarush. ws; // это аннотации, т.е. способ отметить наши классы и методы, // как связанные с веб-сервисной технологией import javax. jws. WebMethod; import javax. jws. WebService; import javax. jws. soap. SOAPBinding; // говорим, что наш интерфейс будет работать как веб-сервис @WebService // говорим, что веб-сервис будет использоваться для вызова методов @SOAPBinding (style = SOAPBinding. Style. RPC) public interface HelloWebService { // говорим, что этот метод можно вызывать удаленно @WebMethod public String getHelloString (String name) ; } В этом коде классы WebService и WebMethod являются так называемыми аннотациям и ничего не делают, кроме как помечают наш интерфейс и его метод, как веб-сервис. Это же относится и к классу SOAPBinding . Разница лишь в том, что SOAPBinding – это аннотация с параметрами. В данном случае используется параметр style со значением, говорящим, что веб-сервис будет работать не через сообщения-документы, а как классический RPC, т.е. для вызова метода. Давайте реализуем логику нашего интерфейса и создадим в нашем пакете класс HelloWebServiceImpl . Кстати, замечу, что окончание класса на Impl – это соглашение в Java, по которому так обозначают реализацию интерфейсов (Impl – от слова implementation, т.е. реализация). Это не требование и вы вольны назвать класс как хотите, но правила хорошего тона того требуют: package ru. javarush. ws; // таже аннотация, что и при описании интерфейса, import javax. jws. WebService; // но здесь используется с параметром endpointInterface, // указывающим полное имя класса интерфейса нашего веб-сервиса @WebService (endpointInterface = "ru.javarush.ws.HelloWebService" ) public class HelloWebServiceImpl implements HelloWebService { @Override public String getHelloString (String name) { // просто возвращаем приветствие return "Hello, " + name + "!" ; } } Запустим наш веб-сервис как самостоятельный сервер, т.е. без участия всяких Tomcat и серверов приложений (это тема отдельного разговора). Для этого в структуре проекта в папке src создадим пакет ru.javarush.endpoint , а в нем создадим класс HelloWebServicePublisher с методом main: package ru. javarush. endpoint; // класс, для запуска веб-сервера с веб-сервисами import javax. xml. ws. Endpoint; // класс нашего веб-сервиса import ru. javarush. ws. HelloWebServiceImpl; public class HelloWebServicePublisher { public static void main (String. . . args) { // запускаем веб-сервер на порту 1986 // и по адресу, указанному в первом аргументе, // запускаем веб-сервис, передаваемый во втором аргументе Endpoint. publish ("http://localhost:1986/wss/hello" , new HelloWebServiceImpl () ) ; } } Теперь запустим этот класс, нажав Shift+F10 . В консоли ничего не появится, но сервер запущен. В этом можно убедиться набрав в браузере строку http://localhost:1986/wss/hello?wsdl . Открывшаяся страница, с одной стороны, доказывает, что у нас на компьютере (localhost) запустился веб-сервер (http://) на порту 1986, а, с другой стороны, показывает WSDL описание нашего веб-сервиса. Если вы остановите приложение, то описание станет недоступно, как и сам веб-сервис, поэтому делать этого не будем, а перейдем к написанию клиента.Клиент
В папке проекта src создадим пакет ru.javarush.client , а в нем класс HelloWebServiceClient с методом main: package ru. javarush. client; // нужно, чтобы получить wsdl описание и через него // дотянуться до самого веб-сервиса import java. net. URL; // такой эксепшн возникнет при работе с объектом URL import java. net. MalformedURLException; // классы, чтобы пропарсить xml-ку c wsdl описанием // и дотянуться до тега service в нем import javax. xml. namespace. QName; import javax. xml. ws. Service; // интерфейс нашего веб-сервиса (нам больше и нужно) import ru. javarush. ws. HelloWebService; public class HelloWebServiceClient { public static void main (String args) throws MalformedURLException { // создаем ссылку на wsdl описание URL url = new URL ("http://localhost:1986/wss/hello?wsdl" ) ; // Параметры следующего конструктора смотрим в самом первом теге WSDL описания - definitions // 1-ый аргумент смотрим в атрибуте targetNamespace // 2-ой аргумент смотрим в атрибуте name QName qname = new QName ("http://ws.сайт/" , "HelloWebServiceImplService" ) ; // Теперь мы можем дотянуться до тега service в wsdl описании, Service service = Service. create (url, qname) ; // а далее и до вложенного в него тега port, чтобы // получить ссылку на удаленный от нас объект веб-сервиса HelloWebService hello = service. getPort (HelloWebService. class ) ; // Ура! Теперь можно вызывать удаленный метод System. out. println (hello. getHelloString ("JavaRush" ) ) ; } } Максимум комментариев по коду я дал в листинге. Добавить мне нечего, поэтому запускаем (Shift+F10). Мы должны в консоли увидеть текст: Hello, JavaRush! Если не увидели, то видимо забыли запустить веб-сервис.Заключение
В данном топике был представлен краткий экскурс в веб-сервисы. Еще раз скажу, что многое из того, что я написал – это мои догадки по поводу того, как это работает, и поэтому мне не стоит сильно доверять. Буду признателен, если знающие люди меня поправят, ведь тогда я чему-нибудь научусь. UPD.SOAP (Simple Object Access Protocol)
является стандартизированным протоколом передачи сообщений между клиентом и сервером. Обычно он используется совместно с HTTP(S), но может работать и с другими протоколами прикладного уровня (например, SMTP и FTP).
Тестирование SOAP с точки зрения техник тестирования ничем принципиально не отличается от работы с другими API, но для его проведения требуются предварительная подготовка (в плане теории протокола) и специальные инструменты для тестирования. В данной статье я хотела бы сформулировать небольшой чек-лист необходимых знаний и навыков, который будет одинаково полезен как тестировщику SOAP (зачастую не представляющему, «за что хвататься» после постановки задачи), так и менеджеру, вынужденному оценивать знания тестировщиков и разрабатывать планы по обучению.
Теоретическая база
Тот факт, что SOAP является протоколом, имеет большое значение для тестирования: нужно изучить сам протокол, «первичные» стандарты и протоколы, на которых он базируется, а также (по мере необходимости) существующие расширения.
XML
XML – язык разметки, схожий с HTML. Любое сообщение, отправляемое/получаемое через SOAP, – это XML-документ, в котором данные удобно структурированы и легко читаемы, например:
Не забудь написать статью!
Более подробно про XML можно узнать на w3schools или codenet (по-русски) . Обязательно обратите внимание на описание namespaces (метод разрешения конфликтов при описании элементов в XML) – в SOAP их использование необходимо.
XSD
При работе всегда удобно иметь стандартизированное описание возможных XML-документов и проверять их на корректность заполнения. Для этого существует XML Schema Definition (или сокращенно XSD). Две главные фичи XSD для тестировщика – это описание типов данных и наложение ограничений на возможные значения. Например, элемент из предыдущего примера можно сделать необязательным для заполнения и ограничить его размер 255 символами с помощью XSD:
...
...
Расширения SOAP
В работе вам также могут встретиться различные «расширения» SOAP – стандарты типа WS-*
. Одним из самых распространенных является WS-Security позволяющий работать с шифрованием и электронными подписями. Нередко вместе с ним применяется WS-Policy, с помощью которого можно управлять правами на использование вашего сервиса.
Пример использования WS-Security:
Все эти расширения – достаточно сложные конструкции, используемые далеко не в каждом SOAP-сервисе; их подробное изучение на начальном этапе освоения тестирования SOAP вряд ли будет актуально.
Инструменты
Как вы уже поняли, SOAP – дело серьезное, для работы с ним нужно знать теорию и многочисленные стандарты. На практике такая сложность привела бы к весьма ощутимым трудозатратам (например, нужно было бы каждый раз смотреть схему в блокнотике и слать запросы curl-ом). Поэтому были созданы инструменты, облегчающие работу с SOAP.
Редакторы XML / XSD
Хороший тестировщик начинает тестирование еще на стадии написания документации, поэтому для проверки схем удобно использовать специальные редакторы. Два самых известных – Oxygen (кроссплатформенный) и Altova (только для Windows); оба они являются платными. Это очень мощные программы, которыми активно пользуются аналитики при описании сервисов.
В моей практике полезными оказались три фичи редакторов: визуализация XSD, генерация XML на основе XSD и валидация XML по XSD.
1. Визуализация XSD нужна для наглядного представления схемы, позволяющего быстро вычленить обязательные элементы и атрибуты, а также существующие ограничения. Например, для запроса CheckTextRequest обязательным является элемент text, а необязательными – все три атрибута (при этом у атрибута options установлено значение по умолчанию – ноль).
Визуализация необходима в том случае, когда типов и ограничений в схеме много. Если вам нужна только она, и вы не хотите платить за специальные редакторы, то можно рассмотреть бесплатные альтернативы (например, JDeveloper).
2. Генерация XML на основе XSD полезна тогда, когда вы хотите увидеть корректный пример сообщения. Я пользуюсь ей для того, чтобы быстро поэкспериментировать с возможным заполнением сообщения и проверить нюансы работы ограничений.
3. После использования фичи из пункта 2 полезно провести валидацию XML по XSD – то есть проверить сообщение на корректность. Вместе фичи 2 и 3 позволяют отлавливать хитрые дефекты в XSD еще тогда, когда сам сервис находится в разработке.
Инструмент тестирования – SoapUI
Тестирование SOAP практически всегда подразумевает использование SoapUI . Прочитать про использование этого инструмента можно в разных источниках ( , ), но эффективнее всего будет ознакомиться с официальной документацией . Я выделяю 8 условных уровней владения SoapUI:
Уровень 1 – умею отправлять запросы
Научитесь создавать проект на основе WSDL. SoapUI может сгенерировать все необходимые запросы для вас; вам останется лишь проверить правильность их заполнения и нажать кнопочку «Send». После выработки навыков создания корректных запросов вы должны овладеть искусством формирования некорректных запросов, вызывающих появление ошибок.
Уровень 2 – умею делать Test Suites и Test Cases
Начните делать мини-автотесты. Тест-комплекты и тест-кейсы позволяют создавать сценарии тестирования API, подготавливать данные для запросов и автоматически проверять полученный ответ на соответствие ожидаемому. На первых порах их можно использовать просто как коллекции запросов. Например, если вы завели дефект и хотите быстро проверить его после фикса, можно выделить отдельный тест-комплект конкретно под запросы-дефекты.
Уровень 3 – умею писать Assertions
После освоения тест-кейсов вам будет полезно научиться делать их автоматически проверяемыми. После этого вам уже не нужно будет искать «глазами» информацию об ответе: при наличии автоматической проверки кейсы будут помечаться зеленым (если проверка пройдена) или красным (если не пройдена). SoapUI предоставляет большой набор возможных проверок (assertions), но самые удобные и простые – это Contains и Not Contains. С их помощью можно проверить факт наличия конкретного текста в полученном ответе. Эти проверки также поддерживают поиск с помощью регулярных выражений.
Уровень 4 – использую XPath и/или XQuery в Assertions
Для тех, кто немного знаком с UI с помощью Selenium, язык XPath – знакомая вещь. Грубо говоря, XPath позволяет искать элементы в XML-документе. XQuery – похожая технология, которая может использовать XPath внутри себя; этот язык гораздо мощнее, он напоминает SQL. Оба эти языка можно использовать в Assertions. Проверки с их помощью получаются более прицельными и стабильными, поэтому ваши кейсы будут пользоваться большим доверием.
Уровень 5 – умею писать сложные тесты с помощью специальных шагов
В тест-кейсах может содержаться не только один запрос, но и несколько (к примеру, когда вы хотите эмулировать стандартный сценарий работы пользователя «создать сущность» → «экспортировать сущность»). Между запросами могут находиться другие специальные шаги, например:
- Properties и Property Transfer (помогают переиспользовать данные и передавать их между запросами);
- JDBC Request (используется для получения данных из базы данных);
- Conditional Goto (позволяет сделать разветвления или циклы в тест-кейсе);
- Run TestCase (помогает вынести какие-то типовые запросы в отдельные тест-кейсы и вызывать их там, где нужно).
Уровень 6 – использую скрипты на Groovy
SoapUI позволяет писать скрипты на Groovy в различных местах. Простейший случай – это генерация данных в самом запросе с помощью вставок ${=}. Я постоянно пользуюсь такими вставками:
- ${=new Date().format(«yyyy-MM-dd’T’HH:mm:ss»)} – для вставки текущей даты и времени в необходимом формате;
- ${=java.util.UUID.randomUUID()} – для вставки корректно сформированного случайного GUID.
Полноценные скрипты можно использовать в качестве шагов в кейсах и проверках. В какой-то момент вы обнаружите, что сразу несколько специальных шагов из пятого уровня можно заменить одним скриптом.
Уровень 7 – использую MockServices
SoapUI на основе WSDL может генерировать Mock-объекты . Mock-объект – это простейшая симуляция сервиса. С помощью «моков» можно начать писать и отлаживать тест-кейсы еще до того, как сервис реально будет доступен для тестирования. Также их можно использовать в качестве «заглушек» для временно недоступных сервисов.
Уровень 8 – бог SoapUI
Вы знаете разницу между платной и бесплатной версиями SoapUI и используете SoapUI API в коде. Вы используете плагины и запускаете выполнение кейсов через командную строку и/или CI. Ваши тест-кейсы просты и легко поддерживаются. В общем, вы «съели собаку» на этом инструменте. Я бы с радостью пообщалась с тем, кто освоил SoapUI на таком уровне. Если вы являетесь таковым – отпишитесь в комментариях!
Тестирование с помощью языков программирования
Приведу пример того, как выглядит запрос к YandexSpeller API, выполненный с помощью groovy-wslite:
import wslite.soap.*
def client = new SOAPClient("http://speller.yandex.net/services/spellservice?WSDL")
def response = client.send(SOAPAction: "http://speller.yandex.net/services/spellservice/checkText") {
body {
CheckTextRequest("lang": "ru", "xmlns":"http://speller.yandex.net/services/spellservice") {
text("ошипка")
}
}
}
assert "ошибка" == response.CheckTextResponse.SpellResult.error.s.text()
assert "1" == [email protected]()
Насколько я знаю, высокоуровневых фреймворков (по типу Rest-assured) для тестирования SOAP пока не существует, но недавно появился интересный инструмент – karate . С его помощью можно описывать кейсы для тестирования SOAP и REST в виде сценариев по типу Cucumber / Gherkin . Для многих тестировщиков обращение к karate будет идеальным решением, ведь такие сценарии по сложности написания и поддержки кейсов будут лежать где-то посередине между использованием SoapUI и написанием собственного фреймворка для тестирования SOAP.
Заключение
Вряд ли вам когда-либо захочется тестировать SOAP просто так, для себя (как могло бы получиться с REST-ом). Это тяжеловесный протокол, который используется в серьезных корпоративных решениях. Но его тяжеловесность одновременно является подарком тестировщику: все используемые технологии стандартизированы, имеются качественные инструменты для работы. От тестировщика требуется лишь желание их изучить и использовать.
Давайте соберем воедино тот самый чек-лист необходимых навыков для тестировщика. Итак, если вы только начинаете тестировать SOAP сервисы, вам нужно знать и уметь использовать:
- WSDL.
- SOAP.
- Редакторы XML / XSD (на уровне визуализации XSD).
- SoapUI на уровне 1.
Как видите, основной упор приходится на изучение стандартов, в SoapUI достаточно просто уметь выполнять запросы. По мере погружения в тестирование SOAP перед вам будут возникать задачи, которые потребуют уже более серьезных навыков и знаний, но не стоит пытаться изучить всё и сразу. Гораздо важнее последовательность в повышении уровня сложности выполняемых задач. Следуя этой рекомендации, в один прекрасный момент вы поймете, что стали хорошим специалистом в этой области!
hats 23 июля 2013 в 13:09
Пишем SOAP клиент-серверное приложение на PHP
- PHP
- Tutorial
Всем привет!
Так случилось, что в последнее время я стал заниматься разработкой веб-сервисов. Но сегодня топик не обо мне, а о том, как нам написать свой XML Web Service основанный на протоколе SOAP 1.2.
Я надеюсь, что после прочтения топика вы сможете самостоятельно:
- написать свою собственную серверную реализацию веб-приложения;
- написать свою собственную клиентскую реализацию веб-приложения;
- написать свое собственное описание веб-сервиса (WSDL);
- отправлять клиентом массивы однотипных данных на сервер.
1 Постановка задачи
1.1 Границы
В начале предлагаю разобраться с тем результатом, которого мы достигнем в конце топика. Как было объявлено выше, мы будем писать сервис по отправке sms-сообщений, а если еще точнее, то к нам будут поступать сообщения из разных источников по протоколу SOAP. После чего, мы рассматрим в каком виде они приходят на сервер. Сам процесс постановки сообщений в очередь для их дальнейшей отправки провайдеру, к сожалению, выходит за рамки данного поста по многим причинам.1.2 Какими данными будем меняться?
Отлично, с границами мы определились! Следующий шаг, который необходимо сделать – решить какими данными мы будем обмениваться между сервером и клиентом. На эту тему предлагаю долго не мудрить и сразу для себя ответить на главные вопросы:- Какой минимум данных надо посылать на сервер, чтобы отправить sms-сообщение абоненту?
- Какой минимум данных надо посылать с сервера, чтобы удовлетворить потребности клиента?
- номер мобильного телефона, а также
- текст sms-сообщения.
- дату отправки 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» описывается следующая информация:
- Какие пространства имен использовались,
- Какие схемы данных использовались,
- Какие типы сообщений веб-сервис ждет от клиентов,
- Какие данные принадлежат каким процедурам веб-сервиса,
- Какие процедуры содержит веб-сервис,
- Каким образом клиент должен вызывать процедуры веб-сервиса,
- На какой адрес должны отправляться вызовы клиента.
3 Введение в XML-Schema
Теперь мы много чего знаем о то, что такое SOAP, что находится у него внутри и имеем обзорное представление о том, какой стек технологий его окружает. Поскольку, прежде всего SOAP представляет собой способ взаимодействия между клиентом и сервером, и в качестве транспорта для него используется язык разметки XML, то в данном разделе мы немного разберемся каким образом происходит автоматическая валидация данных посредством XML-схем.Основная задачи схемы – описать структуру данных которые мы собираемся обрабатывать. Все данные в 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 », но максимальное число таких элементов не ограничено!
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-файл. Для этого можно либо просто скопировать его содержимое из предыдущего раздела, либо позволить себе вольности и немного его «шаблонизировать»:
";
?>
На этом этапе получившийся сервер нас должен устроить полностью, т.к. поступающие к нему конверты мы можем логировать и потом спокойно анализировать приходящие данные. Для того, чтобы мы могли что-либо получать на сервер, нам необходим клиент. Поэтому давайте им и займемся!
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
И это замечательно, т.к. теперь мы точно знаем о том, что наш сервер работает и не просто работает, но еще и может возвращать на клиент какие-то значения!
Теперь посмотрим на лог, который мы предусмотрительно ведем на серверной стороне! В первой его части мы видим необработанные данные, которые поступили на сервер:
Это и есть конверт. Теперь вы знаете как он выглядит! Но постоянно на него любоваться нам вряд ли будет интересно, поэтому давайте десереализуем объект из лог-файла и посмотрим все ли у нас хорошо:
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;
В наших логах числится, что пришел следующий пакет от клиента:
Что за ерунда, скажете вы? И будете правы в некотором смысле, т.к. только что мы узнали о том, что какой объект ушел от клиента, то абсолютно в том же виде он пришел к нам на сервер в виде конверта. Правда, 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)
Как говорится – «Почти»! На этой (немного печальной) ноте предлагаю потихонечку закругляться и сделать некоторые для себя выводы.
8 Заключение
Наконец-то мы добрались сюда! Давайте определимся с тем, что вы теперь умеете делать:- вам по силам написать необходимый для вашего веб-сервиса WSDL-файл;
- вы без всяких проблем можете написать свой собственный клиент способный общаться с сервером по протоколу SOAP;
- вы можете написать свой собственный сервер общающийся с окружающим миром по SOAP;
- вы можете отправлять массивы однотипных объектов на сервер со своего клиента (с некоторыми ограничениями).
- нативный класс SoapClient не умеет правильно сериализовывать однотипные структуры данных в XML;
- при сериализации массива в XML он создает лишний элемент с именем Struct ;
- при сериализации объекта в XML он создает лишний элемент с именем BOGUS ;
- BOGUS меньшее зло чем Struct из-за того, что конверт получается компактнее (не добавляются лишние namespace"ы в XML заголовке конверта);
- к сожалению, класс SoapServer автоматически не валидирует данные конверта нашей XML-схемой (возможно, и другие сервера этого не делают).
- Tutorial
Всем привет!
Так случилось, что в последнее время я стал заниматься разработкой веб-сервисов. Но сегодня топик не обо мне, а о том, как нам написать свой XML Web Service основанный на протоколе SOAP 1.2.
Я надеюсь, что после прочтения топика вы сможете самостоятельно:
- написать свою собственную серверную реализацию веб-приложения;
- написать свою собственную клиентскую реализацию веб-приложения;
- написать свое собственное описание веб-сервиса (WSDL);
- отправлять клиентом массивы однотипных данных на сервер.
1 Постановка задачи
1.1 Границы
В начале предлагаю разобраться с тем результатом, которого мы достигнем в конце топика. Как было объявлено выше, мы будем писать сервис по отправке sms-сообщений, а если еще точнее, то к нам будут поступать сообщения из разных источников по протоколу SOAP. После чего, мы рассматрим в каком виде они приходят на сервер. Сам процесс постановки сообщений в очередь для их дальнейшей отправки провайдеру, к сожалению, выходит за рамки данного поста по многим причинам.1.2 Какими данными будем меняться?
Отлично, с границами мы определились! Следующий шаг, который необходимо сделать – решить какими данными мы будем обмениваться между сервером и клиентом. На эту тему предлагаю долго не мудрить и сразу для себя ответить на главные вопросы:- Какой минимум данных надо посылать на сервер, чтобы отправить sms-сообщение абоненту?
- Какой минимум данных надо посылать с сервера, чтобы удовлетворить потребности клиента?
- номер мобильного телефона, а также
- текст sms-сообщения.
- дату отправки 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» описывается следующая информация:
- Какие пространства имен использовались,
- Какие схемы данных использовались,
- Какие типы сообщений веб-сервис ждет от клиентов,
- Какие данные принадлежат каким процедурам веб-сервиса,
- Какие процедуры содержит веб-сервис,
- Каким образом клиент должен вызывать процедуры веб-сервиса,
- На какой адрес должны отправляться вызовы клиента.
3 Введение в XML-Schema
Теперь мы много чего знаем о то, что такое SOAP, что находится у него внутри и имеем обзорное представление о том, какой стек технологий его окружает. Поскольку, прежде всего SOAP представляет собой способ взаимодействия между клиентом и сервером, и в качестве транспорта для него используется язык разметки XML, то в данном разделе мы немного разберемся каким образом происходит автоматическая валидация данных посредством XML-схем.Основная задачи схемы – описать структуру данных которые мы собираемся обрабатывать. Все данные в 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 », но максимальное число таких элементов не ограничено!
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-файл. Для этого можно либо просто скопировать его содержимое из предыдущего раздела, либо позволить себе вольности и немного его «шаблонизировать»:
";
?>
На этом этапе получившийся сервер нас должен устроить полностью, т.к. поступающие к нему конверты мы можем логировать и потом спокойно анализировать приходящие данные. Для того, чтобы мы могли что-либо получать на сервер, нам необходим клиент. Поэтому давайте им и займемся!
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
И это замечательно, т.к. теперь мы точно знаем о том, что наш сервер работает и не просто работает, но еще и может возвращать на клиент какие-то значения!
Теперь посмотрим на лог, который мы предусмотрительно ведем на серверной стороне! В первой его части мы видим необработанные данные, которые поступили на сервер:
Это и есть конверт. Теперь вы знаете как он выглядит! Но постоянно на него любоваться нам вряд ли будет интересно, поэтому давайте десереализуем объект из лог-файла и посмотрим все ли у нас хорошо:
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;
В наших логах числится, что пришел следующий пакет от клиента:
Что за ерунда, скажете вы? И будете правы в некотором смысле, т.к. только что мы узнали о том, что какой объект ушел от клиента, то абсолютно в том же виде он пришел к нам на сервер в виде конверта. Правда, 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)
Как говорится – «Почти»! На этой (немного печальной) ноте предлагаю потихонечку закругляться и сделать некоторые для себя выводы.
8 Заключение
Наконец-то мы добрались сюда! Давайте определимся с тем, что вы теперь умеете делать:- вам по силам написать необходимый для вашего веб-сервиса WSDL-файл;
- вы без всяких проблем можете написать свой собственный клиент способный общаться с сервером по протоколу SOAP;
- вы можете написать свой собственный сервер общающийся с окружающим миром по SOAP;
- вы можете отправлять массивы однотипных объектов на сервер со своего клиента (с некоторыми ограничениями).
- нативный класс SoapClient не умеет правильно сериализовывать однотипные структуры данных в XML;
- при сериализации массива в XML он создает лишний элемент с именем Struct ;
- при сериализации объекта в XML он создает лишний элемент с именем BOGUS ;
- BOGUS меньшее зло чем Struct из-за того, что конверт получается компактнее (не добавляются лишние namespace"ы в XML заголовке конверта);
- к сожалению, класс SoapServer автоматически не валидирует данные конверта нашей XML-схемой (возможно, и другие сервера этого не делают).
SOAP -текстовый протокол, который использует XML для обмена структурированными сообщениями в распределённой вычислительной среде. Первоначально SOAP предназначался, в основном, для реализации удалённого вызова процедур (RPC), а название было аббревиатурой: Simple Object Access Protocol - простой протокол доступа к объектам. Сейчас протокол используется для обмена произвольными сообщениями в формате XML, а не только для вызова процедур. Официальная спецификация последней версии 1.2 протокола никак не расшифровывает название SOAP. SOAP является расширением протокола XML-RPC. SOAP может использоваться с любым протоколом прикладного уровня: SMTP, FTP, HTTP и др. Однако его взаимодействие с каждым из этих протоколов имеет свои особенности, которые должны быть определены отдельно. Чаще всего SOAP используется поверх HTTP. SOAP является одним из стандартов, на которых базируется технологии веб-сервисов. Связь между web-службами и их клиентами осуществляется посредством сообщений в формате XML. SOAP (Simple Object Access Protocol - простой протокол доступа к объектам) представляет собой протокол сообщений для выбора web-служб. Можно сказать, что формат SOAP идеально подходит для технологии RPC (Remote Procedure Call - вызов удаленной процедуры), так как SOAP-сообщение содержит направляемые клиентом параметры или отсылаемую службой возвращаемую величину.
Преимущества применения формата SOAP:
· Более гибкие типы данных.
· Поддержка заголовков и расширений:
Недостатки:
· Использование SOAP для передачи сообщений увеличивает их объём и снижает скорость обработки. В системах, где скорость важна, чаще используется пересылка XML документов через HTTP напрямую, где параметры запроса передаются как обычные HTTP параметры.
· Хотя SOAP является стандартом, различные программы часто генерируют сообщения в несовместимом формате. Например, запрос сгенерированный AXIS-клиентом, не будет понят сервером WebLogic.
Основные концепции протокола: Сторона, которая посылает сообщение SOAP, называется отправителем SOAP, которая получает – получателем SOAP. Путь, который сообщение SOAP проходит от исходного отправителя до конечного получателя называется маршрутом сообщения. Маршрут сообщения содержит исходного отправителя, конечного получателя и 0 или более посредников SOAP. Объекты, которые обрабатывают сообщения согласно правилам определенным протоколам SOAPназываются SOAP-узлами. Элементарная единица информации, которая учавствует в обмене между узлами SOAP называется сообщением SOAP – это документ XML с SOAP оболочкой корневого элемента.