Как сделать Mason2 UTF-8 чистым?
Переформулировать вопрос, потому что
- @opional спросил меня
- это было непонятно и связано одно решение на базе HTML::Mason Четыре простых шага, чтобы очистить Mason UTF-8 Unicode с помощью Apache, mod_perl и DBI, что вызвало путаницу
- оригиналу 4 года и тем временем (в 2012 году) создан "поэт"
Комментарий: Этот вопрос уже получил "значок популярного вопроса", так что, вероятно, я не единственный безнадежный человек. :)
К сожалению, демонстрация полного стека проблем приводит к очень длинному вопросу, и он очень специфичен для Мейсона.
Во-первых, мнения только часть:)
Я использую HTML::Mason на протяжении веков, и сейчас пытаюсь использовать Mason2. Поэт и Мейсон - самые передовые фреймворки в CPAN. Ничего не сравнимого с тем, что "из коробки" позволяет писать такие чистые / но очень хакерские:)/ веб-приложения со многими включенными батареями (ведение журнала, кеширование, управление конфигурацией, родная PGSI и т. Д.)
К сожалению, автору нет дела до остального слова, например, по умолчанию, оно основано только на ascii, без какого-либо руководства, часто задаваемых вопросов или советов о том, как использовать его с юникодом.
Теперь факты. Demo. Создать приложение для поэта:
poet new my #the "my" directory is the $poet_root
mkdir -p my/comps/xls
cd my/comps/xls
и добавить в dhandler.mc
следующее (что будет демострировать две основные проблемы)
<%class>
has 'dwl';
use Excel::Writer::XLSX;
</%class>
<%init>
my $file = $m->path_info;
$file =~ s/[^\w\.]//g;
my $cell = lc join ' ', "ÅNGSTRÖM", "in the", $file;
if( $.dwl ) {
#create xlsx in the memory
my $excel;
open my $fh, '>', \$excel or die "Failed open scalar: $!";
my $workbook = Excel::Writer::XLSX->new( $excel );
my $worksheet = $workbook->add_worksheet();
$worksheet->write(0, 0, $cell);
$workbook->close();
#poet/mason output
$m->clear_buffer;
$m->res->content_type("application/vnd.ms-excel");
$m->print($excel);
$m->abort();
}
</%init>
<table border=1>
<tr><td><% $cell %></td></tr>
</table>
<a href="?dwl=yes">download <% $file %></a>
и запустить приложение
../bin/run.pl
перейдите по http://0.0.0.0:5000/xls/hello.xlsx, и вы получите:
+----------------------------+
| ÅngstrÖm in the hello.xlsx |
+----------------------------+
download hello.xlsx
Нажав на скачивание hello.xlsx, вы получите hello.xlsx
в загрузках.
Вышесказанное демонстрирует первую проблему, например, источник компонента не "под" use utf8;
, Итак lc
не понимает персонажей.
Вторая проблема заключается в следующем, попробуйте [ http://0.0.0.0:5000/xls/h%C3%A9ll%C3%B3.xlsx] или http://0.0.0.0:5000/xls/h%C3%A9ll%C3%B3.xlsx, и вы увидеть:
+--------------------------+
| ÅngstrÖm in the hll.xlsx |
+--------------------------+
download hll.xlsx
#note the wrong filename
Конечно, вход (path_info
) не декодируется, скрипт работает с октетами в кодировке utf8, а не с символами perl.
Итак, говоря perl - "источник находится в utf8", добавив use utf8;
в <%class%>
, Результаты
+--------------------------+
| �ngstr�m in the hll.xlsx |
+--------------------------+
download hll.xlsx
добавление use feature 'unicode_strings'
(или же use 5.014;
) еще хуже:
+----------------------------+
| �ngstr�m in the h�ll�.xlsx |
+----------------------------+
download h�ll�.xlsx
Конечно, источник теперь содержит широкие символы, для этого нужны Encode::encode_utf8
на выходе.
Можно попробовать использовать фильтр такой:
<%filter uencode><% Encode::encode_utf8($yield->()) %></%filter>
и отфильтровать весь вывод:
% $.uencode {{
<table border=1>
<tr><td><% $cell %></td></tr>
</table>
<a href="?dwl=yes">download <% $file %></a>
% }}
но это помогает только частично, потому что нужно заботиться о кодировке в <%init%>
или же <%perl%>
блоки. Кодирование / декодирование внутри perl-кода во многих местах (читай: не на границах) приводит к неправильному коду.
Кодирование / декодирование должно быть четко сделано где-то на границах поэта / масона - конечно, Plack работает на уровне байтов.
Частичное решение.
К счастью, поэт ловко позволяет изменять его (и масонские) части, поэтому в $poet_root/lib/My/Mason
Вы могли бы изменить Compilation.pm
чтобы:
override 'output_class_header' => sub {
return join("\n",
super(), qq(
use 5.014;
use utf8;
use Encode;
)
);
};
что вставит нужную преамбулу в каждый компонент Mason. (Не забудьте коснуться каждого компонента или просто удалить скомпилированные объекты из $poet_root/data/obj
).
Также вы можете попробовать обработать запрос / ответы на границах, отредактировав $poet_root/lib/My/Mason/Request.pm
чтобы:
#found this code somewhere on the net
use Encode;
override 'run' => sub {
my($self, $path, $args) = @_;
#decode values - but still missing the "keys" decode
foreach my $k (keys %$args) {
$args->set($k, decode_utf8($args->get($k)));
}
my $result = super();
#encode the output - BUT THIS BREAKS the inline XLS
$result->output( encode_utf8($result->output()) );
return $result;
};
Кодировать все - это неправильная стратегия, она ломает, например, XLS.
Итак, 4 года спустя (я задал оригинальный вопрос в 2011 году) до сих пор не знаю:(как правильно использовать юникод в приложениях Mason2 и до сих пор не существует никакой документации или помощников по этому поводу.:(
Основные вопросы: - где (какие методы должны быть модифицированы модификаторами методов Moose) и как правильно декодировать входные данные и где выходные данные (в приложении Poet/Mason).
- но только текстовые, например
text/plain
или жеtext/html
и тому подобное... - сделайте вышеупомянутое "без сюрпризов" - например, то, что будет просто работать.;)
Может ли кто-нибудь помочь с реальным кодом - что я должен изменить в приведенном выше?
3 ответа
Хорошо, я проверял это с Firefox. HTML-код правильно отображает UTF-8 и оставляет почтовый индекс в покое, поэтому должен работать везде.
Если вы начнете с poet new My
применить патч, который вам нужен patch -p1 -i...path/to/thisfile.diff
,
diff -ruN orig/my/comps/Base.mc new/my/comps/Base.mc
--- orig/my/comps/Base.mc 2015-05-20 21:48:34.515625000 -0700
+++ new/my/comps/Base.mc 2015-05-20 21:57:34.703125000 -0700
@@ -2,9 +2,10 @@
has 'title' => (default => 'My site');
</%class>
-<%augment wrap>
- <html>
+<%augment wrap><!DOCTYPE html>
+ <html lang="en-US">
<head>
+ <meta charset="utf-8">
<link rel="stylesheet" href="/static/css/style.css">
% $.Defer {{
<title><% $.title %></title>
diff -ruN orig/my/comps/xls/dhandler.mc new/my/comps/xls/dhandler.mc
--- orig/my/comps/xls/dhandler.mc 1969-12-31 16:00:00.000000000 -0800
+++ new/my/comps/xls/dhandler.mc 2015-05-20 21:53:42.796875000 -0700
@@ -0,0 +1,30 @@
+<%class>
+ has 'dwl';
+ use Excel::Writer::XLSX;
+</%class>
+<%init>
+ my $file = $m->path_info;
+ $file = decode_utf8( $file );
+ $file =~ s/[^\w\.]//g;
+ my $cell = lc join ' ', "ÅNGSTRÖM", "in the", $file ;
+ if( $.dwl ) {
+ #create xlsx in the memory
+ my $excel;
+ open my $fh, '>', \$excel or die "Failed open scalar: $!";
+ my $workbook = Excel::Writer::XLSX->new( $fh );
+ my $worksheet = $workbook->add_worksheet();
+ $worksheet->write(0, 0, $cell);
+ $workbook->close();
+
+ #poet/mason output
+ $m->clear_buffer;
+ $m->res->content_type("application/vnd.ms-excel");
+ $m->print($excel);
+ $m->abort();
+ }
+</%init>
+<table border=1>
+<tr><td><% $cell %></td></tr>
+</table>
+<p> <a href="%c3%85%4e%47%53%54%52%c3%96%4d%20%68%c3%a9%6c%6c%c3%b3">ÅNGSTRÖM hélló</a>
+<p> <a href="?dwl=yes">download <% $file %></a>
diff -ruN orig/my/lib/My/Mason/Compilation.pm new/my/lib/My/Mason/Compilation.pm
--- orig/my/lib/My/Mason/Compilation.pm 2015-05-20 21:48:34.937500000 -0700
+++ new/my/lib/My/Mason/Compilation.pm 2015-05-20 21:49:54.515625000 -0700
@@ -5,11 +5,13 @@
extends 'Mason::Compilation';
# Add customizations to Mason::Compilation here.
-#
-# e.g. Add Perl code to the top of every compiled component
-#
-# override 'output_class_header' => sub {
-# return join("\n", super(), 'use Foo;', 'use Bar qw(baz);');
-# };
-
+override 'output_class_header' => sub {
+ return join("\n",
+ super(), qq(
+ use 5.014;
+ use utf8;
+ use Encode;
+ )
+ );
+};
1;
\ No newline at end of file
diff -ruN orig/my/lib/My/Mason/Request.pm new/my/lib/My/Mason/Request.pm
--- orig/my/lib/My/Mason/Request.pm 2015-05-20 21:48:34.968750000 -0700
+++ new/my/lib/My/Mason/Request.pm 2015-05-20 21:55:03.093750000 -0700
@@ -4,20 +4,27 @@
extends 'Mason::Request';
-# Add customizations to Mason::Request here.
-#
-# e.g. Perform tasks before and after each Mason request
-#
-# override 'run' => sub {
-# my $self = shift;
-#
-# do_tasks_before_request();
-#
-# my $result = super();
-#
-# do_tasks_after_request();
-#
-# return $result;
-# };
+use Encode qw/ encode_utf8 decode_utf8 /;
-1;
\ No newline at end of file
+override 'run' => sub {
+ my($self, $path, $args) = @_;
+ foreach my $k (keys %$args) {
+ my $v = $args->get($k);
+ $v=decode_utf8($v);
+ $args->set($k, $v);
+ }
+ my $result = super();
+ my( $ctype, $charset ) = $self->res->headers->content_type_charset;
+ if( ! $ctype ){
+ $ctype = 'text/html';
+ $charset = 'UTF-8';
+ $self->res->content_type( "$ctype; $charset");
+ $result->output( encode_utf8(''.( $result->output())) );
+ } elsif( ! $charset and $ctype =~ m{text/(?:plain|html)} ){
+ $charset = 'UTF-8';
+ $self->res->content_type( "$ctype; $charset");
+ $result->output( encode_utf8(''.( $result->output())) );
+ }
+ return $result;
+};
+1;
В руководстве Mason2 представлено, как работает наследование компонентов, поэтому я думаю, что размещение этого общего кода в вашем основном компоненте Base.mp (от которого все остальные наследуют) может решить вашу проблему.
Создание плагинов описано в Mason:: Manual:: Plugins.
Таким образом, вы можете создать свой собственный плагин, который изменяет Mason:: Request и переопределяет request_args()
Вы можете вернуть декодированные параметры UTF-8.
Редактировать:
Что касается вывода UTF-8, вы можете добавить директиву Apache, чтобы гарантировать, что вывод text/plain и text/HTML всегда интерпретируется как UTF-8:
AddDefaultCharset utf-8
В списке рассылки mason-users был вопрос об обработке UTF-8 для
- вывод компонентов с UTF-8
- обработка аргументов UTF-8 GET/POST
Вот ответ Джона:
Я бы хотел, чтобы Мейсон разумно занимался кодированием, но поскольку я не регулярно работаю с utf8, вам и другим придется помочь мне с дизайном.
Это, вероятно, должно быть в плагине, например, Mason::Plugin::UTF8.
Так что для вещей, которые вы особенно упоминаете, что-то вроде этого может работать:
package Mason::Plugin::UTF8;
use Moose;
with 'Mason::Plugin';
1;
package Mason::Plugin::UTF8::Request;
use Mason::PluginRole;
use Encode;
# Encode all output in utf8 - ** only works with Mason 2.13 and beyond **
#
after 'process_output' => sub {
my ($self, $outref) = @_;
$$outref = encode_utf8( $$outref );
};
# Decode all parameters as utf8
#
around 'run' => sub {
my $orig = shift;
my $self = shift;
my %params = @_;
while (my ($key, $value) = each(%params)) {
$value = decode_utf8($value);
}
$self->$orig(%params);
}
1;
Вероятно, было бы лучше, если бы вы или кто-то еще знал о проблемах utf8, создал этот плагин, а не я. Но дайте мне знать, если в ядре масона есть вещи, необходимые для облегчения этого процесса.
ИМХО, нужно добавить и следующее, для добавления "use utf8;" в каждый компонент.
package Mason::Plugin::UTF8::Compilation;
use Mason::PluginRole;
override 'output_class_header' => sub {
return(super() . 'use utf8;');
};
1;