IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  SOA и Web-сервисы  >

Добавление шаблонов Ruby в приложения Project Zero и WebSphere sMash

Использование RHTML в Project Zero для создания динамических интерфейсов пользователя

developerWorks
Опции документа

Опции документа, требующие включения JavaScript, не отображаются

Обсудить

Исходные тексты примера


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: средний

Ден Джемиоло, программист-консультант, IBM

06.02.2009

Программисты Ruby, внимание! Теперь у вас есть те же возможности для создания приложений Project Zero, что и у разработчиков на Groovy или PHP. В предыдущей статье рассказывалось о расширении среды Project Zero для поддержки скриптового языка Ruby. В результате разработчики Ruby получили возможность применить свои навыки программирования на скриптовых языках при работе с платформой Zero, используя все преимущества ее уникальной модели для разработки приложений. Разумеется, скрипты – это не единственный путь создания приложений Ruby. Например, разработчики, работающие с инфраструктурой Ruby on Rails, вставляют фрагменты кода на Ruby в шаблоны HTML, аналогично страницам JSP и PHP. Эти шаблоны, называемые файлами RHTML, оказываются очень удобными при создании динамических интерфейсов. В этой статье мы расширим нашу поддержку Ruby в Project Zero, включив в нее шаблоны RHTML. Как вы увидите, теперь разработчики Ruby получили те же возможности для создания приложений Zero, что и программисты Groovy или PHP.

Примечание редактора: IBM® WebSphere® sMash и IBM WebSphere sMash Developer Edition основаны на проекте-инкубаторе Project Zero, получившем широкое признание. Project Zero - это сообщество разработчиков WebSphere sMash, которое по-прежнему будет предлагать бесплатную платформу разработки приложений с новейшими сборками, функциональными возможностями и поддержкой.

Перед началом работы

Статья ориентирована на читателей, которые загрузили Project Zero M2 и либо прочитали вводное руководство, либо самостоятельно создали простое приложение. Также рекомендуется прочитать предыдущую статью под названием "Добавление поддержки скриптов Ruby в приложения Project Zero" и иметь представление об основах серверных шаблонных языков, таких как JSP, PHP и RHTML.

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

Введение

В предыдущей статье "Добавление поддержки скриптов Ruby в приложения Project Zero" рассказывалось о расширении платформы Project Zero с целью поддержки скриптов Ruby. Однако предложенное решение не охватывало все способы применения Ruby при работе с инфраструктурой Ruby on Rails. Данная инфраструктура позволяет вставлять код на Ruby внутрь шаблонов HTML, аналогично JSP и PHP. Эти шаблоны (так называемые файлы RHTML) очень удобно применять при создании динамических интерфейсов, так как они помогают избежать многочисленных вызовов print для вывода HTML-кода (подобные операторы характерны для сервлетов J2EE). Ввиду того, что Zero поддерживает шаблоны Groovy и PHP (файлы с расширениями .gt и php соответственно), почему бы не реализовать то же самое и для Ruby?

Стратегия создания процессора RHTML

Самое главное при реализации RHTML-процессора для Zero – это избежать необходимости создания парсера и интерпретатора RHTML «с нуля». Несмотря на все преимущества шаблонов Ruby, они не стоят вовлечения в проект большого количества библиотек, а также написания компилятора, который будет подвержен ошибкам до тех пор, пока большое количество пользователей не займется его тестированием. Однако при работе со скриптами Ruby, всю ответственность за их выполнение можно делегировать JRuby. Было бы прекрасно, если бы JRuby также взял на себя задачу выполнения Ruby-кода, содержащегося в файлах RHTML. В этом случае нам не пришлось бы создавать свой собственный специализированный интерпретатор.

Проблемой, как и следовало ожидать, является то, что JRuby может исполнять только скрипты, написанные на чистом Ruby, а не файлы RHTML. Поэтому возникает вопрос: как добиться того, что Ruby-код, вложенный в файлы RHTML, исполнялся JRuby, а остальное содержимое – шаблон HTML – передавался клиенту без изменений? При этом задача не ограничивается только извлечением кода Ruby из шаблонов и передаче его JRuby. Нужно также позаботиться о том, что результат выполнения вновь вставлялся в статический HTML. Необходимо учитывать, что фрагменты кода на Ruby, рассыпанные по всему шаблону, должны исполняться в единой области видимости и иметь доступ к переменным друг друга. Поэтому становится очевидно, что недостаточно просто уметь отделять код от разметки HTML и передавать его JRuby. Нам требуется более совершенное решение.

Правильным подходом будет трансформировать весь файл RHTML, включающий код и разметку, в скрипт Ruby, подобно тому, как JSP-компилятор преобразует страницы JSP в классы J2EE-сервлетов. Как только это сделано, скрипт можно передать на вход JRuby, которому все равно, как именно данный скрипт был создан. Далее мы обратимся к подробностям синтаксиса RHTML, а также расскажем о процессе трансляции – главному звену нашего RHTML-процессора.

Синтаксис RHTML

Файлы RHTML представляют собой файлы HTML, в которые вставляются фрагменты кода на Ruby, заключенные в специальные теги: <% и %>. Содержимое данных тегов интерпретируется сервером до передачи страницы клиенту, который, таким образом, не имеет доступа к исходному коду на Ruby. Данный код обязан содержать один или несколько синтаксически корректных выражений Ruby, причем каждый фрагмент, заключенный внутри <% и %>, может оказывать влияние на исполнение последующих фрагментов. Подобное разбиение кода на отдельные участки позволяет разработчикам избежать необходимости генерировать HTML при помощи операторов print, обеспечивая тем самым идеальное отделение кода от оформления. Пример содержимого файла RHTML приведен в листинге 1.


Листинг 1. Пример файла RHTML, содержащего код на Ruby
        
    
    <html>
        
      <body>
        <p>
        Here is a list of numbers 1 - 10:
        <%
          (1..10).each do |n| 
            print n, ' '
          end
        %>
        </p>
      </body>
    
    </html>
      

Существует один частный случай в правилах вставки кода на Ruby в шаблоны HTML: если требуется вставить единичное значение или результат простой операции, то открывающий тег принимает вид <%= >. В листинге 2 показан пример использования данного сокращенного синтаксиса при выводе простых значений на страницу.


Листинг 2. Пример файла RHTML с упрощенным синтаксисом выражений
        
      
    <html>
        
      <body>
        <p>
        The sum of 3 and 4 is <%= 3 + 4 %>
        </p>
      </body>
    
    </html>
      

Генерация корректных скриптов Ruby

Для того, чтобы преобразовывать файлы, наподобие показанных в листингах 1 и 2, в скрипты на Ruby, необходимо генерировать выражения Ruby не только для вставленных фрагментов кода, но и для представления статического HTML. Все, что не является кодом на Ruby, то есть участки HTML и JavaScript, должно преобразовываться в вызовы операторов print, выводящих на страницу строковые значения. Данные операторы должны вставляться в текст скрипта, перемежаясь с фрагментами кода Ruby, который без изменений копируется из исходных файлов. Специальный синтаксис для вставки значений (<%= ... %>) требует дополнительного оператора print, который выводит результат вычисления выражения Ruby, заключенного между этими тегами. В листинге 3 показан Ruby-скрипт, полученный в результате преобразования файла, приведенного ранее в листинге 2.


Листинг 3. Скрипт на Ruby, соответствующий приведенному выше файлу RHTML
        
    
    print "<html><body><p>The sum of 3 and 4 is "
    print 3 + 4
    print "</p></body></html>"
      

Как видите, по своему характеру генерируемые скрипты должны быть просты, они в основном содержат операторы print с вкраплениями других выражений Ruby там, где это необходимо. При этом код в листинге 3 можно оптимизировать, добавив символы перевода строк таким образом, чтобы генерируемый в результате работы скрипта HTML был более похож на исходный шаблон. Это должно помочь в отладке шаблонов RHTML, поэтому мы реализуем данную оптимизацию.

Хранение скриптов Ruby

Последний вопрос, который следует рассмотреть при проектировании RHTML-процессора – это что делать со сгенерированными скриптами. Наиболее простой вариант – это сохранять их в дисковых файлах, присваивая им имена, по которым было бы легко определить исходные файлы RHTML. Например, если исходный файл назывался my-file.rhtml, то скрипт можно сохранить в файле my-file.rhtml.rb (.rb – это стандартное расширение скриптов Ruby). В таком случае наш процессор будет генерировать и исполнять скрипты, действуя по следующему алгоритму:

  1. Преобразовать имя исходного файла RHTML в имя файла скрипта.
  2. Проверить, существует ли данный скрипт. Если он существует, и файл RHTML не редактировался после создания скрипта, то пропустить этап генерации.
  3. В противном случае сгенерировать скрипт и сохранить его в файле с именем, полученном на шаге 1.
  4. Выполнить скрипт.

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

Реализация RHTML-процессора

Теперь, закончив проектирование процессора RHTML, необходимо связать его с основным процессором Ruby. Как и в прошлой статье, мы будем добавлять функциональность в наш проект шаг за шагом, однако вы сразу можете загрузить исходный код проекта целиком (ссылка находится в разделе Загрузка). Если вы предпочитаете первый способ, то имеет смысл начать с проекта, который мы создали в предыдущей статье, так как там уже реализована поддержка Ruby со стороны Project Zero.

На данный момент поддержка Ruby базируется на классе JRubyInterpreter, который реализует интерфейс Interpreter ядра Zero и обрабатывает запросы HTTP, используя интерпретатор JRuby. Для работы с файлами RHTML нам придется создать еще одну реализацию Interpreter, которая сначала будет генерировать Ruby-скрипты, а затем вызывать JRuby. При этом можно унаследовать новый класс от JRubyInterpreter, избежав тем самым дублирования кода, отвечающего за работу с JRuby. В результате получился класс RHTMLInterpreter, показанный в листинге 4.


Листинг 4. Класс RHTMLInterpreter
        
    
    package zero.scripting.ruby;

    import java.io.File;

    import zero.core.context.GlobalContext;
    import zero.core.events.HandlerInfo;
    import zero.scripting.html.HTMLCompiler;

    public class RHTMLInterpreter extends JRubyInterpreter 
    {
        public void invoke(HandlerInfo handlerInfo) 
        {
            try 
            {
                File scriptFile = new File(handlerInfo.handler);
                HTMLCompiler compiler = new HTMLCompiler();
                
                //
                // Конвертация файла RHTML в скрипт Ruby
                //
                File compiledFile = compiler.compile(scriptFile, ".rb");
                
                super.invoke(compiledFile);
            } 
            
            catch (Throwable error) 
            {
                GlobalContext.put("/request/status", 500);
                GlobalContext.put("/request/error", error);
            }
        }
    }
      

Обратите внимание, что в методе RHTMLInterpreter.invoke() используется класс HTMLCompiler, который берет на себя «грязную» работу по конвертации RHTML в Ruby; остается только гарантировать, что он будет вызван до передачи управления базовому классу. Класс HTMLCompiler содержит довольно много кода, но центральным местом в нем является метод compile(), показанный в листинге 5. Остальной код находится в архиве, ссылка на который приведена в разделе Загрузка.


Листинг 5. Метод HTMLCompiler.compile()
        
    
    public File compile(File htmlFile, String extension)
        throws IOException, HTMLCompilerException
    {
        File compiledFile = getCompiledFile(htmlFile, extension);
        
        //
        // Если файл не изменялся, то повторная генерация не требуется
        //
        if (compiledFile.exists() && 
            compiledFile.lastModified() > htmlFile.lastModified())
            return compiledFile;
        
        String html = getHTML(htmlFile);
        
        int current = 0;
        int codeStart = html.indexOf(_EMBEDDED_START_TOKEN, current);
        
        FileWriter writer = new FileWriter(compiledFile);
        
        //
        // Фрагменты кода на Ruby переносятся в скрипт без изменения.
        // HTML-разметка конвертируется в вызовы операторов print
        //
        while (codeStart >= 0)
        {
            //
            // Вывод всего HTML-кода, предшествующего 
            // данному участку кода на Ruby
            //
            writePrintStatements(writer, html.substring(current, codeStart));
            
            int codeEnd = html.indexOf(_EMBEDDED_END_TOKEN, codeStart);
            
            if (codeEnd < 0)
                throw new HTMLCompilerException("No matching end token found.");
            
            int stmtStart = codeStart + _EMBEDDED_START_TOKEN.length();
            String code = html.substring(stmtStart, codeEnd);
            
            //
            // Упрощенный синтаксис должен обрабатываться специальным образом:
            // встретив выражение между тегами <%= %>, мы просто выводим его значение,
            // а не копируем целиком
            //
            if (html.charAt(stmtStart) == _EMBEDDED_VALUE_TOKEN)
                writeValuePrintStatement(writer, code.substring(1));
            
            //
            // Обычный синтаксис: участок кода копируется без изменений
            //
            else
            {
                writer.write(code);
                writer.write('\n');
            }
            
            current = codeEnd + _EMBEDDED_END_TOKEN.length();
            codeStart = html.indexOf(_EMBEDDED_START_TOKEN, current);
        }
        
        //
        // Проверить, есть ли HTML-код после последнего фрагмента кода на Ruby.
        // Если да, то преобразовать его как обычно
        //
        if (current < html.length())
            writePrintStatements(writer, html.substring(current));
        
        writer.flush();
        writer.close();
        
        return compiledFile;
    }
      

Как видите, в методе compile() осуществляется проверка, предложенная нами выше (см. шаг 2 алгоритма) в целях оптимизации. Первый оператор if, что генерация скрипта будет происходить только в случае необходимости. Кроме того, посмотрев на метод writePrintStatements(), вы увидите, что символы перевода строки соответствуют тем, что были в исходном файле, поэтому генерируемый в результате выполнения скрипта HTML выглядит практически идентично шаблону.

Редактирование конфигурационного файла Zero

На этом реализация заканчивается, но необходимо сделать еще кое-что: аналогично интерпретатору Ruby, RHTML-процессор должен быть зарегистрирован в ядре Zero, указывая тем самым на то, как должны обрабатываться запросы к ресурсам с расширением .rhtml. Регистрация заключается в добавлении строк, приведенных в листинге 6, в конфигурационный файл /config/zero.config.


Листинг 6. Добавьте следующую строку в файл /config/zero.config file
        
    
    [/app/interpreters]
    .rhtml=zero.scripting.ruby.RHTMLInterpreter
      

Тестирование шаблонов Ruby

Теперь осталось протестировать поддержку RHTML-шаблонов, добавив файл RHTML в наше приложение и вызвав его в Web-браузере. Для того, чтобы проиллюстрировать всю полноту интеграции Ruby и RHTML со средой Zero, наш шаблон будет использовать API GlobalContext для чтения данных запроса и формирования ответа. RHTML-файл, показанный в листинге 7, полностью раскрывает содержимое глобального контекста, помещая его в сериализованном виде внутрь HTML-ответа.


Листинг 7. Тестовый файл RHTML
        
    
    <%require "java"%>
    <%include_class "zero.core.context.GlobalContext"%>
    <%include_class "zero.core.context.SimpleFormatter"%>
    <html>
      <head>
      <title>Sample RHTML Page</title>
      </head>
      <body>
        <p>The current contents of the global context are:
          <pre>
            <%
              formatter = SimpleFormatter.new
              GlobalContext.dump formatter
              print formatter
            %>
          </pre>
        </p>
      </body>
    </html>
      

Создайте файл под названием test-ruby-and-markup.rhtml, добавьте в него код, приведенный в листинге 7, и сохраните файл в каталоге /public вашего приложения. Затем следует запустить приложение при помощи команды zero run и обратиться по ссылке http://localhost:8080/test-ruby-and-markup.rhtml в вашем любимом браузере. В результате вы должны увидеть простую HTML-страницу, показывающую содержимое глобального контекста. Внешний вид страницы в браузере Mozilla Firefox показан на рисунке 1.


Рисунок 1. Внешний вид HTML-страницы, сгенерированной на основе шаблона Ruby
Рисунок 1. Внешний вид HTML-страницы, сгенерированной на основе шаблона Ruby

Заключение

Хотя поддержка Ruby в приложениях Zero сама по себе привлекательна, еще лучше иметь возможность использовать шаблоны Rails. Благодаря разработанному нами RHTML-процессору, разработчики Ruby теперь обладают тем же кругом возможностей при создании приложений Zero, что и программисты на Groovy и PHP. Стоит заметить, что наш подход к конвертации HTML с вложенными фрагментами кода на Ruby в исполняемые скрипты носит достаточно общий характер и в случае необходимости может применяться для добавления более гибких шаблонов или новых языков в платформу Project Zero.




В начало


Загрузка

ОписаниеИмяРазмерМетод загрузки
Sample JRuby integration code and test fileszero.scripting.zip8419 KBHTTP
Информация о методах загрузки


Ресурсы

Научиться

Получить продукты и технологии
  • Загрузите Project Zero M2 и начните создавать приложения, применяя методы, о которых рассказывалось в данной статье. (EN)

  • Загрузите JRuby 1.0, который необходим для компиляции и запуска примеров к данной статье. (EN)


Обсудить


Об авторе

Ден Джемиоло (Dan Jemiolo) работает программистом-консультантом в команде IBM Autonomic Computing в Research Triangle Park, NC. Он возглавлял проектирование и разработку Apache Muse 2.0 и продолжает работать над проектом. Ден также участвовал в WS-RF TC как редактор спецификации по WS-ResourceMetadataDescriptor и в стратегии IBM по расширению применения стандартов Web-сервисов. Он пришел в IBM два года назад, после того как получил степень магистра вычислительных наук в Rensselaer Polytechnic Institute.




Выскажите мнение об этой странице


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



 


 


 


Поделиться этой статьей:

забобрить забобрить memori сохранить в memori




В начало


IBM обладает всеми авторскими правами касательно информации, расположенной на developerWorks. Использование информации приведенной на этом ресурсе без явного письменного разрешения от IBM или первоначального автора запрещены. Если Вы желаете использовать информацию с developerWorks, пожалуйста воспользуйтесь регистрационной формой для того, чтобы связаться с нами запрос на использование материалов developerWorks Россия.
    IBM в России Конфиденциальность Контакты