Вечер с Sinatra для создания сервиса TwitterBar

  • написал: MpaK
  • 442
Вообще я бы хотел представить и немного рассказать о своём маленьком детище, так сказать о коде за вечер. Я всегда считал и считаю, что если хочешь, что-то выучить, а особенно понять технологию, то надо конечно много читать, а еще главное взять, сесть и сделать на этой технологии, что-нибудь пусть даже just for fun. Все равно ведь программирование это творческая работа и у каждого из нас есть в голове какие-то идеи проектов, сервисов, стартапов и если даже нет, то всё равно бывают возникают потребности, какие-нибудь маленькие, но если их взять и решить так за вечер — это и есть удовольствие от изучения. Это моё конечно имхо.

Но вот как-то и у меня возникло одно желание, точнее потребность в твиттер «эксгибиционизме» в широком плане. Я регулярно веду свой твиттер где регулярно делюсь ссылочками о технологиях, программировании, на музыку иногда хорошую, иногда просто рассказываю о жизни в азии или где мы там путешествуем. Так вот иногда, мне хочется чтобы эти твиты были видны большему количеству людей и по аналогии например с Last.fm который даёт возможность показывать, что вы слушаете в текущий момент (я кстати делал и для этого сервис LastFM.bar) я решил, что хорошо бы чтобы например посетители форума в котором я пишу, видели в подписи мой последний твит. Проблема, что форум не мой, а в подпись вставлять можно только например изображения, никакой динамики на JS и прочем, только BBCode, только картинки.
Потому я решил, что было бы здорово собрать очень легкий и простой сервис, который будет всего делать 3 вещи:
1. брать последний твитт пользователя, например в формате JSON
2. разобрать это JSON, взять тематическую картинку для фона, настроить шрифты, размеры, расставить переносы по тексту и нарисовать этот самый текст на картинке
3. отдать заголовки на кэширование и отдать саму картинку
Всё.

Ну а так как последнее время я всё больше и больше проектов стараюсь делать на Ruby, в частности Ruby on Rails, но в данном случае я считаю, что для меня этот большой фрэймворк был бы избыточным. На ум сразу пришел старый, добрый Sinatra. Его можно называть прародителем вебовских фрэймворков, он смог дать жизнь таким последователям на PHP как Slim, Fat3, на Perl есть его младший брать Dancer, и даже многое взял Mojolicious::Lite в свою копилку. Но все эти фрэймворки лишь тень, у них свой и немного уродливый DSL, нет той красоты Ruby, попробую вам немного показать эту красоту.

Начнём с демонстрации, так как проект не использует Базу Данных и чисто не коммерческий, в таких вариантах есть отличный хостинг сервис Heroku.com его я и использовал, тем более, что размещение на хостинге еще не было таким простым как это сделал Heroku. И так, демо здесь: twitterbar.heroku.com
А исходники можно скачать прямо отсюда, их очень мало и они очень простые github.com/mpakus/twitterbar

Давайте немного расскажу о Sinatra в целом и пробежимся по структуре и исходниках приложения.

Что такое Sinatra? Вообще-то это обычный gem (библиотека) которую можно поставить в одну строку
> gem install sinatra

создать новый файл
> mcedit first_app.rb

написать в нём
require 'sinatra'
get '/' do
  "Hello World"
end


и запустить этот файл в консоли
> ruby first_app.rb
после чего создастся веб-сервер на локальном хосте по порту 4567, теперь можно открыть свой любимый браузер и набрать в нём localhost:4567 и увидеть работу своего первого приложения на Sinatra.
Вуа-ля, просто и удобно!

В чем соль? В простоте, подключили одну библиотеку через require, расписали через удобный DSL язык нужные роуты, при желании взяли любимый шаблонизатор например встроенный в язык ERB или HAML, кто-то и Slim уже успел полюбить, вынесли в нужную нам папочку например views шаблоны, подключили их и показали статичные файлы из папки public, а можно вообще шаблоны хранить прямо в основном файле, еще проще и еще компактнее. Всё это так гибко и просто настраиваемо, что диву даёшься как это ярко и понятно из документации www.sinatrarb.com/documentation

Теперь о моём простеньком приложении за вечер.

В корне лежит основной файл приложения index.rb и есть два вспомогательных класса, методы которых я сделал синглетными так как объекты мне не нужны, у нас и так в Ruby даже классы объекты :) это сложно понять, но что-то подобное мне казалось я видел еще в Basic языке, выполнение прямо по коду, строка за строкой. Но это я отвлекся, и так, два класса, которые я написал и вынес в папочку classes: twitter.rb — который просто достаёт последний твит пользователя в формате JSON и превращает его в обычную ruby хэш
imagebar.rb — так же простенький класс для рисования на картинке нашего текста, использует библиотеку rmagick, это адаптер к утилите ImageMagick

А теперь пойдём по приложению и я попробу расписать каждую строку с комментарием, кстати комменты на Ruby как и в Perl начинаются с # мне например после стольких лет Perl разработки было вообще приятно и удобно.

И так index.rb по строкам, чтобы понимать что используем, для чего и как.

require 'rubygems' # в 1.9 Ruby уже не надо, но для пакетов
require 'sinatra' # подключили сам фрэймворк
require 'sinatra/reloader' # класс для автоматической перегрузки сервера приложений при внесении изменений
require Dir.pwd + '/classes/twitter.rb' # наш класс для парсинга сообщений из Твиттера
require Dir.pwd + '/classes/imagebar.rb' # наш класс для рисования текста на изображении
require 'ostruct' # класс для приведения yaml конфигов в структуры ruby
require 'yaml' # для чтения yaml конфига

set :env,  :production # устанавливаем переменную окружения

configure do  # блок конфигурации, вызывается при старте приложения
  set :root, File.dirname(__FILE__)
  enable :static # чтобы работала отдача статичных файлоф из папки publc
  enable :logging # логгирование
  enable :dump_errors # для дампа ошибок
  disable :sessions # а вот сессии нам не нужны
end

get '/' do # наш первый роутинг для главной страницы
  response['Cache-Control'] = "public, max-age=#{5*60*10}" # заголовок для кэширования страницы
  erb :index # покажем наш уже готовый шаблон views/index.html.erb внутри стандартного layout
end # этот роутинг просто отрисовывает интерфейс для пользователя

get '/twit/:theme/:user.gif' do # второй роутинг, на запрос отрисовывания картинки
  theme_dir = Dir.pwd + '/themes/' # наши настройки к ТЕМИЗАЦИИ картинки
  user_name = params[:user] # получаем из адресной строки имя пользователя
  theme     = params[:theme] # получаем название ТЕМЫ для картинки тоже из адреса

  # наши обработчики разных ошибок, что показать и сказать если чего-то нет  
  halt [ 404, "Page not found" ]           unless user_name
  halt [ 500, "Sorry wrong theme" ]        unless theme
  halt [ 404, "Can't find themes file" ]   unless FileTest.exists? theme_dir + theme + '.yml'
  halt [ 404, "Can not found background" ] unless FileTest.exists? theme_dir + theme + '.gif'
  
  # читаем yaml файл конфигурации темы и преобразовываем в ruby объект
  conf = OpenStruct.new( YAML.load_file theme_dir + theme + '.yml' )
  
  # получаем данные последнего твита пользователя
  twit = Twitter.get_last_post user_name

  # выдаём заголовки для кэширования картинки, например на 2 минуты
  response['Cache-Control'] = "public, max-age=#{60*2}"
  content_type 'image/gif' # теперь mime тип контента, что хотим отдать
  ImageBar.draw theme_dir, theme, twit, conf # рисуем текст на картинке и отдаём в браузер
end


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

Пойдём дальше? Два наших класса, первый это Twitter:
class Twitter # объявляем наш класс
  require 'open-uri' # библиотечка для простого получения страниц, а-ля CURL утилита
  require 'json' # для чтения и разбора JSON структур
  
  class << self # внутри этого блока получается всё будет синглетным и принадлежать классу

    last_post = nil # наше свойство в котором будем хранить последний твит
     
    def get_last_post user_name # метод которому передаём имя пользователя
      download user_name # метод для скачивания твита
      get_data user_name # метод для разбора твита и возврат результатов
    end
    
    protected # дальше пойдут защищенные методы, используемы внутри класса
  
    def download user_name # скачали по хитрому адресу json текст твита
      @last_post = open("http://api.twitter.com/1/statuses/user_timeline/#{user_name}.json").read
    end
    
    def get_data user_name
      JSON.parse @last_post # разобрали строку JSON в объект и вернули
    end

  end
end

Просто ведь согласитесь?

Ну и наш второй класс ImageBar для отрисовки твита на картинке:
class ImageBar # объявили наш класс
  require 'RMagick' # подключили библиотеку для работы с изображениями
   
  class << self # дальше тоже синглетные методы
    
    def draw theme_dir, theme, twit, conf # наш метод для отрисовки по куче параметров
      # подгрузим нашу картинку из настроек ТЕМЫ
      img = Magick::ImageList.new theme_dir + theme + '.gif'
      
      text = Magick::Draw.new # начинаем на настраивать отображение текста
      text.font        = conf.font # это шрифт из настроек
      text.pointsize   = conf.font_size.to_i # это размеры
      text.gravity     = Magick::NorthWestGravity # а это положение на картинке
      text.fill        = conf.color # цвет текста
      text.kerning     = conf.kerning # межбуквенное расстояние
      text.interline_spacing = conf.interline_spacing # межстрочное расстояние 

      # теперь рисуем текст, но прежде его через метод wrap_text подгоняем под размеры
      text.annotate img, conf.width, conf.height, conf.left, conf.top, wrap_text( twit[0]['text'], conf.chars_in_row )      
      img.to_blob # и возвращаем наше сырое изображение    
    end

    def wrap_text(txt, col = 30) # метод для разбиения текста на строки 
      txt.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/, "\\1\\3\n") 
    end
    
  end
end

Вот и всё, больше никакой магии, всё просто, как кирпич и работает так же надежно и просто.

С учетом всего изучения всяческих манов по RMagick например и Sinatra получил за вечер массу удовольствия и знаний, а это очень важно, чтобы мозги держать в тонусе и не утонить в этом PHP однообразии.

Сейчас вот с удовольствием почитываю книжку по Sinatra с названием Sinatra Up and Running — доступная и простая в освоении литература, рассказывает все тонкости этого простого, но в тоже время очень мощного и лаконичного Ruby фрэймворка, рекомендую и книгу и фрэймворк, так как мир Ruby жив не только Ruby on Rails фрэймворком, но и вот такими очень удобными лошадками, которые в ряде случаев даже лучше в использовании на задачах, когда нет нужны в большой инфраструктуре Rails, как создание всяких API, быстрых сборов статистики, отрисовке каких-то вот таких статичных картинок (счетчики, рейтинги, голосовалки), в общем всё, что требует простоты, скорости и минимализма кода и подгружаемого оверхэда.

На этом всё, всем спасибо. Если есть какие-то вопросы, предложения или замечания, то с радостью готов их прочитать и подискутировать.
  • +1

5 комментариев

avatar
чегота я не вкурил, как так, под каждый проект сервер отдельный запускается? или это для примера просто написал?)
avatar
  • MpaK
  • 0
Ну вариантов запуска очень много, можно юзать прям как mod_php установив например Passenger под Apache или NGINX, но тогда теряется немного гибкость, так как разные версии Ruby сложнее использовать. Я использую RVM и обычно связка у меня NGINX на всё, а рубишные серверы это Unicorn или Thin, они очень быстрые, очень прям, что Unicorn даже наружу выставлять нельзя, так как закрывает соединение не дожидаясь клиента, просто отдав ответ и всё :)
Тебе по идеи запуск сервера приложений под каждое приложение должен быть знаком, из Ява мира, не?
avatar
Ну с явой типа того, да, но там схема немного другая же. Там и само по себе приложение можно сказать копилируется в несколько этапов и вообще. Тут больше ведь «скриптовая» схема подходит, разве же нет.
avatar
  • MpaK
  • 0
Ну как скриптовая, тоже получается после запуская в памяти сервера — это уже а-ля «скомпилированный» код, то бишь он не компилица на каждый запрос как например голый PHP скрипт (без всяких байткомпилеров а-ля eAccelerator).
Потом есть еще JRuby под которым тоже можно отлично пускать рубишные скрипты на JVM, еще интересный проект Rubynius — это реализация руби машины на самом руби, а-ля PyPy, там уже немного по другому.
комментарий был удален
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.