Последнее в категории Веб

Шаблонизаторы — зло.

use XSLT or die.

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

Оказалось, что это происходит только с MSIE, и именно с теми ссылками, которые находятся в блогах на livejournal.com. В строке запроса (QUERY_STRING) в таких ссылках обычно четыре параметра — два текстовых и два числовых:

http://whoyougle.ru/place/distance/?from=%D0%9F%D0%B0%D1%80%D0%B8%D0%B6&to=%D0%91%D0%B0%D1%80%D1%81%D0%B5%D0%BB%D0%BE%D0%BD%D0%B0&from_which=498817&to_which=1819729

Значения параметров from и to — то, что пользователь вводил в полях ввода, а from_which и to_which уточняют запрос, выбирая из нескольких одноименных названий одно конкретное. (В общем-то, несколько избыточно, и от первых двух параметров мы уже отказались, но суть не в этом.)

После перехода по ссылке в адресной строке вместо URI-escaped-последовательности оказывались русские буквы, и первое подозрение было — MSIE не очень хорошо работает с такими адресами и передает на сервер что-то искаженное.

MSIE, однако, хорош тем, что щелкает при загрузке страницы, и вместо одного щелчка при переходе по ссылке было два. Оказалось, что товарищи жжсты подменяли ссылку и превращали ее в ссылку на их бесконечно хитрый редиректер (который сначала загружал ту же страницу ЖЖ, а потом делал редирект):

http://community.livejournal.com/ru_travel/14519597.html?thread=168965421&dr_log=-1&linkout=http%3A//whoyougle.ru/place/distance/%3Ffrom%3D%25D0%259F%25D0%25B0%25D1%2580%25D0%25B8%25D0%25B6%26to%3D%25D0%2591%25D0%25B0%25D1%2580%25D1%2581%25D0%25B5%25D0%25BB%25D0%25BE%25D0%25BD%25D0%25B0%26from_which%3D498817%26to_which%3D1819729

После этого происходил переход на адрес, в котором вместо from=%D0%9F%D0%B0%D1%80%D0%B8%D0%B6 оказывалась неэкранированная последовательность байт, эквивалентная запросу from=%c1%e0%f0%f1%e5%eb%ee%ed%e0. А это уже не изначальный UTF-8, а почему-то голый Windows-1251.

Реальные же непонятки возникли, когда оказалось, что оставшиеся параметры from_which и to_which были вообще не видны на сервере. Но такого же не может быть! В переменной $ENV{QUERY_STRING} строка была вся и целиком.

Мы пользуемся библиотекой libapreq2 для разбора параметров, чтения кук и загрузки файлов. Perl-модули Apache2::Request и др. предоставляют интерфейс, аналогичный традиционному CGI.pm, и в нехохошем до сих пор замечены не были.

А тут в списке параметров, возвращаемых вызовом $req->param(), оказывались только те, которые стояли в строке запроса до первой пары со значением в кодировке Windows-1251.

Круто, лезем с Сергеем в код libapreq2 (предварительно обновившись до последней версии, но неадекватное поведение осталось).

Сказать, что я был удивлен, — ничего не сказать. Libapreq2 при разборе строки делает бешеное число действий для того, чтобы распознать кодировку запроса. Казалось бы, зачем она это делает, если Apache2::Request все равно в итоге отдает октеты?

Алгоритм выявления кодировки (см. файл util.c) такой:

  1. изначально ожидается ASCII. Если нет, то
  2. предполагаем UTF-8. Если нет, то
  3. предполагаем Latin-1. Если нет управляющих символов, то
  4. устанавливаем CP1252.

Да-да, Latin-1 и CP1252. (Сразу видно, что писано моноглотами.) Функция, выполняющая эти эвристики, кстати, называется apreq_charset_divine. Divine — англ. гадать, предсказывать, пророчествовать.

Первый байт строки %c1%e0%f0%f1%e5%eb%ee%ed%e0 содержит два единичных старших бита, и функция-пророк считала его началом UTF-последовательности. Правда, на втором же байте понимало, что это не так, и... и в итоге функция apreq_parse_query_string (см. файл param.c) завершала работу:

s = apreq_param_decode(&param, pool, start, nlen, vlen);
if (s != APR_SUCCESS)
    return s;

apreq_param_tainted_on(param);
apreq_value_table_add(&param->v, t);

Ни сама пара, в которой содержалась строка в кодировке Windows-1251, ни все последующие не попадали в таблицу параметров строки запроса.

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

s = apreq_param_decode(&param, pool, start, nlen, vlen);
if (s == APR_SUCCESS) {
    apreq_param_tainted_on(param);
    apreq_value_table_add(&param->v, t);
}

Собираем libapreq2, подменяем libapreq.so, и — вуаля! — видим значения всех параметров после тех, что проигнорировали.

P. S. В исходниках есть моноглотские определения типа

enum   apreq_charset_t { APREQ_CHARSET_ASCII = 0, APREQ_CHARSET_LATIN1 = 1, APREQ_CHARSET_CP1252 = 2, APREQ_CHARSET_UTF8 = 8 }

и самописаня функция apreq_cp1252_to_utf8, реализация которой напоминает мой код 2002 года :-)

Great Circle

| Нет комментариев

Гугл-карты с третьей версией API научились рисовать путь между двумя пунктами на Земле по «большому кругу» (great circle), поэтому без жалости можно отправить в архив фрагмент кода, вычисляющий координаты дуги.

my $span = 10;
$span = int($distance) / 100 if int($distance) > $span * 100;

my $arc = (new Geo::GoogleEarth::Pluggable)
          ->GreatCircleArcSegment(
    startPoint => {
        lat => $coords_pair[0],
        lon => $coords_pair[1]
    },
    endPoint   => {
        lat => $coords_pair[2],
        lon => $coords_pair[3]
    },
    span        => $span * 1000, # meters
);

my $arcNode = add_node($pairNode, 'arc');   
for (@{$arc->coordinates}) {
    add_node(
        $arcNode,
        'point', {
            lat  => $_->lat,
            long => $_->lon,
        },
    );
}

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

Great circle

REST::Client

| Нет комментариев

В феврале этого года O’Reilly издало книгу «RESTful Web Services Cookbook», и в этой связи есть смысл упомянуть модуль, который появился на спане в начале прошлого года, а сегодня обновился, — REST::Client, который, возможно будет в чем-то удобнее традиционных LWP et. al.

Использование весьма прямолинейно (хотя и не без странностей): названия методов REST::Client совпадают с именами методов HTTP-протокола: GET, PUT, POST, DELETE, OPTIONS, HEAD.

use v5.10;
use strict;

use REST::Client;

my $client = new REST::Client;
$client->OPTIONS('http://onperl.ru/');
say $client->responseCode();
say $client->responseContent();

$client->HEAD('http://onperl.ru/');
say $client->responseCode();

$client->GET('http://onperl.ru/');
say $client->responseCode();

Дополнительно имеется возможность использовать SSL, и, что интересно, создать для полученного XML-ответа XPath-контекст.

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

#!/usr/bin/perl

use v5.10;
use strict;

use REST::Client;

my ($from, $to) = map {uc} @ARGV;
unless ($from) {
    say "Usage: rate FROM [TO]\n";
    exit;
}
$to //= 'RUB';

my $client = new REST::Client;
$client->GET("http://whoyougle.com/money/api/$from/$to");
say $client
    ->responseXpath
    ->findvalue('/currency/rate/value/text()')
;

Проверка:

$ ./rate usd
29.2416

$ ./rate eur usd
1.3393

Автор ясень, автоматически переводятся с английского языка.

Краткая версия: SayPerl.org сайт запущен. Она обеспечивает Английский перевод для Perl сообщения со всего мира.

Длинная версия:

Железный Человек и PlanetPerl.ru показали, что Есть много людей Perl, которые пишут о Perl и вы никогда не слышали о них.

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

Копирование-вставка после URL-адреса в Google Translate на ежедневной основе очень раздражает. Я поймал себя на начало чтения последних итальянских сообщения о предстоящих YAPC:: Европе, а затем переход к Google Translate, как я не могу много читать итальянский еще. То же самое с немецким. Оба они являются более понятными, чем японские, хотя :-)

Сегодня я начал SayPerl.org сайт, который блоге агрегатор сообщений в сочетании с автоматическим переводчиком на английский язык.

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

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

Чтобы продемонстрировать преимущества SayPerl.org, сравнивать эти две должности, оригинальных и переводных:

Слово "Rakvdv", который выглядит так, как это будет написано древние римляне, четко понятно сейчас.

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

Что касается технических деталей, я только хотел бы отметить еще одну проблему с обработкой Unicode. Так произошло и с WebService:: Google:: Язык. Такие ошибки являются одним из наиболее распространенных проблем с модулями Perl сегодня (вспомните, например, еще одна проблема с Unicode возникает тогда, когда Алекс кодирования PlanetPerl.ru).

sayperl.org

Начало использования SayPerl.org прямо сейчас!

В серверном коде, обслуживащем сайт, блоки given/when весьма удобны в частности для того, чтобы последовательно протестировать URL запрошенного ресурса и выполнить соответствующие действия:

given($url) {
    when(m{^/$}) {home_page()}
    when(m{^/about/$} {about_page()}
    . . .
}

В Perl 5.10 помимо операторов given/when доступны именованные сохраняющие скобки, которые могут облегчить выделение параметров из адреса одновременно с его разбором:

given($url) {
    . . .
    when(m{^/news/(?<year>\d+)/(?<month>\d+)/(?<day>)/$})
        {news_that_day()}
}

Параметры, захваченные именованными скобками, попадают в хеши %+ и %-, которые следует передать дальше на обработку.

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

given($url) {
    . . .
}
say Dumper(\%+); #
Пусто

В этом месте кода переменные %+ и %- сохраняют значения, полученные до начала блока given.

Вплоне логично, хорошо локализовано, но не всегда удобно.

Разумеется, возможно сохранить собранные данные непосредственно в блоке given, хотя при легкомысленном проектировании это легко приводит к большим фрагментам повторяющегося кода:

given($url) {
    . . .
    when(m{^/(?<type>this)_page/$})
        {%data = %+; do_this()}
    when(m{^/(?<type>that)_page/$})
        {%data = %+; do_that()}
}

XSLT::Dependencies

| Нет комментариев

Небольшая предыстория.

Верстка сайта Microsoft сделана на XSLT. Разумеется, многие XSLT-файлы зависимы от других, и при верстке довольно часто приходилось пересохранять неизмененный основной файл, хотя изменения делались где-то в другом файле, который подключался. А это напрягало.

Студийный parser долгое время тоже не умел обновлять кеш зависимых XSLT-шаблонов. Это тоже напрягало (хотя более напрягал, конечно, сам parser).

По мере возможности я стараюсь кешировать разобранные и скомпилированные XSLT-файлы. На спане есть модуль XSLT::Cache, который, однако, не отслеживает зависимости. Недостаток призван компенсировать модуль XSLT::Dependencies.

use XSLT::Dependencies;
my @dep_list = XSLT::Dependencies->new->explore('myfile.xslt');

В списке @dep_list оказывается перечень всех файлов, которые прямо или косвенно подключены к файлу myfile.xslt.

Интересно, что даже на весьма шаблоноемком проекте нашлось лишь три-четыре шаблона, которые зависят от четырех других, в то время как большинство (130 файлов) зависит всего от двух.

В todo числится обучить модуль читать XSLT не только напрямую из файла, но и из уже разобранного дерева XML::LibXSLT.

P. S. На типовом сайте, однако, отслеживать зависимости и не нужно: при разработке кешировать ничего не требуется, а на продакшне ничего не обновляется :-)

Template::Semantic

| Нет комментариев

Один из бешеных японцев™ — Наоки Томита — сегодня опубликовал на спане интересный модуль Tempalte::Semantic, один из немногих yet another-шаблонизаторов, на который стоит обратить внимание.

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

Например, чтобы вывести заголовок на страницу, сначала создаем HTML-заготовку:

<html>
    <head>
        <title></title>
    </head>
</html>

А затем описываем правила а-ля CSS:

use v5.10;
use strict;
use Template::Semantic;

say Template::Semantic->process(
    'title.html',
    {
        'html title' => 'My Title',
    }
);

Эта программа напечатает HTML-код с подставленным заголовком:

<html>
    <head>
        <title>My Title</title>
    </head>
</html>

Fantastique!

Описания с CSS-селекторами или XPath-адресами допустимо создавать и более сложной структуры, в том числе с вложенными хешами и списками (последние позволяют размножать фрагменты HTML-шаблонов).

Примеры наглядно описаны в документации модуля, а кроме того собраны в отдельном файле Template::Semantice::Cookbook.

Недавно на спане появился модуль HTML::DOM, который одновременно — и парсер и билдер. Пара первых простейших экспериментов с этим модулем.

Создание HTML

use v5.10;
use strict;
use HTML::DOM;

my $dom = new HTML::DOM;
$dom->write('<html>');
say $dom->innerHTML;

Здесь модуль создаст HTML с полным набором нужных тегов:

<html><head></head><body></body></html>

Более того, правильно обрабатываются и неправильные вызовы, например такой код

$dom->write('<html>');
$dom->write('<title>My Title</title>');

аккуратно размещает тег <title> внутри <head>:

<html><head><title>My Title</title></head><body></body></html>

Чтение HTML

Базовая операция чтения файла проста:

use v5.10;
use strict;
use HTML::DOM;

my $dom = new HTML::DOM;
$dom->parse_file('test.html');
say $dom->innerHTML;

Опять же, происходит внятное преобразование до валидного HTML-кода. Если файл test.html содержит одну строку <title>My Title</title>, то на выходе получается следующее:

<html><head><title>My Title</title>
</head><body></body></html>

Этими примитивными действиями модуль не ограничивается. В его распоряжении набор методов, напоминающих методы, доступные в XML::LibXML и при обработке DOM с помощью JavaScript.

 

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

WhoYOUgle

Страницы

  • img

Об архиве

Эта страница содержит последние записи категории Веб.

Предыдущая категория — CPAN.

Следующая категория — Версии Perl.

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