Вызов SOAP Web-сервисов с помощью Ajax: Часть 1. Построение клиента Web-сервисов

Реализация основанного на Web-браузере клиента SOAP Web-сервисов с использованием шаблона проектирования Ajax (Asynchronous JavaScript and XML)

Джеймс Снелл, разработчик программного обеспечения, IBM

Джеймс Снелл (James Snell) является членом исследовательской лаборатории IBM WebAhead, занимающейся разработкой опытных образцов стандартов и технологий программного обеспечения, находящегося в стадии создания, для собственных нужд IBM. Его научно-исследовательские интересы охватывают широкий круг направлений современных технологий, а именно Atom, AJAX, REST, Open Source, персональные издательские системы, семантические и ситуационные Web-приложения. Он является активным участником проекта Apache Abdera, находящегося в настоящее время на этапе разработки и нацеленного на создание высокоэффективной и функционально законченной реализации стандартов формата синдикации Atom и протокола публикации Atom.



11.09.2009

Данная статья, первая в короткой серии, описывает реализацию межплатформенного, основанного на JavaScript, клиента SOAP Web-сервисов на базе шаблона проектирования Web-приложений под названием Ajax (Asynchronous JavaScript and XML).

Технология Ajax, получившая известность благодаря использованию в популярных сервисах для Web-приложений, таких как GMail, Google Maps, Flickr и Odeo.com, предоставляет Web-разработчикам способ для повышения полезности и функциональности Web-приложений посредством асинхронного обмена XML-сообщениями. Представленная в этой статье библиотека Web Services JavaScript Library расширяет фундаментальные механизмы, лежащие в основе шаблона проектирования Ajax, реализуя поддержку вызова Web-сервисов на базе SOAP (так называемых «SOAP Web-сервисов»).

Web-сервисы в браузере

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

Одним из таких механизмов, одинаково работающих во всех браузерах, является API-интерфейс XMLHttpRequest, который лежит в основе шаблона проектирования Ajax. Механизм XMLHttpRequest подробно описан в статье Филиппа Маккарти (Philip McCarthy), опубликованной на сайте developerWorks. XMLHttpRequest – это Javascript-объект, используемый для выполнения асинхронных HTTP-запросов. В указанной статье представлена диаграмма последовательности (рисунок 1), позволяющая понять место XMLHttpRequest-объекта в шаблоне проектирования Ajax (в разделе Ресурсы приведена ссылка на полный текст этой статьи).

Рисунок 1. Диаграмма последовательности Ajax (автор: Филипп Маккарти)
Рисунок 1. Диаграмма последовательности Ajax (автор: Филипп Маккарти)

Эта диаграмма показывает, как именно функционирует объект XMLHttpRequest. Часть JavaScript-кода, исполняющегося внутри Web-браузера, создает экземпляр LHttpRequest-объекта и функцию, которая служит в качестве асинхронного обратного вызова. Затем скрипт использует объект XMLHttpRequest для выполнения HTTP-операции с сервером. После получения запроса осуществляется вызов функции обратного вызова. Внутри этой функции может осуществляться обработка возвращаемых данных. Если эти данные представлены в формате XML, то объект XMLHttpRequest автоматически осуществляет разбор этих данных с помощью встроенных механизмов обработки XML-данных.

К сожалению, основная трудность подхода на основе Ajax заключается именно в деталях того, каким образом XMLHttpRequest-объект осуществляет автоматический разбор XML-данных. Для примера предположим, что запрашиваемые мною данные представляют собой так называемый SOAP Envelope (конверт SOAP), содержащий элементы из нескольких разных пространств имен XML Namespace, и я хочу получить значение атрибута >attr, принадлежащего элементу yetAnotherElement (листинг 1).

Листинг 1. SOAP Envelope с несколькими пространствами имен
<s:Envelope 
  xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <s:Header/>
  <s:Body>
    <m:someElement xmlns:m="http://example">
      <n:someOtherElement 
        xmlns:n="http://example" 
        xmlns:m="urn:example">
        <m:yetAnotherElement 
          n:attr="abc" 
          xmlns:n="urn:foo"/>
      </n:someOtherElement>
    </m:someElement>
  </s:Body>
</s:Envelope>

В браузерах Mozilla и Firefox извлечение значения атрибута attr представляет собой достаточно простую процедуру (листинг 2).

Листинг 2. Метод для извлечения значения атрибута attr в браузерах Mozilla и Firefox (не работает в браузере Internet Explorer)
var m = el.getElementsByTagNameNS(
  'urn:example',
  'yetAnotherElement')[0].
    getAttributeNS(
      'urn:foo',
      'attr');
alert(m); // displays 'abc'

Несколько слов о безопасности

Вследствие некоторых вполне реальных проблем безопасности возможности XMLHttpRequest-объекта в большинстве Web-браузеров по умолчанию ограничены – ему разрешается взаимодействовать только с теми ресурсами и сервисами, которые размещены в том же домене, что и просматриваемая пользователем Web-страница. Например, если я в настоящее время просматриваю страницу, размещенную по адресу http://example.com/myapp/, то XMLHttpRequest-объекту разрешается получать доступ только к ресурсам, размещенным в домене example.com. Эта предосторожность необходима для предотвращения несанкционированного доступа потенциально злонамеренного приложения к защищаемой информации. Поскольку представленный в этой статье клиент Web-сервисов базируется на XMLHttpRequest-объекте, описанное ограничение аналогичным образом применяется и к Web-сервисам, которые вы сможете вызвать.

Если вам необходимо получить доступ к Web-сервисам, расположенным в другом домене, то вы можете использовать следующие два решения:

  • Цифровая подпись скрипта JavaScript. Посредством цифровой подписи вы «говорите» Web-браузеру, что вашему скрипту JavaScript можно доверять, т.е. он не станет осуществлять какой-либо злонамеренной деятельности, поэтому ограничение на доступ XMLHttpRequest-запроса к данным должно быть снято.
  • Использование прокси. Более простое решение состоит в передаче всех запросов от XMLHttpRequest-объекта через прокси-ресурс, расположенный в том же домене, что и загруженная страница. Этот прокси-ресурс пересылает указанные запросы на удаленное местоположение и возвращает результаты в браузер. С точки зрения XMLHttpRequest-объекта, это взаимодействие происходит внутри существующей конфигурации безопасности.

К сожалению, приведенный выше код не будет работать в Internet Explorer 6, поскольку в этом браузере функция getElementsByTagNameNS не реализована, а фактически применяется достаточно бесполезный подход – префиксы XML Namespace рассматриваются как к части имен элементов и атрибутов.

Недостаточная поддержка механизма XML Namespaces в браузере Internet Explorer сильно затрудняет «браузеронезависимое» применение XML-форматов, активно использующих пространства имен, таких как SOAP. Для выполнения таких простых операций как извлечение значения атрибута из результата, приходится писать специальный код, обеспечивающий единообразие ожидаемого поведения во всех браузерах. К счастью, такой «специальный» код допускает инкапсуляцию и многократное использование.

Для вызова Web-сервисов изнутри Web-браузера и для надежной обработки SOAP-сообщений вам необходимо сначала осознать проблемы безопасности (см. врезку «Несколько слов о безопасности»). Кроме того, вы должны написать библиотеку JavaScript-скриптов (рисунок 2), которая обеспечит абстрагирование от несогласованности XML-реализаций в различных браузерах. Это позволит вам работать с данными Web-сервисов непосредственно.

Рисунок 2. Вызов Web-сервиса из скрипта Javascript изнутри Web-браузера с помощью библиотеки Web Services JavaScript Library
Рисунок 2. Вызов Web-сервиса из скрипта Javascript изнутри Web-браузера с помощью библиотеки Web Services JavaScript Library

Показанная на рисунке 2 библиотека Web Services JavaScript Library (ws.js) представляет собой набор объектов и вспомогательных функций JavaScript, обеспечивающих базовый уровень поддержки Web-сервисов на основе спецификации SOAP 1.1. В состав библиотеки Ws.js входят следующие объекты:

  • WS.Call: Клиент Web-сервиса, который служит оболочкой для XMLHttpRequest-объекта
  • WS.QName: Реализация XML Qualified Name
  • WS.Binder: Основа для специальных сериализаторов/десериализаторов XML
  • WS.Handler: Основа для обработчиков запросов/ответов
  • SOAP.Element: Базовый элемент SOAP, который служит оболочкой для модели XML DOM
  • SOAP.Envelope: Объект SOAP Envelope, расширяющий SOAP.Element
  • SOAP.Header: Объект SOAP Header, расширяющий SOAP.Element
  • SOAP.Body: Объект SOAP Body, расширяющий SOAP.Element
  • XML: Межплатформенные обслуживающие методы для обработки XML

Ядром библиотеки ws.js является объект WS.Call, предоставляющий методы для вызова Web-сервиса. Именно WS.Call несет основную ответственность за взаимодействие с XMLHttpRequest-объектом и за обработку SOAP-откликов.

Объект WS.Call представляет следующие три метода.

  • add_handler. Добавляет обработчики запросов/обработчики откликов в цепочку обработки. Вызов объектов-обработчиков осуществляется до и после обращения к Web-сервису, что обеспечивает расширяемые возможности обработки до и после обращения.
  • invoke. Посылает заданный объект SOAP.Envelope в Web-сервис, а после получения ответа осуществляет обратный вызов. Рекомендуется применять этот метод при вызове ориентированных на документы Web-сервисов, использующих буквенное кодирование XML.
  • invoke_rpc. Создает конверт SOAP.Envelope, инкапсулирующий запрос RPC-типа, и посылает его в Web-сервис, а после получения ответа осуществляет обратный вызов.

Хотя в общем случае объект WS.Call – это всего лишь тонкая «обертка» поверх XMLHttpRequest-объекта, он действительно выполняет многие полезные действия. К числу этих действий относится и настройка HTTP-заголовка SOAPAction, который требуется в соответствии со спецификацией SOAP 1.1.


Использование библиотеки ws.js

API-интерфейс, представляемый библиотекой Web services JavaScript Library, достаточно прост.

Объекты SOAP.* (SOAP.Element, SOAP.Envelope, SOAP.Header и SOAP.Body) предоставляют разработчику средства для построения и чтения пакетов SOAP Envelope (листинг 3), благодаря чему достигается абстрагирование от вспомогательных деталей работы с моделью XML DOM.

Листинг 3. Построение пакета SOAP Envelope
var envelope = new SOAP.Envelope();
var body = envelope.create_body();
var el = body.create_child(new WS.QName('method','urn:foo'));
el.create_child(new WS.QName('param','urn:foo')).set_value('bar');

В листинге 4 показан пакет SOAP Envelope, созданный кодом из листинга 3.

Листинг 4. Построение пакета SOAP Envelope
<Envelope xmlns="http://schemas.xmlsoap.org">
  <Body>
    <method xmlns="urn:foo">
      <param>bar</param>
    </method>
  </Body>
</Envelope>

Если создаваемый вами пакет SOAP Envelope является запросом в стиле RPC, то элемент SOAP.Body предоставляет эффективный метод set_rpc (листинг 5), который сконструирует все тело запроса из таких компонентов как заданное имя операции, массив входных параметров и URI-идентификаторы, закодированные в стиле SOAP.

Листинг 5. Построение пакета RPC-Request Envelope
var envelope = new SOAP.Envelope();
var body = envelope.create_body();
body.set_rpc(
  new WS.QName('param','urn:foo'),
  new Array(
    {name:'param',value:'bar'}
  ), SOAP.NOENCODING
);

