Nejdříve bych rád předeslal, že tento článek si neklade za cíl vymezit jediné možné řešení konkrétního problému a zcela jistě se najdou jiné způsoby, jak dosáhnou téhož. Použití funkcí mod_rewrite má ale jednu velkou výhodu, a tou je přítomnost tohoto modulu přímo v distribuci Apache. Samozřejmě tím ještě není řečeno, že bude zkompilován nebo zapnut, ale i tak je naděje, že bude k dispozici, poněkud vyšší, než třeba u specializovaných modulů. Tolik jenom na úvod (minule jsem na podobný odstaveček zapomněl a pěkně jsem to schytal – teď už vím, proč je na konci titulků každého filmu ta větička o „podobnosti čistě náhodné“ :)
Ale teď už pojďme na věc.
Příklad druhý: adresářový virtuální hosting
Nedávno měl jeden náš zákazník ne zcela obvyklý požadavek: chtěl mít kontrolu nad zakládáním domén třetího řádu ve své doméně řádu druhého. Samozřejmě jsem už o podobných „vychytávkách“ slyšel, ale poprvé jsem to zakusil na svou kůži. Protože šlo o ojedinělý případ a specializované řešení se mi zdálo zbytečně komplexní, začal jsem si zase po delší době hrát s mod_rewrite. A výsledek? Jak za chvíli uvidíte, jde to.
Konec planého povídání, pojďme se rovnou podívat, jak na to. Nejdříve je třeba říct, že první krok je konfigurace DNS. Je třeba přidat „hvězdičkový“ záznam, který zajistí převod libovolného (jinak neurčeného) jména na IP adresu. Konkrétní provedení samozřejmě záleží na zvoleném DNS serveru a lze dokonce předpokládat, že některé servery hvězdičkovou syntaxi vůbec neumožňují (názory na ní se také poněkud různí, existují jak zastánci, tak zarytí odpůrci – o rozlousknutí tohoto problému se snažit nemíním). V případě Bindu (rozhodně ve verzi 8.2.2, za starší nemohu ručit) to lze udělat řádkem v zónovém souboru podobným tomuto:
* IN CNAME muj.server.cz.
Tolik ke konfiguraci DNS a teď se pojďme podívat na nastavení Apache. Vše potřebné obstará těchto několik řádků v konfiguraci virtuálního serveru:
ServerAlias *.domena.cz RewriteEngine On RewriteMap lowercase int:tolower RewriteCond %{HTTP_HOST} ^([a-zA-Z0-9]*).domena.cz$ RewriteRule ^/(.*)$ %{DOCUMENT_ROOT}/${lowercase:%1}/$1 [L] RewriteCond %{HTTP_HOST} ^domena.cz$ RewriteRule ^/(.*)$ %{DOCUMENT_ROOT}/www/${lowercase:$1} [L]
První řádek ještě nenastavuje mod_rewrite, jde pouze o jakýsi doplněk k „hvězdičkovému“ nastavení DNS. To umožňuje převádět libovolné jméno v dané doméně na IP adresu a takto formulovaný ServerAlias se zase postará o to, aby se Apache věděl, ke kterému virtuálu jména patří. Zapomněl jsem podotknout, že se v tomto případě bavíme o tzv. name-based, nikoliv o IP-based virtuálních serverech (více viz dokumentace k Apachi).
Další řádky se již týkají mod_rewrite:
- Ti, kdo četli první díl této minisérie vědí, že první řádek slouží k zapnutí přepisovacího enginu a pokud je tento již zapnut, například v globální konfiguraci, je možné jej vynechat.
- Hned na druhém řádku narážíme na novinku, se kterou jsme se doposud nesetkali. Direktiva RewriteMap umožňuje vytvářet tzv. přepisovací mapy (rewriting map), které pak lze používat přímo v přepisovacích pravidlech. Tyto mapy umožňují například zaměňovat text v adrese pomocí definičního souboru se seznamem řetězců a jejich náhrad nebo dokonce filtrovat URL pomocí externího programu. V tomto případě je ale použití daleko prozaičtější, jde pouze o namapování interní funkce tolower pro převod řetězce na malá písmena na slovo lowercase. Použití této mapy uvidíme dále.
- Na třetím řádku je další novinka. Jak jsem již minule zmínil, přepisování je možné řídit podmínkami – k tomu právě slouží direktiva RewriteCond. Každá podmínka přebírá dva parametry, text a srovnávací vzorek, a platí, že následující pravidlo RewriteRule se provede jenom tehdy, je-li podmínka platná (tedy vyhovuje-li řetězec vzorku). V našem případě chceme aplikovat pravidlo jenom tehdy, je-li obsah proměnné HTTP_HOST ve tvaru neco.domena.cz. Navíc si pro jistotu ohlídáme, zda jsou před první tečkou pouze písmena a číslice, neboť právě tato část bude použita dále k určení adresáře a my jistě nechceme, aby někdo dělal pokusy s všelijakými speciálně formulovanými požadavky ve snaze získat od serveru něco, co by mělo zůstat utajeno.
Na podmínkách je zajímavá ještě jedna věc: podobně, jako přímo v rámci pravidla, je možné se zpětně odkazovat na části řetězce podle regulárního výrazu ve srovnávacím vzorku. Proto jsme ohraničili první část jména serveru závorkami – budeme ji ještě potřebovat.
- A na řadu přichází staré známé přepisovací pravidlo. Nebudu jej podrobně rozebírat, to jsem udělal již v první části minisérie, zaměřím se pouze na odlišnosti od minulého příkladu. Nové prvky jsou zde dva, nicméně spojují se nám v jeden řetězec „${lowercase:%1}“ v druhém parametru pravidla. Konstrukce ${} slouží právě k odkazu na dříve definovanou přepisovací mapu – zde tedy voláme funkci pro převod řetězce na malá písmena. Tím řetězcem je pak ta část proměnné HTTP_HOST, kterou jsme si ohraničili závorkami v předchozí podmínce, protože pomocí řetězce %N se můžeme odkazovat na N-tý prvek regulárního výrazu z RewriteCond (neplést s odkazováním na regulární výraz z prvního parametru RewriteRule – pro tyto účely se používá řetězec $N). Tímto způsobem se sestaví nové URL, které sestává z cesty určené nastavením DocumentRoot, adresáře, který odpovídá první části požadovaného jména serveru (po první tečku) a zbytku URL.
- Další dva řádky v podstatě dělají totéž, ovšem pro případ, kdy je požadavek vznesen přímo na adresu domena.cz, tedy bez domény třetího řádu ve jménu. V našem příkladu je každý takový požadavek přesměrován natvrdo do podadresáře www, aby byl obsah vrácené stránky stejný, jako v případě volání www.domena.cz. Toto pravidlo s podmínkou je možné vypustit a pak bude volání domena.cz vracet obsah určený konfigurační direktivou DocumentRoot.
Domnívám se, že všem, kdo rozumí regulárním výrazům, by měla být teď tento příklad v podstatě jasný, ale pro jistotu si ještě rozepíšeme, co se bude dít v konkrétním případě. Představme si, že uživatel napíše do adresního řádku svého prohlížeče adresu http://DOC.domena.cz/manual/. Browser zformuluje tento (poněkud zjednodušený) požadavek:
GET /manual/ HTTP/1.1 Host: DOC.domena.cz
Apache jej přijme a díky hvězdičkové zápisu v ServerAlias jej přířadí našemu virtuálnímu serveru. V něm je již zapnut mod_rewrite a vytvořená přepisovací mapa. Ke slovu tedy přijde první podmínka RewriteCond. Ta vyhovuje, neboť proměnná HTTP_HOST (do níž je dosazen obsah hlavičky Host z požadavku) vyhovuje výrazu ^([a-zA-Z0–9]*).domena.cz$. Navíc se v tuto chvíli uchová pro pozdější zpětné odkazování část vyhovující výrazu ([a-zA-Z0–9]*), tedy slovo „DOC“.
Podmínka vyhovuje, je tedy možné přejít na vykonání pravidla. Na URL ve tvaru /manual/ se aplikuje výraz ^/(.*)$, který vyhovuje a opět se uloží pro zpětné odkazování jeho jedna část – vlastně vše, vyjma první lomítko, což dělám pouze pro přehlednost zápisu druhého parametru. Tento druhý parametr tvoří nové, přepsané URL. Za %{DOCUMENT_ROOT} se dosadí reálná cesta dle konfigurace Apache, za ${lowercase:%1} slovo „DOC“, ovšem převedené na malá písmena, a za $1 pak již zmíněné URL bez prvního lomítka. V praxi by to mohlo být třeba toto:
/www/domena/doc/manual/
a Apache tedy vrátí obsah tohoto adresáře (nebo třeba jeho indexu).
Toť vše. Nyní by již mělo být opravdu všem jasné, jak přepisování funguje i jak tvořit složitější (a všelijak podmíněná) pravidla. Je teď na vás, jestli pro tento modul najdete uplatnění nebo jestli si naopak řeknete, že je lepší dát od něj ruce pryč.
P.S. Onen zákazník si to nakonec rozmyslel a nic z toho se do praxe nedostalo. No nic, alespoň jsem se něco přiučil a doufám, že i pro vás je tento článek, který díky tomu vznikl, alespoň trochu přínosný.