La gestion des changements d’adresse des pages d’un site Web est quelque chose d’important dans la vie d’un site.
Pour cela, il existe principalement un système de redirection HTTP sous Apache, mais celui-ci présente plusieurs inconvénients comme le fait de n’être pas portable sous d’autres serveurs Web comme IIS, d’alourdir rapidement un site, et d’être difficilement testable ou portable sur un serveur avec une adresse différente. Dans le cas du serveur Microsoft IIS, la gestion des redirections est quasi inexistante.
En réponse à cela, je propose sur cette page un système de gestion des redirections basé sur les pages d’erreur 404 personnalisées. C’est-à-dire que le système de redirection n’est activé que lorsque qu’une ressource n’a pas été trouvée, ce qui supporte une montée en charge supérieure. Le système est compatible avec les serveurs Web Apache, Microsoft IIS, et d’autres. Une implémentation est déjà disponible en PHP, et le portage dans un autre langage est simple (Microsoft ASP.NET envisagé).
En construction
Lorsqu’une page n’est pas trouvée, plutôt que d’afficher tout de suite une page d’erreur 404, un script va chercher dans une liste de règles si une redirection est disponible. Si une règles est disponible, une redirection HTTP sera envoyée au client ; sinon, une page d’erreur 404 sera affichée.
En admettant que vous placez le script de redirection dans /erreurs/redirection-404.php, celui-ci va par défaut chercher les règles de redirections dans /erreurs/404//404.txt, c’est à dire dans les fichiers 404.txt situés dans le dossier /erreurs/404/ ou ses sous-répertoires, comme l’illustre l’arborescence suivante :
Les noms des dossiers et sous-dossiers doivent correspondre à des dossiers du site actuel ou à des anciens dossiers à rediriger.
La profondeur et la largeur de l’arborescence ne sont pas limitées.
Faire des sous-répertoires n’est pas obligatoire et toutes les règles de redirections peuvent être dans /erreurs/404/404.txt.
Mais cette possibilité d’arborescence permet d’éviter d’avoir un seul gros fichier 404.txt, permet de mieux structurer des règles ensemble,
et permet d’augmenter les performances en évitant d’avoir trop de règles à lire dans un même fichier.
Un sous-répertoire est typiquement créé lorsqu’il y a plusieurs règles de redirection le concernant.
Dans le nom des dossiers, les caractères spéciaux non-acceptables dans une URL (en gros tout ce qui n’est pas a-z_.0-9-) doivent être %-encodés.
En particulier, une espace sera encodée %20 (voir exemple ci-dessus : dossier%20avec%20espaces/).
Lorsqu’un sous-dossier existe (ce qui n’est pas obligatoire), toutes les règles de redirection le concernant doivent se trouver dans son
fichier 404.txt.
Au final, pour une requête de redirection donnée, un seul fichier 404.txt sera lu :
celui le plus précis possible dans l’arborescence de /erreurs/404//.
La syntaxe est expliquée juste après. Considérez déjà la règle de redirection suivante :
404.txt permanent /ancien-dossier/sous-dossier/fichier\.html /nouveau-dossier/fichier.html
Cette règle pourra être placée dans le fichier /erreurs/404/404.txt, ou bien /erreurs/404/ancien-dossier/404.txt, ou encore /erreurs/404/ancien-dossier/sous-dossier/404.txt. Mais si le dossier /erreurs/404/ancien-dossier/ existe, cette règle ne sera pas lue si elle se trouve dans le dossier parent /erreurs/404/404.txt.
Notez que par défaut, et contrairement aux fichiers .htaccess de Apache, les fichiers 404.txt ne sont pas mélangés au reste du site (ils sont dans /erreurs/404/), et cela pour éviter d’avoir à conserver d’anciens dossiers. Néanmoins, vous pouvez personnaliser l’endroit où les fichiers 404.txt sont cherchés à la ligne 27 du script redirection-404.php, dans la variable $path.
La syntaxe utilisée pour les règles de redirection est proche de celle utilisée par l’instruction RedirectMatch de Apache ;
voici les différences majeures :
RedirectMatch (c’est-à-dire à base d’expressions régulières), et non simplement Redirect.^ du début et le $ de la fin du motif sont sous-entendus et ne doivent pas être écrits.La syntaxe d’une règle de redirection est la suivante (une règle par ligne, et les trois parties de la règle sont séparées par des tabulations ou des espaces) :
404.txt <type de redirection> <ancienne adresse en expression régulière> <nouvelle adresse>
Les différents types de redirection pris en charge sont :
permanentHTTP/1.1 301 Moved Permanently)temp ou foundHTTP/1.1 302 Found)seeotherHTTP/1.1 303 See Other)temporaryHTTP/1.1 307 Temporary Redirect)goneHTTP/1.1 410 Gone)En construction
/:a-z_.0-9-) doivent être %-encodés.
\.
En construction
$1, $2 etc. pour référencer une capture () dans l’ancienne adresse./erreurs/404/404.txt #Redirige un fichier précis permanent /ancien-dossier/sous-dossier/fichier\.html /nouveau-dossier/fichier.html #Même exemple, avec un nom de répertoire et de fichier contenant une espace (codée %20) permanent /ancien%20dossier/mon%20fichier\.html /nouveau%20dossier/mon%20fichier.html #Redirige un site complet permanent /(.*) http://nouveau-site.fr/$1 #Redirige uniquement la racine du site permanent / /dossier/ #Redirige un répertoire et tous ses sous-dossiers et fichiers permanent /ancien-dossier/sous-dossier2/(.*) /nouveau-dossier2/$1 #Même effet, en permettant aussi la redirection lorsque le / final est omis permanent /ancien-dossier/sous-dossier2(.*) /nouveau-dossier2$1 #Redirige un répertoire mais pas ses sous-dossiers ni fichiers permanent /ancien-dossier/sous-dossier2/ /nouveau-dossier2/ #Redirige un répertoire et tous ses fichiers mais pas ses sous-dossiers permanent /ancien-dossier/sous-dossier2/([^/\\]*) /nouveau-dossier2/$1 #Redirige en masse des fichiers correspondant à un motif donné, #et réutilise une partie de leur nom (avec $n) pour former la nouvelle adresse permanent /images/image([0-9]+)\.(gif|jpg) /images/image$1.png #Même effet, syntaxe de destination allégée permanent /images/image([0-9]+)\.(gif|jpg) image$1.png #Redirection temporaire #(l’adresse d’origine doit continuer à être celle utilisée et référencée) temp /raccourci /adresse/plus-compliquee/ #Indique qu’une ressource n’est plus disponible gone /dossier/ancien\.pdf
$path='./404/';$customRedirect='/';$customRedirectTimeOut=5;$defaultNewServer='';$enableExternalServer=false;
Sous Apache, l’ajout de cette page d’erreur personnalisée se fait via la directive ErrorDocument,
qui peut en particulier être utilisée dans le fichier de configuration général ./conf/httpd.conf
dans le répertoire d’installation de Apache,
ou dans un fichier /.htaccess à la racine du site Web concerné, comme illustré ci-dessous :
/.htaccess ErrorDocument 404 /erreurs/redirection-404.php
Les redirections apparaissent dans les logs d’accès Apache, (codes 301, 302, 410…), et dans les logs d’erreur :
access.log 127.0.0.1 - - [05/Jan/2008:18:50:39 +0100] "GET /ancien-dossier/ HTTP/1.1" 301 651 "-" "Mozilla" 127.0.0.1 - - [05/Jan/2008:18:50:40 +0100] "GET /nouveau-dossier/ HTTP/1.1" 200 8397 "-" "Mozilla"
error.log [Sat Jan 05 18:50:39 2008] [error] [client 127.0.0.1] File does not exist: E:/www/ancien-dossier
(Facultatif et rare): Dans le cas où vous ne voulez ou ne pouvez pas traiter les redirection sur un serveur donné, l'option suivante permet de déléguer cette tâche à un autre serveur, après avoir activé cette possibilité dans la variable $enableExternalServer, ligne 31 de redirection-404.php.
/.htaccess
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ http://example.net/erreurs/redirection-404.php?REDIRECT_URL=$1 [R=301,L]
</IfModule>
Dans IIS, l’ajout de cette page d’erreur personnalisée se fait depuis sa console d’administration (accessible entre autres depuis les outils d’administration, "Gestion de l’ordinateur", ou "Gestionnaire des services Internet").
La configuration se fait ensuite dans les propriétés du site Web, dans l’onglet “Messages d’erreur personnalisés” (IIS 5 & 6) ou à partir de l’icone “Pages d’erreurs” (IIS 7). Après avoir sélectionné la ligne du code d’erreur 404, cliquer sur le bouton ou le lien “Modifier”, puis entrer une adresse de type URL indiquant le chemin du script redirection-404.php à partir de la racine du site Web comme par exemple /erreurs/redirection-404.php
Dans IIS 7 (voir capture d’écran ci-dessus), le lien “Modifier les paramètres de fonction…” (à droite) permet entre autres de spécifier si l’on souhaite utiliser cette page d’erreur personnalisée y compris lorsqu’on se connecte en local (localhost), ce qui est désactivé par défaut.
redirection-404.php
<?php
/*
Script PHP pour gérer les redirections HTTP. À utiliser en tant que page d’erreur 404 personnalisée.
http://alexandre.alapetite.net/doc-alex/redirection-404/
*/
//Constantes
$path=(empty($_SERVER['SCRIPT_FILENAME']) ? '' : dirname($_SERVER['SCRIPT_FILENAME'])).'/404/'; //Root of the redirection structure (404.txt files)
$customRedirect='/'; //To specify an optional custom HTML redirection after the error message
$customRedirectTimeOut=5; //Time-out in second before redirection to the optional above address
$defaultNewServer=''; //Change the server of the new relative addresses: 'http://example.net:80'
$enableExternalServer=false; //Allow this server to receive errors from another server
//Ancienne adresse
$oldUrl='';
if ($enableExternalServer&&isset($_GET['REDIRECT_URL'])) $oldUrl=substr($_GET['REDIRECT_URL'],0,1024); //Pour la redirection depuis un autre serveur
elseif (!empty($_SERVER['REQUEST_URI'])) $oldUrl=substr($_SERVER['REQUEST_URI'],0,1024); //Apache, IIS6
elseif (!empty($_SERVER['QUERY_STRING'])) $oldUrl=substr($_SERVER['QUERY_STRING'],0,1024); //IIS5
else $oldUrl='/_unknown_';
if (($sc=strpos($oldUrl,';'))!==false) $oldUrl=trim(substr($oldUrl,++$sc)); //IIS
$oldUrlParsed=parse_url($oldUrl);
$oldPath=$oldUrlParsed['path'];
//Cherche le meilleur fichier 404.txt
$absolute='/';
$dirs=explode('/',$oldPath); //Pas de urldecode(), donc les caractères spéciaux doivent être %-encodés : ./404/Bonjour%20Monde/404.txt
foreach($dirs as $dir)
if (strlen($dir)>0)
{
if (is_dir($path.$dir))
{
$path.=$dir.'/';
$absolute.=$dir.'/';
}
else break;
}
$path.='404.txt';
//Cherche dans le fichier 404.txt la première correspondance pour $oldPath
$newPath='';
$httpStatus=302;
$found=false;
if (is_file($path)&&($handle=@fopen($path,'r')))
{
while (!feof($handle))
{
$line=trim(fgets($handle,4096));
if ((strlen($line)<3)||($line[0]=='#')) continue; //Commentaire ou invalide
$map=preg_split('"\s+"',$line,4);
if (count($map)<2) continue; //invalide
$mapOld=$map[1];
if ($mapOld[0]!='/') $mapOld=$absolute.$mapOld;
if (@preg_match('"^'.$mapOld.'$"iD',$oldPath)&&
((($status=$map[0])==='gone')||
((count($map)>2)&&
(strlen($newPath=@preg_replace('"^'.$mapOld.'$"iD',$map[2],$oldPath))>0))))
{
$found=true;
switch ($status)
{
case 'permanent': $httpStatus=301; break;
case 'found':
case 'temp': $httpStatus=302; break;
case 'seeother': $httpStatus=303; break;
case 'temporary': $httpStatus=307; break;
case 'gone': $httpStatus=410; break;
default: $found=false;
}
if ($found&&(strlen($newPath)>0))
{
if (!preg_match('"^(?:(?:[a-z]{3,6}:)|(?:\.\./))"i',$newPath)) //Pas de protocole URI, et pas de ../ au début
{//Quand c'est possible et que ce n'est pas déjà le cas, transforme en une URL absolue
if (empty($defaultNewServer)&&isset($_SERVER['HTTP_HOST']))
{
$defaultNewServer='http://'.$_SERVER['HTTP_HOST'];
if (isset($_SERVER['SERVER_PORT'])&&($_SERVER['SERVER_PORT']!='80'))
$defaultNewServer.=':'.$_SERVER['SERVER_PORT'];
if ($newPath[0]!=='/') //adresse relative
$newPath=rtrim(substr($oldPath,-1,1)==='/' ? $oldPath : dirname($oldPath),'/\\').'/'.$newPath;
}
$newPath=$defaultNewServer.$newPath;
}
break;
}
}
}
fclose($handle);
}
if ($found) //Redirection si une nouvelle adresse a été trouvée
{
if ($httpStatus===410)
{
header('HTTP/1.1 410 Gone');
header('Status: 410 Gone');
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ',
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'."\n",
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">'."\n",
'<head>'."\n",
'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'."\n",
'<meta http-equiv="Content-Language" content="en-GB" />'."\n",
empty($customRedirect) ? '' : '<meta http-equiv="Refresh" content="'.$customRedirectTimeOut.'; url='.$customRedirect.'" />'."\n",
'<title>410 Gone</title>'."\n",
'<meta name="language" content="en-GB" />'."\n",
'<meta name="robots" content="noindex,follow" />'."\n",
'</head>'."\n",
'<body>'."\n",
'<h1>Gone</h1>'."\n",
'<p>The requested resource <kbd>'.$oldPath.'</kbd> is no longer available on this server and there is no forwarding address. ',
'Please remove all references to this resource.</p>'."\n",
'</body>'."\n",
'</html>'."\n";
}
else
{
if (isset($oldUrlParsed['query'])) $newPath.='?'.$oldUrlParsed['query'];
$status=array(301=>'Moved Permanently',302=>'Found',303=>'See Other',307=>'Temporary Redirect');
header('Location: '.$newPath);
header('HTTP/1.1 '.$httpStatus.' '.$status[$httpStatus]);
header('Status: '.$httpStatus.' '.$status[$httpStatus]);
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ',
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'."\n",
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">'."\n",
'<head>'."\n",
'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'."\n",
'<meta http-equiv="Content-Language" content="en-GB" />'."\n",
'<meta http-equiv="Refresh" content="0; url='.$newPath.'" />'."\n",
'<title>'.$httpStatus.' '.$status[$httpStatus].'</title>'."\n",
'<meta name="language" content="en-GB" />'."\n",
'<meta name="robots" content="noindex,follow" />'."\n",
'</head>'."\n",
'<body>'."\n",
'<h1>'.$status[$httpStatus].'</h1>'."\n",
'<p>The document has moved <a href="'.$newPath.'">here</a>.</p>'."\n",
'</body>'."\n",
'</html>'."\n";
}
}
else //Message d’erreur 404
{
header('HTTP/1.1 404 Not Found');
header('Status: 404 Not Found');
echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ',
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'."\n",
'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">'."\n",
'<head>'."\n",
'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'."\n",
'<meta http-equiv="Content-Language" content="en-GB" />'."\n",
empty($customRedirect) ? '' : '<meta http-equiv="Refresh" content="'.$customRedirectTimeOut.'; url='.$customRedirect.'" />'."\n",
'<title>404 Not Found</title>'."\n",
'<meta name="language" content="en-GB" />'."\n",
'<meta name="robots" content="noindex,follow" />'."\n",
'</head>'."\n",
'<body>'."\n",
'<h1>Not Found</h1>'."\n",
'<p>The requested <abbr title="Uniform Resource Locator">URL</abbr> <kbd>'.$oldPath.'</kbd> was not found on this server.</p>'."\n",
'</body>'."\n",
'</html>'."\n";
}
?>
Ce contenu est protégé par une licence
Creative Commons Paternité - Partage des Conditions Initiales à l’Identique 2.0 France “BY-SA (FR)”
Si vous utilisez et aimez ce logiciel (surtout pour un usage professionnel), merci de considérer faire un don.
Si vous souhaitez une réponse ou si c’est pour rapporter un problème avec ce script, merci de me contacter par courriel.