Каждый параметр передается внутрь в виде структуры JavaScript-объектов со следующими ожидаемыми свойствами:

  • name. Строка или объект WS.QName; определяет имя параметра. Обязательное свойство.
  • value. Значение параметра. Если тип значения не относится к категории простых типов данных (строка, целое число и т.д.), то должен быть указан объект WS.Binder, способный сериализовать это значение в соответствующую XML-структуру. Обязательное свойство.
  • xsitype: Имя WS.Qname, идентифицирующее тип параметра XML Schema Instance Type (например, если xsi:type=”int”, то xsitype:new WS.Qname(‘int’,’http://www.w3.org/2000/10/XMLSchema’)). Опциональное свойство.
  • encodingstyle.: URI-идентификатор в стиле SOAP, используемый текущим параметром. Опциональное свойство.
  • binder: Реализация WS.Binder, способная сериализовать данный параметр в XML. Опциональное свойство

Например, для задания параметра с именем "abc", относящимся к пространству XML-имен "urn:foo", со свойством xsi:type="int" и со значением "3" можно использовать следующий код: new Array({name:new WS.QName('abc','urn:foo'), value:3, xsitype:new WS.QName('int','http://www.w3.org/2000/10/XMLSchema')}).

После того как будет построен «конверт» SOAP.Envelope для обращения к сервису, я передам этот конверт в метод invoke объекта WS.Call с целью вызова метода, закодированного внутри конверта: (new WS.Call(service_uri)).invoke(envelope, callback)

В качестве альтернативного варианта для «ручного» построения SOAP.Envelope я могу передать операцию WS.QName, массив параметров и стиль кодирования в метод invoke_rpc объекта WS.Call (листинг 6).

Листинг 6. Использование объекта WS.Call для вызова Web-сервиса
var call = new WS.Call(serviceURI); 
var nsuri = 'urn:foo';
var qn_op = new WS.QName('method',nsuri);
var qn_op_resp = new WS.QName('methodResponse',nsuri);  
  call.invoke_rpc(
    qn_op,
    new Array(
      {name:'param',value:'bar'}
    ),SOAP.NOENCODING,
    function(call,envelope) {
      // envelope is the response SOAP.Envelope
      // the XML Text of the response is in arguments[2]
    }
  );

После вызова метода invoke или метода invoke_rpc объект WS.Call создаст обеспечивающий XMLHttpRequest-объект, передаст XML-элементы, содержащие конверт SOAP Envelope, получит и обработает ответ, а затем инициирует функцию обратного вызова.

Для поддержания возможности расширенной пред- и постобработки SOAP-сообщений объект WS.Call позволяет вам зарегистрировать набор объектов WS.Handler (листинг 7). Вызов этих объектов осуществляется для каждого запроса, для каждого отклика и для каждой ошибки на протяжении цикла вызова. Новые обработчики могут быть реализованы посредством расширения JavaScript-объекта WS.Handler.

Листинг 7. Создание и регистрация обработчиков запросов/обработчиков откликов
var MyHandler = Class.create();
MyHandler.prototype = (new WS.Handler()).extend({
  on_request : function(envelope) {
     // pre-request processing
  },
  on_response : function(call,envelope) {
     // post-response, pre-callback processing
  },
  on_error : function(call,envelope) {
  }
});

var call = new WS.Call(...);
call.add_handler(new MyHandler());

Максимальную пользу такие обработчики приносят при внесении информации в передаваемые конверты SOAP Envelope или при извлечении информации из этих конвертов. Например, такой обработчик мог бы автоматически вставлять соответствующие элементы адресации Web-сервисов в заголовок SOAP Envelope (см. пример в листинге 8).

Листинг 8. Пример обработчика, добавляющего к запросу заголовок WS-Addressing
var WSAddressingHandler = Class.create();
WSAddressingHandler.prototype = (new WS.Handler()).extend({
  on_request : function(call,envelope) {  		
    envelope.create_header().create_child(
        new WS.QName('Action','http://ws-addressing','wsa')
      ).set_value('http://www.example.com');
  }
});

Объекты WS.Binder (листинг 9) осуществляют специальную сериализацию/десериализацию объектов SOAP.Element. Любая реализация WS.Binder обязана предоставлять следующие два метода:

  • to_soap_element. Сериализует объект JavaScript в элемент SOAP.Element. Первый передаваемый внутрь параметр представляет собой значение, подлежащее сериализации. Второй параметр – это элемент SOAP.Element, в который должно быть сериализовано передаваемое значение. Этот метод не возвращает никаких значений.
  • to_value_object. Десериализует элемент SOAP.Element в объект JavaScript. Этот метод должен возвратить объект типа «десериализованное значение».
Листинг 9. Пример реализации WS.Binding
var MyBinding = Class.create();
MyBinding.prototype = (new WS.Binding()).extend({
  to_soap_element : function(value,element) {  		
    ...
  },
  to_value_object : function(element) {
    ...
  }
});

Простой пример

Ниже приведен пример проекта, иллюстрирующего основные функциональные возможности библиотеки Web Services JavaScript Library. Использованный в этой демонстрации Web-сервис (показанный в листинге 10) развернут на сервере приложений WebSphere Application Server и реализует простую функцию Hello World.

Листинг 10. Простой Web-сервис «Hello World» на базе Java
package example;

public class HelloWorld {
  public String sayHello(String name) {
    return "Hello " + name;
  }
}

После реализации и развертывания рассматриваемого сервиса на сервере WebSphere Application Server его WSDL-описание (листинг 11) определяет SOAP-сообщение, которое вы должны передать в сервис Hello World.

Листинг 11. Фрагмент кода HelloWorld.wsdl
<wsdl:portType name="HelloWorld">
  <wsdl:operation name="sayHello">
    <wsdl:input 
      message="impl:sayHelloRequest" 
      name="sayHelloRequest"/>
    <wsdl:output 
      message="impl:sayHelloResponse" 
      name="sayHelloResponse"/>
  </wsdl:operation>
</wsdl:portType>

С помощью библиотеки Web Services JavaScript Library вы можете реализовать метод, который будет осуществлять вызов сервиса Hello World (листинг 12).

Листинг 12. Использование объекта WS.Call для вызова сервиса HelloWorld
<html>
<head>
...
<script 
  type="text/javascript" 
  src="scripts/prototype.js"></script>
<script 
  type="text/javascript" 
  src="scripts/ws.js"></script>
<script type="text/javascript">
function sayHello(name, container) {
  var call = new WS.Call('/AjaxWS/services/HelloWorld'); 
  var nsuri = 'http://example';
  var qn_op = new WS.QName('sayHello',nsuri);
  var qn_op_resp = new WS.QName('sayHelloResponse',nsuri);  
  call.invoke_rpc(
    qn_op,
    new Array(
      {name:'name',value:name}
    ),null,
    function(call,envelope) {
      var ret = 
        envelope.get_body().get_all_children()[0].
          get_all_children()[0].get_value();
      container.innerHTML = ret;
      $('soap').innerHTML = arguments[2].escapeHTML();
    }
  );
}
</script>
</head>
...

После этого вы можете вызвать сервис Hello World посредством обращения к функции sayHello из любого места своего Web-приложения (листинг 13).

Листинг 13. Обращение к функции sayHello
<body>
<input name="name" id="name" />
<input value="Invoke the Web Service"
       type="button" 
       onclick="sayHello($('name').value,$('result'))" />
<div id="container">Result:
<div id="result">
</div>
<div id="soap">
</div>
</div>
</body>

Успешный вызов даст результат, показанный на рисунке 3. Исполнение этого примера в браузерах Mozilla, Firefox и Internet Explorer должно приводить к одинаковым результатам.

Рисунок 3. Пример Hello World в браузере Firefox
Рисунок 3. Пример Hello World в браузере Firefox

Следующие шаги

Библиотека Web Services JavaScript Library может быть использована для интеграции базовых SOAP Web-сервисов в ваши Web-приложения – простым и независимым от браузера образом. В следующей статье этой серии будет описано применение этой библиотеки для вызова более сложных Web-сервисов, основанных на семействе спецификаций WS-Resource Framework, а также будут исследованы способы, посредством которых способности этих Web-сервисов могут быть расширены и интегрированы в то или иное Web-приложение.


Загрузка

ОписаниеИмяРазмер
Пример проектаws-wsajaxcode.zip19 KB

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=SOA и web-сервисы
ArticleID=427733
ArticleTitle=Вызов SOAP Web-сервисов с помощью Ajax: Часть 1. Построение клиента Web-сервисов
publish-date=09112009