Программирование в стиле Representational State Transfer (REST) на Ruby

Построение простого RESTful-клиента с использованием языка Ruby

REST, или Representational State Transfer, ― это распределенная архитектура связи, которая быстро становится лингва-франка облачных вычислений. Это простая, но достаточно универсальная архитектура, способная представлять самые разнообразные облачные ресурсы вместе с общей конфигурацией и структурой управления. Эта статья демонстрирует, как на языке Ruby разработать простой агент REST с нуля в целях изучения реализации и использования этой архитектуры.

M. Тим Джонс, инженер-консультант, Emulex Corp.

М. Тим ДжонсМ. Тим Джонс - архитектор встроенного ПО и, кроме того, автор книг Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (выдержавшей на данный момент второе издание), AI Application Programming (второе издание) и BSD Sockets Programming from a Multilanguage Perspective. Он имеет обширный опыт разработки ПО в самых разных предметных областях - от ядер специальных ОС для геосинхронных космических аппаратов до архитектур встраиваемых систем и сетевых протоколов. Тим - инженер-консультант Emulex Corp., Лонгмонт, Колорадо.



12.03.2013

Передача репрезентативного состояния (Representational State Transfer ― REST) представляет собой архитектурный стиль Web-коммуникаций, который предоставляет клиентам уникальные возможности для сообщения с серверами. В частности, REST представляет ресурсы в пределах данного сервера в виде универсальных идентификаторов ресурсов (URI), что упрощает реализацию REST-архитектур в сетях, основанных на протоколе Hypertext Transport Protocol (HTTP). Мы начнем со знакомства с идеями, стоящими за REST и HTTP. Затем рассмотрим представление данных и, наконец, реализацию простого REST-клиента на языке Ruby.

Краткое введение в HTTP

Для лучшего понимания отдельных операций REST полезно сделать краткое введение в HTTP. Это основной протокол связи, соединяющий Web-браузеры с серверами, но он используется для передачи не только HTML, но и многих других типов данных.

HTTP ― это протокол типа "запрос-ответ", то есть клиенты делают запросы, а серверы на них отвечают. Протокол HTTP довольно удобочитаем для человека, и для его демонстрации я воспользуюсь клиентом telnet, чтобы передать запрос к Web-серверу.

Запрос из браузера

Замечу, что запрос, который я сделал вручную в листинге 1, должен выглядеть как URI http://www.mtjones.com/index.html. Web-браузер разбивает этот URI на части (как видно в листинге 1).

В листинге 1 приведен HTTP-запрос и часть ответа Web-сервера. Я передал запрос с помощью клиента telnet, указав доменное имя Web-сервера и порт (порт 80, типичный для HTTP). Telnet сначала преобразует доменное имя Domain Name System в IP-адрес, а затем сообщает, что я подключен к Web-серверу. После этого я ввожу строку запроса (которая содержит метод HTTP GET, путь к ресурсу на Web-сервере и используемый протокол ― в данном случае HTTP версии 1.1). Далее, я ввожу набор заголовков запроса (которые могут быть довольно большими, но поскольку я набираю их вручную, я просто указываю заголовок запроса Host, содержащий узел и ― опционально ― порт, из которого я делаю запрос). Запрос завершается пустой строкой, что указывает Web-серверу на то, что запрос окончен. Web-сервер дает ответ, содержащий используемый протокол и код состояния (в данном случае это 200 OK, что указывает на то, что запрос в порядке) и дополнительные заголовки ответа. Затем следует пустая строка и собственно ответ из 1944 символов. Это содержание ресурса ― в данном случае HTML-документ.

Листинг 1. Выполнение HTTP-транзакции с помощью telnet
$ telnet mtjones.com 80
Trying 198.145.43.103...
Connected to mtjones.com.
Escape character is '^]'.
GET /index.html HTTP/1.1Host: example.org

HTTP/1.1 200 OK
Date: Sun, 25 Mar 2012 05:33:07 GMT
Server: Apache
Last-Modified: Sat, 26 Sep 2009 20:22:36 GMT
ETag: "2c984bf-798-d3451b00"
Accept-Ranges: bytes
Content-Length: 1944
Vary: Accept-Encoding
Content-Type: text/html

<DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" ...

Этот пример иллюстрирует простую транзакцию, но фактически в HTTP реализованы несколько методов. Метод GET используется для извлечения ресурса из Web-сервера, причем HEAD указывает на то, что это только метаданные о ресурсе (а не фактическое содержание). Метод POST используется для передачи на Web-сервер новых данных, а метод PUT ― для помещения данных в существующий ресурс на Web-сервере. Полный перечень методов HTTP приведен в таблице 1.

