Serveur Apache HTTP Version 2.3
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.
[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.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.
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\
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
.
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
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.
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 :
L
(last) et l'absence
de chaîne de substitution ('-
') dans la
quatrième partie.!
(not) et le
drapeau C
(chain) dans la première règle de la
dernière partie.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
!
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
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
.
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
?
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##
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.
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]
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).
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]
RewriteEngine on RewriteCond /miroir/du/site-distant/$1 -U RewriteRule ^http://www\.site-distant\.com/(.*)$ /miroir/du/site-distant/$1
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.
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]
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 ?
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
:
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.
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.
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##
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.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.
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.
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é.
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.
# 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
).
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 ?
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 "<b>ERREUR</b>: 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 "<b>ERREUR</b>: 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, "<$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 < $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##
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.
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}}] :
Comment interdire l'accès à notre serveur à une liste d'hôtes ?
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 -
Comment interdire l'utilisation du mandataire d'Apache pour un certain hôte, ou même seulement pour un utilisateur de cet hôte ?
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]
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
).
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]
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 ?
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: