четверг, 28 августа 2008 г.

Веб-сервисы и Ruby

Введение

Веб-сервисы - это технология, позволяющая приложениям, написанным на разных языках программирования и работающих на различных программно-аппаратных платформах легко обмениваться данными через четко-определенные интерфейсы. По своей сути, веб-сервисы являются одним из воплощений технологии RPC - удаленного вызова процедур. В основе веб-сервисов лежат следующие стандарты:
  • XML - для передачи структурированных данных;
  • SOAP - протокол обмена сообщениями на базе XML;
  • WSDL - язык описания интерфейсов веб-сервисов;
  • UDDI - каталог веб-сервисов.
В этой статье рассказывается о том как подключаться к веб-сервисам с использованием языка программирования Ruby. В качестве примера мы рассмотрим веб-сервис ЦБРФ для получения курса валют.

Установка

Для работы с веб-сервисами потребуется библиотека SOAP4R. Не смотря на то, что облегченная версия этой библиотеки уже включена в стандартную поставку 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", прочитайте этот пост. Там описывается решение проблемы.

Подключение к веб-сервису

Описание веб-сервиса, к которому мы собираемся подключаться, расположено по адресу http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?WSDL. Описание задается на языке 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.
Для работы с веб-сервисом необходимы первые три. Последний файл не обязателен. Это пример клиентского кода. Итак, теперь все готово для написания тестового приложения:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/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. Таким образом, если на вашем компьютере используется другая кодировка, то названия валют будут нечитаемыми. В этом случае придется конвертировать данные вручную.

Как это работает

Итак, давайте разберемся как работает эта программа. Нам известно, что метод getCursOnDateXML получает на вход дату и возвращает информацию о курсе валют. Но каким образом передать методу аргумент и как интерпретировать полученный ответ? Для начала, откроем файл defaultDriver.rb и найдем в нем название интересующего нас метода:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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. Теперь код тестовой программы становится очевидным: мы формируем запрос, отправляем его на сервер, получаем ссылку на массив валют, а затем бежим по этому массиву и выводим значения атрибутов. Вот и все!

Отмечу, что в более сложных ситуациях неоценимую помощь оказывают инструменты для отладки веб-сервисов, например, soapUI. При помощи этой программы можно вручную формировать SOAP-запросы и анализировать ответ от сервера.

Заключение

Веб-сервисы - это идеальное решение для интеграции программных систем. По сути, это единый язык на котором могут говорить приложения, написанные разными людьми на разных языках программирования и работающих на разных программно-аппаратных платформах. Сама технология базируется на открытых стандартах и имеет хорошую поддержку во всех современных языках программирования. Не исключение и Ruby. Как мы смогли убедиться, код на Ruby получается очень простым, компактным и выразительным.

Статья опубликована на портале CITForum

пятница, 22 августа 2008 г.

Научная деятельность

Общая информация

Аспирантура Сыктывкарского государственного университета, математический факультет, кафедра прикладной математики и информатики, специальность 05.13.18 "Математическое моделирование, численные методы и комплексы программ".

Публикации
Код Хаффмана

В этой статье дается подробное описание кода Хаффмана. С тех пор как Дэвид Хаффман опубликовал свою статью "A method for the construction of minimum-redundancy codes" прошло более 50 лет, а его идея живет и находит широчайшее практическое применение и в наши дни.

Статья также опубликована на портале compression.ru

Сжатие изображений при помощи вейвлетных преобразований

В данной статье рассказано как устроено вейвлетное преобразование Баттерворта и вейвлетное преобразование Добеши 9/7. Кроме того изложен принцип работы алгоритма SPIHT (Set Partitioning in Hierarchical Trees) - одного из самых известных и эффективных алгоритмов для кодирования коэффициентов вейвлетного разложения. Все упомянутые в работе алгоритмы реализованы в виде программной библиотеки TiLib.

Симаков А.В. Сжатие изображений при помощи вейвлетных преобразований // Вестник молодых ученых. Серия Прикладная математика и механика. 2004. Вып. 4. С. 53-62.

Прогрессивная передача изображений через Интернет

В данной статье описывается модификация вейвлетного преобразования Баттерворта которая повышает его эффективность. Результаты испытаний и программная реализация прилагаются.

Симаков А.В. Прогрессивная передача изображений через Интернет // Нелинейные проблемы механики и физики деформируемого твердого тела. СПбГУ, 2004. Вып. 8. С. 147-161.

Прогрессивная передача аудио через Интернет

В статье описывается метод для прогрессивного кодирования и передачи аудио сигнала по низкоскоростным каналам связи. Программная реализация (кодек AGRESS) прилагается.

Статья также опубликована на портале CitForum

Параллельное сжатие больших изображений

В этой статье описан эффективный метод для параллельного сжатия больших изображений. Алгоритм сжатия базируется на вейвлетных преобразованиях. Основной акцент в работе делается на исследование практических аспектов процесса сжатия. Алгоритм реализован в виде программы EPSILON, исходный текст которой свободно доступен для загрузки через Интернет.

Симаков А.В. Параллельное сжатие больших изображений // Вестник Сыктывкарского университета. 2008. Вып. 6. Сер. 1.

Уменьшение граничных искажений при разбиении изображения на блоки

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

Симаков А.В. Уменьшение граничных искажений при разбиении изображения на блоки // Статья в печати.

Система параллельного сжатия изображений EPSILON

В данной статье описывается разработанная автором система параллельного сжатия изображений EPSILON. Система базируется на вейвлетных преобразованиях и промышленных стандартах для распараллеливания сложных вычислений. Программа апробирована на различных вычислительных комплексах и продемонстрировала хорошую производительность и масштабируемость. Полные исходные тексты программы доступны для загрузки через Интернет.

Симаков А.В. Система параллельного сжатия изображений EPSILON // Статья в печати.

Параллельное сжатие изображений

В этой статье описывается алгоритм для параллельного сжатия изображений. Приводятся результаты испытаний многопоточной и кластерной версий программы.

Симаков А.В. Параллельное сжатие изображений // Программные продукты и системы. 2008. Вып. 4. С. 88-91.

Гранты

Исследования в области сжатия изображений были поддержаны следующими грантами:
  • РФФИ N 02-01-00084
  • КЦФЕ N М03-2.1Д-35
  • КЦФЕ N М04-2.1Д-541

Конференции

Московская обл., Звенигород, п. Ершово, с 13 по 18 декабря 2004 г. Федеральная итоговая научно-техническая конференция в рамках всероссийского конкурса на лучшие научные работы студентов по естественным, техническим наукам и инновационным научно-образовательным проектам. По результатам конференции был получен диплом I-й степени.

Семинары

Москва, 17 декабря 2007. Лаборатория компьютерной графики и мультимедиа при факультете ВМиК МГУ им. Ломоносова. Доклад "Параллельное сжатие больших изображений".

Проекты
  • Программа для сжатия изображений EPSILON
    • Хорошее соотношение размер/качество
    • Степень сжатия указывается заранее
    • Высокая устойчивость файла к повреждениям
    • Поддержка больших файлов (>> 4 Gb)
    • Более 30 различных вейвлетных фильтров
    • Поддержка потоков стандарта POSIX (для SMP-машин)
    • Поддержка стандарта MPI: программа протестирована на кластерах Научно-исследовательского вычислительного центра МГУ
    • Открытый исходный код (GNU GPL)

  • Хаффман кодер SHCODEC
    • Очень высокая скорость работы
    • Минимальные требования к объему памяти
    • Открытый исходный код

  • Прогрессивный аудио кодер AGRESS
    • Адаптация к пропускной способности канала
    • Нестандартный подход к кодированию аудио
    • Открытый исходный код (GNU GPL)

  • Библиотека для сжатия изображений TiLib
    • Хорошее соотношение размер/качество
    • Степень сжатия указывается заранее
    • Есть инструмент для удаленного просмотра изображений
    • Открытый исходный код (GNU GPL)

  • Сайт по сжатию данных ENTROPYWARE.INFO

воскресенье, 17 августа 2008 г.

Создание графических интерфейсов на Ruby/GTK2

Что такое GTK+

GTK+ или GIMP Toolkit - это одна из наиболее популярных библиотек для построения графических интерфейсов под X Window System. Изначально GTK+ была разработана Спенсером Кимбелом (Spencer Kimball) и Питером Матисом (Peter Mattis) для нужд графического редактора GIMP (GNU Image Manipulation Program), но потом была выделена в отдельный проект. Сама библиотека GTK+ написана на C, но существуют привязки и ко многим другим языкам программирования. Всего их более 15. Среди них есть поддержка и языка Ruby. Именно о связке Ruby/GTK+ и пойдет речь в этой статье.

Установка под Linux

Количество дополнительного программного обеспечения которое придется установить зависит от вашего дистрибутива. Вот приблизительный список:
  • Интерпретатор Ruby
  • Заголовочные файлы для Ruby
  • Библиотека GTK2+ и друзья: GLib2, Pango и др.
  • Заголовочные файлы для GTK2+
После того как базовые библиотеки установлены скачиваем с сайта проекта Ruby-GNOME2 привязки GTK+ для Ruby. На момент написания статьи последней версией была 0.17.0-rc1. Распаковываем и конфигурируем пакет:
$ tar -xzf ruby-gnome2-0.17.0-rc1.tar.gz
$ cd ruby-gnome2-0.17.0-rc1
$ ruby extconf.rb
Обратите внимание на вывод последней команды. В конце должен появиться список обнаруженных библиотек:
-----
Target libraries: glib, gdkpixbuf, pango, atk, gtk, libart
Ignored libraries: gnome, gtkglext, rsvg, libglade, gnomevfs,
gstreamer, gconf, gtkmozembed, vte, bonobo, gtkhtml2,
gtksourceview, gnomeprintui, gnomeprint, libgda, poppler,
bonoboui, gnomecanvas, panel-applet
-----
Done.
Eсли среди Target libraries нет, как минимум, GTK и Pango вернитесь назад и установите недостающие библиотеки и заголовочные файлы. К примеру в Mandriva Linux девелоперские пакеты имеют окончание -devel: ruby-devel, libgtk+2.0_0-devel, libpango1.0-devel и т.д.

Теперь осталось откомпилировать и установить расширения Ruby:
$ make
# make install
Для проверки работоспособности библиотеки и привязок перейдите в директорию gtk/sample/gtk-demo и запустите скрипт main.rb Если запустилось - примите поздравления!

Установка под Windows

Несмотря на то что GTK+ изначально разрабатывалась для UNIX-систем сейчас она с успехом работает и под Windows и под OSX.

Пользователям Windows необходимо скачать One-Click инсталятор Ruby, а также инсталятор привязок для GTK+ под Windows с набором необходимых библиотек. Установка не вызывает проблем: методично нажимаем кнопку Next.

Отмечу, что с помощью Ruby/GTK+ можно легко писать приложения, которые без изменений будут работать как в Linux так и в Windows. Разумеется, в процессе разработки нужно периодически проверять приложение на всех целевых системах.

Hello World!

Ну куда же без него! Напишем простейшую программу на Ruby/GTK+


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/ruby

#
# hello-gtk.rb
#
# Александр Симаков, <xdr (тчк) box на Google Mail>
# http://alexander-simakov.blogspot.com/
#

# Подключаем библиотеку gtk2
require 'gtk2'

# Наследуем классу Gtk::Window
class HelloWindow < Gtk::Window
# Конструктор класса
def initialize(title)
# Вызываем конструктор родительского класса (Gtk::Window)
super
# Устанавливаем заголовок окна
set_title(title)
# Разрешаем отображение окна со всеми дочерними элементами
show_all
end
end

# Создаем экземпляр нашего главного окна (HelloWindow)
win = HelloWindow.new("Hello World!")

# Запускаем цикл обработки событий
Gtk.main

На экране должно появиться окно с заголовком "Hello World!"


Linux
Вид приложения в Linux

Windows
Вид приложения в Windows


Что дальше

Дальше следует вооружиться терпением! Дело в том, что документации по Ruby/GTK+ не так много, поэтому порой приходится непросто. Но не стоит отчаиваться: документация по самой библиотеке GTK+, т.е. по родному интерфейсу на C, всегда поддерживается в актуальном состоянии, кроме того, можно заглянуть и в документацию по привязкам к другим языкам если она более актуальна. Чаще всего разница в интерфейсах незначительна. Вот несколько полезных ссылок по теме:

Заключение

Мир open-source необычайно богат и многообразен. Не исключение и Ruby. Помимо привязок для GTK+ существует поддержка и многих других графических тулкитов: Qt, Tk, FOX, wxWidgets и т.д. Среди этого многообразия GTK+ удерживает очень прочные позиции: проект активно развивается, а сама библиотека имеет очень богатый набор виджетов и развитый функционал. Другими словами, если вы собираетесь программировать кросс-платформенные графические интерфейсы на Ruby и не знаете с чего начать - присмотритесь к GTK+

Статья опубликована в журнале Системный администратор (Октябрь, 2008)

пятница, 1 августа 2008 г.

Настройка WvDial в Linux

Введение

WvDial - это удобная программа для дозвона, альтернатива написанию CHAT-скиптов для PPP. В режиме конфигурации WvDial автоматически опрашивает последовательные порты, определяет модем и генерирует шаблон конфигурационного файла. Конфигурация очень компактна и интуитивно понятна. Как правило, единственное что остается сделать - ввести номер телефона, имя пользователя и пароль.

Установка

Сборка программы производилась в системе Mandriva Linux 2008. Для работы, помимо PPP потребуется скачать WvDial и библиотеку WvStreams.

http://alumnit.ca/download/wvdial-1.60.tar.gz
http://wvstreams.googlecode.com/files/wvstreams-4.4.1.tar.gz

Сначала распаковываем и собираем WvStreams:
$ tar -xzf wvstreams-4.4.1.tar.gz
$ cd wvstreams-4.4.1
$ ./configure
$ make
# make install
Поскольку мы не указали префикс, подразумевается /usr/local. Убедитесь, что путь /usr/local/lib прописан в файле /etc/ld.so.conf и при необходимости добавьте его туда. Затем можно обновить кэш библиотек
# ldconfig
и проверить появилась ли там библиотека WvStreams:
# ldconfig -p | grep wvstreams

libwvstreams.so.4.4 (libc6) => /usr/local/lib/libwvstreams.so.4.4
libwvstreams.so (libc6) => /usr/local/lib/libwvstreams.so
Если все в порядке, то двигаемся дальше. Теперь необходимо собрать сам WvDial:
$ tar -xzf wvdial-1.60.tar.gz
$ cd wvdial-1.60
$ export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig/
$ make
# export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig/
# make install
Пакет WvDial не использует Automake/Autoconf поэтому нет этапа конфигурирования. Ручная установка переменной окружения PKG_CONFIG_PATH может потребоваться если путь /usr/local/lib/pkgconfig/ еще не прописан в конфигурации pkg-config(1).

Итак, программа собрана переходим к ее настройке.

Настройка

Для запуска WvDial в режиме конфигурации выполните следующую команду:
# wvdialconf

Editing `/etc/wvdial.conf'.

Scanning your serial ports for a modem.

ttyS0<*1>: ATQ0 V1 E1 -- OK
ttyS0<*1>: ATQ0 V1 E1 Z -- OK
ttyS0<*1>: ATQ0 V1 E1 S0=0 -- OK
ttyS0<*1>: ATQ0 V1 E1 S0=0 &C1 -- OK
ttyS0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 -- OK
ttyS0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
ttyS0<*1>: Modem Identifier: ATI -- 1501
ttyS0<*1>: Speed 4800: AT -- OK
ttyS0<*1>: Speed 9600: AT -- OK
ttyS0<*1>: Speed 19200: AT -- OK
ttyS0<*1>: Speed 38400: AT -- OK
ttyS0<*1>: Speed 57600: AT -- OK
ttyS0<*1>: Speed 115200: AT -- OK
ttyS0<*1>: Max speed is 115200; that should be safe.
ttyS0<*1>: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 -- OK
ttyS1<*1>: ATQ0 V1 E1 -- failed with 2400 baud, next try: 9600 baud
ttyS1<*1>: ATQ0 V1 E1 -- failed with 9600 baud, next try: 115200 baud
ttyS1<*1>: ATQ0 V1 E1 -- and failed too at 115200, giving up.
Modem Port Scan<*1>: S2 S3

Found a modem on /dev/ttyS0.
/etc/wvdial.conf: Can't open '/etc/wvdial.conf' for reading: No such file or directory
/etc/wvdial.conf: ...starting with blank configuration.
Modem configuration written to /etc/wvdial.conf.
ttyS0: Speed 115200; init "ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0"
Программа нашла модем на /dev/ttyS0, опросила его и сгенерировала шаблон конфигурационного файла в /etc/wvdial.conf:
[Dialer Defaults]
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Modem Type = Analog Modem
; Phone = <Target Phone Number>
ISDN = 0
; Username = <Your Login Name>
Init1 = ATZ
; Password = <Your Password>
Modem = /dev/ttyS0
Baud = 115200
Нам осталось лишь указать номер телефона, имя пользователя и пароль. Теперь все готово для запуска.

Запуск

Для запуска с параметрами по умолчанию достаточно всего одной команды:
# wvdial

--> WvDial: Internet dialer version 1.60
--> Initializing modem.
--> Sending: ATZ
ATZ
OK
--> Sending: ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
OK
--> Modem initialized.
...
--> Connected... Press Ctrl-C to disconnect
Немного послушав модем мы подключаемся к сети! Для того чтобы отключить звук на модеме при соединении добавьте следующий параметр в wvdial.conf
Init3 = ATM0L0
Все возможные параметры конфигурации WvDial описаны в wvdial.conf(5)

Автоматизация дозвона

Иногда требуется звонить и обмениваться данными в автоматическом режиме, например по Cron-у. В этом случае возникает вопрос, как узнать что PPP-соединение уже поднялось? Можно конечно запускать wvdial в одном процессе, а в другом - ждать несколько секунд в надежде на то, что соединение поднимется. По понятным причинам этот способ ненадежен и как правило неприемлем. Существует более изящное решение.

Когда поднимается PPP-соединение, pppd запускат скрипт /etc/ppp/ip-up, а когда соединение разрывается - то /etc/ppp/ip-down. Также для удобства есть две директории: /etc/ppp/ip-up.d/ и /etc/ppp/ip-down.d/ скрипты из которых также запускаются в момент создания и разрыва PPP-соединения. Этот механизм удобно использовать для обмена данными, отправки/приема почты, входа в VPN и т.д.

Для надежной работы также рекомендуется соответствующим образом выставить в wvdial.conf параметр Dial Attempts.

Заключение

WvDial - это простой и удобный инструмент который подходит как для персонального использования так и для задач автоматизации обмена данными через коммутируемое соединение. Настройка программы выполняется автоматически и не вызывает особых сложностей.

Статья также опубликована на портале OpenNET