Archive

Archives pour la catégorie ‘système’

MySQL, mysqldump et PHP : convertir de latin1 vers utf8

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.

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
...

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: