lancer gunicorn avec supervisord

Après avoir mis en place une méthode pour lancer gunicorn avec runit, voici un second article pour lancer ce même gunicorn (qui poutre du poney, rappelons-le), mais avec un outil que je trouve à l’utilisation bien plus pratique : supervisord

Pourquoi supervisord et pas runit ?

Après l’avoir utilisé sans aucun soucis depuis quelques temps sur le serveur de secours de notre site web, je trouve que supervisord est plus convivial à utiliser, et plus « clair » à mettre en place.

Plus convivial à utiliser parce qu’il a un client CLI qui permet d’interagir directement avec les processus monitorés, mais il y a aussi les mêmes fonctionnalités par le biais d’une interface web fort pratique.

Plus « clair » à mettre en place parce qu’il se lance grâce à un simple script d’init, et qu’on peut faire la configuration de tous ses processus à monitorer dans un seul et même fichier de configuration, au lieu d’avoir un répertoire et des liens symboliques à gérer (certains préfèrent peut-être cette modularité, mais personnellement je trouve ça beaucoup plus pénible à maintenir).

Installer supervisord

Etant donné que supervisord est en python, il est possible de l’installer avec un simple

$ pip install supervisor

Pour les old-school qui ne profitent pas encore de la puissance et de l’ergonomie de ce magnifique outil de Ian Bicking, il est possible de remplacer pip par easy_install (même si je vous conseille plutôt de faire un easy_install pip puis de vous passer de easy_install par la suite!).

Configurer supervisord

On le configure en deux temps : une première configuration de supervisord lui-même, puis l’ajout des processus qu’on veut qu’il monitore.

Dans le fichier /etc/supervisord.conf :

; configuration de supervisord lui-meme
[unix_http_server]
file=/tmp/supervisor.sock   ; chemin vers le fichier socket

[inet_http_server]
port=192.168.0.21:9001      ; adresse ip _LOCALE_ de la machine pour la connection web

[supervisord]
logfile=/var/log/supervisord.log ; fichier de log principal de supervisord

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; connection pour le client CLI

; liste des processus qu'on veut confier a supervisord
[program:gu-www]
command=/path/to/venv/bin/python /path/to/venv/bin/gunicorn_django -b localhost:8080 --log-file=/path/to/log/gunicorn_gu-www.log --workers=3
directory=/folder/containing/settings/file/
user=www-data
autostart=true
autorestart=true
startsecs=10
redirect_stderr=true
stdout_logfile=/path/to/log/supervisor_gu-www.log

Bien entendu, faites en sortes que l’utilisateur www-data ai accès en écriture aux fichiers de log gunicorn_gu-www.log configuré où le processus échouera lors de son lancement.

ATTENTION: comme indiqué dans le commentaire pour la ligne de configuration inet_http_server il faut absolument faire en sorte que l’adresse ne soit pas accessible à tout le monde! En effet il est possible de redémarrer ou tout simplement stopper les processus par cette interface! Vous pouvez désactiver complètement cette possibilité en supprimant ces deux lignes, il n’y aura alors pas de moyen de contrôler supervisord par une page web.

La commande indiquée pour information lance gunicorn_django avec dans un environnement virtuel (ce que vous devriez faire vous aussi!). Il n’est pas nécessaire d’indiquer le chemin vers le fichier settings.py dans les options de gunicorn_django étant donné que le processus sera lancé directement dans le répertoire le contenant (si vous le configurez correctement avec le paramètre directory comme indiqué ci-dessus).

ATTENTION: il faut absolument que gunicorn_django soit lancé en foreground (en tâche principale, pas en arrière-plan/background/démon/daemon), ce qui est le cas par défaut si vous n’avez pas explicitement dit le contraire dans votre fichier de configuration (ou en paramètre de la commande) de gunicorn_django.
En effet c’est supervisord qui se charge de le faire : si le processus est lancé en arrière-plan, supervisord ne peut pas prendre la main dessus et le contrôler ni le monitorer, et va essayer de le lancer plusieurs fois d’affilée pensant qu’il échoue à chaque fois.

Lancer supervisord et le relancer à chaque reboot

Il suffit pour cela de créer un script de démarrage dans /etc/init.d/ et de le faire se  lancer à chaque démarrage.

Dans /etc/init.d/supervisord :

#! /bin/sh
### BEGIN INIT INFO
# Provides:          supervisord
# Required-Start:    $remote_fs
# Required-Stop:     $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Example initscript
# Description:       This file should be used to construct scripts to be
#                    placed in /etc/init.d.
### END INIT INFO

# Author: Dan MacKinlay
# Based on instructions by Bertrand Mathieu
# http://zebert.blogspot.com/2009/05/installing-django-solr-varnish-and.html

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
DESC="supervisord"
NAME=supervisord
DAEMON=supervisord
MANAGE=supervisorctl
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

case "$1" in
  start)
    $DAEMON
        ;;
  stop)
    $MANAGE shutdown
        ;;
  reload|force-reload)
    $MANAGE reload
    ;;
  restart)
    $MANAGE restart
        ;;
  *)
        echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
        exit 3
        ;;
esac

Ce script contient des commentaires en début de fichier qui permettent de le faire se lancer automatiquement à chaque démarrage :

$ chmod a+x /etc/init.d/supervisord
$ update-rc.d supervisord defaults

Cette commande devrait indiquer qu’elle a créé tous les liens symbolique nécessaire pour lancer ce script à chaque démarrage du serveur.
Il ne reste plus qu’à lancer supervisord maintenant et vérifier que notre site est accessible :

$ /etc/init.d/supervisord start

Monitorer et contrôler ses processus

Soit en utilisant le client CLI :

$ supervisorctl

Soit en accédant à la page web dont on a configuré l’adresse dans le paramètre inet_http_server dans le fichier de configuration de supervisord. De là vous pouvez redémarrer vos processus, les arrêter, visualiser le contenu du fichier de log…

Un soucis?

  • Pensez à regarder dans les fichiers de log indiqués dans les paramètres pour voir si il y a des indications sur le problème
  • Vérifiez que la commande configuré dans /etc/supervisord.conf se lance correctement manuellement, et que le processus est bien en foreground
  • Faites un tour sur l’excellente documentation du projet supervisord

PyCon.fr 2010 : retour sur une conférence organisée par l’AFPY

30/08/2010 admin Comments off

Cette édition de PyCon.fr était la deuxième à laquelle j’ai assisté. Elle s’est déroulée, comme l’année dernière, à la CyberBase de la Cité des Sciences à la Villette, Paris.

Et comme l’année dernière, j’ai apporté ma maigre contribution à l’organisation sur place, aux côté des super-motivés de l’Association Francophone PYthon (liste non exhaustive : Christophe Combelles, Michael Scherrer, Gaël Pasgrimaud, Jean-Philippe Camguilhem, Olivier Grisel).

Le cadre

Nous avons été encore une fois super bien accueillis par la CyberBase. A disposition, un amphitéatre et une « base technique » (salle équipée de nombreux ordinateurs, idéale pour les mises en applications et les travaux pratiques). Les deux salles ont tout l’équipement nécessaire pour la rétro-projection des présentations, et avaient cette année encore la couverture vidéo de Ubicast.

Les sponsors

Vous trouverez la liste des généreux sponsors sur le site de PyCon.fr 2010. C’est en très grande partie grâce à eux qu’une telle conférence peut être organisée, en restant gratuite, j’en profite donc pour les remercier à nouveau (et les encourager à recommencer l’année prochaine bien évidemment ;) .

Nous avons entre autres eu des présentations de plusieurs produits libres et open-source des sponsors Logilab (avec leur solution Cubic Web qui fait tourner le site de pycon.fr) et Itaapy.

Les présentations

Vous trouverez là aussi la liste sur le site de pycon.fr, et je vais lister ici les présentations auxquelles j’ai pu assister et qui m’ont marqué (les autres je les verrais en vidéo, grâce à Ubicast, dès qu’elles seront en ligne) :

  • plusieurs lightning talks, en particulier sur Pyaler, le GSoC sur distutils 2 (vivement que ça sorte!), pysandbox, restkit
  • Optimisation d’applications Django de Bruno Renié (mon co-champion « planet » pour django-fr ;) : plein d’astuces pour limiter le nombre de requêtes, utiliser le cache de manière intelligente …
  • MongoKit de Nicolas Clairon : comment utiliser une base NoSQL MongoDB facilement et efficacement avec MongoKit
  • Analyse statistique et classification automatique de texte de Olivier Grisel : ça m’a replongé dans mon mémoire de DEA sur l’apprentissage automatique ;)
  • l’incontournabl)e Gunicorn de Benoit Chesneau : à mon avis, l’un des plus grands pas en avant pour rendre python (et Django) très facilement déployable
  • Construire un CMS sur mesure avec Django de Yann Malet : il faut arriver à vendre non pas du forfait (trop risqué pour le développeur, trop cher pour le client, et risque d’effet tunnel), mais des cycles de développement (par exemple de deux semaines, avec review + paiement à chaque cycle, puis décision sur le cycle suivant). Voilà quelque chose qui me plait, mais qui est très difficile à vendre à des clients qui veulent surtout pouvoir chiffrer de manière très précise leur budget
  • Programmation en Flux de Jonathan Schemoul : présentation de PyF qui permet de traiter de très nombreuses données en limitant la mémoire utilisée (grâce aux « flux »). Très intéressant, et un outil graphique en ligne qui m’a l’air très simple et pratique d’utilisation

Les rencontres

J’ai pu revoir nombre de personnes déjà rencontrées à l’édition de l’année précédent de PyCon.fr, ainsi qu’à Djangocong, personnes que je connaissais déjà auparavant par internet (irc, twitter, mailing lists de l’afpy et de django-fr …). Revoir « en vrai » des personnes qu’on côtoie très souvent de manière électronique est pour moi toujours une joie et un enrichissement, car l’échange est bien plus facile, et on tisse des liens bien plus étroits autour d’une bière que par internet!

Et comme je l’ai déjà twitté, j’ai par ailleurs été ravi de faire de nouvelles connaissances, que j’espère pouvoir revoir bientôt (un an entre chaque pycon.fr, ça fait long!). Il paraît qu’il devrait y avoir d’ici peu un autre afpyro national organisé, il faudra essayer de rassembler les troupes sur Sophia/Nice à cette occasion! Peut-être une occasion de tester le pastis magique de Jean-Philippe (il m’a promis qu’il ferait un article là-dessus, que je lierai ici ;)

Les bonus

Comme si cela ne suffisait pas, il y a eu quelques bonus très sympathiques :

  • un écran géant et tactile dans la CyberBase qui affichait en boucle isparade.jp sur le mot clé #pyconfr
  • j’ai découvert aspirator de Bruno Renié, un news reader fait en django, et qui à l’air vraiment génial… ce gars est vraiment doué, ça en est limite énervant ;) . Il travaille aussi sur wombat, un projet de client mail
  • j’ai revu une bonne partie de mon ex équipe de badminton qui prenait le même avion que moi! (ils étaient aux championnats du monde qui avait lieu ce même week-end sur Paris)
  • Alexis Métaireau (@ametaireau) nous a parlé de son travail pour le GSoC avec Tarek Ziadé sur distutils 2 : vivement que ce soit dans la stdlib!
  • Rémi Ubscher (@natim) est Eclaireur lui aussi! Salut Fennec ;)
  • Benoit Calvez (@_dzen) m’a parlé de fabulator et de vineolia : énorme potentiel (et il a d’autres projets dans les cartons ;)

Conclusion

Vivement la prochaine édition (peut-être sur Pau? ping Jean-Philippe ;) et d’ici là, peut-être une nouvelle édition d’une rencontre Django en france!

Encore un énorme merci aux organisateurs, sponsors, présentateurs, et aux participants, qui font de cet évènement un moment agréable et enrichissant!

Categories: Python, django Tags: , ,

djangocong : rencontre Django à Marseille

28/04/2010 admin 3 commentaires

Comment décrire en un mot cette rencontre Django à Marseille le 24 et 25 Avril :

Excellent !

Après cette entrée en matière fort peu cavalière, permettez-moi de développer en deux points :

Ce que j’ai retenu de ma conférence

Ma présentation portait sur toutes les astuces et raccourcis à la disposition des développeurs web, et s’intitulait Django Pour Les Fainéants.

Les points positifs :

  1. le format « interactif » : faire participer le public le plus possible permet de le garder attentif, éveillé et intéressé
  2. les « bons points » : un autre moyen de captiver l’attention, en récompensant par un bon point (humoristique) les participants
    • bon point « glue » : le participant m’a posé une colle, à laquelle je ne sais répondre
    • bon point « fainéant » : le participant a proposé une astuce/raccourcis/outil que je ne connaissais pas, ou n’ai pas présenté dans la présentation
    • bon point « django pony » : le participant a partagé une bonne pratique django, ou parlé d’une astuce/raccourci/outil que je présentais dans une diapo suivante
    • bon point « casse nouilles » : donné à un complice, ou quelqu’un dont on est sûr qu’il le prendra à la rigolade, et qui a fait une remarque sur une typo, une parenthèse manquante…
  3. les diapos « concises » ne contenant pas de phrases, mais des mots clé ou listes de points : permet de garder l’attention du public sur ce que l’on dit, en ne se servant des diapos que comme un support visuel (pour pointer du doigt)

Les points négatifs :

  1. le format « interactif » : difficulté à prévoir le temps de parole pour la présentation. Si il y a beaucoup de participation, le temps de parole prévu empiètera sur le temps consacré (25mn de présentation + 10mn de participation > 30mn). A contrario, si il n’y a pas assez (pas du tout!) de participation, le temps de parole prévu sera trop court (25mn < 30mn).
  2. le format « interactif » et les « bons points » : applicable pour des salles plus grandes, avec un public plus nombreux? Avec 50 personnes, je me demande si on atteignait pas la limite pour ce genre de format…
  3. les diapos « concises » l’étaient peut-être trop : rajouter des photos comme par exemple pour les présentations de Olivier Meunier pour que ce soit plus agréable visuellement (ou comme pour celles de Cyril Baÿ, private joke inside ;)

Dans l’ensemble, je pense que la présentation s’est bien passée, et a été bien reçue d’après les retours que j’en ai eu (merci à tous ceux qui sont venus me complimenter, ça fait chaud au coeur!). N’hésitez pas à me faire vos retours (positifs ET négatifs) dans les commentaires!

Ce que j’ai retenu des autres conférences

Il y a déjà plusieurs comptes rendus très complets sur la toile (liste exhaustive faite par Nicolas Ferrari sur le blog d’AlwaysData), je vais donc essayer de résumer ce que j’ai retenu de chaque présentation :

  1. Django 1.2 : un point de nouveautés, par Nicolas Ferrari : lire les releases notes n’est pas suffisant! pour connaître toutes les excellentes améliorations de la 1.2, il faut soit lire la conf de Nicolas, soit suivre DjangoAdvent ;)
  2. Boîte à outils Django, par Eric Veiras Galisson : de multiples outils pour vraiment être un fainéant (cf ma présentation) et se simplifier la vie !
  3. Cours de géo, par Samuel Adam : c’est pas si compliqué finalement, et LA TERRE N’EST PAS RONDE! présentation très claire sur les outils disponibles et leur puissance
  4. Une authentification pour les « contrôler » tous, par Olivier Meunier : une « fail story », au moins aussi importante que la « success story » aussi présentée, sur comment adapter l’authentification django à sa sauce
  5. Tester son projet Django, par Bruno Renié : on ne le dit jamais assez, il faut tester ses applications. Non seulement un garde-fou, c’est aussi un gage de la qualité du code final! (et chapeau pour la présentation faite au pied levé pour remplacer un conférencier qui n’a pu venir, et ce moins de trois jours avant la conférence!)
  6. Les dessous d’alwaysdata, par Cyril : on a pas vu beaucoup de dessous, mais on en a appris énormément sur AlwaysData, leur sérieux, leur compétence, et leur boulot au quotidien pour nous simplifier la vie (et nous permettre d’être fainéants!)
  7. Django pour les développeurs Symfony, par Nicolas Perriault : superbe présentation, donnée de main de maître (et hilarante), qui donne un regard nouveau sur ce qu’il est possible de faire avec un langage comme PHP. Au final, Symfony à l’air d’être une bonne alternative à Django quand le client ne veut pas entendre parler d’autre chose que PHP.
  8. Beer over IP, par Bruno Bord : retour d’expérience sur un site reconnu (par moi en tout cas ;) d’utilité publique.
  9. Django et XMPP, par Jean-Michel Armand : quand utiliser XMPP, et surtout quand ne pas l’utiliser! Un retour d’expérience donné par notre cher Jean-Michel préféré!
  10. Gunicorn, Django et WSGI, par Benoît Chesneau : présentation d’un outil que j’utilise en production depuis bientôt un mois sans jamais avoir eu le moindre soucis.
  11. Des lapins et des poneys, quand AMPQ rencontre Django, par Olivier Meunier : explication d’une mise en oeuvre de AMPQ comme moyen de communication entre différents processus, très instructif
  12. Introduction à Pinax, par David Paccoud : boîte à outil pleine d’applications réutilisables qui permet d’arriver à un produit fini avec moins d’efforts et plus rapidement. La question étant, comment faire la part entre un projet « fait main » qu’on maîtrise, et une boîte à outil qu’on ne maîtrise pas forcément. Qu’en est-il de la courbe d’apprentissage?
  13. « ‘Tain cong’, Django speaks marseillais« , par Stéphane Raimbault : il y a plusieurs formes plurielles dans certains langages (Polonais par exemple), et beaucoup plus de spécificités que je ne m’en doutais! Il va falloir que je reprenne ma copie…
  14. Internationalisation de contenus avec Django, par Benoît Bryon : présentation très claire des limites (et de certaines solutions) de Django pour tout ce qui à trait à la traduction et localisation de sites
  15. CouchDB et Django, l’utilisation de CouchDBKit, par Benoît Chesneau : pas forcément évident à prendre en main, CouchDB est sensé être le choix incontournable quand on tient à ses données (multiples mécanismes comme l’écriture régulière sur le disque pour éviter les pertes de données lors d’un crash)
  16. Les limites de Django, par David Larlet : qui n’a pas été confronté à certaines de ces limites? Un regard réaliste sur ce qu’on ne peut tout simplement pas faire (à l’heure actuelle) avec Django.

Ce que j’ai retenu de l’orga

Un immense merci à David et Jean-Michel, sans qui nous n’aurions tout simplement pas eu cette occasion de se rencontrer entre passionnés de Django (et d’autres! dédicace à Jérémy Lecour), et de voir en chair et en os des personnes (oserai-je dire amis?) qu’on cotoie parfois tous les jours sur internet, certains depuis des années! Merci aussi à Johan Charpentier pour l’impression des TShirts!

J’ai été vraiment impressionné par la ponctualité (malgré les aléas de la restauration et ses délais imprévus), le contenu très fourni et divers de toutes les présentations, le professionnalisme et en même temps l’accessibilité de chacun. Pouvoir discuter et échanger des expériences avec des collègues (comme on dit dans le sud ;) est vraiment enrichissant.

Plus que tout, je tiens encore une fois à remercier Jean-Michel, qui s’est occupé d’une (très) grande partie de la logistique, de nous avoir trouvé les salles, chouchoutés (combien d’aller/retours il a fait pour remplir la cafetière? et de trajets en voiture pour amener/chercher des gens au métro/restau?), et de s’être autant appliqué à nous apporter tout le confort possible.

Vivement la prochaine rencontre!

Categories: django Tags:

MySQL, mysqldump et PHP : convertir de latin1 vers utf8

08/03/2010 admin Comments off

Cet article à pour but de vous éviter, à vous lecteur, de vivre la perte de neurones (et le gain de cheveux blancs) que j’ai subit dernièrement, à investiguer des soucis de charset dans une base de donnée MySQL (et l’affichage sur une page web par le biais de PHP).

Je tiens à préciser que je ne suis pas un expert MySQL, et encore moins un expert en encoding, et certaines définitions ou mots utilisés dans cet article peuvent ne pas être utilisés à bon escient. Le fond et la méthode présentée ont par contre été vérifiés et testés!

Introduction à l’encoding

Je ne parlerais pas ici de ASCII ou Unicode (normes utilisées pour stocker les données), mais du jeu de caractères utilisé pour encoder ces données (et les afficher de manière lisible pour un être humain). Pour commencer, quelques définitions:

  1. encoding =  character set = charset : jeu de caractères utilisé pour représenter des données
  2. utf8 = UTF-8 : un encoding qui associe un caractère à chaque « codepoint » Unicode (particularité: tous les caractères hors latin1 sont stockés sur deux octets)
  3. latin1 = latin-1 = ISO-8859-1 : un encoding qui associe un caractère à chaque octet de la table ASCII

Pour résumer, chaque caractère peut être stocké sur le disque en Unicode (ou en ASCII, beaucoup plus limité). Il est ensuite encodé (traduit, représenté) avec un jeu de caractères pour être affichable et lisible par un être humain.

Historiquement, pour les pays occidentaux, l’encodage était fait en latin1 (caractères latins avec ses accentuations). De nos jours, de plus en plus d’applications se tournent vers Unicode et l’encodage en UTF-8 qui permet de représenter l’ensemble des caractères utilisés universellement (il n’est donc pas limité aux caractères latins, mais inclut par exemple les caractères cyrilliques, chinois…).

Charset utilisé par les tables et les champs

Pour consulter l’encodage utilisé par défaut pour une table ou un champ particulier :