Таблица 1. Основные методы запросов HTTP 1.1
МетодИдемпотентныйОписание
OPTIONSДаЗапрос информации о возможных способах связи
GETДаИзвлекает представление URI
HEADДаИзвлекает метаданные URI
PUTДаСоздает или заменяет содержание ресурса
POSTНетДобавляет содержание в существующий ресурс
DELETEДаУдаляет ресурс с указанным URI

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

А теперь поднимемся выше по стеку протоколов и рассмотрим уровень REST.


Что такое REST?

Преимущества REST

REST – это не протокол и не реализация, а архитектурный стиль. За REST стоят некоторые базовые принципы, но в конечном итоге это не конкретная реализация, а абстракция.

REST ― это не столько конкретная конструкция или реализация, сколько архитектурный стиль. Как показано на рисунке 1, архитектура RESTful определяется простым набором ограничений. В основе RESTful-архитектуры лежит набор ресурсов. Эти ресурсы определяются своим URI (например, Uniform Resource Locator [URL]) и внутренним представлением (обычно это форма самоописываемых данных, которые мы рассмотрим ниже). Наконец, существует набор операций, с помощью которых этими ресурсами можно управлять.

Рисунок 1. Общее представление архитектуры RESTful
Общее представление RESTful-архитектуры

На самом деле эти ресурсы могут представлять объекты данных с использованием различных типов (например, JavaScript Object Notation [JSON]). К ресурсам можно обращаться через URL-адреса (например, http://www.mtjones.com) с использованием набора стандартных операций (GET, POST, DELETE и т.п.). Использование HTTP в качестве транспорта значительно упрощает разработку RESTful-архитектур, так как это известный и стабильный базовый протокол. К тому же HTTP широко доступен, и для его использования, в частности, в таких интернет-службах, как шлюзы, прокси, средства безопасности и службы кэширования HTTP, не требуется новая конфигурация. Чтобы добиться высокой масштабируемости серверов REST, можно использовать другие полезные возможности, такие как выравнивание нагрузки.


Характеристики RESTful-архитектуры

Хотя RESTful-архитектура предоставляет широкую свободу реализации, одна группа характеристик имеет особое значение.

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

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


Пример интерфейса REST

Чтобы проиллюстрировать некоторые характеристики RESTful-архитектуры, рассмотрим пример реализации REST. Напомним, что REST опирается на взаимодействие клиент-сервер (см. рисунок 2). Клиентское приложение делает запрос, который преобразуется в HTTP-запрос RESTful. Как и любая другая HTTP-транзакция, этот запрос инициируется от клиента к серверу. Сервер обрабатывает запрос и отвечает соответствующим образом.

Рисунок 2. Многоуровневая архитектура RESTful-взаимодействий
Многоуровневая архитектура взаимодействий RESTful

Интересным примером API-интерфейса REST, который можно использовать для построения простого клиента, служит CrunchBase. CrunchBase — бесплатная база данных, предложенная некоторыми ИТ-компаниями, отдельными разработчиками и инвесторами. Помимо традиционного Web-интерфейса, CrunchBase предлагает интерфейс на основе REST/JSON поверх протокола HTTP.

Через свой API CrunchBase реализует три действия:

  • отображение (получение информации по определенной записи);
  • поиск (получение списка записей, соответствующих заданному условию поиска);
  • перечисление (получение всех записей в заданном пространстве имен).

CrunchBase экспортирует пять пространств имен для своих данных (см. рисунок 3) вместе с URL-формой, используемой для REST-взаимодействий CrunchBase. (Эти пространства имен: company, person, product, financial-organization и service-provider.) Обратите внимание, что v/1 указывает версию API, в настоящее время это 1. Также обратите внимание на поле permalink, где указано уникальное имя записи в базе данных.

Рисунок 3. Пространства имен API CrunchBase
Схема пространств имен API CrunchBase

Если нужно получить текущую информацию о компании IBM, достаточно составить URL с использованием пространства имен company (попробуйте сделать это в своем браузере):

http://api.crunchbase.com/v/1/company/ibm.js

Если ввести этот URL в Web-браузер, он выдаст текстовый (на основе JSON) ответ (проглотив HTTP-заголовки). Мы рассмотрим это подробнее при изучении представления данных CrunchBase в формате JSON.


Самоописываемые данные

Сообщение между неоднородными системами приводит к некоторым интересным проблемам, одна из которых ― последовательное упорядочение данных для передачи. Машины представляют данные разными способами (от различных представлений с плавающей точкой до нестандартного порядка следования байтов). В число ранних реализаций входили формат Abstract Syntax Notation (ASN.1) и протокол External Data Representation (XDR) (используется в Network File System). К числу других подходов относится XML, который кодирует данные в текстовых документах ASCII.

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

А теперь рассмотрим пример JSON, в частности, его реализацию через REST-интерфейс CrunchBase. В этом примере используется интерактивная оболочка Ruby (irb), которая позволяет экспериментировать с Ruby в режиме реального времени.

Как показано в листинге 2, сначала исполняется интерактивная оболочка Ruby. Для подготовки среды вам придется загрузить несколько модулей (в частности, компоненты JSON и HTTP) и определить свой URI. Заметим, что URI ― это полный запрос к CrunchBase (в пространстве имен company с постоянной ссылкой ibm). Он передается в метод get_response класса Net::HTTP, который выполняет запрос GET по указанному URI (разложенному на отдельные компоненты с помощью метода URI.parse). Если набрать resp.body, то можно увидеть возвращенные данные JSON. Это набор пар имя-значение (таких как name и IBM). Для преобразования ответа в объектную структуру Ruby используется метод JSON.parse. Наконец, извлекаем нужное значение, указав его имя.

Листинг 2. Обращение к базе денных CrunchBase с помощью Ruby
$ irb
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'json'
=> true
irb(main):003:0> require 'net/http'
=> true
irb(main):004:0> uri = "http://api.crunchbase.com/v/1/company/ibm.js"
=> "http://api.crunchbase.com/v/1/company/ibm.js"
irb(main):005:0> resp = Net::HTTP.get_response(URI.parse(uri))
=> #<Net::HTTPOK 200 OK readbody=true>
irb(main):006:0> puts resp.body
{"name": "IBM",
 "permalink": "ibm",
 "crunchbase_url": "http://www.crunchbase.com/company/ibm",
 "homepage_url": "http://www.ibm.com",
 "blog_url": "",
 "blog_feed_url": "",
 "twitter_username": "",
 "category_code": "software",
 "number_of_employees": 388000,
...
=> nil
irb(main):007:0> parsedresp = JSON.parse(resp.body)
=> {"updated_at"=>"Wed Feb 01 03:10:14 UTC 2012", "alias_list"=>nil, 
...
irb(main):008:0> 
irb(main):009:0* puts parsedresp['founded_year']
1896
=> nil
irb(main):010:0>

Из листинга 2 видно, как легко написать код для быстрого извлечения данных из ответа JSON (7 строк). Теперь пойдем дальше и построим простой универсальный API для взаимодействия с CrunchBase.


Создание простого REST-клиента

Перед тем как взяться за REST-клиент, необходимо кое-что установить. Если у вас не установлен Ruby, установите его. Я работаю под Ubuntu и для большинства таких установок использую Advanced Packaging Tool (а для других ― менеджер пакетов Ruby).

Чтобы установить пакет Ruby, выполните:

$ sudo apt-get install ruby

Можно также установить Interactive Ruby Shell (irb), полезное средство экспериментирования с языком Ruby:

$ sudo apt-get install irb

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

$ sudo apt-get install rubygems1.8
$ sudo apt-get install ruby-dev
$ sudo gem install json

Если среда готова, приступим к построению простого API REST-клиента с помощью Ruby. В листинге 1 было показано, как взаимодействовать с HTTP-сервером посредством Ruby и как получить простое имя из объекта JSON. Опираясь на эти знания, создадим набор классов Ruby, реализующих простой API.

Во-первых, рассмотрим два примера классов, которые взаимодействуют с REST-сервером CrunchBase. Первый ориентирован на пространство имен company; второй ― на пространство имен person. Обратите внимание, что оба они расширяют минимальный набор и легко распространяются на другие элементы данных.

В листинге 3 представлен API для взаимодействия с пространством имен company. Этот класс расширяет метод конструктора (initialize) и четыре метода, используемые для извлечения данных из JSON-записи о компании. Принцип работы этого класса состоит в том, что пользователь создает экземпляр объекта, указывая имя компании (постоянную ссылку). В рамках конструктора в CrunchBase запрашивается запись о компании. URL создается динамически путем добавления имени компании, переданного в составе метода new. Для извлечения ответа используется метод get_response, а готовый результат (хеш-объект) загружается в переменную экземпляра (@record).

При наличии проанализированной записи каждый вызываемый метод просто извлекает нужные данные и возвращает их пользователю. Методы founded_year, num_employees и company_type имеют простую структуру с учетом обсуждения листинга 1. Метод people требует немного большего внимания.

Ответ JSON от CrunchBase представлен хешем, содержащим некоторое количество ключей. Обратите внимание, что в первых трех методах мы указали ключ для возврата значения. Метод people перебирает хеш, указанный ключом relationships. Каждая запись содержит ключ is_past (который используется для определения того, работает ли еще человек в данной компании), ключ title и персональный ключ, состоящий из элементов first_name, last_name и permalink. Итератор просто перебирает их, и если ключ is_past имеет значение false, извлекает имя и должность и создает новый хеш с этой информацией. Если ключей больше нет, этот хеш возвращается. Весь этот хеш можно просмотреть в IRB, просто отправив ответ, обработанный методом JSON.parse.

Листинг 3. Ruby-API CrunchBase для пространства имен company (company.rb).
require 'rubygems'
require 'json'
require 'net/http'

class Crunchbase_Company

  @record = nil

  def initialize( company )

    base_url = "http://api.crunchbase.com"
    url = "#{base_url}/v/1/company/#{company}.js"

    resp = Net::HTTP.get_response(URI.parse(url))

    @record = JSON.parse(resp.body)

  end

  def founded_year
    return @record['founded_year']
  end

  def num_employees
    return @record['number_of_employees']
  end

  def company_type
    return @record['category_code']
  end

  def people

    employees = Hash.new

    relationships = @record['relationships']

    if !relationships.nil?

        relationships.each do | person |
            if person['is_past'] == false then
                permalink = person['person']['permalink']
                title = person['title']
                employees[permalink] = title
            end
        end

    end

    return employees

  end

end

Листинг 4 содержит аналогичную функцию для класса Company, который мы обсуждали в связи с листингом 3. Конструктор initialize работает точно так же, и в нашем распоряжении есть два простых метода для извлечения имени нужной персоны (по постоянной ссылке). Метод companies анализирует хеш для ключа relationships и возвращает компании, с которыми этот человек был связан в прошлом. В этом случае компании возвращаются в виде простого массива (постоянных ссылок) Ruby.

Листинг 4. Ruby-API CrunchBase для пространства имен person (person.rb).
require 'rubygems'
require 'json'
require 'net/http'

class Crunchbase_Person

  @record = nil

  def initialize( person )

    base_url = "http://api.crunchbase.com"
    url = "#{base_url}/v/1/person/#{person}.js"

    resp = Net::HTTP.get_response(URI.parse(url))

    @record = JSON.parse(resp.body)

  end

  def fname
    return @record['first_name']
  end

  def lname
    return @record['last_name']
  end

  def companies

    firms = Array.new

    @record['relationships'].each do | firm |

        firms << firm['firm']['permalink']

    end

    return firms

  end

end

Эти два простых класса Ruby скрывают всю сложность взаимодействия с HTTP-сервером в классе Net::HTTP. Сложность анализа ответа JSON полностью снимается Ruby-компонентом JSON. Теперь рассмотрим пару приложений на базе этих API для демонстрации их использования.


Создание простых приложений

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

Листинг 5. Поиск людей, связанных с компанией (people.rb)
#!/usr/bin/ruby

load "company.rb"
load "person.rb"

# Получение аргумента (постоянная ссылка для компании)
input = ARGV[0]

company = Crunchbase_Company.new(input)

people = company.people

# Анализ хеша персон
people.each do |name, title|

    # Извлечение персональной записи
    person = Crunchbase_Person.new( name )

    # Выделение имени и должности
    print "#{person.fname} #{person.lname} | #{title}\n"

end

people = nil
company = nil

Нужно выполнить сценарий из листинга 5, как показано в листинге 6. В этом сценарии мы передаем постоянную ссылку компании, а результатом служат имя, фамилия и должность.

Листинг 6. Тестирование сценария people.rb
$ ./people.rb emulex
Jim McCluney | President and CEO
Michael J. Rockenbach | Executive Vice President and CFO
Jeff Benck | Executive Vice President & COO
$

Теперь рассмотрим более сложный пример. В нем определяется состав руководства компании. Затем определяются те компании, в которых эти руководители работали в прошлом. Соответствующий сценарий influence.rb. предоставлен в листинге 7. В качестве аргумента принимается название компании и извлекается соответствующая запись, а затем хеш персон, работающих в этой компании (с помощью метода people). Из этих персон выбираются руководители (не вполне точно ― из-за вариаций названий должностей в CrunchBase). Наконец, для всех выявленных руководителей выделяются компании, в которых они работали раньше (путем извлечения массива компаний из персональной записи).

Листинг 7. Связи и влияние руководства (influence.rb)
#!/usr/bin/ruby

load "crunchbase.rb"

input = ARGV[0]

puts "Executive Relationships to " + input

company = Crunchbase_Company.new(input)

people = company.people

# Перебор всех, кто связан с этой компанией
people.each do |name, title|

    # Поиск только определенных должностей
    if title.upcase.include?("CEO") or
       title.upcase.include?("COO") or
       title.upcase.include?("CFO") or
       title.upcase.include?("CHIEF") or
       title.upcase.include?("CTO") then

        person = Crunchbase_Person.new( name )

        companies = person.companies

        companies.each do | firm |
            if input != firm
                puts "  " + firm
            end
        end

    end

end

Этот сценарий для данных крупной компании Cloudera приведен в листинге 8. Как видно из листинга 7, Ruby и его компоненты скрывают многие технические детали, позволяя сосредоточиться на самой задаче.

Листинг 8. Тестирование Ruby-сценария для определения связей руководства
$ ./influence.rb cloudera
Executive Relationships to cloudera
  accel-partners
  bittorrent
  mochimedia
  yume
  lookout
  scalextreme
  vivasmart
  yahoo
  facebook
  rock-health
$

Использование других методов HTTP REST

В приведенных здесь простых примерах используется только метод GET для извлечения данных из базы данных CrunchBase. Другие сайты могут предоставлять расширенный интерфейс к своему REST-серверу для извлечения и записи данных. Листинг 9 демонстрирует другие методы Net::HTTP.

Листинг 9. HTTP-методы для других RESTful-взаимодействий
http = Net::HTTP.new("site url")

# Удаление ресурса
transaction = Net::HTTP::Delete.new("resource")
response = http.request(transaction)

# Передача ресурса
resp, data = Net::HTTP.post_form( url, post_arguments )

# Запись ресурса
transaction = Net::HTTP::Put.new("resource")
transaction.set_form_data( "form data..." )
response = http.request(transaction)

Для дальнейшего упрощения разработки REST-клиентов можно использовать дополнительные Ruby-компоненты, такие как rest-client (см. раздел Ресурсы). Этот компонент обеспечивает уровень REST поверх протокола HTTP, предлагая такие методы, как get, post и delete.


Что дальше

Я надеюсь, что это краткое введение в принципы программирования REST с применением языка Ruby иллюстрирует возможности Web-архитектуры и объясняет, почему Ruby ― один из моих любимых языков программирования. REST ― одна из популярнейших архитектур API-интерфейсов облачных систем вычислений и хранения данных, и потому стоит выделить некоторое время на ее освоение и изучение. В разделе Ресурсы приведены ссылки на дополнительные сведения по технологиям, используемым в этой статье, а также на другие статьи по REST, предлагаемые на портале developerWorks.

Ресурсы

  • Оригинал статьи: Understand Representational State Transfer (REST) in Ruby.
  • REST — это программная архитектура для связи между распределенными системами. Впервые REST предложил Рой Филдинг (Roy Fielding) в своей докторской диссертации Архитектурные стили и проектирование сетевых архитектур программного обеспечения в Калифорнийском университете в Ирвине.
  • REST построен на основе HTTP. Подробную информацию о HTTP можно найти в документе Комиссии по технологиям Интернета Request for Comments-2616 (для версии 1.1).
  • Интересное видеовведение в REST Intro to REST, размещенное на Google Джозефом Грегорио (Joe Gregorio).
  • Подробнее о полезных идеях и технологиях, которые рассматриваются в этой статье: URI, URL и JSON.
  • Ruby ― мой любимый язык сценариев и один из самых интуитивно понятных языков программирования, которыми я когда-либо пользовался. Ruby хорош не только для быстрого прототипирования и экспериментальных разработок, но и для разработки производственного ПО. Те, кто только знакомится с Ruby, найдут полезным это прекрасное введение в Ruby: Ruby за 20 минут (EN).
  • Подробнее о REST:
  • Чтобы еще больше упростить реализацию REST в Ruby, попробуйте Ruby-компонент rest-client, размещенный на Github. Этот REST-клиент упрощает наиболее важные операции и позволяет сосредоточиться на разрабатываемом приложении.

Комментарии

developerWorks: Войти

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


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


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

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

 


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

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

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



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

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

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

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

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

 


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


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Linux
ArticleID=861183
ArticleTitle=Программирование в стиле Representational State Transfer (REST) на Ruby
publish-date=03122013