Web-сервисы

Web-сервисы

Веб-сервисы и 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.
Для работы с веб-сервисом необходимы первые три. Последний файл не обязателен. Это пример клиентского кода. Итак, теперь все готово для написания тестового приложения:
#!/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 и найдем в нем название интересующего нас метода:
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по одному на каждую валюту. Названия атрибутов также вполне ожидаемые - VnameVnomVcursVcode и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>
Действительно, каждый элемент массива соответствует определенной валюте. К атрибутам можно обратиться либо при помощи методов vnamevnomvcursvcodevchCode либо как к элементам хеша. Названия ключей при этом совпадают с названиями тегов в ответном XML-файле: VnameVnomVcurs,Vcode и VchCode. Теперь код тестовой программы становится очевидным: мы формируем запрос, отправляем его на сервер, получаем ссылку на массив валют, а затем бежим по этому массиву и выводим значения атрибутов. Вот и все!
Отмечу, что в более сложных ситуациях неоценимую помощь оказывают инструменты для отладки веб-сервисов, например, soapUI. При помощи этой программы можно вручную формировать SOAP-запросы и анализировать ответ от сервера.

Заключение

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

http://php.net/manual/ru/refs.webservice.php

Веб-сервисы

Веб-сервисы

Веб-сервисы

Web-services
автор: 2002 (c) Patrick Cooney и A List Apart
перевод: Александр Качанов
Идея веб-сервисов была разработана такими гигантами компьютерной индустрии как Sun, Oracle, HP, Microsoft и IBM. В этой идее нет ничего нового, но это большой шаг вперед к упрощенному доступу к программам через сеть. Основываясь на стандартных форматах связи, веб-сервисы могут вообще поменять наше представление о том, как мы должны делать веб-сайты.

Что такое веб-сервис?

Благодаря веб-сервисам функции любой программы могут стать доступными через Интернет. Таким образом такие программы как PHP, ASP, JSP скрипты, JavaBeans, COM-объекты и все остальные наши любимые средства программирования могут теперь обращаться к какой-нибудь программе, работающей на другом сервере (т.е. к веб-свервису), и использовать ответ, полученный от нее на своем веб-сайте, или приложении.
Скажем, если мне нужно выполнить какую-либо программную задачу, и я слишком занят (или не выжил из ума, чтобы самому изобретать в очередной раз велосипед), я могу воспользоваться услугами веб-сервиса, к которому мой сайт будет обращаться через Интернет. Передавая веб-свервису запрос с параметрами, я ожидаю получить ответ, в котором будет содержаться результат выполнения моего запроса.
Любой, кто хоть раз работал в последнее время с Hotmail, уже отчасти столкнулся с веб-сервисами: система аутентификации пользователей Passport - это один из сервисов, входящих в инициативу Microsoft .NET. пока он доступен бесплатно, так что создатели веб-сайтов могут запросто внедрить аутентификацию пользователей на своём сайте.

Основы

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

Стандарты в основе

Причина, по которой мы все вдруг заинтересовались веб-сервисами, в том, что в их основе лежат стандарты, открытые протоколы обмена и передачи данных.
До этого многие компании разрабатывали свои собственные закрытые стандарты и форматы. А сейчас нам для работы нужно знать всего лишь простой XML (eXtensible Markup Language), который передается по старому знакомому протоколу HTTP. Это значит, что информация о работе веб-сервисов доступна для всех, и веб-разработчики, которые по роду профессии знакомы с этими технологиями, могут начать играться с веб-сервисами уже сегодня.
Разница между веб-сервисами и другими технологиями, с которыми разработчикам приходилось сталкиваться (например, DCOM, именованные каналы - named pipes, RMI) в том, что веб-сервисы основаны на открытых стандартах, ими легко овладеть, и эти стандарты широко поддерживаются на всех платформах Unix и Windows.
Протокол Simple Object Access Protocol (SOAP) является стандартным протоколом, разработанным W3C. Он определяет формат запросов к веб-сервисам.
Сообщения между веб-сервисом и его пользователем пакуются в SOAP-конверты (SOAP envelopes). Сообщения содержат либо запрос на осуществление какого-либо действия, либо ответ - результат выполнения этого действия. Конверт и его содержимое закодировано языком XML, и его достаточно просто понять. Вот как выглядит простой SOAP-запрос, который отправляется через HTPP к веб-сервису:
<env:Envelope
xmlns:env="http://www.w3.org/2001/06/soap-envelope">
<env:Body>
<m:ValidatePostcode
env:encodingStyle="http://www.w3.org/2001/06/soap-encoding"
xmlns:m="http://www.somesite.com/Postcode">
<Postcode>WC1A8GH</Postcode>
<Country>UK</Country>
</m:ValidatePostcode>
</env:Body>
</env:Envelope>
Ключевые элементы SOAP-конверта узнать достаточно просто: это два параметра (<postcode> ("почтовый индекс") и <country> ("страна")), которые содержатся внутри элемента под названием <ValidatePostcode>. Этот элемент является названием веб-сервиса, к которому мы обращаемся с запросом. Прочие данные в конверте, такие как кодировка текста и версия SOAP помогают веб-сервису правильно обработать запрос.
А ответ будет выглядеть вот так:
<env:Envelope
xmlns:env="http://www.w3.org/2001/06/soap-envelope" >
<env:Body>
<m:ValidatePostcodeResponse
env:encodingStyle="http://www.w3.org/2001/06/soap-encoding"
xmlns:m="http://www.somesite.com/Postcode">
<Valid>Yes</Valid>
</m:ValidatePostcodeResponse>
</env:Body>
</env:Envelope>
Это сообщение еще проще расшифровать. Элемент <ValidatePostcode> в нашем запросе поменялся на элемент <ValidatePostcodeResponse> в ответе на запрос. В этом элементе содержится только один элемент <Valid>, значение которого обозначает, верен наш почтовый индекс или нет. Таким образом с помощью волшебства SOAP мы создали запрос, который делает для нас полезную работу. В ответ через сеть мы получаем определенного вида ответ на языке XML.

Теперь об UDDI

Даже при всей простоте протокола SOAP пользы в веб-сервисах было бы немного, если бы у нас не было никакой возможности их найти. К счастью IBM, Microsoft и компания Ariba выступили с инициативой и создали проект Universal Description, Discovery and Integration (UDDI), который, как они надеются, станет общим каталогом всех веб-сервисов в Web-е.
Система UDDI позволяет компаниям представить свой веб-сервис для публики. Этот каталог работает как телефонная книга всех веб-сервисов. Регистрация в каталоге UDDI осуществляется бесплатно, и основатели проекта надеются, что этот каталог будет содержать описания всех-всех-всех сервисов по всей Сети, так что для поиска нужного веб-сервиса достаточно будет обратиться лишь к одному каталогу UDDI.

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

Так как же мне найти нужный веб-сервис?
Представим себе, что я - разработчик сайта, и мой клиент попросил меня добавить к сайту новую функцию: необходимо добавить проверку правильности почтового индекса в регистрационной форме.
Для осуществления этой проверки мне понадобилось бы создавать базу данных всех почтовых индексов всех 30 стран, где наша компания ведет бизнес, а потом проверять при регистрации соответствие почтового индекса указанному в регистрации городу. Но у меня этих данных нет, и я думаю, что на сбор подобных данных придется потратить ощутимую сумму денег.
Вместо того, чтобы раскошеливаться на покупку базы данных, писать самому код, следить за целостностью и правильностью всех данных и отлаживать работу скриптов, я просто иду в каталог UDDI и ищу, нет ли там веб-сервиса, который мог бы сделать эту работу за меня. Придя на сайт www.uddi.org, я запускаю поиск и нахожу прекрасный сервис от компании XYZ Corp.
Я внимательно рассматриваю определение формата веб-сервиса (определение записано на языке WSDL (Web Services Description Language), убеждаюсь, что сервис делает именно то, что мне нужно. Затем справляюсь у своих коллег о репутации компании XYZ Corp., узнаю, что она солидная, и затем обращаюсь к компании XYZ с вопросом о цене. Если цена на доступ к сервису доступна для моего бюджета, я пишу простую JSP-страницу для своего сайта, который вызывает веб-сервис компании XYZ Corp, и опля, на сайте появляется моментальная проверка почтового индекса.

На это стоит потратить время

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

Разработка сервиса

Разумеется, разработчики вовсе не обязаны довольствоваться только веб-сервисами, созданными другими. С помощью одного из ниже перечисленных наборов инструментов вы можете создать свой собственный веб-сервис, и предоставить его услуги другим обитателям сети.
Выбор инструментов для разработки веб-сервисов обширен. В него входят инструментарии таких компании как Sun (Open Net), Microsoft (.NET),HP (e-services), и IBM (Web Services). Существуют также инструментарии с открытыми исходными кодами (open source frameworks). Например, проект Mono Project стремится заменить собой инструментарий Microsoft .NET, предоставив систему компиляции (compilers), исполнения кода (runtime) и библиотек (libraries) для работы одних и тех же веб-сервисов на всех платформах, включая Unix.
Несмотря на многообразие серверов и средств разработки веб-сервисов, все они поддерживают один и тот же протокол SOAP, язык XML и систему UDDI.

Минусы

Прежде чем я полностью откажусь от карьеры программиста и посвящу себя использованию веб-сервисов, я должен задать себе вопрос: "Уж слишком розовая картинка. Что в ней не так?". К сожалению, за великий потенциал веб-сервисов приходится платить определенную цену:
  • Использование XML в качестве формата передачи данных приводит к тому, что ваши сообщения будут очень большими по размеру: сами теги XML занимают много места, а это накладывает на нас определенную нагрузку по созданию, передаче и интерпретации сообщений.
  • Так как мы используем удаленные компьютеры для выполнения определенных функций, мы полностью полагаемся на Интернет, что создает слишком много ненадежных звеньев в цепи между нашим веб-сервером и веб-сервисом.
  • Сейчас лишь немногие компании создают веб-сервисы, и немногие компании ими пользуются. На отладку и улучшение системы веб-сервисов еще требуется длительное время.
  • Система лицензирования и взимания платежей за пользование веб-сервисами еще должна быть принята разработчиками. Из-за того, что веб-сервисов еще слишком мало, большинство компаний пытается провести на своих потенциальных клиентов хорошее впечатление намеренно снижая стоимость услуг и предлагая благоприятные условия лицензирования. Должно еще пройти какое-то время, прежде чем будет выяснена реальная стоимость услуг веб-сервисов.
Когда же веб-сервисы займут свое место и станут доступны всем, они станут неоценимой помощью для веб-разработчиков. Они дадут нам гибкий доступ ко всей мощи всех компьютеров Сети. Пришло время для тех, кто делает веб-сайты, заинтересоваться веб-сервисами и узнать побольше о том, что они могут от них получить.
Patrick Cooney

Веб-сервисы как средство интеграции приложений в WWW

Что есть веб-сервис?

Всемирная паутина является готовой платформой для создания и использования распределенных машинно-ориентированных систем на основе веб-сервисов. Веб-сервер выступает в качестве сервера приложений, к которым обращаются не конечные пользователи, а сторонние приложения. Это позволяет многократно использовать функциональные элементы, устранить дублирование кода, упростить решение задач интеграции приложений.
Веб-служба, веб-сервис (англ. web-service) — это сетевая технология, обеспечивающая межпрограммное взаимодействие на основе веб-стандартов. Консорциум W3C определяет веб-сервис, как «программную систему, разработанную для поддержки интероперабельного межкомпьютерного (machine-to-machine) взаимодействия через сеть»
Веб-сервисыРис. 1. Концепция веб-сервиса


Стек протоколов веб-сервисаРис. 2. Протоколы веб-сервисов

Веб-службы: концепции и протоколы

Веб-сервис идентифицируется строкой URI. Веб-сервис имеет программный интерфейс, представленный в машинно-обрабатываемом формате WSDL. Другие системы взаимодействуют с этим веб-сервисом путем обмена сообщениями протокола SOAP. В качестве транспорта для сообщений используется протокол HTTP. Описание веб-сервисов и их API могут быть найдены средствами UDDI. Концептуальная схема технологии приведена на рисунке 1.
  • SOAP (Simple Object Access Protocol) — протокол обмена сообщениями между потребителем и поставщиком веб-сервиса;
  • WSDL (Web Services Description Language) — язык описания внешних интерфейсов веб-службы;
  • UDDI (Universal Discovery, Description and Integration) — универсальный интерфейс распознавания, описания и интеграции, используемый для формирования каталога веб-сервисов и доступа к нему.
Все эти спецификации основаны на XML и, соответственно, наследуют его преимущества (структурированность, гибкость и т.д.) и недостатки (громоздкость, медлительность).

SOAP

SOAP (изначально Simple Object Access Protocol, а в версии 1.2 официальная расшифровка аббревиатуры отсутствует) — простой протокол доступа к объектам (компонентам распределенной вычислительной системы), основанный на обмене структурированными сообщениями. Как любой текстовый протокол, SOAP может использоваться с любым протоколом прикладного уровня: SMTP, FTP, HTTPS и др., но чаще всего SOAP используется поверх HTTP.
Все сообщения SOAP оформляются в виде структуры, называемой конвертом (envelop), включающей следующие элементы:
  • Идентификатор сообщения (локальное имя).
  • Опциональный элемент Header (заголовок):
    • Ноль или более ссылок на используемые пространства имен;
    • Ноль или более свойств, доступных в этом пространстве имен.
  • Обязательный элемент Body (тело сообщения)
    • Ноль или более ссылок на используемые пространства имен;
    • Дочерние элементы тела сообщения
Развернутый список элементов сообщения SOAP приведен в схеме данных (для SOAP версии 1.2).
Пример сообщения SOAP:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
 <env:Header>
 <n:alertcontrol xmlns:n="http://example.org/alertcontrol">
 <n:priority>1</n:priority>
 <n:expires>2001-06-22T14:00:00-05:00</n:expires>
 </n:alertcontrol>
 </env:Header>
 <env:Body>
 <m:alert xmlns:m="http://example.org/alert">
 <m:msg>Get up at 6:30 AM</m:msg>
 </m:alert>
 </env:Body>
</env:Envelope>

XML-RPC: Не конкурент, а альтернатива SOAP

XML-RPCКонцепция XML-RPC
XML-RPC — очень простой и эффективный протокол взаимодействия веб-сервисов. Он не предназначен для решения глобальных задач, как SOAP, но широко используется во многих веб-разработках.
XML-RPC — это "... спецификация и набор реализаций, которые позволяют программному обеспечению, работающему на разных операционных системах и в различных условиях, вызывать процедуры через Интернет. Это удаленный вызов процедуры с использованием HTTP как транспорта и XML как способа кодирования. XML-RPC разработан настолько простым, насколько это возможно для сложных структур данных, подлежащих передаче, обработке и приему". — [хmlrpc.com]
"Мы хотели, чистый, расширяемый и очень простой формат. Он должен представлять HTML-кодеру возможность заглянуть в файл, содержащий описание XML-RPC вызова, понять, что тот делает и быть в состоянии изменить его, чтоб он заработал с первой или второй попытки... Мы также хотели, чтобы это был легко реализуемый протокол, который может быть быстро адаптирован для работы в другой среде или на других операционных системах."- [xmlrpc.com]

WSDL

Язык описания веб-сервисов (Web services Description Language, WSDL) предназначен для унифицированного представления внешних интерфейсов веб-службы. Текущая версия протокола (на момент написания этой лекции) WSDL 2.0 и она имеет некоторые отличия от предыдущих версий (см.табл. 1 и рис. 3).
Структура WSDLРис. 3. Структура протокола WSDL
Таблица 1. Основные элементы протокола WSDL.
Элемент WSDL 1.1Элемент WSDL 2.0Краткое описание
PortTypeInterfaceПредставляет описание интерфейса веб-сервиса (список операций и их параметров).
ServiceServiceСписок системных функций
BindingBindingСпецифицирует интерфейсы и задает параметры связывания с протоколом SOAP: стиль связывания (RPC/Document) и транспорт (SOAP). Эта секция доступна и для каждой из операций
OperationOperationОпределяет операцию, представляемую веб-сервером. WSDL-операция — это аналог традиционным функциям и процедурам.
Messageне использ.Сообщение, связанное с определенной операцией. Содержит информацию, необходимую для выполнения данной операции. Каждое сообщение может состоять из нескольких логических частей, описывающих типы данных и имена атрибутов. В версии 2.0 было исключено, т.к. была внедрена поддержка XML Schema для всех элементов.
TypesTypesОписание данных в соответствии с XML Schema.
В спецификации WSDL 1.1 было определено 4 шаблона обмена сообщениями (типы операций):
  • Однонаправленные операции (One-way): операция может принимать сообщение, но не будет возвращать ответ.
  • Запрос-ответ (Request-response): операция может принять запрос и должна вернуть ответ.
  • Вопрос-ответ (Solicit-response): операция может послать запрос и будет ждать ответ на него.
  • Извещение (Notification): операция может послать сообщение, но не будет ожидать ответ.
В версии WSDL 2.0 эти шаблоны изменены и расширены в сторону поддержки сообщений об ошибках (например, шаблон Robust-in-only), но для обратной совместимости поддерживаются типы WSDL 1.1

UDDI

Концепция UDDIРис. 4. Место UDDI в стеке протоколов веб-служб
Universal Description, Discovery and Integration (UDDI, универсальный интерфейс распознавания, описания и интеграции) — открытый стандарт, утвержденный OASIS, определяющий методы публикации и обнаружения сетевых программных компонентов сервис-ориентированной архитектуры (SOA). В практической реализации UDDI представляет собой сетевой реестр (службу каталогов), представляющий данные и метаданные о веб-сервисах и доступный по адресуhttp://uddi.xml.org/services.
UDDI опирается на отраслевые стандарты HTTP, XML, XML Schema (XSD), SOAP и WSDL. Концептуальная связь между UDDI и другими протоколами стека веб-сервисов показана на рисунке 4.
Функциональное назначение реестра UDDI — представление данных и метаданных о веб-службах. Он может использоваться как в сети общего пользования, так и в пределах внутренней инфраструктуры организации. Реестр UDDI предлагает основанный на стандартах механизм классификации, каталогизации и управления веб-службами, позволяющий применять их (веб-сервисы) другими приложениями. Этот механизм включает средства для поиска сервиса, вызова этой службы, а также для управления метаданными об этой службе.
Ключевыми функциями UDDI являются публикация информации о службе в реестре и поиск этой информации сторонними приложениями. Наряду с этими, реализованы и типовые для службы каталогов функции: представление модели хранимых данных и структуры информационной базы, отношения между объектами реестра, репликация, обеспечение безопасности и т.д. —Все основные функции реестра представлены в виде веб-сервисов и доступны через API UDDI.

Веб-сервисы: Pro et Contra

Достоинства

  • Обеспечивают взаимодействие программных систем независимо от платформы.
  • Основаны на базе открытых стандартов и протоколов.
  • Использование HTTP позволяет приложениям взаимодействовать через межсетевой экран.

Недостатки

  • Меньшая производительность и больший объем сетевого трафика по сравнению с такими технологиями как CORBA или DCOM.

Веб-сервисы в теории и на практике для начинающих

Что такое веб-сервисы?



Прежде всего, веб-сервисы (или веб-службы) — это технология. И как и любая другая технология, они имеют довольно четко очерченную среду применения.

Если посмотреть на веб-сервисы в разрезе стека сетевых протококолов, мы увидим, что это, в классическом случае, не что иное, как еще одна надстройка поверх протокола HTTP.

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

Но и сам Интернет — разнороден, т. е. различные приложения на различных узлах сети функционируют на разных аппаратно-программных платформах, и используют различные технологии и языки.

Чтобы связать все это и предоставить возможность одним приложениям обмениваться данными с другими, и были придуманы веб-сервисы.

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

Именно с появлением веб-сервисов развилась идея SOA — сервис-ориентированной архитектуры веб-приложений (Service Oriented Architecture).

Протоколы веб-сервисов



На сегодняшний день наибольшее распространение получили следующие протоколы реализации веб-сервисов:

  • SOAP (Simple Object Access Protocol) — по сути это тройка стандартов SOAP/WSDL/UDDI
  • REST (Representational State Transfer)
  • XML-RPC (XML Remote Procedure Call)


На самом деле, SOAP произошел от XML-RPC и является следующей ступенью его развития. В то время как REST — это концепция, в основе которой лежит скорее архитектурный стиль, нежели новая технология, основанный на теории манипуляции объектами CRUD (Create Read Update Delete) в контексте концепций WWW.

Безусловно, существуют и иные протоколы, но, поскольку они не получили широкого распространения, мы остановимся в этом кратком обзоре на двух основных — SOAP и REST. XML-RPC ввиду того, что является несколько «устаревшим», мы рассматривать подробно не будем.

Нас в первую очередь интересуют вопросы создания новых веб-служб, а не реализация клиентов к существующим (как правило поставщики веб-сервисов поставляют пакеты с функциями API и документацией, посему вопрос построения клиентов к существующим веб-службам менее интересен с точки зрения автора).

SOAP против REST



Проблемы данного противостояния хорошо описаны в статье Леонида Черняка, найденой на портале www.citforum.ru.

По мнению же автора, кратко можно выделить следующее:

SOAP более применим в сложных архитектурах, где взаимодействие с объектами выходит за рамки теории CRUD, а вот в тех приложениях, которые не покидают рамки данной теории, вполне применимым может оказаться именно REST ввиду своей простоты и прозрачности. Действительно, если любым объектам вашего сервиса не нужны более сложные взаимоотношения, кроме: «Создать», «Прочитать», «Изменить», «Удалить» (как правило — в 99% случаев этого достаточно), возможно, именно REST станет правильным выбором. Кроме того, REST по сравнению с SOAP, может оказаться и более производительным, так как не требует затрат на разбор сложных XML команд на сервере (выполняются обычные HTTP запросы — PUT, GET, POST, DELETE). Хотя SOAP, в свою очередь, более надежен и безопасен.

В любом случае вам решать, что больше подойдет вашему приложению. Вполне вероятно, вы даже захотите реализовать оба протокола, чтобы оставить выбор за пользователями службы и — это ваше право.

Практическое применение веб-сервисов



Поскольку речь идет о практическом применении, нам нужно выбрать платформу для построения веб-службы и поставить задачу. Так как автору ближе всего PHP 5, мы и выберем его в качестве технологии для построения службы, а в качестве задачи примем следующие требования.

Допустим, нам необходимо создать службу, предоставляющую доступ к информации о курсах валют, которая собирается нашим приложением, и накапливается в базе данных. Далее посредством веб-сервиса, данная информация передается сторонним приложениям для отображения в удобном для них виде.

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

Этап первый — реализация приложения сбора информации о курсах валют.



Информацию о курсах валют мы будем собирать со страниц сайта НБУ (Национального Банка Украины) ежедневно и складывать в базу данных под управлением СУБД MySQL.

Создадим структуру данных.

Таблица валют (currency):

+-------------+------------------+
| Field       | Type             |
+-------------+------------------+
| code        | int(10) unsigned |
| charcode    | char(3)          |
| description | varchar(100)     |
| value       | int(10) unsigned |
| base        | tinyint(1)       |
+-------------+------------------+


Таблица номиналов обмена (exchange):

+------------+------------------+
| Field      | Type             |
+------------+------------------+
| id         | bigint(20) ai    |
| rate_date  | timestamp        |
| rate_value | float            |
| code       | int(10) unsigned |
+------------+------------------+


Для работы с базой данных воспользуемся ORM слоем на базе пакета PHP Doctrine. Реализуем граббер:

класс Grubber (models/Grabber.php):

<?php
/*
 * @package Currency_Service
 */
class Grabber {

    /**
     * Extracts data from outer web resource and returns it
     *
     * @param  void
     * @return array
     */
    public static function getData() {
        /**
         * Extracting data drom outer web-resource
         */
        $content = file_get_contents( 'http://www.bank.gov.ua/Fin_ryn/OF_KURS/Currency/FindByDate.aspx');
        if(preg_match_all( '/(\d+)<\/td>([A-Z]+)<\/td>(\d+)<\/td>(.+?)<\/td>(\d+\.\d+)<\/td>/i', $content, $m) == false) {
            throw new Exception( 'Can not parse data!');
        }

        /**
         * Preformatting data to return;
         */
        $data = array();
        foreach ($m[1] as $k => $code) {
            $data[] = array(
                'code'        => $code,
                'charcode'    => $m[2][$k],
                'value'       => $m[3][$k],
                'description' => $m[4][$k],
                'rate_value'  => $m[5][$k]
            );
        }
        return $data;
    }

    public static function run() {
        $data = self::getData();

        /**
         * Sets default currency if not exists
         */
        if (!Doctrine::getTable( 'Currency')->find( 980)) {
            $currency = new Currency();
            $currency->setCode( 980)
                     ->setCharcode( 'UAH')
                     ->setDescription( 'українська гривня')
                     ->setValue( 1)
                     ->setBase( 1)
                     ->save();
        }

        foreach ($data as $currencyData) {
            /**
             * Updating table of currencies with found values
             */
            if (!Doctrine::getTable( 'Currency')->find( $currencyData['code'])) {
                $currency = new Currency();
                $currency->setCode( $currencyData['code'])
                         ->setCharcode( $currencyData['charcode'])
                         ->setDescription( $currencyData['description'])
                         ->setValue( $currencyData['value'])
                         ->setBase( 0)
                         ->save();
            }

            /**
             * Updating exchange rates
             */
            $date = date( 'Y-m-d 00:00:00');
            $exchange = new Exchange();
            $exchange->setRateDate( $date)
                     ->setRateValue( $currencyData['rate_value'])
                     ->setCode( $currencyData['code'])
                     ->save();
        }

    }
}
?>


и сам граббер (grabber.php):

<?php
require_once('config.php');
Doctrine::loadModels('models');
Grabber::run();
?>


Теперь заставим наш граббер отрабатывать раз в сутки в 10:00 утра, путем добавления команды запуска граббера в таблицы cron:

0 10 * * * /usr/bin/php /path/to/grabber.php


Все — у нас есть достаточно полезный сервис.

Теперь реализуем веб-сервис, который позволит другим приложениям извлекать данные из нашей базы.

Реализация SOAP сервиса



Для реализации веб-сервиса на базе SOAP протокола, мы воспользуемся встроенным пакетом в PHP для работы с SOAP.

Поскольку наш веб-сервис будет публичным, хорошим вариантом будет создание WSDL файла, который описывает структуру нашего веб-сервиса.

WSDL (Web Service Definition Language) — представляет из себя XML файл определенного формата. Подробное описание синтаксиса можно найти здесь.

На практике будет удобно воспользоваться функцией автоматической генерации файла, которую предоставляет IDE Zend Studio for Eclipse. Данная функция позволяет генерировать WSDL файл из классов PHP. Поэтому, прежде всего, мы должны написать класс, реализующий функциональность нашего сервиса.

класс CurrencyExchange (models/CurrencyExchange.php):

<?php
/**
 * Class providing web-service with all necessary methods
 * to provide information about currency exchange values
 *
 * @package Currency_Service
 */
class CurrencyExchange {

    /**
     * Retrievs exchange value for a given currency
     *
     * @param  integer $code - currency code
     * @param  string $data - currency exchange rate date
     * @return float - rate value
     */
    public function getExchange( $code, $date) {
        $currency = Doctrine::getTable( 'Currency')->find( $code);
        $exchange = $currency->getExchange( $date);
        return $exchange ? (float)$exchange->getRateValue() : null;
    }

    /**
     * Retrievs all available currencies
     *
     * @return array - list of all available currencies
     */
    public function getCurrencyList() {
        return Doctrine::getTable( 'Currency')->findAll()->toArray();
    }

}
?>


Отметим, что для автоматической генерации WSDL, нам необходимо написать комментарии в стиле javadoc, потому что именно в них мы прописываем информацию о типах принимаемых аргументов и возвращаемых значений. Неплохо также описывать в нескольких словах работу методов — ведь WSDL послужит описанием API для сторонних разработчиков, которые будут использовать ваш веб-сервис.

Не пишите в докблоках @param void или @return void — для WSDL это не критично, но вот при реализации REST доступа к тому-же классу у вас возникнут проблемы.


Теперь в Zend Studio входим в меню File->Export..., выбираем PHP->WSDL, добавляем наш класс, прописываем URI-адрес нашего сервиса и создаем WSDL-файл. Результат должен быть примерно таким: http://mikhailstadnik.com/ctws/currency.wsdl

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

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

Реализация же самого сервера не предстваляет теперь никакой сложности:

файл index.php:

<?php
require_once('config.php');

Doctrine::loadModels('models');

$server = new SoapServer( 'http://mikhailstadnik.com/ctws/currency.wsdl');
$server->setClass( 'CurrencyExchange');
$server->handle();
?>


Вы можете попробовать веб-сервис в работе по адресу: http://mikhailstadnik.com/ctws/
Там же доступен тестовый клиент: http://mikhailstadnik.com/ctws/client.php

Код простейшего клиента может быть таким:

<?php
$client = new SoapClient( 'http://mikhailstadnik.com/ctws/currency.wsdl');
echo 'USD exchange: ' . $client->getExchange( 840, date( 'Y-m-d'));
?>


Реализация REST сервиса



REST — это не стандарт и не спецификация, а архитектурный стиль, выстроенный на существующих, хорошо известных и контролируемых консорциумом W3C стандартах, таких, как HTTP, URI (Uniform Resource Identifier), XML и RDF (Resource Description Format). В REST-сервисах акцент сделан на доступ к ресурсам, а не на исполнение удаленных сервисов; в этом их кардинальное отличие от SOAP-сервисов.

И все же удаленный вызов процедур применим и в REST. Он использует методы PUT, GET, POST, DELETE HTTP протокола для манипуляции объектами. Кардинальное отличие его от SOAP в том, что REST остается HTTP-запросом.

Поскольку в PHP пока еще нет реалзации REST, мы воспользуемся Zend Framwork, в который включена реализация как REST клиента, так и REST севера.

Воспользуемся уже готовым классом CurrencyExchange. Напишем сам сервер:

rest.php:

<?php
require_once 'config.php';
require_once 'Zend/Rest/Server.php';

Doctrine::loadModels('models');

$server = new Zend_Rest_Server();
$server->setClass( 'CurrencyExchange');
$server->handle();
?>


Как видите все очень сходно и просто.

Однако, следует оговорить, что наш REST-сервис менее защищен, чем SOAP-сервис, так как любой добавленый метод в класс CurrencyExchange при его вызове отработает (сам класс определяет сруктуру сервиса).

Проверим работу нашего сервиса. Для этого достаточно передать параметры вызова метода в сроке GET-запроса:

?method=getExchange&code=840&date=2008-11-29


или

?method=getExchange&arg1=840&arg2=2008-11-29


При желании или необходимости вы можете самомтоятельно задавать структуру ваших XML ответов для сервиса REST. В этом случае, также будет необходимо позаботиться и о создании определения типа вашего XML документа (DTD — Document Type Definition). Это будет минимальным описанием API вашего сервиса.

Простейший тестовый клиент к REST сервису может быть в нашем случае таким:

<?php
$client = new Zend_Rest_Client( 'http://mikhailstadnik.com/ctws/rest.php');
$result = $client->getExchange( 840, date( 'Y-m-d'))->get();
if ($result->isSuccess()) {
    echo 'USD exchange: ' . $result->response;
}
?>


В принципе, Zend_Rest на сегодняшний день нельзя назвать наиболее точной реализацией принципов REST. Утрируя, можно говорить о том, что эта реализация свелась к удаленному вызову процедур (RPC), хотя философия REST гораздо шире.

Вы можете скачать пример в исходных кодах c PHP Doctrine и Zend Framework (4,42 Мб).

Заключение



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

Кроме того мы увидели, что реализация веб-сервиса — задача довольно простая при использовании современного инструментария, который позволяет сконцентрироваться, в первую очередь, на разработке функциональности самого сервиса, не заботясь о низкоуровневой реализации протоколов.

Автор надеется, что данный материал будет действительно полезен тем, кто становится на тропу разработки веб-служб.

Удачи в девелопменте!