mysql> SHOW CREATE TABLE bar;
+-------+-------------------------------+
| Table | Create Table                  |
+-------+-------------------------------+
| bar   | CREATE TABLE `bar` (
  `id` int(11) default NULL,
  `firstname` char(20) default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 |
+-------+-------------------------------+
1 row in set (0.00 sec)

ATTENTION : les charset définits au niveau de la base de donnée, de la table et du champ sont des « default charset ». Il est tout à fait possible d’avoir une table avec un champ dont le contenu est en latin1, puis changer le DEFAULT CHARACTER SET à utf8 pour ce champ. Toutes les données existantes seront toujours en latin1, par contre toutes les nouvelles données entrées en utf8 seront en utf8. On est alors confronté au pire des problèmes : des charsets différents au sein d’une table pour un même champ.

Charset utilisé par le serveur, la database, les tables, les champs, le client, la connexion, les résultats…

Les encodages utilisés par le client, la connexion, le serveur, et l’affichage des résultats sont consultable par la commande suivante:

mysql> SHOW VARIABLES WHERE variable_name like 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | utf8                       |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

Dans cet exemple, le client, la connexion, la database et les résultats sont tous en utf8. Il n’y a que le serveur lui-même qui est en latin1 par défaut. Pour configurer le client, la connexion et les résultats, on peut soit utiliser la commande

mysql> SET NAMES utf8;

Soit configurer les variables décrites dans le paragraphe suivant :

Les variables de configuration

Elles peuvent être définies au niveau du fichier /etc/mysql/my.cnf (peut être situé à un autre endroit selon la distribution):

[mysqld]
default-character-set=utf8
character-set-server=utf8
collation-server=utf8_general_ci # utilisé pour les comparaisons

[mysqldump]
default-character-set=utf8

[mysql]
default-character-set=utf8

L’encoding du terminal: attention au piège!

Quels que soit les charsets (par défaut) que vous avez configurés, il faut savoir que c’est le charset du terminal (si vous êtes en ligne de commande sur mysql par exemple) qui sera utilisé lors d’un UPDATE ou INSERT dans une table.

Ainsi, même si vous avez tout configuré (y compris le SET NAMES) pour être en latin1, lors d’une insertion dans une table, si votre terminal est en utf8 la donnée sera stockée en utf8.

Reportez-vous à cette astuce pour tester le charset (latin1 ou utf8) de votre terminal. Il faut ABSOLUMENT que votre client ai le même encoding que votre terminal pour éviter les conflits (à partir de la page 21).

Détecter le charset utilisé pour un champ

Commençons par une astuce pour différencier une donnée stockée en utf8 de latin1 :

mysql> select firstname, length(firstname) from bar;
+-----------+-------------------+
| firstname | length(firstname) |
+-----------+-------------------+
| dédé    |                 6 |
+-----------+-------------------+
1 row in set (0.00 sec)

6 octets pour stocker 4 caractères ? C’est de l’utf8 ! Les accents sont stockés sur deux octets. Si ça avait été du latin1, la longueur de la donnée aurait été de 4 octets.

Mais alors, je peux demander à MySQL de convertir mes données ?

Oui, mais pour savoir où aller, il faut savoir d’où on vient: avant de demander à MySQL de convertir une donnée, il faut connaitre son encodage actuel, et surtout dans quel encodage MySQL croit que ces données sont.

Il faut bien garder en tête que lorsqu’on parle du charset d’une table, d’un champ, d’une base de donnée… on parle du charset par défaut, et donc du charset que le serveur va utiliser pour insérer/retourner des données. Cela n’a aucune incidence sur l’encodage utilisé auparavant pour les données.

  • charset du serveur égal au charset du client : aucune conversion n’est faite
  • charset du serveur en latin1, charset du client en utf8 : la donnée va être encodée en utf8 (même si elle l’était déjà => problème de double encoding)
  • charset du serveur en utf8, charset du client en latin1 : la donnée va être encodée en latin1

Mes données sont stockées en utf8 et MySQL ne le sait pas!

Symptôme: quand on affiche le length d’une donnée avec des caractères accentués, ça donne un nombre d’octets plus grands que le nombre de caractères. La donnée est donc en utf8. Par contre, le serveur, la db, la table, le champ… sont configurés pour être en latin1. Et quand on essaie de faire un SET NAMES utf8, la donnée s’affiche avec des « Ã© » : dans ce cas, c’est une donnée stockée en utf8, mais qui est interprétée comme du latin1 par MySQL, qui va donc l’encoder une seconde fois en utf8 (problème de double encoding).

La solution :

Le serveur pense que les données sont en latin1 et on sait qu’elles sont en utf8 (notre charset final souhaité). Il suffit de

  1. faire un dump de la base dans un fichier en latin1 pour qu’il n’y ai aucune conversion (pas de double encoding)
      $ mysqldump -u <user> -p<pass> --default-character-set=latin1 foo bar > temp.sql
  2. modifier ce fichier pour y faire disparaitre toute trace de « latin1″
      $ cat temp.sql | sed 's/SET NAMES latin1/SET NAMES utf8/g' > tmp
      $ cat tmp | sed 's/CHARSET=latin1/CHARSET=utf8/g' > temp.sql
  3. configurer la table, la base de donnée et le serveur pour qu’ils soient en « default charset utf8 » (cf le chapitre sur les variables de configuration)
  4. réimporter les données dedans
      $ mysql -u <user> -p<pass> foo < temp.sql

Mes données sont stockées en latin1 et MySQL le sait, mais je les veux en utf8

Vu que le serveur sait que ses données sont en latin1, il suffit de lui demander de nous les fournir en utf8 :

  1. faire un dump de la base dans un fichier en utf8 pour qu’il y ai une conversion automatique à partir de latin1
      $ mysqldump -u <user> -p<pass> --default-character-set=utf8 foo bar > temp.sql
  2. modifier ce fichier pour y faire disparaitre toute trace de « latin1″
      $ cat temp.sql | sed 's/SET NAMES latin1/SET NAMES utf8/g' > tmp
      $ cat tmp | sed 's/CHARSET=latin1/CHARSET=utf8/g' > temp.sql
  3. configurer la table, la base de donnée et le serveur pour qu’ils soient en « default charset utf8″ (cf le chapitre sur les variables de configuration)
  4. réimporter les données dedans
      $ mysql -u <user> -p<pass> foo < temp.sql

Et PHP dans tout ça? Avant ça marchait, maintenant j’ai des � !

Ce cher PHP (hint: passez à Django! c’est bien plus beau!) ne prends pas en compte les configurations mises au niveau du serveur (ou du fichier de configuration) pour son encoding!

Par défaut la commande mysql_connect va toujours utiliser le charset latin1 : vous pouvez en avoir la preuve avec la commande mysql_client_encoding.

PHP va donc vous fournir des données interprétées en latin1 alors qu’elles sont en utf8, d’où les caractères � non valides.

Il suffit alors d’utiliser la commande mysql_set_charset(‘utf8′, $connection) sur la connexion ouverte avec mysql_connect.

Faites bien attention d’avoir définit utf8 pour l’encoding de vos pages HTML soit par une balise meta dans votre entête de page, ou en ayant configuré votre serveur web pour servir les pages en utf8. Un moyen simple de vérifier ça est d’afficher les informations de la page.

Django : Envoyer des emails HTML avec images inline (intégrées)

17/02/2010 admin Comments off

Voyons comment envoyer des emails multiparties (texte et HTML) avec des images inline (intégrées dans le mail lui-même, et non en pièce jointe), et ceci en utilisant des templates afin de profiter (par exemple) de l’i18n avec gettext, des filtres et tags, de l’utilisation du contexte…

Ce code est un mélange de deux méthodes complémentaires, une sur les mails HTML par Ross Poulton, et l’autre venant d’un djangosnippet par sleytr.

Pour en faciliter l’utilisation et la maintenance, j’ai mis ce petit module django-nice-emails sur bitbucket.

Plutôt que de copier le code ici, je vais plutôt en décrire les grandes étapes:

  1. Utiliser le setting DEFAULT_FROM_EMAIL si l’expéditeur n’est pas fourni
  2. Créer un django.template.Context à partir du dictionnaire fourni (permet de remplacer les {{ var }} dans les templates)
  3. Utiliser le contexte créé pour initialiser le contenu texte, HTML ainsi que le sujet
  4. Transformer le destinataire fourni en liste (si ce n’est pas déjà une liste de destinataires)
  5. Créer un django.core.mail.EmailMultiAlternatives qui est la base de notre email (basé sur le contenu texte)
  6. Rajouter la partie HTML
  7. Rajouter les images en inline si nécéssaire
  8. Envoyer le mail

Rien de compliqué donc dans ce code qui fait moins de 20 lignes « utiles ».

Voyons maintenant un exemple d’utilisation avec de la traduction et de l’héritage de templates:

Les templates

On utilise ici la méthode de Ross Poulton qui consiste à ne fournir en paramètre template_name que la base du nom de fichier, sans l’extension. On fournit ensuite au django.template.loader ce template_name avec l’extension .txt et .html, ces templates doivent donc exister tous les deux.

templates/test_email.txt

{% load i18n %}
{% trans "Bonjour" %} {{ nom }},

{% blocktrans %}Ceci est un exemple de "nice-email" que je vous fait parvenir,
à titre d'exemple, et bien que vous vous en fichiez{% endblocktrans %}.

{% trans "Cordialement" %}

Mathieu Agopian

templates/test_email.html

{% extends "base_email.html" %}
{% load i18n %}
{% block email_content %}
<p>{% trans "Bonjour" %} {{ nom }},</p>

<p>
{% blocktrans %}Ceci est un exemple de "nice-email" que je vous fait parvenir,
à titre d'exemple, et bien que vous vous en fichiez{% endblocktrans %}.
</p>

<p>{% trans "Cordialement" %}</p>

<p><em>Mathieu Agopian</em></p>
<img src="cid:signature" />
{% endblock email_content %}

templates/base_email.html

<table width="600">
<tr><td><img src="cid:logo" /></td></tr>
<tr><td>
    {% block email_content %}{% endblock email_content %}
</td></tr>
</table>

Le contexte

Un simple dictionnaire python pour chaque tag utilisé dans les templates:

context = {'nom': 'Johnny Biboul'}

Les images

Elles doivent être passées en paramètres dans un tuple de tuples, sous la forme ((‘/chemin/vers/image.png’, ‘tagimage’), ‘/chemin/vers/image2.png’, ‘tagimage2′), …). Si les images sont dans le répertoire images du MEDIA_ROOT:

