Guide de réécriture des URLs - Sujets avancés - Serveur Apache HTTP

Serveur Apache HTTP Version 2.3

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

- Documentation du module
- Introduction à mod_rewrite
- Guide de réécriture - exemples utiles
- Détails techniques

## 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\

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

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

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

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

## 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]

## 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]

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

## 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]

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

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

## 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`).

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

## 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}}]
          :

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

## 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]

## 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]

## 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: