 | Уровень сложности: средний Джеймс Снелл, разработчик программного обеспечения, IBM
11.09.2009 Реализация основанного на Web-браузере клиента SOAP Web-сервисов с использованием шаблона проектирования Ajax (Asynchronous JavaScript and XML)
Данная статья, первая в короткой серии, описывает реализацию межплатформенного, основанного на 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 (автор: Филипп Маккарти)
Эта диаграмма показывает, как именно функционирует объект 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 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
Следующие шаги
Библиотека Web Services JavaScript Library может быть использована для интеграции базовых SOAP Web-сервисов в ваши Web-приложения – простым и независимым от браузера образом. В следующей статье этой серии будет описано применение этой библиотеки для вызова более сложных Web-сервисов, основанных на семействе спецификаций WS-Resource Framework, а также будут исследованы способы, посредством которых способности этих Web-сервисов могут быть расширены и интегрированы в то или иное Web-приложение.
Загрузка | Описание | Имя | Размер | Метод загрузки |
|---|
| Пример проекта | ws-wsajaxcode.zip | 19 KB | HTTP |
|---|
Ресурсы Научиться
Получить продукты и технологии
Обсудить
Об авторе  | |  | Джеймс Снелл (James Snell) является членом исследовательской лаборатории IBM WebAhead, занимающейся разработкой опытных образцов стандартов и технологий программного обеспечения, находящегося в стадии создания, для собственных нужд IBM. Его научно-исследовательские интересы охватывают широкий круг направлений современных технологий, а именно Atom, AJAX, REST, Open Source, персональные издательские системы, семантические и ситуационные Web-приложения. Он является активным участником проекта Apache Abdera, находящегося в настоящее время на этапе разработки и нацеленного на создание высокоэффективной и функционально законченной реализации стандартов формата синдикации Atom и протокола публикации Atom. |
Выскажите мнение об этой странице
|  |