Как это работает
Итак, давайте разберемся как работает эта программа. Нам известно, что метод getCursOnDateXML получает на вход дату и возвращает информацию о курсе валют. Но каким образом передать методу аргумент и как интерпретировать полученный ответ? Для начала, откроем файл defaultDriver.rb и найдем в нем название интересующего нас метода: require 'default.rb' require 'defaultMappingRegistry.rb' require 'soap/rpc/driver'
class DailyInfoSoap < ::SOAP::RPC::Driver DefaultEndpointUrl = "http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx"
Methods = [ ... [ "http://web.cbr.ru/GetCursOnDateXML", "getCursOnDateXML", [ ["in", "parameters", ["::SOAP::SOAPElement", "http://web.cbr.ru/", "GetCursOnDateXML"]], ["out", "parameters", ["::SOAP::SOAPElement", "http://web.cbr.ru/", "GetCursOnDateXMLResponse"]] ], { :request_style => :document, :request_use => :literal, :response_style => :document, :response_use => :literal, :faults => {} } ], ... end end
Видно, что на вход поступает объект класса GetCursOnDateXML, а на выходе мы получаем объект класса GetCursOnDateXMLResponse. Определение этих классов находится в файле default.rb: require 'xsd/qname'
...
# {http://web.cbr.ru/}GetCursOnDateXML # on_date - SOAP::SOAPDateTime class GetCursOnDateXML attr_accessor :on_date
def initialize(on_date = nil) @on_date = on_date end end
# {http://web.cbr.ru/}GetCursOnDateXMLResponse # getCursOnDateXMLResult - # GetCursOnDateXMLResponse::GetCursOnDateXMLResult class GetCursOnDateXMLResponse
# inner class for member: GetCursOnDateXMLResult # {http://web.cbr.ru/}GetCursOnDateXMLResult class GetCursOnDateXMLResult attr_reader :__xmlele_any
def set_any(elements) @__xmlele_any = elements end
def initialize @__xmlele_any = nil end end
attr_accessor :getCursOnDateXMLResult
def initialize(getCursOnDateXMLResult = nil) @getCursOnDateXMLResult = getCursOnDateXMLResult end end
...
С классом GetCursOnDateXML все достаточно очевидно. В его конструктор достаточно передать дату, что мы и делаем в строке 26 листинга get_curs.rb. Класс GetCursOnDateXMLResponse выглядит несколько сложнее. В нем определен метод getCursOnDateXMLResult который, судя по всему, и возвращает результат. Но в каком формате? Давайте заглянем в WSDL-файл и найдем там описание типа GetCursOnDateXMLResult: ... <s:element minOccurs="0" maxOccurs="1" name="GetCursOnDateXMLResult"> <s:complexType mixed="true"> <s:sequence> <s:any/> </s:sequence> </s:complexType> </s:element> ... Из этого фрагмента можно заключить, что GetCursOnDateXMLResult - это список "чего угодно" (s:any). В таких ситуациях на помощь приходит включение отладочного вывода (см. строку 23 листинга get_curs.rb) и irb - интерактивная консоль Ruby. При помощи irb можно наблюдать как исполняется код по мере его написания.
Итак, запускаем irb (из директории где находятся наши файлы), загружаем библиотеку SOAP4R и клиентские заглушки:
$ irb irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require_gem 'soap4r'
=> true
irb(main):003:0> require 'defaultDriver.rb'
=> true
irb(main):004:0> Создаем объект-драйвер для работы с веб-сервисом и включаем отладочный вывод:
irb(main):004:0> serv = DailyInfoSoap.new
=> #<DailyInfoSoap:#<SOAP::RPC::Proxy:http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx>>
irb(main):005:0> serv.wiredump_dev = STDERR
=> #<IO:0xb7c4ff60>
irb(main):006:0> Далее создаем запрос с текущей датой и отправляем его на сервер:
irb(main):006:0> request = GetCursOnDateXML.new(DateTime.now)
=> #<GetCursOnDateXML:0xb76797d0 @on_date=#<DateTime: 70695916331796971/28800000000,1/6,2299161>>
irb(main):007:0> response = serv.getCursOnDateXML(request)
Wire dump:
= Request
! CONNECT TO www.cbr.ru:80 ! CONNECTION ESTABLISHED POST /DailyInfoWebServ/DailyInfo.asmx HTTP/1.1 SOAPAction: "http://web.cbr.ru/GetCursOnDateXML" Content-Type: text/xml; charset=utf-8 User-Agent: SOAP4R/1.5.8 (/187, ruby 1.8.6 (2007-03-13) [i586-linux-gnu]) Date: Tue, 09 Sep 2008 19:36:45 GMT Content-Length: 405 Host: www.cbr.ru
<?xml version="1.0" encoding="utf-8" ?> <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body> <n1:GetCursOnDateXML xmlns:n1="http://web.cbr.ru/"> <n1:On_date>2008-09-09T23:36:35.390913+04:00</n1:On_date> </n1:GetCursOnDateXML> </env:Body> </env:Envelope>
= Response
HTTP/1. 1 200 OK Date: Tue, 09 Sep 2008 19:40:34 GMT Server: Microsoft-IIS/6.0 X-Powered-By: ASP.NET X-AspNet-Version: 2.0.50727 Cache-Control: no-cache Pragma: no-cache Expires: -1 Content-Type: text/xml; charset=utf-8 Content-Length: 7601
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:soap="http://schemas. xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance " xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <GetCursOnDateXMLResponse xmlns="http://web.cbr.ru/"> <GetCursOnDateXMLResult> <ValuteData xmlns=""> <ValuteCursOnDate> <Vname>Австралийский доллар</Vname> <Vnom>1</Vnom> <Vcurs>21.0261</Vcurs> <Vcode>36</Vcode> <VchCode>AUD</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Фунт стерлингов Соединенного королевства</Vname> <Vnom>1</Vnom> <Vcurs>44.9826</Vcurs> <Vcode>826</Vcode> <VchCode>GBP</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Белорусский рубль</Vname> <Vnom>1000</Vnom> <Vcurs>11.9615</Vcurs> <Vcode>974</Vcode> <VchCode>BYR</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Датская крона</Vname> <Vnom>10</Vnom> <Vcurs>48.5829</Vcurs> <Vcode>208</Vcode> <VchCode>DKK</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Доллар США</Vname> <Vnom>1</Vnom> <Vcurs>25.2626</Vcurs> <Vcode>840</Vcode> <VchCode>USD</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Евро</Vname> <Vnom>1</Vnom> <Vcurs>36.2670</Vcurs> <Vcode>978</Vcode> <VchCode>EUR</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Исландская крона</Vname> <Vnom>100</Vnom> <Vcurs>28.8435</Vcurs> <Vcode>352</Vcode> <VchCode>ISK</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Казахский тенге</Vname> <Vnom>100</Vnom> <Vcurs>21.1120</Vcurs> <Vcode>398</Vcode> <VchCode>KZT</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Канадский доллар</Vname> <Vnom>1</Vnom> <Vcurs>23.8642</Vcurs> <Vcode>124</Vcode> <VchCode>CAD</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Китайский юань Жэньминьби</Vname> <Vnom>10</Vnom> <Vcurs>36.9304</Vcurs> <Vcode>156</Vcode> <VchCode>CNY</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Норвежская крона</Vname> <Vnom>10</Vnom> <Vcurs>45.2719</Vcurs> <Vcode>578</Vcode> <VchCode>NOK</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>СДР (специальные права заимствования)</Vname> <Vnom>1</Vnom> <Vcurs>39.0691</Vcurs> <Vcode>960</Vcode> <VchCode>XDR</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Сингапурский доллар</Vname> <Vnom>1</Vnom> <Vcurs>17.7668</Vcurs> <Vcode>702</Vcode> <VchCode>SGD</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Новая турецкая лира</Vname> <Vnom>1</Vnom> <Vcurs>20.7564</Vcurs> <Vcode>949</Vcode> <VchCode>TRY</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Украинская гривна</Vname> <Vnom>10</Vnom> <Vcurs>53.5225</Vcurs> <Vcode>980</Vcode> <VchCode>UAH</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Шведская крона</Vname> <Vnom>10</Vnom> <Vcurs>38.3377</Vcurs> <Vcode>752</Vcode> <VchCode>SEK</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Швейцарский франк</Vname> <Vnom>1</Vnom> <Vcurs>22.5962</Vcurs> <Vcode>756</Vcode> <VchCode>CHF</VchCode> </ValuteCursOnDate> <ValuteCursOnDate> <Vname>Японская иена</Vname> <Vnom>100</Vnom> <Vcurs>23.2653</Vcurs> <Vcode>392</Vcode> <VchCode>JPY</VchCode> </ValuteCursOnDate> </ValuteData> </GetCursOnDateXMLResult> </GetCursOnDateXMLResponse> </soap:Body> </soap:Envelope> => #<GetCursOnDateXMLResponse:0xb7658c74 @getCursOnDateXMLResult=#<GetCursOnDate ...
В отладочном выводе хорошо видно как вызов метода трансформируется в SOAP-сообщение. Ответ также очень наглядный: мы видим последовательность вложенных тегов: GetCursOnDateXMLResponse, GetCursOnDateXMLResult и ValuteData. Внутри ValuteData находится список тегов ValuteCursOnDate по одному на каждую валюту. Названия атрибутов также вполне ожидаемые - Vname, Vnom, Vcurs, Vcode и VchCode. Но как нам получить доступ к этим данным из кода Ruby? Для ответа на этот вопрос давайте выведем имена всех доступных методов объекта response в алфавитном порядке:
irb(main):008:0> response.methods.sort
=> ["==", "===", "=~", "__id__", "__send__", "class", "clone", "dclone", "display", "dup", "eql?", "equal?", "extend", "freeze", "frozen?", "gem", "getCursOnDateXMLResult", "getCursOnDateXMLResult=", "hash", "id", "inspect", "instance_eval", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "instance_variables", "is_a?", "kind_of?", "method", "methods", "nil?", "object_id", "private_methods", "protected_methods", "public_methods", "require", "require_gem", "respond_to?", "send", "singleton_methods", "taint", "tainted?", "to_a", "to_s", "type", "untaint"]
irb(main):009:0> Обратите внимание на метод getCursOnDateXMLResult. Посмотрим, что он возвращает:
irb(main):009:0> response.getCursOnDateXMLResult.methods.sort
=> ["==", "===", "=~", "__id__", "__send__", "__xmlele_any", "class", "clone", "dclone", "display", "dup", "eql?", "equal?", "extend", "freeze", "frozen?", "gem", "hash", "id", "inspect", "instance_eval", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "instance_variables", "is_a?", "kind_of?", "method", "methods", "nil?", "object_id", "private_methods", "protected_methods", "public_methods", "require", "require_gem", "respond_to?", "send", "set_any", "singleton_methods", "taint", "tainted?", "to_a", "to_s", "type", "untaint", "valuteData","valuteData="]
irb(main):010:0> В списке имеется метод valuteData. Посмотрим что внутри:
irb(main):010:0> response.getCursOnDateXMLResult.valuteData.methods.sort
=> ["==", "===", "=~", "[]", "[]=", "__add_xmlele_value", "__id__", "__send__", "__xmlattr", "__xmlele", "class", "clone", "dclone", "display", "dup", "eql?", "equal?", "extend", "freeze", "frozen?", "gem", "hash", "id", "inspect", "instance_eval", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "instance_variables", "is_a?", "kind_of?", "marshal_dump", "marshal_load", "method", "methods", "nil?", "object_id", "private_methods", "protected_methods", "public_methods", "require", "require_gem", "respond_to?", "send", "singleton_methods", "taint", "tainted?", "to_a", "to_s", "type", "untaint", "valuteCursOnDate","valuteCursOnDate="] irb(main):011:0> Мы почти у цели. Посмотрим, какой объект возвращает метод valuteCursOnDate:
irb(main):011:0> response.getCursOnDateXMLResult.valuteData.valuteCursOnDate.class
=> Array
irb(main):012:0> response.getCursOnDateXMLResult.valuteData.valuteCursOnDate.length
=> 18 irb(main):013:0> Ага! Этот метод возвращает массив из 18 элементов. По всей видимости это и есть список валют. Для проверки нашей догадки, возьмем какой-нибудь элемент массива и посмотрим как он выглядит:
irb(main):013:0> response.getCursOnDateXMLResult.valuteData.valuteCursOnDate[4].methods.sort
=> ["==", "===", "=~", "[]", "[]=", "__add_xmlele_value", "__id__", "__send__", "__xmlattr", "__xmlele", "class", "clone", "dclone", "display", "dup", "eql?", "equal?", "extend", "freeze", "frozen?", "gem", "hash", "id", "inspect", "instance_eval", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "instance_variables", "is_a?", "kind_of?", "marshal_dump", "marshal_load", "method", "methods", "nil?", "object_id", "private_methods", "protected_methods", "public_methods", "require", "require_gem", "respond_to?", "send", "singleton_methods", "taint", "tainted?", "to_a", "to_s", "type", "untaint", "vchCode", "vchCode=", "vcode", "vcode=", "vcurs", "vcurs=", "vname", "vname=", "vnom", "vnom="]
irb(main):014:0> response.getCursOnDateXMLResult.valuteData.valuteCursOnDate[4].vchCode
=> "USD"
irb(main):015:0> response.getCursOnDateXMLResult.valuteData.valuteCursOnDate[4]['VchCode']
=> "USD" irb(main):016:0> Действительно, каждый элемент массива соответствует определенной валюте. К атрибутам можно обратиться либо при помощи методов vname, vnom, vcurs, vcode, vchCode либо как к элементам хеша. Названия ключей при этом совпадают с названиями тегов в ответном XML-файле: Vname, Vnom, Vcurs, Vcode и VchCode. Теперь код тестовой программы становится очевидным: мы формируем запрос, отправляем его на сервер, получаем ссылку на массив валют, а затем бежим по этому массиву и выводим значения атрибутов. Вот и все!
Отмечу, что в более сложных ситуациях неоценимую помощь оказывают инструменты для отладки веб-сервисов, например, . При помощи этой программы можно вручную формировать SOAP-запросы и анализировать ответ от сервера.
Подключение к веб-сервису
Описание веб-сервиса, к которому мы собираемся подключаться, расположено по адресу . Описание задается на языке WSDL - Web Service Description Language. Фактически, это XML-документ определенной структуры.
В описании указывается какие методы предоставляет веб-сервис и как их следует вызывать. Нас интересует метод getCursOnDateXML. Он принимает в качестве аргумента дату и возвращает массив записей следующего вида:
Название валюты (Vname);
Номинал (Vnom);
Курс (Vcurs);
Цифровой код валюты (Vcode);
Символьный код валюты (VchCode).
Для того чтобы воспользоваться веб-сервисом нам необходимо сгенерировать клиентские заглушки (stubs). Эта процедура выполняется при помощи программы wsdl2ruby.rb, которая входит в состав библиотеки SOAP4R:
wsdl2ruby.rb --wsdl http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?WSDL --type client
ignored element: {http://schemas.xmlsoap.org/wsdl/soap12/}binding ignored element: {http://schemas.xmlsoap.org/wsdl/soap12/}operation ignored element: {http://schemas.xmlsoap.org/wsdl/soap12/}body ignored element: {http://schemas.xmlsoap.org/wsdl/soap12/}address I, [2008-09-07T00:04:04.847391 #5995] INFO -- app: Creating class definition. I, [2008-09-07T00:04:04.847750 #5995] INFO -- app: Creates file 'default.rb'. I, [2008-09-07T00:04:05.060365 #5995] INFO -- app: Creating mapping registry definition. I, [2008-09-07T00:04:05.060776 #5995] INFO -- app: Creates file 'defaultMappingRegistry.rb'. I, [2008-09-07T00:04:05.119316 #5995] INFO -- app: Creating driver. I, [2008-09-07T00:04:05.119752 #5995] INFO -- app: Creates file 'defaultDriver.rb'. I, [2008-09-07T00:04:05.219052 #5995] INFO -- app: Creating client skelton. I, [2008-09-07T00:04:05.219560 #5995] INFO -- app: Creates file 'DailyInfoClient.rb'. I, [2008-09-07T00:04:05.319125 #5995] INFO -- app: End of app. (status: 0) Как видно из отладочного вывода, программа сгенерировала четыре файла:
default.rb;
defaultMappingRegistry.rb;
defaultDriver.rb;
DailyInfoClient.rb.
Для работы с веб-сервисом необходимы первые три.
Последний файл не обязателен. Это пример клиентского кода. Итак, теперь все готово для написания тестового приложения: #!/usr/bin/ruby
# # get_curs.rb # # Александр Симаков, <xdr (тчк) box на Google Mail> # http://alexander-simakov.blogspot.com/ #
# Подключаем библиотеку SOAP4R require 'rubygems' require_gem 'soap4r'
# Подключаем клиентские заглушки require 'defaultDriver.rb'
# При помощи этого объекта мы будем вызывать # методы веб-сервиса serv = DailyInfoSoap.new
# Выводить отладочную информацию если ruby # был запущен с ключом -d serv.wiredump_dev = STDERR if $DEBUG
# Формируем запрос request = GetCursOnDateXML.new(DateTime.now)
# Отправляем запрос на сервер и получаем ответ response = serv.getCursOnDateXML(request)
# Анализируем ответ и выводим результат items = response.getCursOnDateXMLResult.valuteData.valuteCursOnDate
items.each do |item| puts "---------------------------------" puts "Название: " + item['Vname'].strip puts "Числовой код: " + item['Vcode'] puts "Символьный код: " + item['VchCode'] puts "Номинал: " + item['Vnom'] puts "Курс: " + item['Vcurs'] end
Сохраните этот файл в той же директории и запустите его на выполнение. Вот как выглядел результат на момент написания статьи:
--------------------------------- Название: Австралийский доллар Числовой код: 36 Символьный код: AUD Номинал: 1 Курс: 21.0261 --------------------------------- Название: Фунт стерлингов Соединенного королевства Числовой код: 826 Символьный код: GBP Номинал: 1 Курс: 44.9826 --------------------------------- Название: Белорусский рубль Числовой код: 974 Символьный код: BYR Номинал: 1000 Курс: 11.9615 --------------------------------- Название: Датская крона Числовой код: 208 Символьный код: DKK Номинал: 10 Курс: 48.5829 --------------------------------- Название: Доллар США Числовой код: 840 Символьный код: USD Номинал: 1 Курс: 25.2626 --------------------------------- Название: Евро Числовой код: 978 Символьный код: EUR Номинал: 1 Курс: 36.2670 --------------------------------- Название: Исландская крона Числовой код: 352 Символьный код: ISK Номинал: 100 Курс: 28.8435 --------------------------------- Название: Казахский тенге Числовой код: 398 Символьный код: KZT Номинал: 100 Курс: 21.1120 --------------------------------- Название: Канадский доллар Числовой код: 124 Символьный код: CAD Номинал: 1 Курс: 23.8642 --------------------------------- Название: Китайский юань Жэньминьби Числовой код: 156 Символьный код: CNY Номинал: 10 Курс: 36.9304 --------------------------------- Название: Норвежская крона Числовой код: 578 Символьный код: NOK Номинал: 10 Курс: 45.2719 --------------------------------- Название: СДР (специальные права заимствования) Числовой код: 960 Символьный код: XDR Номинал: 1 Курс: 39.0691 --------------------------------- Название: Сингапурский доллар Числовой код: 702 Символьный код: SGD Номинал: 1 Курс: 17.7668 --------------------------------- Название: Новая турецкая лира Числовой код: 949 Символьный код: TRY Номинал: 1 Курс: 20.7564 --------------------------------- Название: Украинская гривна Числовой код: 980 Символьный код: UAH Номинал: 10 Курс: 53.5225 --------------------------------- Название: Шведская крона Числовой код: 752 Символьный код: SEK Номинал: 10 Курс: 38.3377 --------------------------------- Название: Швейцарский франк Числовой код: 756 Символьный код: CHF Номинал: 1 Курс: 22.5962 --------------------------------- Название: Японская иена Числовой код: 392 Символьный код: JPY Номинал: 100 Курс: 23.2653 Отмечу, что данные возвращаются в кодировке UTF-8.Таким образом, если на вашем компьютере используется другая кодировка, то названия валют будут нечитаемыми. В этом случае придется конвертировать данные вручную.
Установка
Для работы с веб-сервисами потребуется библиотека . Не смотря на то, что облегченная версия этой библиотеки уже включена в стандартную поставку Ruby, настоятельно рекомендуется установить полную версию. Если вы подключены к интернету напрямую, то достаточно всего одной команды:
# gem install soap4r
Bulk updating Gem source index for: http://gems.rubyforge.org Install required dependency httpclient? [Yn] Y Successfully installed soap4r-1.5.8 Successfully installed httpclient-2.1.2 Installing ri documentation for httpclient-2.1.2... Installing RDoc documentation for httpclient-2.1.2... Если вы подключены через прокси, то перед инсталляцией потребуется задать переменную окружения http_proxy. В Линуксе это можно сделать следующим образом:
export http_proxy=http://user:password@host:port Для установки переменной окружения под Windows выберите "Мой компьютер" -> "Свойства" -> "Дополнительно" -> "Переменные среды"-> "Создать".
Если при работе через прокси-сервер вы столкнетесь с ошибкой "407 Proxy Authentication Required", прочитайте . Там описывается решение проблемы.
это технология, позволяющая приложениям, написанным
Веб-сервисы - это технология, позволяющая приложениям, написанным на разных языках программирования и работающих на различных программно-аппаратных платформах легко обмениваться данными через четко-определенные интерфейсы. По своей сути, веб-сервисы являются одним из воплощений технологии - удаленного вызова процедур. В основе веб-сервисов лежат следующие стандарты:
- для передачи структурированных данных;
- протокол обмена сообщениями на базе XML;
- язык описания интерфейсов веб-сервисов;
- каталог веб-сервисов.
В этой статье рассказывается о том как подключаться к веб-сервисам с использованием языка программирования Ruby. В качестве примера мы рассмотрим для получения курса валют.
это идеальное решение для интеграции
Веб-сервисы - это идеальное решение для интеграции программных систем. По сути, это единый язык на котором могут говорить приложения, написанные разными людьми на разных языках программирования и работающих на разных программно-аппаратных платформах. Сама технология базируется на открытых стандартах и имеет хорошую поддержку во всех современных языках программирования. Не исключение и Ruby. Как мы смогли убедиться, код на Ruby получается очень простым, компактным и выразительным.