images = (
    (path.join(settings.MEDIA_ROOT, 'images', 'signature.png'), 'signature'),
    (path.join(settings.MEDIA_ROOT, 'image', 'logo.png'), 'logo'))

Dans les templates, on utilisera les images sous la forme <img src=’cid:tagimage’ />.

Le code

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from os import path
from django.conf import settings
from django.utils.translation import ugettext
from utils.nicemails import send_nice_email

context = {'nom': 'Johnny Biboul'}

images = (
    (path.join(settings.MEDIA_ROOT, 'images', 'signature.png'), 'signature'),
    (path.join(settings.MEDIA_ROOT, 'images', 'logo.png'), 'logo'))

subject = ugettext(u"Test de mail pour %(nom)s") % {'nom': '{{ nom }}'}

send_nice_email(
        template_name='test_email',
        email_context=context,
        subject=subject,
        recipients='johnny@biboul.com',
        sender='foo bar ',
        images=images)

Conclusion

Il vous suffit de mettre ce code dans une de vos vues pour pouvoir faire de jolis mails de confirmation d’inscription, des newsletters, ou voir même (bouh! c’est mal!) du mass-mailing. Veillez néanmoins à ne pas forcer la dose sur le html, ou les images inlines!

lancer gunicorn avec runit

10/02/2010 admin Comments off

ATTENTION votre serviteur a fait le test pour vous sur une ubuntu: après avoir installé runit et runit-run, le système ne démarre plus. Pour suivre les étapes de ce billet, il ne faut pas installer runit-run, qui ne doit être installé que lorsque l’on souhaite remplacer totalement le système d’initialisation (et cela demande plus de configuration qu’une simple installation du paquet).

Pour les malheureux qui ont fait les frais de la première version de ce billet (demandant d’installer runit-run), je ne peux que m’excuser platement, et vous fournir la méthode « au secours rescue moi! »: récuperer une installation avec un cd ubuntu. Une fois chrooté sur la partition root, il vous restera à désinstaller le paquet fautif et redémarrer:
        $ aptitude purge runit-run

Edité le 2010/02/10 à 20:58

Pour faire suite au précédent billet gunicorn: un server wsgi ultra simple à utiliser et configurer, voici une recette rapide pour lancer automatiquement (et monitorer) gunicorn avec runit.

Pourquoi runit et pas sysvinit, inittab, upstart, …

Je vous laisse consulter la page benefits sur le site officiel pour vous faire une idée. Pour les personnes ne parlant pas anglais, voici un bref résumé:

  1. Un répertoire par service, contenant un script run
  2. Un environnement d’exécution propre et prédictible pour chaque processus
  3. Service de logging (optionnel) qui sera lancé en même temps que le processus, et en couvrira toute la durée de vie (redémarrages compris!)
  4. Très peu encombrant, efficace… et peut complètement remplacer le système d’initialisation de votre linux

Utiliser runit avec le système d’initialisation actuel

Pour nous faciliter la vie, et ne pas avoir à modifier/importer de nombreux scripts de démarrage pour tous les démons et services déjà installés, nous allons utiliser runit « avec » le système d’initialisation actuel.

Installer runit

$ aptitude install runit

ATTENTION: Si vous installez aussi runit-run, il vous faut absolument configurer votre système (How to replace init), et ce, avant de rebooter (sinon votre système ne démarrera pas, et vous serez contraint à utiliser une méthode de récupération, comme celle présentée en tête de ce billet).

Créer un répertoire pour le service gunicorn et son script de lancement

$ mkdir /etc/sv/gu-monprojet
$ vi /etc/sv/gu-monprojet/run

Et voici le contenu du script run

#!/bin/bash
source /path/to/venv/bin/activate # activer le virtualenv
cd /path/to/django/project
exec gunicorn_django -b localhost:8080 --workers=3

Avec la version de gunicorn utilisée pour l’écriture de cet article, il est nécessaire d’être dans le répertoire du projet django (là ou se situe le fichier settings.py) pour lancer gunicorn_django.

Dans une future version (la modification est dans le trunk à l’heure de l’écriture) il suffira d’indiquer le chemin vers le fichier settings.py comme paramètre à la commande gunicorn_django.

Enfin, ne pas oublier de rajouter les droits d’exécution sur le script qu’on vient de créer:

$ chmod a+x /etc/sv/gu-monprojet

Indiquer à runit qu’il doit lancer le script

Pour celà, un simple lien symbolique, et dans les secondes qui suivent le script sera lancé:

$ ln -s /etc/sv/gu-monprojet /etc/service/

Et c’est tout!

Il suffit maintenant d’en profiter en allant sur http://localhost:8080, en configurant apache pour proxiser les requêtes directement dessus (cf le billet gunicorn: un server wsgi ultra simple à utiliser et configurer), ou encore en utilisant la commande sv pour gérer le service gunicorn:

$ sv status gu-monprojet
$ sv check gu-monprojet
$ sv up gu-monprojet
$ sv down gu-monprojet
$ sv restart gu-monprojet
$ sv hup gu-monprojet
...

gunicorn: un server wsgi ultra simple à utiliser et configurer

09/02/2010 admin Comments off

Deux billets le même jour, c’est fête!

Voici une recette simple pour installer, configurer et utiliser gunicorn avec apache et django.

Installer gunicorn

Pour installer gunicorn dans son environnement virtuel:

$ pip install -E /path/to/venv install gunicorn

Configurer Apache en proxy

Apache servira les fichiers statiques, et « proxisera » toutes les autres requêtes directement à gunicorn qui sera lancé en local sur le port 8080:

<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com

DocumentRoot /path/to/django/project

<Proxy *>
    Order deny,allow
    Allow from all
</Proxy>

# laisser apache servir les fichiers statiques
ProxyPass /robots.txt !
ProxyPass /favicon.ico !
ProxyPass /static/ !

# proxiser toutes les autres requêtes vers gunicorn
ProxyPass / http://localhost:8080/

# robots.txt et favicon.ico sont dans /path/to/django/project/static/
Alias /robots.txt /path/to/django/project/static/robots.txt
Alias /favicon.ico /path/to/django/project/static/favicon.ico

<Directory /path/to/django/project>
    Order deny,allow
    Allow from all
    Options -Indexes
</Directory>
</VirtualHost>

Pour que le tout fonctionne correctement, il faut activer les modules mod_proxy et mod_proxy_html (et en option mod_cache):

$ a2enmod proxy proxy_http cache

Puis de redémarrer le server Apache:

$ /etc/init.d/apache2 restart

Lancer gunicorn

Il suffit de se placer dans le répertoire du projet django (avec le virtualenv activé), puis de taper:

$ gunicorn_django -b localhost:8080 --workers=2

Un ordre d’idée pour le calcul des workers: un de plus que le nombre de CPUs de la machine.

Conclusion

On peut alors se créer un script (a placer dans /etc/init.d) et l’activer pour qu’il se lance automatiquement au démarrage avec la commande update-rc.d (sous Debian), ou utiliser runit (jamais testé, peut-être un futur billet?).

Encore mieux, remplacer Apache par Nginx! (jamais testé non plus, et sûrement un futur billet ;) ).

On peut difficilement faire plus simple!

Installer PIL (Python Imaging Library) facilement avec pip

