Программирование в стиле Representational State Transfer (REST) на Ruby
Построение простого RESTful-клиента с использованием языка Ruby
Передача репрезентативного состояния (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 приведен 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 ― это не столько конкретная конструкция или реализация, сколько архитектурный стиль. Как показано на рисунке 1, архитектура RESTful определяется простым набором ограничений. В основе RESTful-архитектуры лежит набор ресурсов. Эти ресурсы определяются своим URI (например, Uniform Resource Locator [URL]) и внутренним представлением (обычно это форма самоописываемых данных, которые мы рассмотрим ниже). Наконец, существует набор операций, с помощью которых этими ресурсами можно управлять.
Рисунок 1. Общее представление архитектуры 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-взаимодействий

Кликните, чтобы увидеть увеличенное изображение
Интересным примером 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

Кликните, чтобы увидеть увеличенное изображение
Если нужно получить текущую информацию о компании 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:
- RESTful Web services: The basics (Alex Rodriguez, ноябрь 2008 г.): полезное введение в REST и его основные особенности.
- REST application programming (Paul Sonnenberg, сентябрь 2010 г.): Java™-программирование на основе REST.
- Using the Twitter REST API (Brian M. Carey, июнь 2009 г.): знакомство с REST-API Twitter.
- Чтобы еще больше упростить реализацию REST в Ruby, попробуйте Ruby-компонент
rest-client
, размещенный на Github. Этот REST-клиент упрощает наиболее важные операции и позволяет сосредоточиться на разрабатываемом приложении.