<-
Apache > Serveur HTTP > Documentation > Version 2.3 > Rewrite

Guide de réécriture des URLs - Sujets avancés

Langues Disponibles:

Ce document complémente la documentation de référence du module mod_rewrite. Il décrit les différentes manières d'utiliser le module d'Apache mod_rewrite pour résoudre les problèmes d'URLs typiques auxquels sont souvent confrontés les webmasters. Nous fournissons une description détaillée de la résolution de chaque problème par la configuration d'un jeu de règles de réécriture.

ATTENTION: il pourra s'avérer nécessaire de modifier les exemples en fonction de la configuration de votre serveur, par exemple en ajoutant le drapeau [PT] si les modules mod_alias et mod_userdir sont utilisés, etc... Les jeux de règles devront également être adaptés pour passer d'un contexte de serveur à un contexte de répertoire (fichiers .htaccess). Essayez de toujours bien comprendre ce que fait un jeu de règles avant de l'utiliser, ce qui pourra vous éviter bien des problèmes.

Voir aussi

top

Accès à une grappe de serveurs via un espace d'adressage compatible

Description :

Comment créer un espace d'adressage homogène et compatible avec tous les serveurs WWW d'une grappe de serveurs d'un intranet ? C'est à dire que toutes les URLs (par définition locales à un serveur et dépendant donc de celui-ci) deviennent véritablement indépendantes du serveur ! Nous voulons disposer, pour accéder à l'espace de nommage WWW, d'un seul espace d'adressage compatible : aucune URL ne doit inclure d'information quelconque à propos du serveur cible physique. La grappe de serveurs doit elle-même nous diriger automatiquement vers le bon serveur cible physique, selon les besoins, et ceci de manière transparente.

Solution :

Tout d'abord, la connaissance des serveurs cibles est issue de tables de correspondances externes (distribuées) qui contiennent des informations sur la localisation de nos utilisateurs, groupes et entités. Elles se présentent sous la forme :

utilisateur1  serveur_utilisateur1
utilisateur2  serveur_utilisateur2
:      :

On les enregistre sous forme de fichiers map.xxx-vers-serveur. On doit ensuite faire rediriger à tous les serveurs les URLs de la forme :

/u/utilisateur/chemin
/g/groupe/chemin
/e/entité/chemin

vers

http://serveur-physique/u/utilisateur/chemin
http://serveur-physique/g/groupe/chemin
http://serveur-physique/e/entité/chemin

si il n'est pas nécessaire que chaque chemin d'URL être valide sur chaque serveur. Le jeu de règles suivant le fait pour nous à l'aide des fichiers de correspondance (en supposant que serveur0 soit un serveur par défaut qui sera choisi si l'utilisateur ne possède aucune entrée dans la table) :

RewriteEngine on

RewriteMap      utilisateur-vers-serveur   txt:/chemin/vers/map.utilisateur-vers-serveur
RewriteMap     groupe-vers-serveur   txt:/chemin/vers/map.groupe-vers-serveur
RewriteMap    entité-vers-serveur   txt:/chemin/vers/map.entité-vers-serveur

RewriteRule   ^/u/([^/]+)/?(.*)
http://${utilisateur-vers-serveur:$1|serveur0}/u/$1/$2
RewriteRule   ^/g/([^/]+)/?(.*)
http://${groupe-vers-serveur:$1|serveur0}/g/$1/$2
RewriteRule   ^/e/([^/]+)/?(.*)
http://${entité-vers-serveur:$1|serveur0}/e/$1/$2

RewriteRule   ^/([uge])/([^/]+)/?$          /$1/$2/.www/
RewriteRule   ^/([uge])/([^/]+)/([^.]+.+)   /$1/$2/.www/$3\
top

Répertoires utilisateurs structurés

Description :

Certains sites possédant des milliers d'utilisateurs organisent les répertoires home de manière structurée, c'est à dire que chaque répertoire home se situe dans un sous-répertoire dont le nom commence (par exemple) par le premier caractère du nom de l'utilisateur. Ainsi, /~foo/chemin est dans /home/f/foo/.www/chemin, tandis que /~bar/chemin est dans /home/b/bar/.www/chemin.

Solution :

Le jeu de règles suivant permet de développer les URLs avec tilde selon la représentation ci-dessus.

RewriteEngine on
RewriteRule   ^/~(([a-z])[a-z0-9]+)(.*)  /home/$2/$1/.www$3
top

Réorganisation du système de fichiers

Description :

Voici un cas d'espèce : une application très efficace qui fait un usage intensif de règles RewriteRule dans le contexte du répertoire pour présenter un aspect compréhensible sur le Web sans modifier la structure des données. Les coulisses de l'affaire : net.sw rassemble mes archives de paquetages de logiciels Unix librement accessibles, que j'ai commencé à collectionner en 1992. Pour moi, c'est un passe-temps, mais aussi un travail, car alors que j'étudie la science informatique, j'ai aussi travaillé depuis de nombreuses années comme administrateur système et réseau à mes heures perdues. Chaque semaine j'ai besoin de tel ou tel logiciel, et j'ai donc créé une arborescence très ramifiée de répertoires où je stocke les paquetages :

drwxrwxr-x   2 netsw  users    512 Aug  3 18:39 Audio/
drwxrwxr-x   2 netsw  users    512 Jul  9 14:37 Benchmark/
drwxrwxr-x  12 netsw  users    512 Jul  9 00:34 Crypto/
drwxrwxr-x   5 netsw  users    512 Jul  9 00:41 Database/
drwxrwxr-x   4 netsw  users    512 Jul 30 19:25 Dicts/
drwxrwxr-x  10 netsw  users    512 Jul  9 01:54 Graphic/
drwxrwxr-x   5 netsw  users    512 Jul  9 01:58 Hackers/
drwxrwxr-x   8 netsw  users    512 Jul  9 03:19 InfoSys/
drwxrwxr-x   3 netsw  users    512 Jul  9 03:21 Math/
drwxrwxr-x   3 netsw  users    512 Jul  9 03:24 Misc/
drwxrwxr-x   9 netsw  users    512 Aug  1 16:33 Network/
drwxrwxr-x   2 netsw  users    512 Jul  9 05:53 Office/
drwxrwxr-x   7 netsw  users    512 Jul  9 09:24 SoftEng/
drwxrwxr-x   7 netsw  users    512 Jul  9 12:17 System/
drwxrwxr-x  12 netsw  users    512 Aug  3 20:15 Typesetting/
drwxrwxr-x  10 netsw  users    512 Jul  9 14:08 X11/

J'ai décidé en 1996 de rendre cette archive disponible pour le monde via une interface web agréable. "Agréable" signifie que je voulais vous offrir une interface où vous pourriez naviguer directement à travers la hiérarchie des archives. Mais "agréable" signifie aussi que je ne voulais rien changer dans cette hiérarchie - même pas en ajoutant queques scripts CGI à son sommet. Pourquoi ? Parceque j'avais prévu de rendre ultérieurement la structure ci-dessus accessible aussi via FTP, et je ne voulais pas voir de fichiers CGI ou Web à ce niveau.

Solution :

La solution comporte deux parties : la première consiste en un ensemble de scripts CGI qui créent toutes les pages à tous les niveaux de répertoires à la volée. Je les ai placés dans /e/netsw/.www/ comme suit :

-rw-r--r--   1 netsw  users    1318 Aug  1 18:10 .wwwacl
drwxr-xr-x  18 netsw  users     512 Aug  5 15:51 DATA/
-rw-rw-rw-   1 netsw  users  372982 Aug  5 16:35 LOGFILE
-rw-r--r--   1 netsw  users     659 Aug  4 09:27 TODO
-rw-r--r--   1 netsw  users    5697 Aug  1 18:01 netsw-about.html
-rwxr-xr-x   1 netsw  users     579 Aug  2 10:33 netsw-access.pl
-rwxr-xr-x   1 netsw  users    1532 Aug  1 17:35 netsw-changes.cgi
-rwxr-xr-x   1 netsw  users    2866 Aug  5 14:49 netsw-home.cgi
drwxr-xr-x   2 netsw  users     512 Jul  8 23:47 netsw-img/
-rwxr-xr-x   1 netsw  users   24050 Aug  5 15:49 netsw-lsdir.cgi
-rwxr-xr-x   1 netsw  users    1589 Aug  3 18:43 netsw-search.cgi
-rwxr-xr-x   1 netsw  users    1885 Aug  1 17:41 netsw-tree.cgi
-rw-r--r--   1 netsw  users     234 Jul 30 16:35 netsw-unlimit.lst

Le sous-répertoire DATA/ contient la structure de répertoires proprement dite mentionnée plus haut, c'est à dire les véritables ressources net.sw et est mis à jour automatiquement via rdist à intervalles de temps réguliers. Reste la seconde partie du problème : comment relier ces deux structures selon une arborescence d'URL facile d'accès ? Il nous faut cacher le répertoire DATA/ à l'utilisateur durant l'exécution des scripts CGI appropriés aux différentes URLs. Voici comment : tout d'abord, j'ajoute ces deux règles dans le fichier de configuration du répertoire racine DocumentRoot du serveur afin de réécrire le chemin d'URL public /net.sw/ vers le chemin interne /e/netsw :

RewriteRule  ^net.sw$       net.sw/        [R]
RewriteRule  ^net.sw/(.*)$  e/netsw/$1

La première règle concerne les requêtes qui ne comportent pas de slash de fin ! C'est la seconde règle qui fait le véritable travail. Et maintenant vient la super configuration qui se trouve dans le fichier de configuration de répertoire /e/netsw/.www/.wwwacl :

Options       ExecCGI FollowSymLinks Includes MultiViews

RewriteEngine on

#  l'accès s'effectue via le préfixe /net.sw/
RewriteBase   /net.sw/

#  tout d'abord, on réécrit le répertoire racine vers
#  le script CGI qui lui est associé
RewriteRule   ^$                       netsw-home.cgi     [L]
RewriteRule   ^index\.html$            netsw-home.cgi     [L]

#  on supprime les sous-répertoires lorsque
#  le navigateur nous atteint depuis des pages de répertoire
RewriteRule   ^.+/(netsw-[^/]+/.+)$    $1                 [L]

#  on stoppe maintenant la réécriture pour les fichiers locaux
RewriteRule   ^netsw-home\.cgi.*       -                  [L]
RewriteRule   ^netsw-changes\.cgi.*    -                  [L]
RewriteRule   ^netsw-search\.cgi.*     -                  [L]
RewriteRule   ^netsw-tree\.cgi$        -                  [L]
RewriteRule   ^netsw-about\.html$      -                  [L]
RewriteRule   ^netsw-img/.*$           -                  [L]

#  ce qui reste est un sous-répertoire qui peut être traité
#  par un autre script CGI
RewriteRule   !^netsw-lsdir\.cgi.*     -                  [C]
RewriteRule   (.*)                     netsw-lsdir.cgi/$1

Quelques indices pour l'interprétation :

  1. Remarquez le drapeau L (last) et l'absence de chaîne de substitution ('-') dans la quatrième partie.
  2. Remarquez le caractère ! (not) et le drapeau C (chain) dans la première règle de la dernière partie.
  3. Remarquez le modèle qui correspond à tout dans la dernière règle.
top

Rediriger les URLs erronées vers un autre serveur Web

Description :

Une question typique de la FAQ à propos de la réécriture revient souvent : comment rediriger vers un serveur B les requêtes qui échouent sur un serveur A ? On s'acquitte en général de cette tâche via des scripts CGI ErrorDocument en Perl, mais il existe aussi une solution avec mod_rewrite. Notez cependant que les performances sont moindres qu'avec l'utilisation d'un script CGI ErrorDocument !

Solution :

La première solution possède des performances supérieures mais moins de souplesse, et est moins sure :

RewriteEngine on
RewriteCond  %{DOCUMENT_ROOT/%{REQUEST_URI}  !-f
RewriteRule   ^(.+)                             http://serveurB.dom/$1

Le problème réside dans le fait que seules les pages situées dans la racine DocumentRoot seront redirigées. Mais même si vous pouvez ajouter des conditions supplémentaires (par exemple pour traiter aussi les répertoires home, etc...), il existe une meilleure solution :

RewriteEngine on
RewriteCond   %{REQUEST_URI} !-U
RewriteRule   ^(.+)          http://serveurB.dom/$1
reprendre ici

On utilise ici la fonctionnalité de prévision des URLs futures de mod_rewrite. Et cette solution fonctionne pour tous les types d'URLs et de manière sûre. Par contre, cette méthode a un impact sur les performances du serveur web, car chaque requête entraîne le traitement d'une sous-requête interne supplémentaire. Par conséquent, vous pouvez l'utiliser si votre serveur web s'exécute sur un CPU puissant. Dans le cas d'une machine plus lente, utilisez la première approche, ou mieux, un script CGI ErrorDocument.

top

Multiplexeur d'accès aux archives

Description :

Connaissez-vous la grande archive CPAN (Comprehensive Perl Archive Network) située à http://www.perl.com/CPAN ? CPAN redirige automatiquement les navigateurs vers un des nombreux serveurs FTP répartis à travers le monde (généralement un serveur assez proche du client) ; chaque serveur héberge l'intégralité d'un miroir CPAN. Il s'agit ni plus ni moins qu'un service d'accès FTP multiplexé. Alors que le fonctionnement de l'archive CPAN repose sur des scripts CGI, comment implémenter une approche similaire avec mod_rewrite ?

Solution :

Premièrement, remarquons que depuis la version 3.0.0, mod_rewrite accepte aussi le préfixe "ftp:" dans les redirections. Et deuxièmement, l'approximation de la localisation peut être effectuée par une table de correspondances RewriteMap, en se basant sur la racine du domaine du client. Un jeu de règles chaînées astucieux nous permet d'utiliser cette racine du domaine comme clé de recherche dans notre table de correspondances de multiplexage.

RewriteEngine on
RewriteMap    multiplex                txt:/chemin/vers/map.cxan
RewriteRule   ^/CxAN/(.*)              %{REMOTE_HOST}::$1                 [C]
RewriteRule   ^.+\.([a-zA-Z]+)::(.*)$
${multiplex:$1|ftp.défaut.dom}$2  [R,L]
##
##  map.cxan -- Multiplexing Map for CxAN%{DOCUMENT_ROOT/%{REQUEST_URI}
##

de        ftp://ftp.cxan.de/CxAN/
uk        ftp://ftp.cxan.uk/CxAN/
com       ftp://ftp.cxan.com/CxAN/
 :
##EOF##
top

Contenu dépendant du navigateur

Description :

Il est parfois nécessaire, au moins pour les pages principales, de fournir un contenu optimum adapté à chaque type de navigateur, c'est à dire que l'on doit fournir une version pour les navigateurs courants, une version différente pour les navigateurs en mode texte du style de Lynx, et une autre pour les autres navigateurs.

Solution :

On ne peut pas utiliser la négociation de contenu car les navigateurs ne fournissent pas leur type dans cette forme. Nous devons nous baser sur l'en-tête HTTP "User-Agent". La configuration ci-dessous effectue les actions suivantes : si l'en-tête HTTP "User-Agent" commence par "Mozilla/3", la page foo.html est réécrite en foo.NS.html et la réécriture s'arrête. Si le navigateur est "Lynx" ou "Mozilla" version 1 ou 2, la page foo.html est réécrite en foo.20.html. Tous les autres navigateurs reçoivent la page foo.32.html. Voici le jeu de règles :

RewriteCond %{HTTP_USER_AGENT}  ^Mozilla/3.*
RewriteRule ^foo\.html$         foo.NS.html          [L]

RewriteCond %{HTTP_USER_AGENT}  ^Lynx/.*         [OR]
RewriteCond %{HTTP_USER_AGENT}  ^Mozilla/[12].*
RewriteRule ^foo\.html$         foo.20.html          [L]

RewriteRule ^foo\.html$         foo.32.html          [L]
top

Miroir dynamique

Description :

Supposons que nous voulions intégrer dans notre espace de nommage de belles pages web situées sur un serveur distant. Dans le cas d'un serveur FTP, nous aurions utilisé le programme mirror qui maintient vraiment une copie des données distantes mise à jour explicitement sur le serveur local. Pour un serveur web, nous pourrions utiliser le programme webcopy qui utilise le protocole HTTP. Ces deux techniques présentent cependant un inconvénient majeur : la copie locale n'est véritablement à jour qu'au moment où nous avons lancé le programme. Plutôt qu' un miroir statique devant être défini explicitement, il serait préférable d'avoir un miroir dynamique dont le contenu serait mis à jour automatiquement, à la demande, sur le(s) serveur(s) distant(s).

Solution :

Pour y parvenir, on fait correspondre la page web ou même l'ensemble du répertoire web distants à notre espace de nommage en utilisant la fonctionnalité Mandataire (drapeau [P] ou [proxy]) :

RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^page-convoitée/(.*)$  http://www.tstimpreso.com/page-convoitée/$1  [P]
RewriteEngine  on
RewriteBase    /~quux/
RewriteRule    ^usa-news\.html$   http://www.quux-corp.com/news/index.html  [P]
top

Miroir dynamique inverse

Description :
...
Solution :
RewriteEngine on
RewriteCond   /miroir/du/site-distant/$1           -U
RewriteRule   ^http://www\.site-distant\.com/(.*)$ /miroir/du/site-distant/$1
top

Récupérer des données manquantes depuis l'Intranet

Description :

C'est une méthode astucieuse permettant de faire fonctionner virtuellement un serveur web d'entreprise (www.quux-corp.dom) sur l'Internet (extérieur à l'entreprise), tout en maintenant et conservant dans la réalité ses données sur un serveur web (www2.quux-corp.dom) de l'Intranet (interne à l'entreprise) protégé par un pare-feu. L'astuce consiste, sur le serveur web externe, à récupérer à la volée sur le serveur interne les données demandées.

Solution :

Tout d'abord, nous devons nous assurer que notre pare-feu protège bien le serveur web interne, et que seul le serveur web externe est autorisé à y récupérer des données. Dans le cas d'un filtrage par paquets, nous pourrions par exemple définir un jeu de règles du pare-feu du style :

ALLOW serveur www.quux-corp.dom Port >1024 -->
serveur www2.quux-corp.dom Port 80
DENY  serveur *                 Port *     -->
serveur www2.quux-corp.dom Port 80

Il vous suffit d'adapter ces règles à la syntaxe de votre pare-feu. Nous pouvons maintenant définir les règles de mod_rewrite qui serviront à récupérer les données manquantes en arrière-plan via la fonctionnalité de mandataire :

RewriteRule ^/~([^/]+)/?(.*)          /home/$1/.www/$2 [C]
# L'utilisation de REQUEST_FILENAME ci dessous est correcte dans cet
# exemple de contexte au niveau serveur car la règle qui fait référence
# à REQUEST_FILENAME est chaînée à une règle qui définit
# REQUEST_FILENAME.
RewriteCond %{REQUEST_FILENAME}       !-f
RewriteCond %{REQUEST_FILENAME}       !-d
RewriteRule ^/home/([^/]+)/.www/?(.*) http://www2.quux-corp.dom/~$1/pub/$2 [P]
top

Répartition de charge

Description :

Supposons que nous voulions répartir la charge du trafic vers www.example.com entre les serveurs www[0-5].example.com (un total de 6 serveurs). Comment y parvenir ?

Solution :

Il existe de nombreuses solutions à ce problème. Nous décrirons tout d'abord une variante assez connue basée sur DNS, puis une autre basée sur mod_rewrite :

  1. Round-Robin (tourniquet) DNS

    La méthode de répartition de charge la plus simple consiste à utiliser le "DNS round-robin" (rotation d'adresses) de BIND. Vous devez seulement enregistrer les serveurs www[0-9].example.com de manière habituelle dans votre DNS à l'aide d'enregistrements de type A (adresse), comme suit :

    www0   IN  A       1.2.3.1
    www1   IN  A       1.2.3.2
    www2   IN  A       1.2.3.3
    www3   IN  A       1.2.3.4
    www4   IN  A       1.2.3.5
    www5   IN  A       1.2.3.6
    

    Puis vous ajoutez les entrées suivantes :

    www   IN  A       1.2.3.1
    www   IN  A       1.2.3.2
    www   IN  A       1.2.3.3
    www   IN  A       1.2.3.4
    www   IN  A       1.2.3.5
    

    Maintenant, lors de la résolution de www.example.com, BIND renvoie www0-www5 - mais selon une permutation différente à chaque fois. De cette façon, les clients sont répartis entre les différents serveurs. Notez cependant que cette méthode de répartition de charge n'est pas parfaite, car les résolutions DNS sont mises en cache par les clients et les autres serveurs DNS du réseau, si bien que lorsqu'un client s'est vu résoudre www.example.com en un des wwwN.example.com, toutes ses requêtes ultérieures continueront d'aller vers la même adresse IP (et donc le même serveur), au lieu d'être réparties entre les autres serveurs. Le résultat est cependant globalement satisfaisant car les requêtes sont réparties collectivement entre chacun des serveurs web.

  2. Répartition de charge basée sur DNS

    Une méthode de répartition de charge sophistiquée basée sur DNS consiste à utiliser le programme lbnamed que l'on peut trouver à http://www.stanford.edu/~riepel/lbnamed/. Associé à des outils auxiliaires, il s'agit d'un programme en Perl 5 qui permet d'effectuer une véritable répartition de charge basée sur DNS.

  3. Round-Robin basé sur la fonctionnalité de mandataire

    Dans cette variante, nous utilisons mod_rewrite et sa fonctionnalité de mandataire. Tout d'abord, nous définissons www0.example.com comme un autre nom de www.example.com en ajoutant l'entrée

    www    IN  CNAME   www0.example.com.
    

    dans le DNS. Puis nous définissons www0.example.com comme serveur mandataire seulement, c'est à dire que nous configurons cette machine de telle sorte que toutes les URLs qui lui arrivent soient simplement transmises, via le mandataire interne, vers un des 5 autres serveurs (www1-www5). Pour y parvenir, nous définissons tout d'abord un jeu de règles qui contacte un script de répartition de charge lb.pl pour toutes les URLs.

    RewriteEngine on
    RewriteMap    lb      prg:/chemin/vers/lb.pl
    RewriteRule   ^/(.+)$ ${lb:$1}           [P,L]
    

    Puis nous écrivons lb.pl :

    #!/chemin/vers/perl
    ##
    ##  lb.pl -- script de répartition de charge
    ##
    
    $| = 1;
    
    $name   = "www";     # la base du nom du serveur
    $first  = 1;         # le premier serveur (pas 0 ici, car 0 correspond à
    		     # moi-même)
    $last   = 5;         # le dernier serveur du tourniquet
    $domain = "foo.dom"; # le nom de domaine
    
    $cnt = 0;
    while (<STDIN>) {
        $cnt = (($cnt+1) % ($last+1-$first));
        $server = sprintf("%s%d.%s", $name, $cnt+$first, $domain);
        print "http://$server/$_";
    }
    
    ##EOF##
    
    Une dernière remarque : à quoi cela sert-il ? www0.example.com, quant à lui, n'est-il pas toujours surchargé ? La réponse est oui, il est surchargé, mais seulement avec des requêtes de mandataire ! Tous les traitements SSI, CGI, ePerl, etc... sont entièrement effectués sur les autres machines. Ceci peut fonctionner correctement pour un site complexe. Le plus grand risque réside ici dans le fait que www0 est un passage obligé et que s'il est hors service, les autres serveurs deviennent inaccessibles.
  4. Répartiteur de charge dédié

    Il existe aussi des solutions plus sophistiquées. Cisco, F5, et de nombreuses autres sociétés proposent des répartiteurs de charge matériels (utilisés en général en mode doublé à des fins de redondance), qui offrent une répartition de charge sophistiquée et des fonctionnalités de passage automatique en mode de fonctionnement par défaut en cas de problème. Cependant, des solutions logicielles offrent aussi des fonctionnalités similaires avec du matériel standard. Si vos besoins correspondent et si vous êtes assez riche, vous pouvez envisager ces solutions. La liste de diffusion lb-l est un bon point de départ pour vos recherches.

top

Nouveau type MIME, nouveau service

Description :

On trouve de nombreux programmes CGI attractifs sur le réseau. Mais leur emploi est souvent rébarbatif, si bien que de nombreux webmasters ne les utilisent pas. Même la fonctionnalité de gestionnaire Action d'Apache pour les types MIME ne convient que lorsque les programmes CGI ne nécessitent pas d'URLs spéciales (réellement PATH_INFO et QUERY_STRINGS) en entrée. Tout d'abord, définissons un nouveau type de fichier ayant pour extension .scgi (pour CGI sécurisé) qui sera associé pour traitement au programme populaire cgiwrap. Le problème est le suivant : par exemple, si on utilise un style d'URL bien défini (voir ci-dessus), un fichier situé dans le répertoire home de l'utilisateur pourra correspondre à l'URL /u/user/foo/bar.scgi. Mais cgiwrap nécessite des URLs de la forme /~user/foo/bar.scgi/. La règle suivante apporte la solution :

RewriteRule ^/[uge]/([^/]+)/\.www/(.+)\.scgi(.*) ...
... /interne/cgi/utilisateur/cgiwrap/~$1/$2.scgi$3  [NS,T=application/x-http-cgi]

Ou considérons ces autres programmes attractifs : wwwlog (qui affiche le journal des accès access.log pour un sous répertoire correspondant à une URL) et wwwidx (qui exécute Glimpse sur un sous répertoire correspondant à une URL). Nous devons fournir l'URL correspondante à ces programmes afin qu'ils sachent sur quel répertoire ils doivent agir. Mais c'est en général compliqué, car ils peuvent être appelés à nouveau par la forme d'URL alternative, c'est à dire que typiquement, nous exécuterions le programme swwidx depuis /u/user/foo/ via un hyperlien vers

/internal/cgi/user/swwidx?i=/u/user/foo/

ce qui n'est pas satisfaisant, car nous devons expliciter à la fois la localisation du répertoire et la localisation du programme CGI dans l'hyperlien. Si nous devons nous réorganiser, il nous faudra beaucoup de temps pour modifier tous les hyperliens.

Solution :

La solution consiste ici à fournir un nouveau format d'URL qui redirige automatiquement vers la requête CGI appropriée. Pour cela, on définit les règles suivantes :

RewriteRule   ^/([uge])/([^/]+)(/?.*)/\*  /interne/cgi/utilisateur/wwwidx?i=/$1/$2$3/
RewriteRule   ^/([uge])/([^/]+)(/?.*):log /interne/cgi/utilisateur/wwwlog?f=/$1/$2$3

Et maintenant l'hyperlien qui renvoie vers /u/user/foo/ se réduit à

HREF="*"

qui est automatiquement transformé en interne en

/internal/cgi/user/wwwidx?i=/u/user/foo/

Une approche similaire permet d'invoquer le programme CGI du journal des accès lorsque l'hyperlien :log est utilisé.

top

Régéneration de contenu à la volée

Description :

Voici une fonctionnalité vraiment ésotérique : des pages générées dynamiquement mais servies statiquement, c'est à dire que les pages doivent être servies comme des pages purement statiques (lues depuis le système de fichiers et servies en l'état), mais doivent être générées dynamiquement par le serveur web si elles sont absentes. Ainsi, vous pouvez avoir des pages générées par CGI qui sont servies statiquement à moins qu'un administrateur (ou une tâche de cron) ne supprime les contenus statiques. Les contenus sont ensuite actualisés.

Solution :
A cet effet, on utilise le jeu de règles suivant :
# Cet exemple n'est valable que dans un contexte de répertoire
RewriteCond %{REQUEST_FILENAME}   !-s
RewriteRule ^page\.html$          page.cgi   [T=application/x-httpd-cgi,L]

Ainsi, une requête pour page.html entraîne l'exécution interne de la page page.cgi correspondante si page.html n'existe pas ou possède une taille de fichier nulle. L'astuce réside ici dans le fait que page.cgi est un script CGI qui (en plus de STDOUT) écrit sa sortie dans le fichier page.html. Une fois le script exécuté, le serveur sert la page page.html fraîchement générée. Si le webmaster veut actualiser les contenus, il lui suffit de supprimer le fichier page.html (le plus souvent via une tâche de cron).

top

Actualisation automatique d'un document

Description :

Lorsque nous créons une page web complexe, ne serait-il pas souhaitable que le navigateur web actualise automatiquement la page chaque fois que nous en sauvegardons une nouvelle version à partir de notre éditeur ? Impossible ?

Solution :

Non ! Nous allons pour cela combiner la fonctionnalité MIME multipart, la fonctionnalité NPH du serveur web et la puissance de mod_rewrite pour la manipulation d'URLs. Tout d'abord, nous définissons une nouvelle fonctionnalité pour les URLs : l'ajout de :refresh à toute URL fait que la 'page' est actualisée chaque fois que la ressource est mise à jour dans le système de fichiers.

RewriteRule   ^(/[uge]/[^/]+/?.*):refresh  /interne/cgi/apache/nph-refresh?f=$1

Nous appelons maintenant cette URL

/u/foo/bar/page.html:refresh

ce qui entraîne en interne l'invocation de l'URL

/interne/cgi/apache/nph-refresh?f=/u/foo/bar/page.html

Il ne reste plus qu'à écrire le script CGI. Bien que l'on écrive habituellement dans ces cas "laissé à la charge du lecteur à titre d'exercice", ;-) je vous l'offre, aussi.

#!/sw/bin/perl
##
##  nph-refresh -- script NPH/CGI pour l'actualisation automatique de
##  pages
##  Copyright (c) 1997 Ralf S. Engelschall, All Rights Reserved.
##
$| = 1;

#   éclate la variable QUERY_STRING
@pairs = split(/&/, $ENV{'QUERY_STRING'});
foreach $pair (@pairs) {
    ($name, $value) = split(/=/, $pair);
    $name =~ tr/A-Z/a-z/;
    $name = 'QS_' . $name;
    $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
    eval "\$$name = \"$value\"";
}
$QS_s = 1 if ($QS_s eq '');
$QS_n = 3600 if ($QS_n eq '');
if ($QS_f eq '') {
    print "HTTP/1.0 200 OK\n";
    print "Content-type: text/html\n\n";
    print "&lt;b&gt;ERREUR&lt;/b&gt;: Aucun fichier fourni\n";
    exit(0);
}
if (! -f $QS_f) {
    print "HTTP/1.0 200 OK\n";
    print "Content-type: text/html\n\n";
    print "&lt;b&gt;ERREUR&lt;/b&gt;: Fichier $QS_f non trouvé\n";
    exit(0);
}

sub print_http_headers_multipart_begin {
    print "HTTP/1.0 200 OK\n";
    $bound = "ThisRandomString12345";
    print "Content-type: multipart/x-mixed-replace;boundary=$bound\n";
    &print_http_headers_multipart_next;
}

sub print_http_headers_multipart_next {
    print "\n--$bound\n";
}

sub print_http_headers_multipart_end {
    print "\n--$bound--\n";
}

sub displayhtml {
    local($buffer) = @_;
    $len = length($buffer);
    print "Content-type: text/html\n";
    print "Content-length: $len\n\n";
    print $buffer;
}

sub readfile {
    local($file) = @_;
    local(*FP, $size, $buffer, $bytes);
    ($x, $x, $x, $x, $x, $x, $x, $size) = stat($file);
    $size = sprintf("%d", $size);
    open(FP, "&lt;$file");
    $bytes = sysread(FP, $buffer, $size);
    close(FP);
    return $buffer;
}

$buffer = &readfile($QS_f);
&print_http_headers_multipart_begin;
&displayhtml($buffer);

sub mystat {
    local($file) = $_[0];
    local($time);

    ($x, $x, $x, $x, $x, $x, $x, $x, $x, $mtime) = stat($file);
    return $mtime;
}

$mtimeL = &mystat($QS_f);
$mtime = $mtime;
for ($n = 0; $n &lt; $QS_n; $n++) {
    while (1) {
        $mtime = &mystat($QS_f);
        if ($mtime ne $mtimeL) {
            $mtimeL = $mtime;
            sleep(2);
            $buffer = &readfile($QS_f);
            &print_http_headers_multipart_next;
            &displayhtml($buffer);
            sleep(5);
            $mtimeL = &mystat($QS_f);
            last;
        }
        sleep($QS_s);
    }
}

&print_http_headers_multipart_end;

exit(0);

##EOF##
top

Hébergement virtuel de masse

Description :

La fonctionnalité <VirtualHost> d'Apache est intéressante et fonctionne de manière satisfaisante jusqu'à quelques douzaines de serveurs virtuels. Par contre, si vous êtes un FAI et devez héberger des centaines de serveurs virtuels, cette méthode n'est pas optimale.

Solution :

Pour fournir cette fonctionnalité avec mod_rewrite, on fait correspondre à notre espace de nommage la page web ou même le répertoire complet distants en utilisant la fonctionnalité Mandataire (drapeau [P]) :

##
##  vhost.map
##
www.vhost1.dom:80  /chemin/vers/racine-doc/vhost1
www.vhost2.dom:80  /chemin/vers/racine-doc/vhost2
     :
www.vhostN.dom:80  /chemin/vers/racine-doc/vhostN
##
##  httpd.conf
##
    :
#   utilisation du nom d'hôte canonique pour les redirections, etc...
UseCanonicalName on

    :
#   ajout du serveur virtuel en tête du format CLF
CustomLog  /chemin/vers/access_log  "%{VHOST}e %h %l %u %t \"%r\" %>s %b"
    :

#   activation du moteur de réécriture pour le serveur principal
RewriteEngine on

#   définition de deux tables de correspondances : une première pour
#   corriger les URLs et une seconde qui associe les serveurs virtuels
#   disponibles avec leurs racines des documents correspondantes.
RewriteMap    lowercase    int:tolower
RewriteMap    vhost        txt:/chemin/vers/vhost.map

#   et enfin sélection proprement dite du serveur virtuel approprié via
#   une seule règle longue et complexe :
#
#   1. on s'assure de ne pas sélectionner un hôte virtuel pour les
#   adresses communes

RewriteCond   %{REQUEST_URI}  !^/adresse-commune1/.*
RewriteCond   %{REQUEST_URI}  !^/adresse-commune2/.*
    :
RewriteCond   %{REQUEST_URI}  !^/adresse-communeN/.*
#
#   2. on vérifie que l'on dispose bien d'un en-tête Host, car
#   actuellement, cette méthode ne peut faire de l'hébergement virtuel
#   qu'avec cet en-tête
RewriteCond   %{HTTP_HOST}  !^$
#
#   3. mise en minuscules du nom d'hôte
RewriteCond   ${lowercase:%{HTTP_HOST}|NONE}  ^(.+)$
#
#   4. recherche ce ce nom d'hôte dans vhost.map et
#      enregistrement de celui-ci seulement s'il s'agit d'un chemin
#      (et non "NONE" en provenance de la condition précédente)
RewriteCond   ${vhost:%1}  ^(/.*)$
#
#   5. nous pouvons enfin faire correspondre l'URL avec la racine des
#   documents correspondant au serveur virtuel approprié et enregistrer
#   ce dernier à des fins de journalisation
RewriteRule   ^/(.*)$   %1/$1  [E=VHOST:${lowercase:%{HTTP_HOST}}]
    :
top

Interdiction d'hôtes

Description :

Comment interdire l'accès à notre serveur à une liste d'hôtes ?

Solution :

Pour Apache >= 1.3b6 :

RewriteEngine on
RewriteMap    hôtes-interdits  txt:/chemin/vers/hôtes-interdits
RewriteCond   ${hôtes-interdits:%{REMOTE_HOST}|NOT-FOUND} !=NOT-FOUND [OR]
RewriteCond   ${hôtes-interdits:%{REMOTE_ADDR}|NOT-FOUND} !=NOT-FOUND
RewriteRule   ^/.*  -  [F]

Pour Apache <= 1.3b6 :

RewriteEngine on
RewriteMap    hôtes-interdits  txt:/chemin/vers/hôtes-interdits
RewriteRule   ^/(.*)$ ${hôtes-interdits:%{REMOTE_HOST}|NOT-FOUND}/$1
RewriteRule   !^NOT-FOUND/.* - [F]
RewriteRule   ^NOT-FOUND/(.*)$ ${hôtes-interdits:%{REMOTE_ADDR}|NOT-FOUND}/$1
RewriteRule   !^NOT-FOUND/.* - [F]
RewriteRule   ^NOT-FOUND/(.*)$ /$1
##
##  hosts.deny
##
##  ATTENTION! Ceci est une table de correspondances, pas une liste,
##  même si on l'utilise en tant que telle. mod_rewrite l'interprète
##  comme un ensemble de paires clé/valeur ; chaque entrée doit donc
##  au moins posséder une valeur fictive "-".
##

193.102.180.41 -
bsdti1.sdm.de  -
192.76.162.40  -
top

Interdiction du mandataire

Description :

Comment interdire l'utilisation du mandataire d'Apache pour un certain hôte, ou même seulement pour un utilisateur de cet hôte ?

Solution :

Nous devons tout d'abord nous assurer que mod_rewrite arrive après(!) mod_proxy dans le fichier de configuration lors de la compilation du serveur web Apache. De cette façon, il est appelé avant mod_proxy. Nous pouvons ensuite définir cette règle pour une interdiction dépendant de l'hôte :

RewriteCond %{REMOTE_HOST} ^hôte-à-rejeter\.mon-domaine\.com$
RewriteRule !^http://[^/.]\.mon-domaine.com.*  - [F]

...et celle-ci pour une interdiction dépendant de utilisateur@hôte :

RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST}  ^utilisateur-à-
rejeter@hôte-à-rejeter\.mon-domaine\.com$
RewriteRule !^http://[^/.]\.mon-domaine.com.*  - [F]
top

Variante particulière d'authentification

Description :

On a parfois besoin d'une authentification très particulière, par exemple une authentification qui vérifie la présence d'un utilisateur dans une liste explicitement définie. Seuls ceux qui sont présents dans la liste se voient accorder un accès, et ceci sans avoir à s'identifier/authentifier (comme c'est le cas avec une authentification de base via mod_auth).

Solution :

On définit une liste de conditions de réécriture pour interdire l'accès à tout le monde, sauf aux utilisateurs autorisés :

RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^ami1@client1.quux-corp\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^ami2@client2.quux-corp\.com$
RewriteCond %{REMOTE_IDENT}@%{REMOTE_HOST} !^ami3@client3.quux-corp\.com$
RewriteRule ^/~quux/seulement-pour-les-amis/      -                                 [F]
top

Redirection basée sur le référent

Description :

Comment écrire un programme souple qui redirige certaines URLs en se basant sur l'en-tête HTTP "Referer", et peut être configuré avec autant de pages de référence que l'on veut ?

Solution :

On utilise le jeu de règles vraiment astucieux suivant :

RewriteMap  deflector txt:/chemin/vers/deflector.map

RewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER}} ^-$
RewriteRule ^.* %{HTTP_REFERER} [R,L]

RewriteCond %{HTTP_REFERER} !=""
RewriteCond ${deflector:%{HTTP_REFERER}|NOT-FOUND} !=NOT-FOUND
RewriteRule ^.* ${deflector:%{HTTP_REFERER}} [R,L]

... en association avec la table de réécriture correspondante :

##
##  deflector.map
##

http://www.mauvais-sujets.com/mauvais/index.html    -
http://www.mauvais-sujets.com/mauvais/index2.html   -
http://www.mauvais-sujets.com/mauvais/index3.html   http://quelque-part.com/

Les requêtes sont redirigées vers la page de référence (lorsque la valeur correspondant à la clé extraite de la table de correspondances est égale à "-"), ou vers une URL spécifique (lorsqu'une URL est définie dans la table de correspondances comme second argument).

Langues Disponibles: