samedi 29 septembre 2007

Internationalisation, i18n, unicode et utf8

Aujourd'hui c'est le week end et j'oublie un peu le code. J'ai donc choisi de vous parler de l'internationalisation et de la saine gestion des caractères exotiques. Ceci afin de pouvoir créer des sites qui acceptent et qui affichent n'importe quel caractère. Vous permettrez ainsi à vos visiteurs suédois, polonais, irakiens, tchèques ou japonais de sentir que votre site à été conçu aussi pour eux. Pour vos tests, vous pouvez vous référer à cette page. Pour clarifier les choses, commençons par quelques définitions.

Unicode définit de manière globale ce qu'est un caractère. Unicode définit un numéro, appellé codepoint pour chaque caractère possible sur la planète terre. Vous pouvez voir la liste de ces caractères ici. Par exemple le caractère 'k' est au codepoint 'U+006B' . Le caractère khmer 'dap roc' est situé au codepoint 'U+19FA' . Unicode est donc une manière globale de traiter les caractères de manière abstraite. Depuis perl 5.8, perl est compatible avec unicode. Ainsi pour faire faire une chaine composée des deux caractères cités en exemple, on peut utiliser la notation suivante:

  1. my $kkhmer = "\x{006B}\x{19FA}"
C'est d'ailleurs un moyen pour écrire du code "obsfuscated". Cette chaine kkhmer est donc de longueur 2 en perl. Il existe aussi un moyen d'écrire des caractères unicode sous forme d'entités html: &006B;&19FA; . Le module HTML::Entities vous permet d'automatiser cette transformation, et mason fournit un mécanisme pour la faire automatiquement.

UTF-8 est un encodage. C'est donc un moyen de représenter un caractère sous une forme binaire pour le stockage dans un fichier, la transmission à travers un réseau ou le stockage dans une base de données. Contrairement à l'encodage ISO-8859-1 (plus connu sous le nom latin 1), UTF-8 est capable d'encoder tout les caractères unicodes. Sans rentrer dans les détails, utf-8 encode chaque caractère sur 1 ou plusieurs octets. Beaucoup de problèmes d'encodage proviennent de la confusion entre la notion de chaine unicode et de chaine binaire d'octets encodant ces chaines unicodes. Pour reprendre notre exemple, le caractère 'k' est représenté en UTF-8 par l'octet '6B' et le caractère khmer est représenté par les 3 octets 'xE1xA7xBA' .

Beaucoup de confusions proviennent aussi du fait que pour les caractères purement ascii (comme dans le bon vieux temps), le codepoint unicode, l'encodage UTF-8 et l'encodage latin1 sont identiques. C'est pourquoi on ne se rend compte de ce genre de problème que lorsqu'il commence à y avoir des accents dans le système.

Cas pratique.

Après ces quelques clarifications, construire un système compatible avec les caractères internationaux est beaucoup plus facile. Il suffit de cloisonner les responsabilités et d'assurer que la communication entre les composants du système se fait dans le bon encodage. Pour un exemple concret, nous allons prendre 3 composants: le navigateur web, perl (sous apache) et mysql. Pour chacun de ces composants, on va définir (et assurer) trois choses: l'encodage de sortie, l'encodage d'entrée et la représentation interne.

  • Le navigateur:
    • Encodage d'entrée: Défini à la fois par l'entête HTTP 'Content-Type: text/html; charset=UTF-8' et éventuellement par la balise méta HTML : <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> . Chaque navigateur à un comportement spécifique lorsque ces deux informations ne sont pas identiques. Assurez vous donc qu'elle le sont. Ajoutez la directive AddDefaultCharset UTF-8 Dans votre config d'apache.
    • Encodage de sortie (POST de formulaire ou GET vers le serveur): par défaut c'est le même que l'encodage d'entrée. Il est cependant bon de le spécifier dans chaque formulaire, au cas ou des utilisateurs changerais l'encodage de la page avant d'envoyer leurs informations. Dans chaque formulaire, ajoutez simplement l'attribut accept-charset="UTF-8" .
    • Encodage interne: Normalement, on n'a pas à connaître cette information. Chaque système à fait un choix. L'important c'est que le système puisse traiter des chaines unicode. C'est le cas des navigateurs et de javascript.
  • Perl:
    • Encodage d'entrée (web): Par défaut rien n'est défini. C'est donc des chaines binaires que vous récupérez. Si vous faite du cgi, spécifiez l'encodage au niveau de l'objet $c avec $c->charset('utf-8'). Les appels à $c->param() vous donnerons des chaines perl correctes (unicode). En mason, le problème est identique. J'utilise personnellement la fonction Encode::decode_utf8 sur chaque paramêtre que je sais pouvoir être du texte libre.
    • Encodage de sortie: Par défaut perl encode les chaines en latin1 en sortie. On peur modifier ce comportement avec 'binmode STDOUT , ':utf8' en CGI. En mason, on peut ajouter un filtre à la sortie en ajoutant la directive suivante dans apache: PerlAddVar MasonOutMethod "sub{ binmode STDOUT , ':utf8' ; print @_ ;}"
    • Encodage interne: Comme je l'ai dit précédemment, on ne devrait pas connaitre cette information. Cependant en perl, les chaines unicode internes sont encodées en UTF-8. C'est aussi une source de confusion, puisque ça amène a penser qu'on peut outputer directement une chaine interne. Ce n'est pas le cas (voir point précédent).
  • MySQL:
    • Encodage d'entrée: Aucun. En utilisation avec perl, ce n'est pas génant, étant donné que les chaines internes sont représentées en UTF-8. Lorsqu'on fait un insert ou un update avec le driver MySQL, c'est donc des octets UTF-8 qui sont envoyés et stockés par MySQL.
    • Encodage de sortie: Aucun. C'est donc des octets qu'on obtient par défault lorsqu'on fait un select avec le driver standard. Pour eviter de les décoder à chaque fois et pour obtenir des vraies chaines perl UTF-8, j'ai écrit un patch pour le driver disponible ici. Ce patch a été inclu dans la version officielle du driver à titre expérimental. Activez le décodage UTF-8 automatique comme ceci: $dbh->{'mysql_enable_utf8} = 1
Conclusion:

Grace à ces quelques techniques, vous êtes maintenant en mesure de construire un site compatible avec tout les caractères de la planète. Dans notre cas pratique, on voit que les choses sont nettes concernant le navigateur et perl. MySQL doit certainement être encore amélioré sur ce point. Pour d'autres bases de données, les choses sont plus claires. Par exemple en Postgresql, si on déclare une colonne comme UTF-8, la communication avec perl à travers le driver se fait de manière transparente.

vendredi 28 septembre 2007

Makefile: Modifier une variable pour une cible specifique

Pour modifier une variable pour une cible specifique, on utilise la forme suivante:
  1. cible: <assignement>
  2. cible: dependance
  3. etc..
Par exemple:
  1. prog : CFLAGS = -g
  2. prog : prog.o foo.o bar.o
Dans ce cas, la valeur de CFLAGS sera -g dans le contexte des dependances de prog.

jeudi 27 septembre 2007

Pages et composants mason

En mason, il n'existe pas de difference entre la notion de page et de composant. Une page est simplement un composant qu'on utilise en bout de chaine de traitement mason pour le rendu du code html. Structurellement, un composant est compose d'html, de directives mason, de divers blocs de code perl et eventuellement de methodes et de sous composants. On nomme generalement les composants qui ne sont pas de pages en .mas. Par exemple, voici un composant qui rend la date courante:

  1. % # date.mas
  2. % my $when = gmtime() ;
  3. <p>Page rendue a <% $when %></p>
On inclut ensuite ce composant dans la page index.html ( voir post précèdent ) de la façon suivante:
  1. ...
  2. <& /time.mas &>
  3. ...
Si vous placez ce composant a la racine du serveur, n'importe qui pourrais recuperer votre code a l'adresse http://www.foobar.com/time.mas . Pour eviter cette situation desagreable, l'usage preconise de stocker dans deux repertoires distincts les pages proprement dites et les composants qui ne servent jamais directement comme page. Par exemple, on cree un repertoire masoncomp au meme niveau que htdocs et on ajoute ce repertoire ou mason cherche les composants (dans la conf d'apache):
  1. PerlAddVar MasonCompRoot "components => /var/www/masoncomp/"
On peut alors déplacer le composant time.mas de htdocs vers masoncomp. Pour l'inclure, on utilise toujours '/time.mas' . C'est en quelque sorte son adresse dans l'espace des composants mason. La prochaine fois, je parlerais de composition de page par héritage. En attendant, vous pouvez trouver plus d'information sur les composants ici.

mercredi 26 septembre 2007

Text::Scan et fin de fichier

Si vous utilisez Text::Scan pour scanner des documents contre un dictionnaire et que votre document provient d'un fichier, il faut faire un chomp de $document. En effet Text::Scan considère que la fin de chaîne est l'octet x00 et non pas l'octet x0A qui est la fin de fichier texte.

mardi 25 septembre 2007

Echappement de code html dans les expressions

En general, on utilise la balise <% $expression %> pour afficher une valeur provenant d'une entrée utilisateur, ou bien d'une base de donnée. Si la valeur de $expression contient du code html, cela peut casser votre mise en page ou pire introduire une faille de sécurité XSS. Pour éviter cela, on peut utiliser le module perl HTML::Entities (fonction encode) pour toute les expressions affichées dans la page. Mason offre un mécanisme plus simple pour éviter cette situation. Si vous réglez la variable suivante dans votre config d'apache:

  1. PerlAddVar MasonDefaultEscapeFlags h
Mason protégera tout les caractères nécessaires dans vos expressions en les remplaçant par des entités html. Je vous conseille aussi de regler ce flag d'echappement (h) pour qu'il provoque seulement l'echappement des caracteres purement html. De cette facon, votre code fonctionnera avec tout les character set:
  1. PerlSetVar MasonEscapeFlags "h => \&HTML::Mason::Escapes::basic_html_escape"
Plus d'infos ici (en anglais) Je reviendrais sur les problèmes d'encoding dans un prochain post.

Afficher du code dans blogger

Une astuce pour afficher du code dans blogger sans (trop) se faire mal au cerveau.

  • Pour vos lignes de codes, utilisez le format liste numérotée.
  • Ajoutez un morceau de css dans votre template pour les afficher
Ça donne ça:
  1. .post ol{
  2. white-space: pre ;
  3. font-family: courier;
  4. margin: 0px;
  5. margin-top: 5px;
  6. margin-bottom: 5px;
  7. padding: 10px;
  8. background-color: lightgray;
  9. line-height: 1em;
  10. }
Avantage: les lignes sont numérotées et on peut s'y référer facilement. Inconvénient: On perd l'usage des listes numérotées (franchement, c'est important ?) Ça ne résout cependant pas un problème, c'est l'affichage des &lt; qu'on est oblige d'encoder a la main en &amp;lt; .

Configuration minimale et test avec apache et mod_perl

Une fois apache2 installé, ainsi que mod_perl2 et apreq2 installes, vous devriez avoir dans vos fichiers de conf apache les éléments suivants:

  1. LoadModule perl_module modules/mod_perl.so
  2. LoadModule apreq_module /usr/lib/httpd/modules/mod_apreq2.so
Voyez les liens de references pour l'installation de ces composants. Assurez vous que apache démarre correctement avec ces modules. L'installation de mason se fait de maniere tout a fait classique via cpan. En root, lancez cpan -i HTML::Mason . On peut maintenant passer a la configuration de mason proprement dite dans apache. On charge tout d'abord le handler mason. C'est un handler mod_perl inclu dans mason configurew pour fonctionner comme point d'entree de votre application. On peut noter que l'utilisation de mason ne necessiste aucune connaissance en mod_perl (a part son installation). Toujours dans apache donc:
  1. # Load mason
  2. PerlModule HTML::Mason::ApacheHandler
Ensuite, on doit specifier a mason ou il va trouver les pages a rendre:
  1. PerlAddVar MasonCompRoot "main => /var/www/htdocs/"
Je vous conseille de régler cette variable a la meme valeur que le DocumentRoot de apache pour éviter les confusions. Ensuite, on doit choisir quelles ressources vont etre servies a travers mason. Ceci se fait logiquement au niveau de l'espace des urls. Pour faire simple, nous allons choisir une extension de fichier qui sera toujours servie a travers mason. On peut choisir .html pour rester classique, .mas si vous voulez montrer que vous utilisez mason, .php pour faire comme tout le monde, .jsp pour vendre le produit plus cher ou .axa si vous bossez pour axa. Ici par exemple, on choisi de servir toute les ressources .html au travers de mason:
  1. # Let mason handle html files
  2. <locationmatch>
  3. SetHandler perl-script
  4. PerlHandler HTML::Mason::ApacheHandler
  5. </locationmatch>
Ceci nous laisse la possibilité de faire des pages .htm qui seront elles vraiment statiques si besoin est. Votre première page mason est maintenant a portée de main. Creez un fichier index.html dans votre DocumentRoot, et admirez le resultat.
  1. <html>
  2. <head><body>1 + 1 = <% 1 + 1 %></body>
  3. </html>
Comme vous l'aurez note , la balise <% %> provoque l'évaluation et l'affichage de l'expression perl a l'intérieur. Il y a plusieurs façons d'inclure du code perl a l'intérieur d'une page mason. Pour les détails, c'est par ici(anglais). Voila, c'est tout pour ce billet. La prochaine fois je vous parlerais de composition des pages, de composants et d'héritage.

Introduction a mason.

Je commence ce blog pour pallier au manque d'informations sur mason sur le web français, comme en témoigne cette recherche google. Mason est a la base un moteur de template écrit en perl qui n'a en théorie rien a voir avec le web. On peut d'ailleurs l'utiliser en dehors d'un environnement web pour produire des mails ou encore vos fiches de cuisines préférées en postscript - si on est un vrai geek . On peut donc l'utiliser en environnement perl, apache+cgi , ou encore apache+mod_perl . Au niveau maturite et stabilite, mason n'as rien a envier aux technologies concurrentes; il est utilise pour les pages de amazon et pour de nombreux autre sites a fort trafic. Sur ce blog je vais donc parler principalement de mason dans un environnement web. Le tout sur un mode didactique.