htaccess – mod_rewrite, hezké URL, přesměrování

Apache je nejpopulárnější webový server. Existuje k němu velké množství doplňků (modulů), které rozšiřují jeho možnosti a funkčnost. Obsahem tohoto textu je popis zejména jednoho z nich – a to mod_rewrite.

Mod_rewrite umožňuje za běhu serveru přepisovat URL adresy přicházejících požadavků. Překlad se uskutečňuje zpracováváním pravidel, která jsou zapsaná regulárními výrazy. Kromě pravidel se překlad může řídit i dalšími podmínkami.

Překlad URL adresy může být následující:
www.example.com/clanek/pocasi -> www.example.com/clanek.php?id=12342

Přepisování

URL adresu můžeme přepsat dvěma způsoby – přesměrováním nebo podstrčením.

  • Přesměrování – klientský prohlížeč obdrží HTTP odpověď redirect (HTTP kód 301 nebo 302) na nové umístění. Uživatel tedy zjistí, že se URL změnilo.
  • Podstrčení – server vrátí prohlížeči obsah „nové“ URL adresy, ale neinformuje ho o tom. Uživatel nezjistí, že k podstrčení došlo. Obsah může pocházet ze stejného webu, z jiného webu na stejném serveru nebo z úplně jiného serveru.

Podstatné je, kdy k přepisu URL adresy vlastně dochází. Děje se tak přijetí HTTP požadavku serverem (po parsování požadavku, hlavičky atd.), ale ještě předtím, než se požadovaná URL začne interpretovat (tj. než se začne hledat soubor, který se má uživateli poslat, nebo než se zavolá skriptovací engine, který má soubor spustit). Interpretuje se až přepsaná URL. Z toho vyplývá, že můžeme libovolným způsobem ovlivnit, co nakonec uživatel do svého prohlížeče dostane.

Na webhostingu WEDOS lze pravidla mod_rewrite zapisovat do souboru .htaccess.

Dříve než začneme psát přepisovací pravidla, je nutné mod_rewrite zapnout pomocí:

RewriteEngine on

RewriteBase

Pomocí této direktivy nastavíme výchozí adresář pro cíle všech přesměrování, od kterého se pak odvozují relativní cesty.

RewriteBase path

RewriteRule

Tato direktiva označuje samotné přepisovací pravidlo a má následující syntaxi:

RewriteRule Pattern Substitution [flags]

Pattern je regulární výraz, kterým specifikujeme, kdy se má pravidlo provést. Syntaxe regulárního výrazu je obdobná jako v Perlu, navíc můžeme používat negaci (znak „!“ na začátku). Pattern se porovnává („matchuje“) s URL adresou, kterou klientský prohlížeč požaduje. Pokud pattern odpovídá, provede se přepis na substitution.

Substitution – nová URL adresa stránky, která se klientovi opravdu zobrazí. Adresa může být buďto absolutní (začínající http nebo https) či relativní. Relativní cesta začínající lomítkem se odvozuje od rootu virtualhostu, v opačném případě od aktuálního adresáře (resp. od RewriteBase).

substitution se také můžeme odkazovat na „namatchované“ části regexpu (části ohraničené kulatými závorkami), systémové proměnné nebo mapovací funkce (vše bude vysvětleno dále v tomto textu). Odkaz na n-tou část patternu provedeme voláním $N. Je možno odkazovat se i na část patternu z podmínky RewriteCond (viz. dále) pomocí %N. Odkazovat na systémovou proměnnou se můžeme pomocí %{VARNAME} a mapovací funkci pomocí ${mapname:key|default}.

substitution lze také pracovat s parametry URL adresy. Jestliže substitution neobsahuje znak otazníku, na konec změněné URL se doplní původní parametry. Uvedením pouhého otazníku na konci se všechny parametry smažou. Za otazník můžeme napsat nové parametry, i zde můžeme použít namatchované části z patternu ($N a %N). Pokud navíc uvedeme příznak QSA (viz. dále), doplní se k novým parametrům i ty původní.

Pokud je v konfiguraci uvedeno více klauzulí RewriteRule, zpracovávají se v pořadí, v němž jsou uvedeny. Jakmile pravidlo URL adrese vyhovuje, provede se přepis a další pravidla pracují s již přepsanou adresou (nikoliv s původní). Může tedy dojít k více přepisům, kdy se URL postupně transformuje. Zde je třeba si dát pozor na zacyklení. Pravidla se vyhodnocují od začátku do konce stále znovu dokud došlo k nějakému přepisu.

[flags] – nepovinné parametry. Mezi nejpoužívanější patří následující:

  • F – zakáže URL, vrací HTTP odpověď 403 (forbidden)
  • L – toto je poslední pravidlo, další se nebude provádět
  • NC – case-insensitive výraz
  • P – force proxy, vynucení zpracování cíle přes mod_proxy
  • QSA – na konec přidat původní URL parametry
  • R[=code] – přesměrovat na novou URL
    • 301 – Moved Permanently
    • 302 – Found (Moved Temporarily) – výchozí

Základní příklady

Příklad 1 – obyčejné přesměrování

RewriteRule puvodni-stranka1\.html nova-stranka1.html [R]

Jedná se o jednoduché přesměrování URL jedné stránky na jinou stránku na stejném webu. Typicky se použije v případě, kdy se změní adresa konkrétní stránky a chceme, aby klienti začali používat adresu novou. Zde je třeba dbát na to, že tečky a jiné znaky v regulárním výrazu je potřeba oescapovat, protože tečka v regulárním výrazu znamená libovolný znak. Druhý parametr již regulární výraz není, tam naopak tečku oescapovat nesmíme. Také zde musíme uvést příznak R, jinak by se implicitně provedlo podstrčení a nikoliv přesměrování (v tomto případě chceme, aby se uživatel dozvěděl novou adresu).

Příklad 2 – obyčejné podstrčení

RewriteRule puvodni-stranka2\.html nova-stranka2.html

Podobná situace, zde však chybí příznak R pro přesměrování. Protože druhý parametr je relativní adresa, implicitně se provede podstrčení. Uživateli se v prohlížeči URL adresa nezmění, do prohlížeče se mu však pošle obsah té nové.

Příklad 3 – přesměrování jinam

RewriteRule stranka\.html http://www.example.com/jinam.html

Zde dojde k implicitnímu přesměrování, protože druhým parametrem je absolutní URL. Nemusíme tedy uvádět [R].

Příklad 4 – zákaz stránek

RewriteRule ^(.*/)?CVS/.* - [F]
RewriteRule ^(.*/)?\.svn/.* - [F]

Příznak F se používá pro zákaz přístupu k některým zdrojům na webu. V tomto případě nechceme, aby se kdokoliv přes prohlížeč dostal k obsahu adresářů nástrojů CVS nebo Subversion. Není podstatné, zda takový adresář nebo nějaký soubor v něm existuje. Podstatné je, že URL adresa vyhovuje danému regulárnímu výrazu. U tohoto příznaku nemá smysl uvádět URL adresu ve druhém parametru, ale kvůli dodržení počtu parametrů je nutné napsat pomlčku.

Příklad 5 – nastavení MIME-type dokumentu

RewriteRule ^(.+\.php)s$ $1 [T=application/x-httpd-php-source]

Toto je trik, jak nabídnout návštěvníkům možnost prohlédnout si zdrojové kódy všech souborů s koncovkou php. Při požadavku na soubor s koncovkou phps se uživateli pošle do prohlížeče stejnojmenný soubor s koncovkou php se speciálním MIME typem (to je zejména proto, aby se php soubor neinterpretoval, tj. nespustil, ale zobrazil se jeho obsah).

Příklad 6 – jazyk schovaný v URL

RewriteRule ^cs/(.*)$ $1?lang=cs [QSA]
RewriteRule ^en/(.*)$ $1?lang=en [QSA]

Tímto způsobem si můžeme v URL adrese přenášet důležité parametry (v tomto případě kód jazyka), pokud se nám nechce přenášet jako skutečný parametr na konci adresy. Nesmíme zapomenout na příznak [QSA], aby se za parametr z jazykem připojily a případné další parametry, které byly v původní URL adrese.

Příklad 7 – přesměrování všeho

RewriteRule (.*) http://www.example.com/

RewriteCond

Pomocí direktiv RewriteCond můžeme stanovit jednu nebo více podmínek, které musí být splněny, aby se aplikovalo následující pravidlo RewriteRule.

RewriteCond TestString CondPattern [flags]

TestString – testovaný řetězec, který budeme matchovat s CondPattern. Může obsahovat systémové proměnné nebo mapovací funkce.

CondPattern – může být standardní regulární výraz s negací. Druhou možností je obyčejný řetězec písmen se speciálním významem. Můžeme využívat porovnání (<,>,= ), kdy dojde k lexikografickému porovnání s TestStringem. Dále můžeme využít následující příznaky, které vždy testují TestString.

  • -d – je TestString adresářem?
  • -f – je TestString souborem?
  • -s – je TestString neprázdným souborem?
  • -l – je TestString symbolickým linkem?
  • -x – má TestString executable právo?

Podmínka je tedy splněna, pokud TestString vyhovuje regulárnímu výrazu nebo jiné podmínce v CondPattern. Pokud je před RewriteRule uvedeno více klauzulí RewriteCond, musí být k provedení přepisu splněny všechny, tedy jako kdyby měly mezi sebou spojku AND (to lze změnit příznakem OR).

[Flags] – u podmínky máme možnost využít 2 příznaky:

  • NC – case-insensitive výrazy
  • OR – následující podmínka je ve vztahu „OR“. Implicitně jsou podmínky ve vztahu „AND“

RewriteRule (v Substitution) i RewriteCond (v TestString) lze používat systémové proměnné, zapisují se ve formátu %{NAZEV_PROMENNE}. K dispozici jsou například:

  • HTTP hlavičky – HTTP_USER_AGENT, HTTP_REFERER, HTTP_HOST, …
  • informace o spojení a požadavku – REMOTE_ADDR, REQUEST_METHOD, QUERY_STRING, …
  • serverové proměnné – DOCUMENT_ROOT, SERVER_NAME, …
  • datum a čas – TIME, TIME_YEAR, TIME_HOUR, TIME_WDAY, …
  • %{ENV:variable} – proměnné prostředí
  • %{SSL:variable} – parametry SSL spojení
  • %{HTTP:header} – libovolná HTTP hlavička

Pokročilé příklady

Příklad 8 – existence souboru

# jen pokud požadovaný soubor opravdu neexistuje
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^forum/topic-([0-9]+)\.html$ forum-topic.php?id=$1 [QSA,L]

Podstrčení jiné stránky se provede jen v případě, že původně požadovaný soubor neexistuje. Toto je také příklad pravidla pro vytváření „hezkých“ URL adres, kdy si parametr (např. číslo tématu diskuze) schováme přímo do názvu souboru a nevyskytuje se jako URL parametr. V tomto případě je potřeba u pravidla uvést příznak QSA, který na konec nové adresy doplní i původní URL parametry.

Příklad 9 – výchozí skript

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)\.html$ /unipage.php?page=$1 [L,QSA]

Pokud požadovaný soubor doopravdy existuje, je toto pravidlo přeskočeno a soubor se odešle do prohlížeče. Jestliže soubor neexistuje, předá se požadavek univerzálnímu skriptu, který může podle URL adresy najít příslušný obsah např. v databázi anebo oznámit chybu (způsob jak zachytávat a řešit chyby 404).

Příklad 10 – spouštění .html souborů jako PHP skript

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)\.html$ $1.php [L]

Pravidlo slouží k tomu, abychom mohli v URL adrese uvádět soubory s příponou .html, ale ve skutečnosti je na serveru mít s příponou .php, díky čemuž se soubor bude spouštět jako PHP skript, ale návštěvníkům se bude jevit jako statický .html soubor. Pravidlo se neuplatní, pokud daný .html soubor existuje. Existence .php souboru se neřeší, když neexistuje, skončí to obvyklou chybou 404.

Příklad 11 – přesměrování podle domény

# example.com -> www.example.com
RewriteCond %{HTTP_HOST} ^example\.com$
RewriteRule (.*) http://www.example.com/$1 [R=301]

Tato podmínka a pravidlo se postarají o to, že návštěvník, který přijde přes domény bez „www“, bude ihned přesměrován na stejnou stránku, ale na doméně s „www“. To je žádoucí, protože některé vyhledávače penalizují stránky za to, že je stejný obsah dostupný pod více URL adresami. Jako HTTP kód je zde uveden 301, který říká, že se jedná o trvalé přesměrování.

Příklad 12 – přesměrování na HTTPS

RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Toto nám zase zajistí to, že je uživatel vždy přesměrován na zabezpečené připojení přes SSL.

Příklad 13 – různé verze stránek podle data/času

RewriteCond  %{TIME_HOUR}%{TIME_MIN}  >0700
RewriteCond  %{TIME_HOUR}%{TIME_MIN}  <1900
RewriteRule  ^foo\.html$              foo.day.html
RewriteRule  ^foo\.html$              foo.night.html

Mnohá kouzla lze dělat podle aktuálního data a času. Využíváme toho, že v RewriteCond lze hodnoty porovnávat lexikograficky a že složky data a času jsou zarovnávány na stejnou délku (doplňovány zleva nulami). V tomto příkladu tedy návštěvníkovi podstrčíme verzi stránky podle denní doby. Zde je podstatné to, že uvedené 2 podmínky se vztahují k nejbližšímu následujícímu pravidlu a je mezi nimi spojka „AND“. Druhé pravidlo nemá podmínku žádnou. Pokud se vykoná první pravidlo, druhé pravidlo již „nenamatchuje“, protože se dále pracuje s již pozměněnou URL.

Příklad 14 – různé verze stránek podle prohlížeče

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]

Podobný příklad pro podstrkávání různých verzí stránek na základě nějaké vlastnosti prohlížeče návštěvníka. V tomto případě máme k dispozici jednodušší verzi stránek pro starší prohlížeče. Podmínky zde mají mezi sebou spojku „OR“. Druhé pravidlo funguje jako výchozí, pokud nejsou podmínky pro první pravidlo splněny.