СтатьиПрограммирование

Использование AJAX в Perl.
n0xi0uzz
28 мая 2008 16:35



Теги: ajax, perl, javascript

Перевод статьи «Using Ajax from Perl» автора Dominic Mitchell


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

Ajax — это «Asynchronous JavaScript and XML». Этот термин ввел Jesse James Garret в «Ajax: A New Approach to Web Applications». Не думайте о футбольной команде, они всего лишь самозванцы ;-).

Что это, в общем-то, значит? Вкратце, это делает ваши веб-страницы более интерактивными. В частности, Ajax позволяет вам обновить части вашей веб-страницы без перезагрузки всей страницы целиком. Многие люди предлагают Google Maps и gmail в качестве отличных примеров того, что с этим можно делать, но мой любимый пример — часть Flickr.

Когда вы заходите на Flickr под своим аккаунтом, вы можете видеть фотографии, которые вы только что загрузили. В этом примере, это то, что я снял своей камерой во время велосипедной прогулки. К несчастью, телефон даёт имена файлам по умолчанию, которые являются неинформативными (рис. 1):
Оригинальное фото на Flickr
Рис. 1. Оригинальная фотография на Flickr

Если вы кликните на название, оно превратится в подсвеченное текстовое поле (рис. 2):
Редактирование названия фотографии
Рис. 2. Редактирование названия фотографии.

После переименования на нечто более подходящее, нажмите на кнопку Save и, прежде чем вернуться к первоначальной форме, вы увидите на экране название текущего действия (рис. 3):
Завершение редактирования названия фотографии
Рис. 3. Завершено редактирование названия фотографии.

За кулисами, Flickr использует JavaScript, чтобы разделить запрос к серверу на обновление информации. Это делается с помощью использования чего-то под названием XMLHttpRequest. Вы услышите многое о нем, пока будете изучать Ajax.

Flickr позволяет вам редактировать практически всю информацию о вашей фотографии, не заставляя вас отправляться на отдельную страницу редактирования. Это очень простое улучшение, но оно делает все приложение гораздо более простым в использовании. Поэтому будет неплохо узнать об этой технологии, чтобы улучшить ваши собственные приложения. Также нелишним будет указать на то, что это расширения. Если JavaScript у вас выключен, Flickr по-прежнему будет позволять вам редактировать информацию о фотографии. Только вам придется идти на отдельную страницу редактирования, совершая круги по серверу. Использование Ajax позволит вам повысить ваш уровень пользователя.

Каким образом здесь замешан JavaScript? Разве у нас не было этого всего несколько лет назад? Ну, да. Вид JavaScript, который выглядит, как Perl Golf уже давно позабыт. Я уверен, что вы чувствуете то же, вспоминая о тех Perl 4 скриптах, которые вы писали несколько лет назад, до того, как открыли для себя, как работают модули в Perl 5. Современный JavaScript — другой зверь. Он относительно стандартен, поэтому проблемы кроссбраузерности, возникавшие ранее, уже далеко в прошлом. Существует также новый взгляд на создание JavaScript скриптов настолько незаметными, насколько это возможно. Со всеми этими изменениями, есть даже попытка ребрендинга JavaScript в браузере на DOM scripting.

Ajax делится на следующие части:
— Асинхронность: любая активность не блокирует браузер во время работы.
— JavaScript: для совершения манипуляций над страницей внутри браузера.
— XML: для возвращения данных серверу.


Ajax и Perl: CGI::Ajax

Вы можете потратить много времени, приводя все кусочки JavaScript на стороне клиента и Perl на стороне сервера в порядок, чтобы они работали по технологии Ajax в вашем коде. Тем не менее, это Perl; мы привыкли быть немного ленивыми. К счастью, уже есть модуль в CPAN, чтобы избавить нас от мучений: CGI::Ajax.

CGI::Ajax предоставляет небольшую инфрастркутуру для ваших CGI-программ. Вы даете ему информацию о некоторых ваших функциях и настраиваете JavaScript для того, чтобы их вызывать и возвращать результаты на страницу. Вам не нужно беспокоиться о написании JavaScript для этого, так как CGI::Ajax берет это на себя. Все, что вам придется сделать — это добавить несколько вызовов JavaScript, определенных в вашем скрипте, и дать CGI::Ajax сделать свою работу.

Функции, которые CGI::Ajax создает для вас на JavaScript, в той или иной степени следуют одному и тому же шаблону. Они принимают два параметра: список HTML-идентификаторов, из которых поступают данные, а второй — список HTML-идентификаторов, куда следует поместить результат. Поэтому наличие идентификаторов в вашем HTML-коде является предусловием для решения этой задачи. CGI::Ajax обрабатывает запросы к вашей веб-странице, принимая входящие значения и вставляет результаты, идущие от сервера.

Делая функции вашего CGI-скрипта доступными из браузера, вы получаете возможность делать такие вещи, которые раньше делать не могли. Например, вы можете получать информацию из базы данных или запрашивать статистику нагрузки на сервер. Все, что вы могли делать на Perl, но не могли на JavaScript, теперь становится возможным.


Проверка имен пользователей.

Для изучения CGI::Ajax, сконцентрируемся на практической проблеме. У вас есть страница для регистрации в вашем приложении. Для этого вам необходимо ввести по порядку имя пользователя и пароль. Поскольку, это общедоступное приложение, имя пользователя может быть уже занято. К несчастью, вам приходится заполнять повторно эту форму и ждать ответа сервера для того, чтобы узнать, что вы не можете взять себе имя пользователя пушистыйкотик. Как CGI::Ajax может решить эту проблему?

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

Он начинается также, как и любой хороший код на Perl, с подключения модулей strict и warnings, за которым следует только один другой необходимый модуль: CGI.pm. Он создает новый CGI-объект и вызывает метод main() для осуществления своей работы:
#!/usr/local/bin/perl
# User registration script.
use strict;
use warnings;

use CGI;
my $cgi  = CGI->new();
main();

main() соединяет вместе части HTML-кода. Для настоящих скриптов, используйте что-нибудь, вроде HTML::Template или Template Toolkit, вместо того, чтобы помещать HTML-код прямо в скрипт.

Интересующие нас вещи происходят в середине. Сначала он проверяет входящий параметр user. Если таковой имеется, код проверяет, подходит ли он и записывает это имя пользователя в нашу базу данных. Если возникают какие-то проблемы, он решает их. В конце он посылает созданный HTML-код для отображения в браузер
sub main {
    my $html = <<HTML;
<html><head>
<title>Пример CGI</title>
</head><body>
<h1>Signup!</h1>
HTML
    if ( my $user = $cgi->param('user') ) {
        my $err = check_username( $user );
        if ( $err ) {
            $html .= "<p class='problem'>$err</p>";
        } else {
            save_username( $user );
            $html .= "<p>Аккаунт <em>$user</em> создан!</p>\n";
        }
    }
    my $url = $cgi->url(-relative => 1);
    $html .= <<HTML;
<form action="$url" method="post">
<p>Пожалуйста, заполните форму для создания нового аккаунта</p>
<p>Имя пользователя: <input type="text" name="user" id="user"/></p>
<p>Пароль: <input type="password" name="pass" id="pass"/></p>
<p><input type="submit" name="submit" value="SIGNUP"/></p>
</form></body></html>
HTML
    print $cgi->header();
    print $html;
}

Для того, чтобы реализовать пользовательскую базу данных простым образом, я решил хранить список пользователей в текстовом файле, построчно. Чтобы проверить, не занято ли имя пользователя, код считывает файл и сравнивает каждую строку с пришедшим значением. Если возникают проблемы, он возвращает строку. Если файл не существует, код позволяет регистрироваться под любым именем пользователя (это позволяет избежать проблем при первом запуске скрипта). И снова, в реальном приложении, вам лучше хранить пользователей в базе данных, используя DBI.
sub check_username {
    my ( $user ) = @_;
    return unless -f '/tmp/users.txt';
    open my $fh, '<', '/tmp/users.txt'
      or return "open(/tmp/users.txt): $!";
    while (<$fh>) {
        chomp;
        return "Username taken!" if lc $_ eq lc $user;
    }
    return;
}

Наконец, для того, чтобы сохранить имя пользователя, код должен записать его в конец файла:
sub save_username {
    my ( $user ) = @_;
    open my $fh, '>>', '/tmp/users.txt'
      or die "open(>>/tmp/users.txt): $!";
    print $fh "$user\n";
    close $fh;
    return;
}

Теперь у вас должен быть скрипт, позволяющий вам вводить имена пользователей и записывать их в файл. Если вы попытаетесь ввести одно и то же имя дважды, он вас остановит, прямо как Hotmail. А теперь представьте, что, как и в Hotmail, вам приходится потратить много времени, пытаясь разобрать изображение на captcha. Тогда, когда вы наконец-то разобрали, что за странные буквы там написаны, вы нажимаете Submit только для того, чтобы узнать, что имя пользователя, введенное вами, занято. И вам приходится придумывать другое имя и снова пытаться распознать текст на картинке. Как вы видите, очень важно сказать пользователю о том, что имя пользователя занято настолько быстро, насколько это возможно.


CGI::Ajax

С помощью небольших изменений, вы можете сделать скрипт, использующий CGI::Ajax для проверки, занято ли имя пользователя. Таким образом, любые проблемы будут отображаться незамедлительно. Будет немного изменений. В начале скрипта, подключите модуль CGI::Ajax и создайте новый объект. В это время, зарегистрируйте функцию check_username() в качестве видимой для Ajax. После этого, вместо вызова main() напрямую, вызовите build_html(), передав ей main() по ссылке. Это очень важная часть того, как работает CGI::Ajax. Это дает CGI::Ajax возможность, при необходимости, контролировать выдачу результатов. Также вы можете скачать код с включенным Ajax.
#!/usr/local/bin/perl
# User registration script.

use strict;
use warnings;

use CGI;
use CGI::Ajax;

my $cgi  = CGI->new();
my $ajax = CGI::Ajax->new( check_username => \&check_username );
print $ajax->build_html( $cgi, \&main );

Ещё одно структурное изменение затронет функцию main(). Вместо печати заголовка и сгенерированного HTML-кода, она теперь возвращает содержимое.
sub main {
    # ...
    # print $cgi->header();
    # print $html;
    return $html;
}

После этого, CGI::Ajax будет отправлять содержимое к вам в браузер.

Изучив Ajax на стороне сервера, пришло время обратить внимание на сторону клиента. Клиенту необходимо применить возможности, предоставляемые CGI::Ajax. Чтобы сделать это, вам понадобится немного JavaScript. Если вы посмотрите исходный код второго скрипта, вы увидите, что CGI::Ajax вставил немного JavaScript-кода в конце секции <head>. Это код, который делает видимыми ваши Perl-функции для JavaScript. Все, что осталось — подключить события, происходящие на странице к этим видимым Perl-функциям.

Если до этого вы использовали JavaScript, то, возможно, подумали об аттрибуте onchange. Это правильная мысль (для произведения вызова Ajax, когда меняется поле имени пользователя), но она не является идеальным способом, чтобы сделать это, так как она назойлива. В действительности, нет никакой нужды здесь добавлять JavaScript в HTML-код. Вместо этого, создайте небольшой файл, чтобы связать разметку с видимыми Perl-функциями (скачать binding.js).

Он начинается с функции addLoadEvent, написанную Simon Willison. Эта функция принимает часть JavaScript и запускает его, когда страница закончит загрузку. Это полезно тем, что вы можете вызывать её больше одного раза без побочных эффектов. Вы можете использовать обработчик window.onload напрямую, но эту удалит весь предыдущий сопутствующий код. Используя addLoadEvent() везде, такой проблемы не возникнет.
function addLoadEvent(func) {
  var oldonload = window.onload;
  if (typeof window.onload != 'function') {
    window.onload = func;
  } else {
    window.onload = function() {
      oldonload();
      func();
    }
  }
}

Следующая часть кода JavaScript делает реальную работу. Но, прежде, чем что-то делать, будет небольшая проверка, чтобы убедиться, что браузер способен обработать весь этот Ajax. Указывая имя функции браузера (document.getElementById) без замыкающих скобок, JavaScript возвращает ссылку на неё. Если функция не существует, он вернет null. Если та функция не существует, код просто возвращает прямо противоположное значение, показывая отсутствие возможностей Ajax на данной странице. Это постепенное понижение уровня, так как оно позволяет вести себя странице обычным образом, если расширенные возможности не работают.

Когда код знает, что он безопасен для дальнейшего выполнения, он запрашивает у страницы строку ввода имени пользователя. Если он находит его, код вызывает функцию check_username() каждый раз, когда поле ввода имени пользователя меняется. Два параметра являются списками идентификаторов в документе. Первый — список параметров, чьи значения проходят в chek_username() на сервер. Второй список содержит идентификаторы, куда вставлять возвращенные значения из функции:
// Set up functions to run when events occur.
function installHandlers() {
  if (!document.getElementById) return;
  var user = document.getElementById('user');
  if (user) {
      // When the user leaves this element, call the server.
      user.onchange = function() {
          check_username(['user'], ['baduser']);
      }
  }
}

Определив installHandlers, все, что остается — убедиться, что код запускается при завершении загрузки страницы.
addLoadEvent( installHandlers );

Закончив binding.js, вам нужно сделать два небольших изменения в генерируемом HTML-коде. Во-первых, добавьте тег script, чтобы загружать скрипт:
<script type="text/javascript" src="binding.js"></script>

Во-вторых, создайте элемент с идентификатором baduser, в который будет вставляться результат. Я создал пустой акцентирующий тег сразу после поля username:
<p>Username: <input type="text" name="user" id="user"/>
<em id="baduser"></em></p>

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


Внутри CGI::Ajax

Теперь вы знаете, как использовать CGI::Ajax для динамического обновления ваших веб-страниц. Что же на самом деле происходит внутри для этого? Вы уже видели, как версия JavaScript функции check_username() собирает значения из полей ввода и передает их в обычный CGI-скрипт. Вы можете просмотреть вызовы, добавив ещё одну строку в скрипт, сразу после того, как вы создали объект CGI::Ajax.
$ajax->JSDEBUG(1);

С помощью неё, CGI::Ajax будет записывать каждый вызов, который он делает к серверу внизу вашей веб-страницы. На моем веб-сервере, это выглядит подобным образом:
/cgi-ajax/ajax.cgi?fname=check_username&args=dom&user=dom

Переходя по этой ссылке, вы увидите, что появится строка Username 'dom' taken! и больше ничего. Просто игнорируется большая часть программы и посылается только результат check_username(). Когда главная часть программы вызывает $ajax->build_html(), CGI::Ajax проверяет наличие параметра под названием fname. Если он его находит, то он проверяет, зарегистрирована ли функция. Если да, он её вызывает, передавая все параметры args. Затем он возвращает результаты выполнения этой функции назад в браузер, завершая выполнение главной программы.


CGI::Ajax в реальности

Ajax — это инструмент, такой же, как и те, которые вы уже использовали при программировании для веб. Есть места, где он более или менее уместен, так же, как и элементы table в HTML. Причиной, почему я использовал проверку имени пользователя в качестве примера, стало моя мысль о том, что это будет хороший пример того, где Ajax действительно уместен для добавления чего-либо в существующее веб-приложение. Используя Ajax для улучшения форм, особенно больших, может давать чудесный результат в удобстве использования. Конечно, есть и другие варианты. Как я указал в начале статьи, упомянув Flickr, редактирование строки будет отличным улучшением.

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

Существует несколько ресурсов для решения вопроса о том, когда стоит использовать Ajax. Самый полный это Ajax Patterns, который должен выйти в скором времени в качестве книги издательства O'Reilly. Пост Alex Bosworth в его блоге поведает вам о «10 местах, где вы должны использовать Ajax» (даже если их на самом деле шесть). Alex также написал хорошую статью «Ошибки Ajax», на которую тоже не помешает обратить внимание.

Также имеются технические стороны, на которые стоит обратить внимание при использовании Ajax в вашем приложении. Вам надо осознавать, что любой пользователь может получить доступ к функциям на стороне сервера, которые вы делаете видимыми, даже если вы думаете о них, только как о внутренних. Ваше приложение внезапно получает API. Из соображений безопасности или производительности, возможно, вам стоит пересмотреть то, что вы делаете открытым (хотя, этот совет применим в равной степени и к обычным веб-страницам вашего приложения). Также учтите, что API, получаемый от CGI::Ajax, тесно связан с выполнением вашего приложения. Если вы поменяете имя экспортируемой функции, вы поменяете API. Если API является частью большого, непрерывного проекта, возможно, вам стоит потратить время на изучение интерфейсов, более похожих на REST. Эта модель сделает проще работу для программирующих на других языках.

Теперь, когда ваш сайт имеет API, у вас может появиться желание о документировании принципов его работы для того, чтобы пользователи с ним работали. Это то, что возымело отличный эффект у Flickr. Сделаны инструменты, о которых разработчики Flickr даже и не задумывались, просто потому, что они дали своим пользователям доступ к API.

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


Теги: ajax, perl, javascript

Статьи с такими же тегами:

Гостевая книга на perl: последние штрихи.
Клиент для LiveJournal.com на Perl
Система авторизации по переменным окружения.
Обработка PDF на Perl.
Выбирая шаблонизатор на Perl.
Регулярные выражения в JavaScript
Введение в O3D от Google