Cramp ‒ полностью асинхронный фреймворк реального времени, написанный Pratik Naik, разработчиком в 37signals и членом Rails core-team. Этот фреймворк предназначен прежде всего для организации двунаправленного общения между клиентом и сервером и имеет встроенную поддержку WebSockets и Server-Sent Events. В этой статье мы разберем основные вещи, касающиеся использования данного инструмента, а также попытаемся разобраться в его архитектуре и понять, как это работает.
Пожалуй основным недостатком фреймворка является малое количество статей и примеров, имеется лишь небольшая страница документации на официальном сайте и собственно сам код проекта на гитхабе. Оттуда в основном и было взято все, написанное ниже.
Cramp основан на EventMachine, ActiveSupport и Thor и запускается на сервере Thin или Rainbows.
Thor используется для генерации приложения — в бинарнике вызывается Cramp::Generators::Application.start, который последовательно выполняет все публичные методы класса Application, названия которых: create_root, create_config, create_models, говорят сами за себя.
Далее, при запуске сервера стартует цикл EventMachine, который и отлавливает все события, запуская привязанные к ним коллбэки. Именно поэтому Cramp работает только на Rainbows или Thin(по умолчанию), которые построены на EM.
Ключевым в философии Cramp является понятие Action(классы Action и Abstract). Action является чем-то средним между контроллером и собственно экшеном в Rails(если вообще уместно сравнивать асинхронный и синхронный фреймворки). Каждый action обрабатывает один запрос или, правильнее будет сказать, одну точку входа, ведь к примеру соединение через веб-сокет одним запросом не назовешь.
Обычный запрос условно делится на 4 стадии:
Киллер-фичей данного фреймворка являются WebSockets. Они поддерживаются из коробки и не требуют отдельного сервера. Чтобы использовать веб-сокеты достаточно в своем action’е создать метод-обработчик on_data, все остальные вопросы решит сервер, реализующий EM-метод receive_data, срабатывающий при получении сообщения из соединения и вызывающий пользовательский обработчик:
Thin
Rainbows
На этом закончим с архитектурой и перейдем к использованию.
Для работы с Cramp рекомендуется использовать Ruby ветки 1.9 для использования файберов, что при наличие таких инструментов как rvm или rbenv не должно стать проблемой даже если вы до сих пор сидите на 1.8.7.
Установка Cramp’а никаких трудностей вызывать не должна:
Далее запускаем
и вышеупомянутый Thor генерирует скелет вашего приложения.
Затем, находясь в папке проекта, выполняем bundle install, который подтянет все зависимости.
У нас уже создан один action — app/actions/home_action.rb, со следующим содержимым:
Как несложно понять из этого кода, обработчик start формирует ответ в виде строки “Hello World!” и завершает обработку запроса. Проверяем это предположение, запустив сервер:
и действительно, все работает, как ожидалось. Насколько я понимаю, Thin сам подхватывает файл config.ru, лежащий в папке проекта, но для верности можно запускать эту команду в таком виде:
или
для отладки в режиме “verbose”.
Естественно нам не нужно рендерить статичесикй текст и html-разметку напрямую, для этого должны использоваться представления(views), находящиеся в отдельных файлах. Создаем папку app/views и в ней файл index.erb со следующим нехитрым содержимым:
А в папке public/stylesheets создаем файл styles.css всего с одной строкой:
Как видно по расширению, в этом примере используется движок Erb, но вы конечно же можете использовать Haml, Slim или все, что вашей душе угодно, предварительно включив соответствующую запись в Gemfile.
Чтобы action рендерил содержимое из erb-файла, для начала подключим сам движок:
Можно добавить эту строку в сам action, но чтобы использовать Erb во всем поекте удобнее подключить его в application.rb.
Далее изменяем home_action.rb:
Здесь ExampleProject — имя самого проекта. Но где же наш css? Чтобы css-файлы были доступны, нужно разрешить раздачу статического контента из содержащей их папки в файле config.ru:
Ну вот, заголовок стал красным, как и ожидалось.
Теперь пойдем дальше и создадим новый action WsAction, который будет общаться с клиентом через веб-сокеты. Для начала установим, на каком сервере будут работать веб-сокеты:
Опять же, вместо Thin можно использовать Rainbows, лично я предпочитаю Thin только потому, что он лежит на Github и доков по нему больше.
Далее в папке app/actions создаем файл ws_action.rb и добавляем в него такой код:
Достаточно добавить в текст класса строку self.transport = :websocket, чтобы он принимал и отсылал сообщения через WebSockets. Затем мы устанавливаем обработчик сообщения и отсылаем приветствие, если было получено сообщение, начинающееся с “hello”.
Чтобы попробовать это в действии, во-первых нужно добавить маршрут(route) для класса WsAction (файл config/routes.rb):
А во-вторых добавляем в index.erb клиентский код примерно такого вида:
Для Firefox’а последних версий WebSocket заменяем на MozWebSocket. Клиентский код очень прост и представлен исключительно для демонстрации(как собственно и серверный), поэтому условие для класса веб-сокетов или бэкграунд на флэше в него не входят, сделайте это сами.
На этом все, если вы захотите более углубленно изучить Cramp, есть еще несколько тем, не затронутых в данной статье: Server-Side Events, файберы, работа с ActiveRecord. Ну и конечно же вы можете помочь развитию проекта с помощью bug report’ов и pull request’ов на гитхабе.
Код проекта
github.com/lifo/cramp
Сайт проекта(документация, ссылки на примеры)
cramp.in
Что такое EventMachine и как его использовать
github.com/eventmachine/eventmachine/wiki
Websockets with Cramp
vinsol.com/blog/2011/05/09/websockets-with-cramp
Еще одна небольшая статья про Cramp и WebSockets
boldr.net/html5-websockets-cramp
Пожалуй основным недостатком фреймворка является малое количество статей и примеров, имеется лишь небольшая страница документации на официальном сайте и собственно сам код проекта на гитхабе. Оттуда в основном и было взято все, написанное ниже.
Архитектура
Cramp основан на EventMachine, ActiveSupport и Thor и запускается на сервере Thin или Rainbows.
Thor используется для генерации приложения — в бинарнике вызывается Cramp::Generators::Application.start, который последовательно выполняет все публичные методы класса Application, названия которых: create_root, create_config, create_models, говорят сами за себя.
Далее, при запуске сервера стартует цикл EventMachine, который и отлавливает все события, запуская привязанные к ним коллбэки. Именно поэтому Cramp работает только на Rainbows или Thin(по умолчанию), которые построены на EM.
Ключевым в философии Cramp является понятие Action(классы Action и Abstract). Action является чем-то средним между контроллером и собственно экшеном в Rails(если вообще уместно сравнивать асинхронный и синхронный фреймворки). Каждый action обрабатывает один запрос или, правильнее будет сказать, одну точку входа, ведь к примеру соединение через веб-сокет одним запросом не назовешь.
Обычный запрос условно делится на 4 стадии:
- Инициализация запроса ‒ вызываются коллбэки before_start.
На этой стадии заголовки еще не отправлены, можно перенаправить пользователя куда-нибудь или прервать обработку запроса. В документации написано, что рекомендуется использовать before_start, чтобы проверять запрос или права доступа пользователя. На before_start можно вешать несколько методов, которые будут выполнены последовательно, но каждый из них должен вызывать yield или halt, чтобы продолжить или прервать обработку запроса соответственно. - Инициализация ответа ‒ соответствует функции respond_with.
Служит для формирования заголовков и обычно используется стандартный заголовок с кодом 200:
def build_headers status, headers = respond_to?(:respond_with, true) ? respond_with.dup : [200, {'Content-Type' => 'text/html'}] headers['Connection'] ||= 'keep-alive' [status, headers] end
- Запрос запущен ‒ выполняется on_start.
Здесь и должно происходить формирование ответа. На on_start можно так же вешать несколько коллбэков, которые будут выполняться одновременно, при этом каждый из них может вызывать метод render и не по одному разу. - Запрос завершен ‒ вызывается on_finish.
Выполняется после формирования ответа и служит, чтобы что-то подчистить, закрыть открытые соединения и т.п.
Киллер-фичей данного фреймворка являются WebSockets. Они поддерживаются из коробки и не требуют отдельного сервера. Чтобы использовать веб-сокеты достаточно в своем action’е создать метод-обработчик on_data, все остальные вопросы решит сервер, реализующий EM-метод receive_data, срабатывающий при получении сообщения из соединения и вызывающий пользовательский обработчик:
Thin
callback = @request.env[Thin::Request::WEBSOCKET_RECEIVE_CALLBACK]
callback.call(data) if callback
Rainbows
callback = @env[WEBSOCKET_RECEIVE_CALLBACK]
callback.call(data) if callback
На этом закончим с архитектурой и перейдем к использованию.
Использование
Для работы с Cramp рекомендуется использовать Ruby ветки 1.9 для использования файберов, что при наличие таких инструментов как rvm или rbenv не должно стать проблемой даже если вы до сих пор сидите на 1.8.7.
Установка Cramp’а никаких трудностей вызывать не должна:
gem install cramp
Далее запускаем
cramp new project_name
и вышеупомянутый Thor генерирует скелет вашего приложения.
Затем, находясь в папке проекта, выполняем bundle install, который подтянет все зависимости.
У нас уже создан один action — app/actions/home_action.rb, со следующим содержимым:
class HomeAction < Cramp::Action
def start
render "Hello World!"
finish
end
end
Как несложно понять из этого кода, обработчик start формирует ответ в виде строки “Hello World!” и завершает обработку запроса. Проверяем это предположение, запустив сервер:
bundle exec thin start
и действительно, все работает, как ожидалось. Насколько я понимаю, Thin сам подхватывает файл config.ru, лежащий в папке проекта, но для верности можно запускать эту команду в таком виде:
bundle exec thin --max-persistent-conns 1024 --timeout 0 -R config.ru start
или
bundle exec thin --max-persistent-conns 1024 --timeout 0 -V -R config.ru start
для отладки в режиме “verbose”.
Естественно нам не нужно рендерить статичесикй текст и html-разметку напрямую, для этого должны использоваться представления(views), находящиеся в отдельных файлах. Создаем папку app/views и в ней файл index.erb со следующим нехитрым содержимым:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<link rel="stylesheet" type="text/css" href="stylesheets/styles.css" />
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
А в папке public/stylesheets создаем файл styles.css всего с одной строкой:
h1{color:red}
Как видно по расширению, в этом примере используется движок Erb, но вы конечно же можете использовать Haml, Slim или все, что вашей душе угодно, предварительно включив соответствующую запись в Gemfile.
Чтобы action рендерил содержимое из erb-файла, для начала подключим сам движок:
require ‘erb’
Можно добавить эту строку в сам action, но чтобы использовать Erb во всем поекте удобнее подключить его в application.rb.
Далее изменяем home_action.rb:
class HomeAction < Cramp::Action
def start
page = ERB.new(File.read(ExampleProject::Application.root('app/views/index.erb')))
render page.result(binding)
finish
end
end
Здесь ExampleProject — имя самого проекта. Но где же наш css? Чтобы css-файлы были доступны, нужно разрешить раздачу статического контента из содержащей их папки в файле config.ru:
use Rack::Static, :urls => ["/javascripts", "/stylesheets"], :root => CrampArticle::Application.root(:public)
Ну вот, заголовок стал красным, как и ожидалось.
Теперь пойдем дальше и создадим новый action WsAction, который будет общаться с клиентом через веб-сокеты. Для начала установим, на каком сервере будут работать веб-сокеты:
Cramp::Websocket.backend = :thin
Опять же, вместо Thin можно использовать Rainbows, лично я предпочитаю Thin только потому, что он лежит на Github и доков по нему больше.
Далее в папке app/actions создаем файл ws_action.rb и добавляем в него такой код:
class WsAction < Cramp::Action
self.transport = :websocket
on_data :process_data
def process_data(data)
render "Hello" if /^hello/i =~ data
end
end
Достаточно добавить в текст класса строку self.transport = :websocket, чтобы он принимал и отсылал сообщения через WebSockets. Затем мы устанавливаем обработчик сообщения и отсылаем приветствие, если было получено сообщение, начинающееся с “hello”.
Чтобы попробовать это в действии, во-первых нужно добавить маршрут(route) для класса WsAction (файл config/routes.rb):
get('socket/').to(WsAction)
А во-вторых добавляем в index.erb клиентский код примерно такого вида:
<script type="text/javascript">
var ws = new WebSocket("ws://localhost:3000/socket/");
ws.onopen = function() {
ws.send("Hello!");
};
ws.onmessage = function(e) {
alert(e.data);
};
ws.onclose = function() {
alert("closed");
};
</script>
Для Firefox’а последних версий WebSocket заменяем на MozWebSocket. Клиентский код очень прост и представлен исключительно для демонстрации(как собственно и серверный), поэтому условие для класса веб-сокетов или бэкграунд на флэше в него не входят, сделайте это сами.
На этом все, если вы захотите более углубленно изучить Cramp, есть еще несколько тем, не затронутых в данной статье: Server-Side Events, файберы, работа с ActiveRecord. Ну и конечно же вы можете помочь развитию проекта с помощью bug report’ов и pull request’ов на гитхабе.
Дополнительные ссылки
Код проекта
github.com/lifo/cramp
Сайт проекта(документация, ссылки на примеры)
cramp.in
Что такое EventMachine и как его использовать
github.com/eventmachine/eventmachine/wiki
Websockets with Cramp
vinsol.com/blog/2011/05/09/websockets-with-cramp
Еще одна небольшая статья про Cramp и WebSockets
boldr.net/html5-websockets-cramp