09/02/2010 admin 3 commentaires

Le fabuleux utilitaire pip de Ian Bicking est un remplacement à easy_install qui fonctionne très bien avec virtualenv (pas étonnant, c’est du même auteur!).

Je laisse le soin au lecteur de consulter la documentation sur ces deux utilitaires très pratiques et indispensables à tout développeur python.

Voici la commande à utiliser pour installer PIL (Python Imaging Library) dans votre environnement virtuel:

$ pip -E /path/to/venv install http://effbot.org/downloads/Imaging-1.1.7.tar.gz

Vous pouvez consulter la liste des versions disponibles en vous rendant sur la page officielle de Python Imaging Library.

ATTENTION: Il faut avoir les sources de python installées et disponibles afin de pouvoir compiler le paquet. Sur debian, il vous suffit de taper

$ aptitude install python-dev

Si vous avez une erreur pip du genre

ImportError: No module named pkg_resources

il vous faut aussi installer python-pkg-resources:

$ aptitude install python-pkg-resources
Categories: Python Tags: , , , ,

Obfuscation de l'email alternative et accessible

27/09/2009 admin Comments off

English translation of this post also available.

Le Problème

Les quatre prérequis pour l’obfuscation d’une adresse mail sont les suivants:

  1. Ne pas pénaliser l’utilisateur: pouvoir cliquer sur le mail
  2. Ne pas pénaliser l’utilisateur: pouvoir copier/coller l’adresse
  3. Ne pas pénaliser l’utilisateur: l’addresse doit rester accessible (en mode texte, sans css et/ou sans js)
  4. Difficilement récupérables par des spambots

L’ordre n’est pas une coïncidence: le but de l’obfuscation est bien entendu d’empêcher les spambots de récuperer l’adresse email, mais celà ne doit en aucun cas pénaliser l’utilisateur du site.

Les différentes méthodes d’obfuscation d’adresse email qui tournent sur la toile, malheureusement, échouent en général au moins sur l’un des 4 points listés.

La Solution

Pour les utilisateurs de webkit avec le javascript désactivé, voir les limitations.

<a href="/contactform.html" id="emailaddress">
    prenom.nom<img src="" alt="@" />example.com
</a>
<script> type="text/javascript">
    mail = new Array("prenom.nom", "example.com").join("@");
    ea = document.getElementById("emailaddress");
    ea.href = "mailto:" + mail;
    ea.innerHTML = mail;
</script>

Et voici le résultat: prenom.nom@example.com

L’Explication

Si javascript est activé, l’attribut href ainsi que le contenu du lien sont modifiés afin que l’obfuscation soit invisible. Rien de nouveau donc.

Si javascript est désactivé, en utilisant le contenu alternatif d’une image inexistante pour remplacer le « @ », on obtient plusieurs résultats intéressants:

  • Les lecteurs d’écrans liront l’attribut alt pour l’image, l’email étant donc très facilement déchiffrable
  • L’attribut alt est affiché, rendant l’obfuscation invisible, même en mode texte ou sans css
  • L’adresse est copiable sans aucun soucis
  • L’email est toujours clickable, le lien menant sur un formulaire de contact

Un gros avantage d’utiliser un attribut d’un tag html est qu’il contre les spambots qui ne récupèrement que le texte, et non les tags html, afin de ne pas être bloqués par les méthodes d’obfuscation basées sur l’insertion de tags invisibles ou de commentaires dans l’adresse.
Ici, supprimer le tag <img> revient à perdre l’information capitale du « @ ».

Les limitations

Il y a deux bugs non résolus dans Webkit à la date de rédaction de cet article:

Les conséquences sont alors visible sur un navigateur utilisant Webkit, et avec le javascript désactivé:

Image manquante sous Webkit

Image manquante sous Webkit

De plus, l’adresse n’est plus copiable, mais le lien (vers le formulaire de contact) reste disponible.

Une solution serait alors d’utiliser une image contenant le symbole « @ », mais celà peut poser des problèmes de style (couleur, famille et taille de la police, anti-aliasing…):

Adresse email avec une image remplaçant le @

Adresse email avec une image remplaçant le @

Note

Il est possible d’afficher l’attribut alt de l’image sous webkit, en forçant la taille de l’image pour qu’elle soit assez grande:

<img src="" style="height: 60px; vertical-align: top;" alt="@" />
Image assez grande pour afficher l'attribut alt

Image assez grande pour afficher l'attribut alt

C’est hideux, mais ça reste lisible, et ça n’impacte que les quelques pourcents d’utilisateurs de webkit qui ont le javascript désactivé.

Conclusion

Voilà donc une nouvelle méthode d’obfuscation d’adresse email, qui devrait être efficace (au moins autant que les autres) contre les spambots, mais qui impacte le moins possible l’utilisateur final.
Et surtout, l’adresse est accessible pour les lecteurs d’écrans!

Image manquante sous Webkit
Categories: technique, web Tags:

Linux: savoir si le processeur est 32bits ou 64bits

24/09/2009 admin 2 commentaires

Voici une astuce rapide pour savoir si le processeur d’une machine donnée supporte le 64bits:

Il suffit de vérifier si le flag lm est présent dans les informations de /proc/cpuinfo:

$ cat /proc/cpuinfo | grep lm
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov
pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe lm constant_tsc pebs
bts pni monitor ds_cpl est cid cx16 xtpr lahf_lm

Le flag lm signifie « long mode », comme on peut le voir dans les sources du noyau (include/asm-i386/cpufeature.h):

#define X86_FEATURE_LM          (1*32+29) /* Long Mode (x86-64) */

Vous trouverez une liste de tous les flags sur le blog de Nick Burch (en anglais).

Categories: linux, système Tags: