Загрузка...


  • 19.1. Программирование CGI на Ruby
  • 19.1.1. Введение в библиотеку cgi.rb
  • 19.1.2. Вывод и обработка форм
  • 19.1.3. Куки
  • 19.1.4. Сеансы пользователей
  • 19.2. FastCGI
  • 19.3. Ruby on Rails
  • 19.3.1. Принципы и техника
  • 19.3.2. Тестирование и отладка приложений Rails
  • 19.3.3. Базовые расширения
  • 19.3.4. Дополнительные инструменты и библиотеки
  • 19.4. Разработка Web-приложений с помощью Nitro
  • 19.4.1. Создание простого приложения Nitro
  • 19.4.2. Nitro и паттерн MVC
  • 19.4.3. Nitro и Og
  • 19.4.4. Решение типичных для Web-приложений задач в Nitro
  • 19.4.5. Прочие детали
  • 19.5. Введение в Wee
  • 19.5.1. Простой пример
  • 19.5.2. Ассоциирование состояния с URL
  • 19.6. Разработка Web-приложений с помощью IOWA
  • 19.6.1. Основные идеи IOWA
  • 19.6.2. Шаблоны в IOWA
  • 19.6.3. Передача управления компоненту
  • 19.7. Ruby и Web-сервер
  • 19.7.1. Модуль mod_ruby
  • 19.7.2. Использование erb
  • 19.7.3. Сервер WEBrick
  • 19.7.4. Сервер Mongrel
  • 19.8. Заключение
  • Глава 19. Ruby и Web-приложения

    Как ловко мы сплетаем сеть…

    (Сэр Вальтер Скотт, «Мармион»)

    Ruby — универсальный язык, его ни в коей мере нельзя считать исключительно «языком Web». Но, несмотря на это, одно из наиболее типичных его применений — создание приложений (да и вообще инструментов в широком смысле) для Web.

    Существует множество способов разрабатывать приложения для Web на Ruby — от сравнительно небольших и низкоуровневых библиотек до каркасов, которые диктуют стиль кодирования и мышления.

    Начнем с низкого уровня и рассмотрим библиотеку

    cgi.rb
    , входящую в стандартный дистрибутив Ruby.

    19.1. Программирование CGI на Ruby

    Всякий, кто знаком с программированием для Web, хотя бы раз встречал аббревиатуру CGI (Common Gateway Interface — общий шлюзовой интерфейс). Спецификация CGI появилась на заре развития Web с целью обогатить взаимодействие между пользователем и Web-сервером. С тех пор были изобретены бесчисленные альтернативные технологии, но CGI все еще живет и прекрасно себя чувствует. Своим успехом и долговечностью технология CGI обязана простоте, благодаря которой программы, удовлетворяющие этой спецификации, можно без труда писать на любом языке. Спецификация определяет, как процесс Web-сервера должен передавать данные своим потомкам. По большей части взаимодействие сводится к стандартным переменным окружения и потокам ввода/вывода.

    Программирование с применением CGI, да и вообще для протокола HTTP, должно учитывать отсутствие «состояния» в механизме запрос-ответ. В общем случае клиент (обычно браузер) для каждого запроса создает новое соединение и посылает по нему единственную команду HTTP. Чаще всего используются команды

    GET
    и
    POST
    (к смыслу этих слов мы вернемся чуть позже). Сервер посылает ответ, после чего закрывает свой конец соединения.

    В следующем примере, который лишь немногим сложнее пресловутой программы «Hello, world», показано, как выполняются ввод и вывод по спецификации CGI.

    def parse_query_string

     inputs = Hash.new

     raw = ENV['QUERY_STRING']

     raw.split("&").each do |pair|

      name,value = pair.split("=")

      inputs[name] = value

     end

     inputs

    end


    inputs = parse_query_string

    print "Content-type: text/html\n\n"

    print "<HTML><BODY>"

    print "<B><I>Hello</I>, #{inputs['name*]}!</B>"

    print "</BODY></HTML>"

    Так, обращение с помощью этой программы к URL http://mywebserver/cgi-bin/hello.cgi?name=Dali приведет к отправке браузеру сообщения «Hello, Dali!».

    Мы уже упомянули, что есть два основных способа обратиться к UTL: методы

    GET
    и
    POST
    , определенные в протоколе HTTP. Для краткости мы предложим простые объяснения, а не строгие определения. Метод
    GET
    обычно вызывается, когда вы щелкаете по ссылке или указываете URL непосредственно (как в предыдущем примере). Параметры передаются в строке запроса, которую CGI-программы видят как значение переменной окружения
    QUERY_STRING
    . Метод
    POST
    обычно применяется для отправки HTML-форм. Параметры включаются в тело сообщения и в URL не видны. CGI-программе они доставляются через стандартный поток ввода.

    Предыдущий пример был предельно простым, но в менее тривиальных случаях программа быстро становится запутанной. Приходится иметь дело с различными методами HTTP, загрузкой файлов, куками, сеансами «с состоянием» и другими сложностями, которые лучше оставить библиотеке общего назначения, поддерживающей спецификацию CGI. К счастью, в Ruby имеется обширный набор классов для автоматизации рутинной работы.

    Есть еще немало библиотек и инструментов, стремящихся упростить разработку в рамках CGI. К числу лучших следует отнести библиотеку ruby-web (прежнее название Narf) Патрика Мэя (Patrick May). Если вам нужно работать на низком уровне, но стандартная библиотека почему-либо не устраивает, попробуйте эту (http://ruby-web.org).

    Если необходимо решение на основе шаблонов, возможно, подойдет библиотека Amrita (http://amrita.sourceforge.jp). Обратите также внимание на Cerise — сервер приложений на базе Amrita (http://cerise.rubyforge.org).

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

    19.1.1. Введение в библиотеку cgi.rb

    Библиотека

    cgi.rb
    включена в стандартный дистрибутив Ruby. Ее функциональность в основном реализована в центральном классе
    CGI
    . Первое, что нужно сделать при работе с данной библиотекой, — создать экземпляр этого класса.

    require "cgi"

    cgi = CGI.new("html4")

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

    cgi.out do

     cgi.html do

      cgi.body do

       cgi.h1 { "Hello Again, "} +

       cgi.b { cgi['name']}

      end

     end

    end

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

    CGI
    берет на себя заботу о разборе и сохранении параметров в структуре, напоминающей хэш. Поэтому, если указать URL
    some_program.cgi?age=4
    , значение параметра
    age
    можно получить как
    cgi['age']
    .

    Отметим, что в данном примере нам понадобилось только значение, возвращаемое блоком; HTML-разметка строится постепенно и сохраняется, а не выводится сразу же. Это означает, что конкатенировать строки абсолютно необходимо, иначе мы увидели бы только последнюю вычисленную строку.

    В классе

    CGI
    есть также удобные механизмы для кодирования строк URL и экранирования специальных символов в HTML и XML-коде. URL-кодирование — это представление «небезопасных» символов с помощью других символов, допустимых в URL. В результате получаются странные строки, изобилующие знаками
    %
    , которые вы часто встречали в Web. На самом деле это просто шестнадцатеричные ASCII-коды символов с предшествующим знаком
    %
    .

    require "cgi"

    s = "This| is"^ (aT$test"

    s2 = CGI.escape(s)    # "This%7C+is%5E%28aT%24test"

    puts CGI.unescape(s2) # Печатается "This| is"(aT$test"

    Аналогично класс

    CGI
    позволяет экранировать части HTML или XML-текста, которые должны отображаться в браузере буквально. Например, без специальных мер строка
    "<some_stuff>"
    не будет показана в браузере именно в таком виде. Если необходимо показать саму разметку, например, в качестве примера в руководстве по HTML , то можно воспользоваться классом
    CGI
    для преобразования специальных символов:

    require "cgi"

    some_text = "<B>This is how you make text bold</B>"

    translated = CGI.escapeHTML(some_text)

    # "<B>This is how you make text bold</B>"

    puts CGI.unescapeHTML(translated)

    # Печатается "<B>This is how you make text bold</B>"

    19.1.2. Вывод и обработка форм

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

    В классе

    CGI
    имеются методы для генерирования всех элементов HTML-форм. В примере ниже показано, как можно вывести и обработать форму.

    require "cgi"


    def reverse_ramblings(ramblings)

     if ramblings[0] == nil then return " " end

      chunks = ramblings[0].split(/\s+/)

     chunks.reverse.join(" ")

    end


    cgi = CGI.new("html4")

    cgi.out do

     cgi.html do

      cgi.body do

       cgi.hi { "sdrawkcaB txeT" } +

       cgi.b { reverse_ramblings(cgi['ramblings'])) +

       cgi.form("action" => "/cgi-bin/rb/form.cgi") do

        cgi.textarea("ramblings") { cgi['ramblings'] } + cgi.submit

       end

      end

     end

    end

    Здесь отображается многострочное поле ввода, текст в котором разбивается на слова и выводится в обратном порядке. Так, если набрать фразу «This is a test», то после обработки вы увидите «test a is This». Метод

    form
    класса
    CGI
    принимает параметр
    method
    — один из методов отправки формы, определенных в протоколе HTTP (
    GET
    ,
    POST
    и т.д.). По умолчанию предполагается значение
    POST
    .

    В этом примере демонстрируется лишь малая толика элементов форм, которые могут встречаться на HTML-странице. Полный перечень вы найдете в любом руководстве по HTML.

    19.1.3. Куки

    Мы уже упоминали, что HTTP — протокол без состояния. Это означает, что после того как сервер закончил обрабатывать запрос, он не может сказать, пришел ли следующий запрос от того же или какого-либо другого браузера. Тут-то и приходят на помощь куки (cookies) — способ, быть может, несколько грубоватый, сохранить состояние между последовательными запросами от одного и того же браузера.

    Механизм куков работает следующим образом. Сервер посылает браузеру команду (в виде HTTP-заголовка) с просьбой сохранить пару имя-значение. Данные могут храниться в памяти или на диске. При каждом последующем запросе к любому серверу из домена, указанного в куке, браузер пошлет сохраненные данные в HTTP-заголовке.

    Можно, конечно, читать и формировать куки вручную, но, как вы, наверное, догадались, в этом нет необходимости, поскольку библиотека CGI предоставляет класс

    Cookie
    , который инкапсулирует все технические детали.

    require "cgi"


    lastacc = CGI::Cookie.new("kabhi",

     "lastaccess=#{Time.now.to_s}")

    cgi = CGI.new("html3")

    if cgi.cookies.size < 1

     cgi.out("cookie" => lastacc) do

      "Hit refresh for a lovely cookie"

     end

    else

     cgi.out("cookie" => lastacc) do

      cgi.html do

       "Hi, you were last here at: "+

       "#{cgi.cookies['kabhi'].join.split(' = ')[1]}"

      end

     end

    end

    Здесь создается кук

    "kabhi"
    , ключ которого
    "lastaccess"
    содержит текущее время. Если у браузера уже был такой кук, то выводится его значение. Куки хранятся в хэше, который является переменной экземпляра в классе CGI. Каждый кук может содержать несколько пар ключ-значение, поэтому при доступе к куку по имени вы получаете массив.

    19.1.4. Сеансы пользователей

    Куки — это хорошо, если вам нужно сохранить простые данные и вы не прочь возложить на браузер заботу об их хранении. Но часто предъявляются более жесткие требования. Что если нужно сохранить много данных и вы не хотите гонять их «взад-вперед» при каждом запросе? К тому же данные могут быть секретными, так что доверять их хранение браузеру нежелательно.

    В таких случаях можно воспользоваться классом

    CGI::Session
    . Он аналогичен классу
    CGI::Cookie
    в том смысле, что значения хранятся в структуре, напоминающей хэш.

    require "cgi"

    require "cgi/session"


    cgi = CGI.new("html4")


    sess = CGI::Session.new(cgi, "session_key" => "a_test",

     "prefix" => "rubysess.")

    lastaccess = sess["lastaccess"].to_s

    sess["lastaccess"] = Time.now

    if cgi['bgcolor'][0] =~ /[a-z]/

     sess["bgcolor"] = cgi['bgcolor']

    end


    cgi.out do

     cgi.html do

      cgi.body ("bgcolor" => sess["bgcolor"]) do

       "Фон этой страницы" +

       "изменяется в зависимости от значения 'bgcolor'," +

       "хранящегося в сеансе каждого пользователя." +

       "Время последнего доступа: #{lastaccess}"

      end

     end

    end

    Если обратиться к URL

    /thatscript.cgi?bgcolor=red
    , то фоновый цвет страницы у данного пользователя станет красным и останется таким до тех пор, пока он не обратится к такому же URL, но с другим значением параметра
    "bgcolor"
    . При создании объекта
    CGI::Session
    указываются объект
    CGI
    и набор параметров в хэше. Необязательный параметр
    session_key
    определяет ключ, с помощью которого браузер будет идентифицировать себя при каждом запросе. Сеансовые данные хранятся во временном файле, своем для каждого сеанса, а параметр
    prefix
    задает строку, с которой должно начинаться имя файла, чтобы проще было опознать все такие файлы в файловой системе сервера.

    Классу

    CGI::Session
    пока недостает многих возможностей, в частности умения хранить объекты, отличные от
    String
    , организации общего хранилища сеансовых данных для нескольких серверов и пр. К счастью, уже готов подключаемый механизм
    database_manager
    , так что некоторые из этих функций нетрудно добавить. Если вы придумаете что-нибудь интересное в отношении класса
    CGI::Session
    , не забудьте поделиться с сообществом.

    19.2. FastCGI

    Чаще всего CGI критикуют за то, что при каждом запросе создается новый процесс, и это заметно снижает производительность. Невозможность сохранять в памяти объекты между последовательными запросами также не украшает дизайн системы. Для разрешения этих проблем была создана технология FastCGI.

    По сути дела, FastCGI — это определение и программная реализация протокола. Обычно она реализуется в надстройки над Web-сервером, например модуля в случае сервера Apache. FastCGI позволяет работающему внутри процесса компоненту перехватывать HTTP-запросы и направлять их через сокет другому процессу, работающему в течение длительного времени. По сравнению с традиционным порождением новых процессов это существенно ускоряет работу. Кроме того, программист получает возможность оставить данные в памяти и найти их там при обработке следующего запроса.

    Серверы, адаптированные для работы с FastCGI, реализованы на многих языках, в том числе на Ruby. Эли Грин (Eli Green) написал целиком на Ruby модуль (он есть в архиве RAA), который реализует протокол FastCGI и упрощает разработку FastCGI-программ.

    Не вдаваясь в детали реализации, мы представили в листинге 19.1 пример приложения. Как видите, он повторяет функциональность предыдущего примера.

    Листинг 19.1. Пример FastCGI

    require "fastcgi"

    require "cgi"


    last_time = ""


    def get_ramblings(instream)

     # He слишком красивый способ извлечь значение из первой пары

     # имя-значение. CGI сделал бы это за нас.

     data = ""

     if instream != nil

      data = instream.split("&")[0].split(" = ")[1] || ""

     end

     return CGI.unescape(data)

    end


    def reverse_ramblings(ramblings)

     if ramblings == nil then return "" end

     chunks = ramblings.split(/\s+/)

     chunks.reverse.join(" ")

    end


    server = FastCGI::TCP.new('localhost', 9000)

    begin

     server.each_request do |request|

      stuff = request.in.read

      out = request.out


      out << "Content-type: text/html\r\n\r\n"

      out << <<-EOF

    <html>

    <head><titlе>Отражатель текста</title></head>

    <h1>sdrawkcaB txeT</h1>

    <i>Вы перед этим сказали: #{last_time}</i><BR>

    <b>#{reverse_ramblings(get_ramblings(stuff))}</b>

    <form method="POST" action="/fast/serv.rb">

    <textarea name="ramblings">

    </textarea>

    <input type="submit" name="submit">

    </form>

    </body></html>

    EOF

      last_time = get_ramblings(stuff)

      request.finish

     end

    ensure

     server.close

    end

    Сразу же бросается в глаза (если вы читали предыдущий раздел) то, что в FastCGI приходится вручную делать кое-какие вещи, которые были не нужны при работе с библиотекой

    CGI
    . Во-первых, «зашивание» в код экранированного HTML-кода. Во-вторых, метод
    get_ramblings
    , который сам разбирает входные данные и возвращает нужное значение. Кстати, этот код будет работать только для POST-запросов — еще одно удобство, утраченное с отказом от библиотеки CGI.

    Но вместе с тем у FastCGI есть и достоинства. Мы не проводили замеров, но — как следует из самого названия — FastCGI быстрее CGI. Вместо накладных расходов на создание нового процесса мы просто открываем соединение с портом 9000 на локальной машине (

    FastCGI::TCP.new('localhost', 9000)
    ). Кроме того, в переменной
    last_tim
    e хранится часть состояния сеанса — вещь, невозможная в традиционной технологии CGI.

    Отметим также, что эти библиотеки можно до определенного предела «смешивать». Вспомогательные функции из

    cgi.rb
    , например метод
    CGI.escapeHTML
    , можно использовать автономно (не делая библиотеку основой приложения). Тогда предыдущий пример оказался бы несколько проще для восприятия.

    19.3. Ruby on Rails

    В сообществе пользователей Ruby одним из самых широко известных каркасов для Web является Ruby on Rails (или просто Rails). Его автор Дэвид Хайнемайер Хансон (David Heinemeier Hansson).

    В каркасе Rails широко используются динамические свойства Ruby. У него также есть собственная философия, направленная на быструю разработку Web-приложений.

    Rails хорошо известен и прекрасно документирован. В этой книге мы рассмотрим его лишь поверхностно.

    19.3.1. Принципы и техника

    Каркас Rails построен на основе паттерна Модель-Вид-Контроллер (Model-View-Controller — MVC). Каждое приложение естественно разбивается на модели (моделирующие предметную область), виды (с помощью которых информация представляется пользователю и организуется возможность взаимодействия) и контроллеры (играющие роль арбитров между моделями и видами).

    В основу поведения Rails как каркаса положены определенные принципы. Один из них — «принцип минимизации кода»: не пишите код для связывания одного с другим, если такое связывание можно организовать автоматически.

    С ним также связан принцип «примата соглашений над конфигурацией». Придерживаясь ряда заранее оговоренных стилей кодирования и именования, можно обойтись почти без конфигурирования (и приблизиться к идеальной среде с «нулевым конфигурированием»).

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

    Web-приложения часто хранят данные в базе, и Rails обеспечивает бесшовную интеграцию с базой данных. У Web-каркасов наблюдается тенденция проявлять «склонность» к какому-то конкретному объектно-реляционному отображению (object-relational mapper, ORM), и Rails — не исключение. Стандартным для Rails является отображение ActiveRecord, которое мы рассматривали в главе 10.

    Базы данных описываются в файле

    config/database.yaml
    — одном из немногих необходимых конфигурационных файлов (конечно же, в формате YAML). В нем перечислены три разных базы данных: для разработки, для тестирования и для промышленной эксплуатации. На первый взгляд, это перебор, но в действительности такая схема оказывается очень удобной.

    Rails генерирует для вас пустые модели и контроллеры. В ходе редактирования моделей вы определяете связи между таблицами базы данных с помощью таких методов, как

    has_many
    и
    belongs_to
    (на самом деле их гораздо больше). Поскольку между моделями и таблицами есть соответствие, то написанный вами код заодно определяет и связи между самими моделями. Для контроля данных служат такие методы, как
    validates_presence_of
    (проверяет, что данные присутствуют) и
    validates_uniqueness_of
    (проверяет, что данные уникальны).

    В результате создания приложения Rails командой вида

    rails appname
    вы получаете каталог
    appname
    с такой структурой:

    арр

     controllers

     helpers

     models

     views

    config

    db

    doc

    lib

    log

    public

    script

    test

    vendor

    Большая часть кода находится в каталоге

    арр
    . Как видите, сама его структура следует паттерну MVC.

    Схемы баз данных находятся в каталоге

    db
    . Инкрементные файлы миграции тоже попадут сюда.

    В Rails есть концепция «обстраивания» (scaffolding), которая очень упрощает жизнь. Если ввести команду

    script/generate scaffold Product
    (
    Product
    — имя модели), то для таблицы
    Products
    (обратите внимание на множественное число) будет сгенерирована функциональность «создать-обновить-удалить».

    Можно обстроиться и не генерируя никакой код, достаточно вызвать внутри контроллера

    Product
    метод
    scaffold
    :

    class ProductController < ActiveRecord::Base

     scaffold :product

    end

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

    В старых версиях Rails расхождение между ActiveRecord и базой данных было более существенным. Недавно появившаяся концепция миграции делает управление базой данных проще. То же касается и уже существующих в базе данных таблиц, работать с которыми было трудно; сейчас можно создать файл

    schema.rb
    , в котором будет перечислены все существующие таблицы (см. также
    rake tasks db:schema:load
    и
    db:schema:dump
    ).

    19.3.2. Тестирование и отладка приложений Rails

    В Rails встроена серьезная поддержка тестирования. Обратите внимание на каталог

    test
    , который создается для каждого нового приложения. Он заполняется по мере развития приложения; вы можете (и должны) добавлять тесты по ходу создания новой функциональности.

    По принятому в Rails соглашению, автономные тесты служат для тестирования моделей, а функциональные — для тестирования контроллеров. Этим объясняется наличие подкаталогов

    unit
    и
    functional
    в каталоге
    test
    . (Вне контекста Ruby on Rails эти термины используются несколько иначе.)

    Для тестирования в Rails большое значение имеет понятие стенда. Стенд (fixture) — это моментальный снимок начального содержимого модели, иными словами, набор фиктивных данных для тестирования. Все данные такого рода хранятся в формате YAML в файлах, расположенных в каталоге

    test/fixtures
    .

    Каталог

    test/mocks
    предназначен для хранения кода, играющего роль объекта-заглушки. Заглушки служат для имитации еще не реализованного сервиса или класса. Считайте это подобием голливудской декорации, которая только внешне похожа на реальность. Классический пример — шлюз в систему обработки кредитных карт; с помощью заглушек мы можем протестировать взаимодействие с ним, не имея настоящего шлюза.

    Следует также знать о понятии консоли в Rails. Запуск сценария

    script/console
    открывает сеанс, похожий на
    irb
    , в котором доступен код модели. В нем вы можете выполнять запросы с помощью ActiveRecord и другие подобные операции.

    Еще полезнее прерыватель (breakpointer). Расположите в любом месте своей программы вызов метода

    breakpoint
    и запустите утилиту
    script/breakpointer
    . Вы окажетесь в сеансе
    irb
    в контексте установленной точки прерывания и сможете просматривать и изменять значения переменных экземпляра и т.п.

    В последних версиях Rails появилась также поддержка для тестирования сопряжений. Для этого применяется специализированный язык DSL, описывающий поток управления в Web-приложении на верхнем уровне. Он ориентирован на заказчиков, не имеющих технической подготовки, но пригодится и тем пользователям, которые хорошо знакомы с внутренним устройством приложения.

    19.3.3. Базовые расширения

    Одно из достоинств Rails — наличие широкого набора вспомогательных методов, определенных в модуле

    ActiveSupport::CoreExtensions
    . Они «безвредны» и после добавления в основные классы становятся доступны всему приложению.

    Некоторые из этих методов имеют отношение к работе с датами и временем. Поскольку внутри системы время хранится в секундах, то имеют смысл методы

    minutes
    и
    hours
    :

    elapsed = 3.days + 4.hours + 17.minutes

    later = Time.now + elapsed

    Можно делать и такие вещи:

    time = 3 .minutes.from_now # To же, что Time.now + 3.minutes.

    t2 = 5.days.from_now

    Операции со временем представлены особенно полно. С помощью методов

    midnight
    ,
    next_month
    и
    beginning_of_week
    можно точно и кратко задавать моменты времени.

    Один из самых известных трюков в этом модуле — метод

    Symbol#to_proc
    . Он позволяет передавать символ, представляющий имя метода, вместо блока. Например, следующие два предложения эквивалентны:

    arr = array.map {|x| x.upcase }

    arr = array.map(&:upcase)

    Помимо упомянутых здесь методов, есть еще десятки других. Одни занимаются преобразованием различных единиц измерения времени, другие — переводом из YAML или XML, третьи — проверкой правописания и расстановкой знаков препинания в строках и т.д.

    19.3.4. Дополнительные инструменты и библиотеки

    Неизбежным было появление внешних инструментов для работы с Rails. Например, в редактор TextMate встроена неплохая поддержка (синтаксическая подсветка, автоматическое дописывание кода и т.д.). Этот редактор, наверное, самый популярный среди программистов на Ruby на платформе OS X.

    Многообещающим выглядит проект InstantRails (http://instantrails.rubyforge.org). Это единый пакет, содержащий Ruby, Rails, MySQL и Apache, причем все продукты уже сконфигурированы и готовы к работе. Первая версия работает только в Windows, но планируется перенос на другие платформы.

    На платформе OS/X есть эквивалентный проект Locomotive. Это достаточно зрелая и хорошо работающая среда для развертывания Rails «одним щелчком».

    Если вы поклонник проекта Eclipse, то должны знать о RadRails — интегрированной среде разработки для Rails, надстроенной над Eclipse. На сайте проекта (http://radrails.org) написано, что «продукт поддерживает управление версиями, отладку, серверы WEBrick, мастер генерации кода, синтаксическую подсветку, инструменты для работы с данными и многое другое». Он должен работать на всех платформах, где работает сам Eclipse.

    Важно также понимать, что такое подключаемые к Rails модули (plugins). Это небольшие автономные программы, которые модифицируют поведение ActiveRecord или Rails. Их несложно писать и развертывать.

    Для установки подключаемого модуля достаточно раскрыть архив и скопировать его в каталог

    vendor/plugins
    . Примером может служить хорошо известный модуль аннотирования (Annotate models), написанный Дэйвом Томасом. Он добавляет в начало каждого исходного файла модели ActiveRecord комментарии, содержащие краткую аннотацию текущей схемы (эта функциональность становится доступной с помощью задачи rake). Существуют сотни небольших подключаемых модулей, занимающихся аутентификацией, генерацией GUID, интернационализацией, поддержкой CSS и т.д.

    Невозможно описать Rails на нескольких страницах — этой теме посвящены целые книги, причем самые первые к лету 2006 года уже устарели. Если вы хотите углубить свои познания в этой области, заходите на сайт http://rubyonrails.org — основное место встреч сообщества пользователей Rails.

    19.4. Разработка Web-приложений с помощью Nitro

    Nitro — еще один комплект инструментов для разработки Web-приложений. Хотя в сочетании с библиотекой Og для объектно-реляционного отображения Nitro хорошо приспособлен к созданию традиционных приложений в духе MVC, задуман он для поддержки различных архитектур.

    Установить Nitro проще всего с помощью системы RubyGems. Gem-пакет зависит от нескольких внешних библиотек, которые тоже придется установить (

    og
    ,
    redcloth
    и ряд других).

    gem install nitro --include-dependencies

    Во время работы над книгой последней была версия Nitro 0.31.0. Но, конечно, API и библиотеки постоянно изменяются. Кроме того, имейте в виду, что приведенный ниже обзор Nitro далеко не полон.

    19.4.1. Создание простого приложения Nitro

    Nitro часто используется в сочетании с Og — библиотекой ORM, обеспечивающей устойчивость объектов Ruby. Но наличие Og необязательно; Nitro, в частности, хорош тем, что не нужно заранее решать, понадобится ли приложению база данных или иная форма обеспечения устойчивости. Если со временем проект изменится и такая нужда возникнет, то для превращения объектов Ruby в устойчивые достаточно будет добавить в модели несколько строк кода. Есть планы модифицировать семейство методов

    attr
    , так что в будущем задача может стать еще проще.

    Библиотека Og рассматривалась в разделе 10.4.7. В примерах работы с Nitro мы почти не будем пользоваться ей.

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

    Сначала создадим каталог приложения:

    /home/jbritt/demo

    Затем добавим в него папку

    public
    , содержащую файл
    index.html
    :

    /home/jbritt/demo/public/index.html

    Д

    ля начала сделаем этот файл совсем простым:

    <html>

     <head>

      <title>Nitro!</title>

     </head>

     <body>

      <h1>The Ruby Way</h1>

      <h2>Hal Fulton</h2>

     </body>

    </html>

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

    run.rb
    :

    require 'nitro'

    Nitro.run

    Чтобы увидеть новое приложение в действии, запустите файл

    run.rb
    (из каталога
    demo/
    ). Потом откройте браузер и введите URL http://127.0.0.1:9999 (здесь 9999 — порт Nitro по умолчанию).

    Если все пройдет удачно, то появится приведенная выше простая страница. Принимайте поздравления — вы создали первое приложения для Nitro! Разумеется, Nitro позволяет делать гораздо больше, поэтому посмотрим, как это приложение можно расширить.

    Прежде всего, интерес представляет файл

    run.rb
    . В зависимости от того, как вы развернули свою программу, его запуском может заниматься диспетчерский сценарий в каталоге
    public
    . Но для демонстрации и тестирования вы можете запустить его вручную и воспользоваться встроенным диспетчером WEBrick. Впрочем, для промышленной эксплуатации имеет смысл изучить предоставляемую Nitro поддержку Mongrel, SCGI или FastCGI.

    Nitro поддерживает много разных архитектур и паттернов, а приложение обычно строится на базе паттерна Модель-Вид-Контроллер (model-view-controller, MVC). Окончательный выбор всегда остается за вами, a Nitro облегчает переход от простых сайтов, предназначенных только для просмотра, к полноценным приложениям с поддержкой базы данных.

    По умолчанию при обработке запроса Nitro сначала ищет файл в папке

    public
    . Предполагается, что если имя страницы явно не указано, речь идет о файле
    index.html
    . В этом отношении Nitro ведет себя как любая статическая система. При желании мы можем поместить в папку
    public
    дополнительные статические HTML-страницы, а в ее подпапках хранить изображения и CSS-файлы.

    Но интереснее то, что происходит, когда Nitro не может найти прямого соответствия запрошенной странице. Давайте изменим расширение файла

    index.html
    на
    .xhtml
    :

    public/index.xhtml

    Перезапустите сценарий

    run.rb
    . Снова введите URL http://127.0.0.1:9999, вы должны увидеть ту же самую страницу. Не найдя файла
    index.html
    , Nitro ищет файл
    index.xhtml
    и загружает его. По умолчанию
    xhtml
    — расширение, применяемое в Nitro для динамического контента. В общем случае, получив запрос, Nitro сначала ищет файл с расширением
    html
    , а потом —
    xhtml
    .

    В XHTML-файлах могут быть подставляемые переменные и программная логика. Измените файл

    index.xhtml
    следующим образом:

    <html>

     <head>

      <title>Nitro!</title>

     </head>

     <body>

      <h1>The Ruby Way</h1>

      <h2>Hal Fulton</h2>

      <p>Page last updated: #{Time.now}</p>

     </body>

    </html>

    После перезагрузки страницы вы увидите текущие дату и время. Nitro также поддерживает синтаксис команд обработки, принятый в XML:

    <?r curr_date = Time.new.strftime( "%a, %b %d, %Y") ?>

    <html>

     <head>

      <title>Nitro!</title>

     </head>

     <body>

      <h1>The Ruby Way</h1>

      <h2>Hal Fulton</h2>

      <p>Page last updated: #{curr_date}</p>

     </body>

    </html>

    Отметим, что из наличия синтаксиса

    <?r ... ?>
    не следует, что весь шаблон должен подчиняться правилам XML. Nitro располагает средствами для обработки шаблонов как XML-документов, а такой синтаксис позволяет иметь шаблоны, которые одновременно являются корректными XML-документами.

    19.4.2. Nitro и паттерн MVC

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

    Начнем с создания класса контроллера

    main.rb
    . Если в каталоге приложения есть папка
    src
    , Nitro добавит ее в список путей, по которым ищутся страницы. Следуя принятым в Nitro соглашениям, создадим файл
    <app_root>/src/controller/book.rb
    :

    class BookController

     def index

      @author = "Hal Fulton"

      @title = "The Ruby Way"

      @last_update = Time.new

     end

    end

    Изменим файл

    index.xhtml
    , включив в него ссылки на эти переменные:

    <html>

     <head>

      <title>Nitro!</title>

     </head>

     <body>

      <h1>#{@title}</h1>

      <h2>#{@author}</h2>

      <p>Page last updated: #{@last_update}</p>

     </body>

    </html>

    Придется также немного подправить файл

    run.rb
    :

    require 'nitro'


    require 'controller/book'

    Nitro.run(BookController)

    Перезапустите сервер WEBrick и перезагрузите страницу, чтобы посмотреть на результат.

    Отметим несколько вещей. Файлы шаблонов могут остаться в папке

    public
    ; класс контроллера не обязан расширять какой-то специальный базовый класс; класс, переданный методу
    Nitro.run
    , автоматически сопоставляется с корневым URL приложения. Все эти аспекты конфигурируемы.

    По умолчанию Nitro ищет шаблоны в каталогах

    template
    и
    public
    . Если вы не хотите помещать шаблоны в каталог
    public
    (скажем, потому, что предпочитаете зарезервировать его только для статических HTML-файлов), то можете создать каталог
    template
    и хранить их там. Предполагается, что пути к шаблонам повторяют пути, заданные в URL, относительно корневой папки шаблонов. Наш файл
    index.xhtml
    мог бы храниться как
    public/index.xhtml
    или
    template/index.xhtml
    . Использование каталога
    public
    упрощает переход от статического сайта к динамическому, но лучше организовать раздельное хранение статических файлов и шаблонов.

    Классы контроллеров могут отображаться на пути URL с помощью файла

    Server.map
    . Например, наше демонстрационное приложение может содержать статическую главную страницу со списком литературы (в котором на данный момент присутствует всего одна книга), находящуюся совсем по другому пути. Для этого нужно следующим образом изменить файл
    run.rb
    :

    require 'nitro'

    require 'controller/book'


    Nitro::Server.map = { '/books' => BookController }

    Nitro.run()

    Соответствующий шаблон нужно будет переместить в другое место, которое соответствовало бы новому пути (

    template/books/index.xhtml
    )

    Перезапустите сервер и укажите новый URL:

    http://127.0.0.1:9999/books

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

    src/model/book.rb
    , содержащий описание модели
    Book
    :

    class Book

     @@items = {}

     attr_accessor :title

     attr_accessor :author

     attr_accessor :update_time


     def initialize( values = {} )

      @title, @author = values[:title], values[:author]

      @update_time = Time.now

     end


     def save

      @@items[author] = self

     end


     def self.find(author)

      @@items[author]

     end

    end

    Теперь контроллер может запрашивать данные у класса

    Book
    . Изменим метод
    index
    :

    def index

     book = Book.find_by_author("Mark Twain")

     @author = book.author

     @title = book.title

     @last_update = book.update_time

    end

    А в файл

    run.rb
    вставим ссылку на модель
    Book
    и загрузим какие-нибудь данные:

    require 'model/book'

    Book.new(:title => "Life on the Mississippi",

             :author => "Mark Twain").save

    После перезапуска приложения и перезагрузки страницы мы увидим сведения о книге. А если книг больше? Вместо того чтобы «зашивать» в код данные о них, мы можем попросить контроллер выполнить поиск по имени автора. Добавим в него новый метод

    find
    :

    def find(author)

     book = Book.find_by_author(author)

     @author = book.author

     @title = book.title

     @last_update = book.update_time

    end

    Это тот же код, что в методе

    index
    (и для создания соответствующего ему шаблона достаточно было бы переименовать
    index.xhtml
    в
    find.xhtml
    ), но он принимает один аргумент — имя автора. Хотя мы разрабатываем Web-приложение, класс контроллера мало чем отличается от класса в любом другом приложении, написанном на Ruby. Самое заметное отличие заключается в том, что методы не возвращают значений, но никаких зависимостей от специальных переменных окружения или каких-то особых объектов нет. (Впрочем, имейте в виду, что Nitro все же добавляет разные «приятности» для программирования Web-приложений; для доступа к ним достаточно унаследовать свой класс от
    Nitro::Controller.
    ) Например, Nitro обеспечивает «обстраивание», быструю передачу сообщений от одного запроса другому и хитроумный конвейер конструирования страницы, но все это только если вы сами захотите.

    Сделаем еще одно замечание по поводу методов контроллера. Методы, предназначенные для обработки запросов на получение страницы, обычно идут в паре с шаблонами, имеющими схожее имя. Для получения конечного результата Nitro объединяет метод и шаблон. Мы видели, что Nitro может работать без контроллеров, ограничиваясь только видами. Но верно и обратное. Метод контроллера может генерировать всю страницу вообще без шаблона. Запросы на получение страниц обрабатываются в Nitro как действия (actions). Действие — это комбинация вида и методов контроллера. Внутри себя Nitro динамически создает методы действия, объединяющие то и другое. Но если какой-то компоненты недостает, ничего страшного не случится. Если у действия нет шаблона, результатом становится значение, возвращаемое методом контроллера.

    Например, некоторый URL может отвечать на запрос, возвращая лишь название книги. Для этого достаточно такого метода контроллера

    BookController
    :

    def sparse(author)

     @context.content_type = 'text/plain'

     book = Book.find_by_author(author)

     book.title

    end

    Если метод контроллера возвращает не HTML-документ, то следует изменить заголовок content-type, отравляемый вместе с ответом в результате присваивания

    @context.content_type=
    . (Кстати, даже если парный шаблон существует, его можно не использовать, переопределив метод
    render_text
    .)

    Но откуда метод

    find
    или
    sparse
    получает аргумент? По умолчанию Nitro следует привычному для Web-приложений паттерну, согласно которому сегменты пути URL отображаются на контроллеры, методы и аргументы. Перезапустите приложение и введите такой URL: http://127.0.0.1:9999/books/find/Hal%20Fulton.

    Обратите внимание, что

    %20
    — это кодированное представление пробела в имени автора. Nitro производит декодирование еще до вызова метода
    find
    .

    В общем случае отображение URL выполняется так:

    /controller/method/arg1
    . Дополнительные аргументы можно передать в виде последующих сегментов пути. Впрочем, в Nitro есть механизм настройки маршрутизации, так что вы не обязаны отражать в URL детали реализации.

    19.4.3. Nitro и Og

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

    run.rb
    :

    # Прямо перед вызовом Book.new :

    require 'og'


    Og.setup(:store => 'mysql',

             :name => 'demo',

             :user => 'root',

             :destroy => true,

             :port => 3316)

    Затем изменим модель

    Book
    :

    require 'glue/timestamped'


    class Book

     is Timestamped


     property :title, String

     property :author, String


     def initialize{ values = {} )

      @title, @author = values[:title], values[:author]

     end

    end

    От хранения экземпляров Book в переменных класса мы отказались. Вызовы

    attr_accessor
    заменены обращением к методу
    property
    , который служит нескольким целям. Он тоже создает методы доступа к переменным, но, кроме того, сообщает
    Og
    , что этот класс должен обладать устойчивостью. Метод
    initialize
    почти не изменяется, но затребовав файл
    timestamped
    и поместив в класс маркер
    is Timestamped
    , мы автоматически получаем атрибут
    update_time
    .

    Остальные методы можно удалить, теперь они реализуются библиотекой Og. При перезапуске приложения Nitro создаст для него базу данных MySQL, а в ней — таблицу для хранения объектов Book. Так разрабатываются приложения «на чистом Ruby без SQL».

    19.4.4. Решение типичных для Web-приложений задач в Nitro

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

    Если вы создаете сайт с одной-двумя страницами, не так уж важно, повторяются ли многократно одна и та же разметка и текст. Но когда страниц больше, обновлять все одинаковые фрагменты вручную становится утомительно и чревато ошибками. Nitro помогает следовать принципу DRY (Don't Repeat Yourself — «Не повторяйся»), предлагая целый ряд способов повторного использования.

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

    <?include href='/footer' ?>

    Сам файл

    footer.xinc
    мог бы выглядеть, к примеру, так:

    <div id='footer'>Read More Ruby Books</div>

    Если в качестве значения атрибута

    href
    указан относительный путь, Nitro будет просматривать папки
    template
    , определенные для текущего контроллера. Если же путь абсолютный, то просматривается только папка
    template
    в корневом каталоге приложения.

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

    <include href='/footer' />

    Результат получается таким, как если бы включаемый текст был частью вызывающего шаблона.

    Более сложная форма включения контента связана с элементом

    render
    :

    render href='/controller/action' />

    где

    href
    — некий путь в каталоге приложения.

    Процедура компиляции при включении частичных видов с помощью

    <render />
    практически не отличается от случая полного вида. В контроллере могут быть методы, соответствующие включаемому файлу; их можно использовать для установки значений переменных экземпляра во включаемом шаблоне.

    В Nitro граница между кодом шаблона и кодом на Ruby размыта. Один из примеров мы уже приводили: действие контроллера может быть «размазано» между методом и файлом шаблона, а может целиком входить туда или сюда. Другой пример — элементы Nitro (Nitro Elements), способ инкапсуляции кода и разметки в нестандартном теге, который можно использовать в видах.

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

    element/layout.xhtml
    :

    <html>

     <head>

      <title>#{@title}</title>

      <style>

       body {

        background-соlor: white; font-family: sans-serif;

       }

      </style>

     </head>

     #{content}

    </html>

    Теперь воспользуемся новым элементом в файле

    template/books/find.xhtml
    :

    <Layout title='Details for #{@title}'>

     <h1>#{@title}</h1>

     <h2>#{@author}</h2>

     <p>Page last updated: #{@last_update}</p>

    </Layout>

    Все содержимое элемента

    Layout
    вставляется в переменную
    content
    в файле
    layout.xhtml
    . Элементы могут принимать параметры; атрибут title в открывающем теге
    Layout
    становится значением переменной экземпляра
    @title
    в файле
    layout.xhtml
    .

    Вам это напоминает вызов метода с передачей ему аргументов? Так оно и есть. Мы можем определить разметку в виде класса Ruby (

    src/element/layout2.rb
    ):

    require 'nitro/element'


    class Layout2 < Nitro::Element

     def render

      %^<html>

      <head>

       <title>#{@title}</title>

       <style>

        body {

         background-color: white; font-family: sans-serif;

        }

       </style>

      </head>

      #{content}

     </html>^

     end


    end

    А затем изменим файл

    find.xhtml
    , воспользовавшись элементом
    Layout2
    (нужно еще будет затребовать класс нового элемента в файле
    run.rb
    ). Элементы могут содержать другие элементы, так что виды можно собирать из повторно используемых компонентов.

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

    Такой вид повторного использования называется частью. Часть (part) — это, по существу, мини-сайт, реализующий одну конкретную функцию. (В дистрибутив Nitro входит одна такая часть под названием

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

    Естественно, части хранятся в папке

    part
    . Пусть имеется некий код аутентификации пользователя, который можно использовать повторно. Тогда дерево частей приложения могло бы выглядеть так:

    <app_root>/part/users

    <app_root>/part/users.rb

    <app_root>/part/users/public/

    <app_root>/part/users/controller.rb

    <app_root>/part/users/model/user.rb

    <app_root>/part/users/model/acl.rb

    <app_root>/part/users/template/login.xhtml

    <app_root>/part/users/template/form.xinc

    <app_root>/part/users/run.rb

    Главный файл

    run.rb
    мог бы включить такую часть с помощью одной директивы
    require
    :

    require 'part/users'

    Теперь Nitro будет рассматривать весь код в каталоге

    part/users
    , как если бы он находился в дереве исходных текстов главного приложения. Поиск шаблонов начинается в папке template приложения и продолжается в подкаталогах каталога
    part
    . Если вы хотите переопределить шаблон, поставляемый вместе с частью, то нужно просто поместить замещающий вариант в соответствующий подкаталог папки
    template
    .

    Часто приложение должно отображать повторяющиеся данные; обычно они оформлены в виде HTML-таблицы в шаблоне. Если число строк заранее не известно, то придется обойти какой-то набор.

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

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

    Morphing
    исследует разметку в шаблоне и ищет специальные атрибуты, которые обозначают различные преобразования.

    В нашем демонстрационном приложении данных мало, но если представить себе много авторов, каждый из которых написал несколько книг, то можно было бы показать список книг данного автора. Метод контроллера поместил бы в переменную

    @books
    список книг, а часть шаблона для обхода этого цикла могла бы выглядеть так:

    <h4>Books by #{@author}</h4>

    <ul>

     <li each="book in @books" > #{book.title}</li>

    </ul>

    Класс

    Morphing
    находит атрибут each элемента li и преобразует его в следующий код:

    <?r for book in @books ?>

    <li>#{book.title} </li>

    <?r end ?>

    Порожденная таким образом разметка передается следующему этапу конвейера.

    Точно так же для повтора элемента можно использовать атрибут

    times
    . Например, фрагмент

    <img src='/img/ruby.png' alt='*' times='@book.rating' />

    будет преобразован в следующий код:

    <?r 3.times do ?>

    <img src='/img/ruby.png' alt='*' />

    <?r end ?>

    19.4.5. Прочие детали

    В Nitro слишком много различных механизмов, чтобы все их можно было рассмотреть здесь. Но в этом разделе мы упомянем некоторые особенно приятные «вкусности».

    В состав Nitro входит вспомогательный код, позволяющий использовать многочисленные библиотеки JavaScript, которые поддерживают различные формы DHTML и Ajax. Для облегчения интеграции в Nitro применяется высокоуровневый синтаксис. Например, в дистрибутиве Nitro есть пример поиска по сайту Flickr и вывода уменьшенных изображений. Текстовое поле для ввода тегов поиска поддерживает технологию Ajax, что достигается следующей разметкой:

    <input type="text" id="tags" name="tags" auto_complete="true" />

    Контроллер реализует метод

    tags_auto_complete
    , который возвращает строку в формате XML, зависящую от содержимого поля.

    Nitro позволяет кэшировать действия (то есть отрисованные страницы целиком), результаты вызова методов и сгенерированные фрагменты текста. Например, чтобы поместить в кэш результат отрисовки страницы index, контроллер должен вызвать следующий метод:

    cache_output :index

    Кэширование можно добавить и в сегменты кода:

    <?r cache(:book_list_cache_key) do ?>

    <ul>

     <li each="book in Books.all">#{book.title}</li>

    </ul>

    <?r end ?>

    В Nitro встроен класс преобразования для локализации, позволяющий автоматически подставлять в шаблон то или иное содержимое. Он не является стандартным этапом конвейерной компиляции, вы сами должны добавить его в файл

    run.rb
    :

    require 'nitro/compiler/localization'

    include Nitro


    Compiler.transformation_pipeline = [

     StaticInclude,

     Elements,

     Morphing,

     Markup,

     Localization,

     Cleanup

    ]

    Отметим, что конвейер можно переконфигурировать, выбросив стандартные преобразования и добавив собственные. Далее определяются локали:

    Localization.locales = {

     :en => 'conf/locales/en.yml',

     :de => 'conf/locales/de.yml'

    }

    Локаль — это просто YAML-файл, сопоставляющий одним строкам другие:

    ---

    :author: Autor

    :language: Sprache

    :book_rank: Buchrank

    В шаблонах применяются специальные конструкции для обозначения подставляемого текста:

    <div class='detail'>[[:author]]: #{@book.author}</div>

    <div class='detail'>[[:language]]: #{@book.language}</div>

    <div class='detail'>[[:book_rank]]: #{@book.rank}</div>

    Выбор файла локали определяется значением

    session[:LOCALE]
    . В методах контроллера можно получить текущую локаль с помощью специальной переменной
    @lc
    .

    @language = @lc[:language]

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

    Дополнительную информацию о Nitro можно найти на следующих ресурсах:

    • http://www.nitroproject.org/ (домашняя страница Nitro)

    • http://rubyforge.org/forum/forum.php?forum_id=5921 (страница проекта Nitro на сайте Ruby Forge);

    • http://oxyliquit.de/ (справочное и учебное руководство по Nitro).

    19.5. Введение в Wee

    Согласно заявлению автора Майкла Ноймана (Michael Neumann), Wee — это «каркас для создания очень динамичных, компонентных Web-приложений, на дизайн которого оказал большое влияние продукт Seaside». Название расшифровывается как «Web Engineering Easy» (сделаем конструирование Web проще).

    Установить Wee проще всего из gem-пакета (

    gem install wee
    ). Во время работы над книгой текущей была версия 0.10.0. В документации по Wee говорится, что, хотя код достаточно устойчив, могут возникать некоторые проблемы из-за продолжений, поэтому пока не стоит использовать этот каркас для критически важных приложений.

    Но даже и с такими оговорками Wee заслуживает изучения ради своей компонентной модели, а также потому, что продолжения — интересная, но недостаточно исследованная область, лежащая в стороне от главного направления разработки для Web. Автор говорит, что на него повлияли идеи Seaside, а основанный на продолжениях каркас для Web-приложений на языке Smalltalk написал Ави Брайант (Avi Bryant).

    В gem-пакет для инсталляции Wee входит большое число разнообразных примеров. Один из них — Web-интерфейс к обозревателю объектного пространства, другой — применение Ajax на основе библиотеки JavaScript-функций Prototype. Есть также пример, демонстрирующий совместную работу Wee и Nitro.

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

    19.5.1. Простой пример

    Во время установки Wee создается генератор простых приложений, который, естественно, называется

    wee
    . Команда
    wee create my-demo
    создает подкаталог
    my-demo
    в текущем каталоге и записывает в него простое приложение на базе WEBrick.

    Созданное приложение всего лишь подсчитывает, сколько раз пользователь щелкнул по ссылке. Файл

    run.rb
    на стороне сервера подготавливает компоненты приложения и главный класс, после чего запускает приложение под управлением сервера WEBrick.

    require 'wee'

    require 'wee/utils'

    require 'wee/adaptors/webrick'


    # Ваши компоненты.

    require 'components/main'


    app = Wee::Utils.app_for do

     Main.new.add_decoration(Wee::PageDecoration.new('Wee'))

    end

    Wee::Utils::autoreload_glob('components/**/*.rb')

    Wee::WEBrickAdaptor.register('/арр' => app).start

    Класс

    Main
    вызывается как главный компонент приложения. Каждый компонент должен реализовать метод
    render
    , порождающий разметку. Вызов метода
    add_decoration(Wee::PageDecoration.new('Wee'))
    изменяет конвейер построения страницы так, что результаты обращения к Main#render дополняются HTML-кодом заголовка и хвостовика.

    Далее конфигурируется автоматическая перезагрузка файлов, чтобы можно было изменить код и проверить, как работает новая версия приложения, не перезапуская WEBrick. И наконец, запускается экземпляр сервера WEBrick, который обслуживает запросы к URL, начинающемуся с пути

    '/арр'
    . По умолчанию подразумевается порт 2000, но можно при запуске указать любой другой номер в качестве параметра:

    Wee::WEBrickAdaptor.register('/арр' => арр).start(:Port => 8787 )

    Компонент

    Main
    определяет метод
    render
    , который порождает разметку.

    class Main < Wee::Component


     def initialize

      super()

      # Здесь должен быть ваш код инициализации...

     end


     def render

      r.anchor.callback(:click).with { r.h1("Welcome to Wee!") }

      r.text "#{ @clicks || 'No' } clicks"

     end


     def click

      @clicks = (@clicks || 0) + 1

     end


    end

    Wee позволяет пользоваться синтаксисом Ruby для генерации HTML-кода примерно так же, как библиотека XML Builder Джима Вайриха и генератор XML в Nitro. Однако в Wee можно еще связать ссылку с действием (в данном случае с методом

    click
    ). Когда пользователь щелкает по ссылке, сгенерированной Wee, приложение понимает, что нужно вызвать метод
    click
    .

    19.5.2. Ассоциирование состояния с URL

    В примере выше отслеживается текущее значение переменной

    @click
    , но она не связывается с URL. Если вы запустите эту программу, что увидите, что Wee генерирует довольно длинный URL, который по сути является GUID'om (globally unique identifier, глобально уникальным идентификатором). URL остается таким же, если не считать завершающего символа косой черты и целого числа. При каждом щелчке по ссылке Welcome to Wee число увеличивается на единицу

    Если вручную изменить URL в браузере, то вы получите ту же самую страницу, показываемый счетчик щелчков не изменится. Нет никакой связи между URL и состоянием сервера. (Когда будете ставить эксперимент, не забудьте отключить кэширование в браузере).

    Впрочем, это положение можно изменить, слегка модифицировав файл

    main.rb
    . Добавьте в метод
    Main
    такой код:

    def backtrack_state(snap)

     super

     snap.add(self)

    end

    Теперь перезапустите приложение. Щелкнув несколько раз по ссылке, вручную измените URL в браузере так, чтобы повторно загрузилась предыдущая страница. Теперь счетчик должен показать то значение переменной

    @click
    , которое соответствует моменту генерации данного URL.

    Чтобы проверить то же самое с использованием механизма продолжений Wee, добавьте после директив

    require
    в файл
    run.rb
    следующую строку:

    require 'wee/continuation'

    В Wee слишком много возможностей, чтобы рассмотреть их здесь подробно. Дополнительную информацию можно найти на следующих ресурсах:

    • страница проекта Wee (http://rubyforge.org/projects/wee/);

    • страница проекта Nemo (http://rubyforge.org/projects/nemo/):

    • проект Seaside (http://seaside.st/)

    Одна из интересных особенностей — возможность иметь вложенные компоненты и организовывать цепочки обязанностей, что позволяет собирать сайты из повторно используемых компонентов пользовательского интерфейса. Стоит также познакомиться с проектом Nemo — реализацией Mewa (Meta-level Architecture for Web Applications, метауровневая архитектура Web-приложений) на Wee.

    19.6. Разработка Web-приложений с помощью IOWA

    IOWA (Interpreted Objects for Web Applications — интерпретируемые объекты для Web-приложений) — это каркас, написанный Кирком Хейнсом (Kirk Haines). Он позволяет создавать повторно используемые, инкапсулированные Web-компоненты для генерации сайта.

    19.6.1. Основные идеи IOWA

    Приложение IOWA работает как фоновый процесс, который прослушивает сокет в ожидании запросов. IOWA включает различные адаптеры, так что источником запроса может быть CGI, Mongrel, WEBrick и т.д.

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

    Загрузить IOWA можно с сайта rubyforge.org. Gem-пакет будет подготовлен вместе с версией 1.0. Примеры, представленные в этом разделе, составлены на базе предварительной версии, доступной в виде zip- или tgz-файла.

    В состав дистрибутива входит несколько примеров и тестов, демонстрирующих работу каркаса. Мы рассмотрим простое приложение, разработанное на основе этих примеров.

    Для приложения IOWA нужен код, который запустит фоновый процесс, а в качестве фронтального процесса мы будем пользоваться встроенным сервером WEBrick. Сценарий

    арр.rb
    решает обе задачи:

    require 'iowa_webrick'


    class HWApplication < Iowa::Application

     self.daemonize = true

     attr_accessor :dbpool


     def initialize(*args)

      super

      Iowa.config[Iowa::Capplication][Iowa::Croot_url] = 'http://127.0.0.1:2000'

     end

    end


    Iowa.run

    По умолчанию конфигурационные данные читаются из файла

    арр.cnf
    в текущем каталоге. Вот как выглядит наш файл:

    socket:

     hostname: localhost

     path: ..

    logging:

     basedir: ../log

     minlevel: 0

     maxsize: 10000000

     maxage: 86400

    application:

     daemonize: false

     sessioncache:

      class: LRUCache

      maxsize: 20

      ttl: 3600

     dispatcher:

      class: StandardDispatcher

     policy:

      class: iowa/Policy

    Этот файл, записанный в формате YAML, содержит различную информацию о желаемом поведении IOWA. Необходим и еще один конфигурационный файл

    mapfile.cnf
    , в котором описывается, как запросы отображаются на компоненты. Вот пример такого файла, содержащий всего одну строку:

    /main.html: Main

    Запросы к IOWA обычно обслуживаются комбинацией HTML-шаблонов и компонентов IOWA. Парные файлы имеют одинаковое базовое имя, но разные расширения. Принимаемый по умолчанию шаблон/объект называется

    Main
    , так что приложение состоит из файлов
    Main.html
    и
    Main.iwa
    .

    Файлы с расширением

    .iwa
    представляют собой программы на Ruby; такое расширение в IOWA используется для того, чтобы отличить их от прочего кода, который может входить в состав приложения. Эти файлы играют ту же роль, что классы контроллеров в Nitro и Rails. Определенные в классе компонента методы доступны из соответствующего HTML-файла.

    Демонстрационный файл

    Main.html
    выглядит так:

    <html>

     <head><title>Текущее время...</title></head>

     <body>

      <р>Текущее время @now.</p>

      <р>Счетчик равен @count.</p>

      <а oid="reload">RELOAD</a>

     </body>

    </html>

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

    Имеется также специальная переменная

    oid
    , IOWA использует ее для динамического изменения шаблона в процессе прорисовки. В нашем примере с ее помощью создается ссылка на метод reload из класса компонента, определенного в файле
    Main.iwa
    . Если задержать мышь над этой ссылкой в полученной странице (или посмотреть ее исходный текст), то вы увидите URL следующего вида:

    http://127.0.0.1:2000/main.html/6b38f6fb-4f087af7-ab6JaqUM9KyWE.a.1.7

    Такие URL позволяют IOWA отслеживать состояние сеанса. Если несколько раз щелкнуть по ссылке, то вы увидите, что URL изменяется. Если вручную восстановить предыдущее значение, вы получите состояние сеанса, соответствующее URL.

    В данном случае состояние — это значение переменной экземпляра

    @count
    . Вот как выглядит файл
    Main.iwa
    :

    class Main < Iowa::Component

     attr_accessor :count

     def awake

      @count = 0

     end


     def setup

      @count += 1

     end


     def now

      Time.now.asctime

     end

    end

    19.6.2. Шаблоны в IOWA

    Большинство Web-приложений только выигрывает от разделения кода и шаблонов представления, но IOWA, как и Nitro, позволяет вообще обходиться без компонентов и помещать весь код на Ruby в представление. Ниже приведен файл PureView.html, содержащий как код класса, так и HTML-разметку:

    <%

     class PureView < Iowa::Component

      def right_now

       Time.now

      end

     end

    %>

    <html>

     <head><titlе>Автономный вид</title></head>

     <body>

      <р>Текущее время @right_now.</p>

     </body>

    </html>

    Но, в отличие от Nitro, это работает только в том случае, когда для представления нет соответствующего компонента. Если имеются оба файла, то IOWA не станет анализировать код, встроенный в HTML-файл.

    Шаблон может содержать циклы и условные предложения. Добавим такой метод в файл

    Main.iwa
    :

    def after_dinner?

     Time.now.hour >19

    end

    Тогда можно реализовать в

    Main.html
    условную прорисовку с помощью элемента
    if
    :

    <if oid='after_dinner?'>

     <р>Обед закончен. Что на десерт?</р>

    </if>

    Хороший вопрос! Что на десерт? Пусть IOWA и ответит. Мы поручим

    Main.iwa
    подготовить меню десертов в виде массива:

    def desserts

     %w{

      Пирожные

      Печенье

      Фрукты

      Мороженое

     }

    end

    А в файле

    Main.html
    выведем его. Изменим содержимое элемента
    if
    , включив список десертов:

    <р>Обед закончен. Вот что мы можем предложить на десерт:</р>

    <ul oid="dessert_list">

     <li>@dessert_item</li>

    </ul>

    </if>

    <p>

    Мы еще должны сообщить IOWA, что выводить на каждой итерации, поэтому в конце файла

    Main.iwa
    после определения класса добавим раздел определения связей:

    <?

     dessert_list {

     item = dessert_item

     list = desserts

    } ?>

    Тем самым производится связывание списка

    dessert_list
    в шаблоне. На каждой итерации элемент списка заполняется из переменной
    dessert_item
    , а данные в целом поступают от метода компонента
    desserts
    .

    19.6.3. Передача управления компоненту

    Бывает полезно разнести логику приложения по нескольким классам компонентов. Мы видели, как можно отобразить URL на компоненты. Имеется также способ передать управление, не изменяя базового пути в URL.

    Добавим в файл

    Main.iwa
    метод для обработки щелчка по ссылке в меню десертов:

    def dessert_choice

     new_page = page_named( 'DessertChoice')

     new_page.choice = @dessert_item

     yield new_page

    end

    Также изменим цикл формирования списка десертов в

    Main.html
    :

    <ul oid="dessert_list">

     <li><a oid= 'dessert_choice'>@dessert_item</a></li>

    </ul>

    Тут происходит немало интересного; атрибут

    oid
    элемента
    ul
    управляет формированием цикла, а такой же атрибут элемента а создает специальную ссылку на только что добавленный метод
    dessert_choice
    . Для довершения дела странице передается еще и текст ссылки (хотя и несколько загадочным способом). Метод
    dessert_choice
    сам по себе короткий, в нем вызывается метод
    page_named
    для создания экземпляра еще одного класса компонента
    DessertChoice
    . Для передачи выбранного десерта вызывается метод
    choice=
    . Затем
    yield
    передает управление новому компоненту.

    Новый компонент также определяется с помощью пары файлов с расширениями

    .iwa
    и
    .html
    . Вот код класса:

    class DessertChoice < Iowa::Component

     attr_accessor :choice

     def details

      "Детали #{@choice} нужно было брать из базы данных."

     end

    end

    А в файле

    DessertChoice.html
    хранится разметка:

    <html>

     <head><title>Выбранный вами десерт</title></head>

     <body>

      <h1>Десерт!</h1>

      <p>@details</p>

     </body>

    </html>

    Об IOWA можно было бы рассказывать еще долго. Для получения дополнительной информации зайдите на домашнюю страницу IOWA (http://enigo.com/proiects/iowa/) или на страницу проекта IOWA на сайте RubyForge (http://rubyforge.org/projects/iowa).

    19.7. Ruby и Web-сервер

    На сегодняшний день одним из самых популярных Web-серверов является Apache. Если вы работаете с ним, то должны знать о модуле

    mod_ruby
    , который описывается в разделе 19.7.1.

    Еще одна полезная возможность на стороне сервера — встроенный Ruby; эту технологию поддерживают инструменты

    erb
    (рассматриваемый ниже) и
    eruby
    . Они позволяют встраивать код на Ruby в текст страницы (обычно HTML или XML), вследствие чего данные можно вставлять динамически. Данный подход описывается в разделе 19.7.2.

    Некоторые разработчики реализовали Web-серверы, написанные целиком на Ruby. Естественно возникает вопрос: зачем писать новый Web-сервер, когда их уже и так существует немало — взять хотя бы тот же Apache?

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

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

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

    И последняя причина заключается в том, что небольшой автономный Web-сервер может упростить развертывание и конфигурирование. Например, перезапустить сервер для приложения Rails гораздо проще, если в этом качестве выступает WEBrick, а не Apache.

    Имея все это в виду, посмотрим, что Ruby предлагает в плане Web-серверов. В прошлом было по крайней мере четыре таких сервера, но летом 2006 года остались два наиболее значимых: WEBrick и Mongrel. Они описаны в разделах 19.7.3 и 19.7.4 соответственно.

    19.7.1. Модуль mod_ruby

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

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

    mod_ruby
    (имеется в архиве RAA).

    Модуль

    mod_ruby
    реализует несколько директив Apache, в частности:

    • 

    RubyRequire
    определяет одну или несколько потребных библиотек;

    • 

    RubyHandler
    определяет обработчик для объекта Ruby;

    • 

    RubyPassEnv
    определяет имена переменных окружения, передаваемых сценариям;

    • 

    RubySetEnv
    устанавливает переменные окружения;

    • 

    RubyTimeOut
    задает величину тайм-аута для Ruby-сценариев;

    • 

    RubySafeLevel
    задает уровень безопасности $SAFE;

    • 

    RubyKanjiCode
    устанавливает кодировку символов для Ruby.

    В состав пакета входят также классы и модули Ruby для взаимодействия с Apache. Модуль

    Apache
    (здесь слово «модуль» употребляется в смысле, принятом в Ruby) включает несколько функций, например
    server_version
    и
    unescape_url
    ; там же определены классы
    Request
    и
    Table
    .

    Apache::Request
    — это обертка для типа данных
    request_rec
    , определяющая такие методы, как
    request_method
    ,
    content_type
    ,
    readlines
    и т.д. Класс
    Apache::Table
    — обертка для типа данных
    table
    ; он определяет, среди прочих, методы
    get
    ,
    add
    и
    each
    .

    Имеются подробные инструкции по компиляции и установке пакета

    mod_ruby
    . Обратитесь к поставляемой в комплекте с ним документации (или эквивалентной информации в Сети).

    19.7.2. Использование erb

    Для начала договоримся о терминологии. Речь не идет о встраивании интерпретатора Ruby в электронные устройства, скажем, в телевизор или тостер. Мы говорим о встраивании кода на Ruby в текст.

    Далее отметим, что встраивать Ruby-код в текстовые файлы можно несколькими способами. В этом разделе мы рассмотрим лишь самый общеупотребительный инструмент, а именно программу

    erb
    (автор Шуго Маэда).

    Почему мы упоминаем подобный инструмент в связи с Web? Очевидно, потому, что чаще всего Ruby-код встраивается в HTML или XML-тексты.

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

    cron
    для динамического создания файла с «сообщением дня» (
    /etc/motd
    ) в полночь. Не ограничивайте свое воображение. Ищите новые интересные применения
    erb
    и делитесь своими находками с сообществом. Большая часть примеров в этом разделе носит общий (и потому искусственный) характер, конкретно с HTML они почти не связаны.

    Утилита

    erb
    — это просто фильтр или препроцессор. Для выделения кода, выражений и комментариев, написанных на Ruby, применяется специальная нотация, весь остальной текст передается без изменений.

    Текст, который нужно специально обрабатывать, заключается в скобки

    <%
    и
    %>
    . Есть три вида такой нотации, отличающиеся первым символом внутри «тега».

    Если первый символ — знак равенства (

    =
    ), то содержимое рассматривается как выражение Ruby; результат его вычисления подставляется в текущее место файла, например:

    This is <%= "ylno".reverse %> a test.

    Do <%= "NOT".downcase %> be alarmed.

    Если назвать этот файл

    myfile.txt
    , то для его фильтрации надо будет выполнить команду:

    erb myfile.txt

    Результат направляется на стандартный вывод:

    This is only a test.

    Do not be alarmed.

    Комментарий вводится символом

    #
    :

    Life <%# so we've heard %> is but a dream.

    Как и следовало ожидать, комментарии игнорируются, то есть мы увидим такой результат:

    Life is but a dream.

    Все остальные символы после знака процента рассматриваются как часть кода. Все, что этот код выводит (не результат вычисления значения), помещается в текстовый поток. Для удобства восприятия рекомендую оставлять после процента пробел, хотя

    erb
    этого и не требует.

    В следующем примере тег в первой строчке ничего не вставляет в текст, так как ничего не выводит. Во вторую же строку вставляется

    42
    , как и следовало ожидать.

    Ответ равен <% "42" %>.

    А точнее, ответ равен <% puts "42" %>.

    Результат получается такой:

    Ответ равен .

    А точнее, ответ равен 42.

    Фрагменты кода на Ruby не являются независимыми. Так, переменную, определенную в одном теге, можно использовать в следующем за ним.

    <% x=3; y=4; z=5 %>

    Если стороны треугольника равны <%=x%>, <%=y%> и <%=z%>,

    мы знаем, что он прямоугольный, поскольку

    <%= x*x %> + <%= y*y %> = <%= z*z %>.

    Пробелы внутри тегов в последней строке необязательны, но с ними текст становится понятнее. Выводится следующий текст:

    Если стороны треугольника равны 3, 4 и 5,

    мы знаем, что он прямоугольный, поскольку

    9 + 16 = 25.

    Попробуйте сделать внутри тега синтаксическую ошибку. Вы обнаружите, что

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

    Что если включить «магическую строку» в сам текст в виде литерала? Экранирование с помощью обратной косой черты не работает. Мы рекомендуем такой способ:

    В этой строке есть цепочка меньше-процент <%="<%"%>,

    а в этой больше-процент <%="%"+">"%>.

    Здесь мы видим <%="<%="%> и <%="<%#"%>.

    Результат таков:

    В этой строке есть цепочка меньше-процент <%,

    а в этой больше-процент >%.

    Здесь мы видим <% и <%#.

    Включать открывающий тег немного проще, чем закрывающий. Объясняется это тем, что теги не могут быть вложенными, а программа

    erb
    недостаточно «умная», чтобы игнорировать закрывающий символ внутри строки.

    Конечно, в

    erb
    есть кое-какие функции, «заточенные» под HTML. Для установки режима работы предназначен флаг
    -M
    , он может принимать значения
    f
    ,
    с
    и
    n
    .

    Режим

    f
    (фильтр) подразумевается по умолчанию, поэтому во всех предыдущих примерах мы не задавали флаг
    -Mf
    явно. В режиме
    -Mc
    (CGI) все ошибки печатаются в виде HTML. В режиме
    -Mn
    (NPH-CGI — без разбора заголовков) автоматически выводятся дополнительные HTTP-заголовки. В двух последних режимах для безопасности переменная
    $SAFE
    устанавливается в 1 (исходя из допущения, что это CGI-приложение, которое может вызвать враждебный пользователь). Флаг
    -n
    (и эквивалентный ему
    --noheader
    ) подавляет вывод CGI-заголовка.

    Можно настроить сервер Apache так, что он будет распознавать страницы с встроенным кодом на Ruby. Для этого нужно ассоциировать тип

    application/x-httpd-erb
    с каким-нибудь расширением (было бы логично использовать
    .rhtml
    ) и определить действие, которое ассоциирует этот тип с исполняемым файлом
    eruby
    . Более подробные сведения вы найдете в документации по Apache.

    19.7.3. Сервер WEBrick

    Авторами WEBrick являются Масаёси Такахаши (Masayoshi Takahashi) и Юзоу Готоу (Yuuzou Gotou) при участии многих других разработчиков. Это библиотека для создания полноценного HTTP-сервера; она входит в стандартный дистрибутив Ruby. Название происходит от слова «brick» (кирпич) — то есть подразумевается нечто небольшое, компактное и автономное.

    WEBrick почти ничего не знает о деталях Web-приложений. Он не понимает, что такое сеанс пользователя и прочие тонкости. Он оперирует лишь сервлетами, работающими независимо друг от друга. Если вам необходима функциональность более высокого уровня, поищите другую библиотеку (возможно, надстройку над WEBrick наподобие IOWA или Tofu) или напишите свою собственную.

    Работа с WEBrick сводится к такой последовательности действий: создается экземпляр сервера; определяются обработчики монтирования и обработчики сигналов; запускается сервер. Вот небольшой пример:

    require 'webrick'


    server = WEBrick::HTTPServer.new(:DocumentRoot => '.')

    # (В этом простом примере нет обработчиков монтирования)

    trap('INT') { server.shutdown}

    trap('TERM') { server.shutdown}

    server.start

    Запустив эту программу, вы получите Web-сервер, работающий на стандартном порте 80. Он раздает файлы из текущего каталога.

    Для создания сервлета требуется создать класс, производный от

    WEBrick::HTTPServlet::AbstractServlet
    . При обработке указанного в запросе URL сервер ищет самый длинный префикс (наилучшее соответствие). Ниже приведен «пустой» пример (в нем обработчики не делают ничего полезного):

    class EventsHandler < HTTPServlet::AbstractServlet

     # ...

    end

    class RecentHandler < HTTPServlet::AbstractServlet

     # ...

    end

    class AlphaHandler < HTTPServlet::AbstractServlet

     # ...

    end


    # ...


    server.mount('/events', EventsHandler)

    server.mount('/events/recent', RecentHandler)

    server.mount('/events/alpha', AlphaHandler)

    Как работает сервлет? Идея в том, чтобы определить метод для каждой поддерживаемой HTTP-операции, например

    do_GET
    для запросов типа GET. Если вы привыкли писать программы, обращающиеся к серверу, то теперь придется встать на противоположную точку зрения, ведь ваш код становится частью Web-сервера. Вы не получаете ошибку с кодом 404, а сами посылаете этот код. Вот простой пример:

    class TinyHandler < WEBrick::HTTPServlet::AbstractServlet


     def do_GET(request, response)

      # Обработать запрос, вернуть ответ.

      status, ctype, body = process_request(request)

      response.status = status

      response['Content-type'] = ctype

      response.body = body

     end


     def process_request(request)

      text = "Очень короткая Web-страница..."

      return 200, "text/html", text

     end


    end

    В более сложном сервлете, вероятно, использовался бы метод

    initialize
    . Тогда передаваемые ему параметры были бы последними при вызове метода
    server.mount
    .

    К счастью, не нужно писать сервлеты для каждой крохотной задачки, которую должен выполнять WEBrick. У него есть несколько своих предопределенных сервлетов (все в пространстве имен

    WEBrick::HTTPServlet
    ):

    • 

    FileHandler

    • 

    ProcHandler

    • 

    CGIHandler

    • 

    ERBHandler

    Поскольку сервлет

    ProcHandler
    особенно интересен, скажем о нем несколько слов. Он позволяет «лениться», то есть не создавать подкласса
    AbstractServlet
    . Вместо этого мы передаем
    proc
    :

    # Непосредственное монтирование блока...


    server.mount_proc('/here') do |req, resp|

     resp.body = "Это то, что выводит мой блок."

    end


    # Создать объект Proc и смонтировать его...


    some_proc = Proc.new do |req, resp|

     resp.body = ' Это то, что выводит мой Proc.'

    end


    server.mount_proc('/there', some_proc)

    # Другой способ смонтировать Proc...


    my_handler = HTTPServlet::ProcHandler.new(some_proc))

    server.mount('/another', my_handler)

    WEBrick располагает и многими другими возможностями, например точками подключения для выполнения дополнительных задач (скажем, запуска какой-нибудь программы при старте). Имеются также развитые средства протоколирования, аутентификации по протоколу HTTP и т.д. Дополнительную информацию ищите в онлайновой документации на сайте http://ruby-doc.org или где-либо еще.

    19.7.4. Сервер Mongrel

    Автором Mongrel является Зед Шоу (Zed Shaw) при участии других. Основная цель этого сервера — повысить производительность по сравнению с WEBrick. В данном отношении он добился значительных успехов и работает во много раз быстрее (хотя провести точные измерения сложно: результат зависит от многих факторов).

    Mongrel часто используют в сочетании с Rails, а документация местами ориентирована на Rails. Но жесткой привязки к Rails нет — сервер может работать и в других контекстах.

    Mongrel — скорее, приложение, тогда как WEBrick больше напоминает библиотеку. Во многом они схожи, но порядок запуска и API различаются.

    Во многих случаях Mongrel можно запустить как приложение без написания какого-либо кода. Он понимает три основные команды:

    start
    ,
    stop
    и
    restart
    . У команды
    start
    много параметров, модифицирующих поведение сервера, например:
    --port portnum
    ,
    --log filename
    ,
    --daemonize
    и т.д. Чтобы получить полный список, введите такую команду:

    mongrel_rails start -h

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

    Простой способ подготовить конфигурационный файл для Mongrel — воспользоваться флагом

    -G
    . Например, можно ввести такую командную строку:

    mongrel_rails start -G myconfig.yml -p 3000 -r /home/hal/docs -l my.log

    Заданные параметры будут сохранены (в формате YAML) в файле

    myconfig.yml
    (при наличии флага
    -G
    сервер завершается сразу после создания конфигурационного файла).

    Для чтения параметров из конфигурационного файла задайте флаг

    -C
    :

    mongrel_rails start -С myconfig.yml

    Не употребляйте флаг

    -C
    вместе с другими. Он предполагает, что все параметры находятся в указанном файле.

    Mongrel предлагает API для тонкой настройки поведения сервера. Флаг

    -S
    позволяет задать имя сценария, написанного с использованием этого API, который представляет собой небольшой язык предметной области (DSL — Domain-Specific Language). В документации приведен пример такого сценария (который добавляет обработчик для каталога, отличного от текущего):

    # Файл: config/mongrel.conf

    uri "/newstuff", :handler => DirHandler.new("/var/www/newstuff")


    # Вызывается такой командой:

    # mongrel_rails start -S config/mongrel.conf

    Mongrel можно использовать и так же, как WEBrick. Следующая интуитивно понятная программа прекрасно работает:

    require 'mongrel'


    class TinyHandler < Mongrel::HttpHandler

     def process(request, response)

      response.start(200) do |head,out|

       head["Content-Type"] = "text/html"

       out.write <<-EOF

        Это всего лишь тест...


       EOF

      end

     end

    end


    server = Mongrel::HttpServer.new("0.0.0.0", "3000")

    server.register("/stuff", TinyHandler.new)

    server.register("/other", Mongrel::DirHandler.new("./other"))

    server.run.join # Ждать в потоке сервера

    Если вы активно работаете с Mongrel, то вас может заинтересовать система GemPlugin. Это, по сути, автозагружаемые gem-пакеты, которые становятся «частью» Mongrel. Например, модуль «Mongrel cluster» позволяет легко управлять кластером серверов Mongrel.

    О Mongrel можно рассказывать еще долго. Дополнительная информация о протоколировании, отладке, деталях системы подключения модулей и пр. содержится в онлайновой документации на сайте http://mongrel.rubyforge.org.

    19.8. Заключение

    В этой главе мы рассмотрели низкоуровневые детали программирования CGI-приложений для Web. Мы видели, как в этом помогают такие инструменты, как модуль mod-ruby. Существует еще немало инструментов, помимо упомянутых в этой книге, например ruby-web, Amrita, Tofu и Cerise. Как обычно, перед тем как сделать выбор, поищите альтернативы.

    Мы также познакомились с высокоуровневыми библиотеками и каркасами: Rails, Nitro, IOWA и Wee. Было уделено внимание и автономному серверу WEBrick (и гораздо более быстрому Mongrel).

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









    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх