Maskování URL

V kapitole Maskování URL byly představeny základní principy a strategie návrhu a tvorby permalinků. Pro účely phpBASE je to otázka velice aktuální. Nativní odkazy v podobě http://www.example.com/index.php?PAGE=main/contact mají totiž do ideální podoby hodně daleko a porušují hned několik zmíněných principů. Je tedy nutné je nějakým způsobem maskovat za „veřejné“ permalinkové adresy typu http://www.example.com/kontakt. Pro dosažení jejich plné funkčnosti je potřeba vyřešit dva problémy:

Více souborů ve webovém prostoru

První možností, jak zajistit správné zpracování permalinku, je použití více souborů ve webovém prostoru. Připomeňme si, že se tam už nachází náš výchozí soubor index.php. V něm jsou definovány konfigurační direktivy pro připojení k databázi, dále cesta k jádru systému SYS_ROOT a k adresáři s moduly MOD_ROOT. Nakonec se do něj includuje hlavní systémový soubor SYS_ROOT/system.php.

Chceme-li nyní, aby se nám v prohlížeči po zadání adresy http://www.example.com/kontakty.php zobrazila stránka main/contact, stačí ve webovém prostoru vytvořit soubor kontakty.php, který přiřadí parametru PAGE příslušnou hodnotu a zavolá soubor index.php:

<?php
if (!IsSet($_REQUEST["PAGE"])) $_REQUEST["PAGE"] = "main/contact";
require_once 
"./index.php";
?>

Tento přístup má ale některá důležitá omezení. Největším je skutečnost, že jej lze použít jen pro menší množství stránek. Kdybychom například měli internetový obchod či on-line noviny a pro každý z tisíců nabízených produktů resp. publikovaných článků chtěli speciální permalinkovou adresu, nebyl by postup vytváření tisíců podobných souborů příliš efektivní.

Další problém se týká přítomnosti přípony .php v adrese. Jak už bylo zmíněno, v permalinku by přípona neměla být vůbec, a pokud výjimečně ano, měla by signalizovat formát souboru (v našem případě tedy zpravidla .html) a nikoliv technologii, kterou byl soubor vygenerován. Pro variantu bez přípony se můžeme zkusit spolehnout na vlastnost serveru Apache, který v závislosti na konfiguraci postupně sám zkouší k zadanému jménu doplňovat různé přípony podle klientem očekávaného MIME typu. U přípony .html zase můžeme všechny soubory ve webovém prostoru ukládat přímo s touto příponou, musíme však v konfiguraci webového serveru zajistit, že budou i přesto posílány k provedení PHP interpreteru.

Je zřejmé, že práce s více skutečnými soubory ve webovém prostoru je poněkud těžkopádná a limitovaná. Naštěstí existuje lepší a komfortnější možnost: přepisovací modul mod_rewrite webového serveru Apache. Výše popsané řešení založené na více souborech se tak hodí jen tam, kde není mod_rewrite k dispozici.

Přepisování přes mod_rewrite

Užitečnou funkcionalitu nabízí modul webového serveru Apache mod_rewrite. Pomocí definovaných pravidel přepisuje klientem zaslané URL na jiné, které se teprve poté ve své nové podobě předává k běžnému zpracování webovým serverem.

Přepisovací pravidla se umisťují do některého z konfiguračních souborů Apache. Mnoho serverů má však zakázáno provádění pravidel ze souborů .htaccess. Vzhledem k rizikům spojeným s chybně navrženými pravidly (například zacyklení jednotlivých pravidel a následné přetížení či shození celého www serveru) umisťují pravidla do hlavního konfiguračního souboru často pouze sami administrátoři, a to až po provedení kontroly jejich nezávadnosti.

Pravidla jsou v zásadě založena na porovnání regulárních výrazů a následném nahrazení vyhovujících podvýrazů určeným textem a podobně. Existuje nepřeberné množství možností, přepínačů a modifikátorů, díky kterým lze s pomocí mod_rewrite vytvořit z původní URL prakticky cokoliv. To činí z mod_rewrite velice silný, robustní, ale také značně složitý mechanizmus.

Pro účely phpBASE tedy stačí mít ve webovém prostoru jen jeden jediný skutečný soubor index.php a veškerá klientem zaslaná URL přepisovat tak, že se bude volat tento soubor s příslušně nastavenými parametry, včetně parametrů PAGE resp. SCRIPT. Ukažme si tři jednodušší příklady pravidel. První přepisuje adresu z úvodu této kapitoly:

RewriteRule ^/kontakty$ /index.php?PAGE=main/contact [QSA,L]

Druhé pravidlo, použitelné třeba v nějakém internetovém obchodě, bere z původní URL kategorii a označení produktu a přepisuje je na běžné parametry. Například klientem zaslaný požadavek http://www.example.com/clanky/webdesign/jak-na-odkazy předá webovému serveru ke zpracování v podobě http://www.example.com/index.php?PAGE=news/article&category=webdesign&ident=jak-na-odkazy.

RewriteRule /clanky/(.+)/([^/]+)$ /index.php?PAGE=news/article?category=$1&ident=$2 [QSA,L]

Poslední z pravidel nám zajistí bezprostřední redirect prostřednictvím HTTP hlavičky 301 Moved Permanently z URL končících lomítkem na stejnou adresu, ale bez lomítka. Například požadavek http://www.example.com/clanky/ přesměruje na http://www.example.com/clanky.

RewriteRule ^(.+)/$ $1 [QSA,R=301,L]

Nahrazování odkazů ve stránkách

Jak jsme si již řekli, cíle odkazů směřujících dovnitř aplikace se určují voláním Url a jí příbuzných funkcí GetUrl, AbsUrl, GetAbsUrl, ale i Redirect. Důsledným dodržováním tohoto přístupu si zajistíme, že budeme mít pod plnou kontrolou jednotnou podobu všech takových odkazů.

Zmíněné funkce implicitně vytvářejí odkazy typu http://www.example.com/index.php?PAGE=main/contact. Ještě před tím se však podívají, jestli nebyla v aplikaci programátorem vytvořena funkce Url_Rewrite. Pokud ano, zavolají nejprve ji. V ní lze totiž definovat libovolnou vlastní podobu vytvářených odkazů.

Základem je parametr $args. Je to asociativní pole všech parametrů, které byly předány původním volajícím funkcím. Klíči v poli jsou názvy jednotlivých parametrů a hodnotami jejich hodnoty. V asociativním poli jsou i případné hodnoty PAGE a SCRIPT. Ty jsou zároveň předávány i prostřednictvím posledních dvou volitelných parametrů $page a $script, tentokrát ovšem jako instance třídy CEntity. Programátor si tedy může vybrat, který způsob práce s identifikátorem těchto dvou entit je mu bližší. V našem případě jsou v seznamu parametrů nadbytečné, nejsou dále použity, byly uvedeny jen pro úplnost.

Zbylé tři parametry $filename, $params a $hash jsou předávány odkazem. Jimi se v podobě prostých řetězců vrací zpět přiřazené jméno souboru, parametry za otazníčkem a hodnota za hashmarkem v URL, ze kterých se následně poskládá výsledná adresa. Každý z těchto parametrů nich může samozřejmě zůstat prázdný.

<?php
function Url_Rewrite(&$args, &$filename, &$params, &$hash$page$script)
{
    switch (
$args["PAGE"]) {
        case 
"main/index":
            
$filename "/";
            UnSet(
$args["PAGE"]);
            break;
        case 
"main/contact":
            
$filename "/kontakty";
            UnSet(
$args["PAGE"]);
            break;
    }
    return 
false;
}
?>

Odkazem je ovšem předáván i parametr $args. Původní volající funkce se do něj totiž na úplném konci dívají a ze všech prvků, které v něm ještě zůstaly, vytvoří standardní parametry na konec URL. Nechceme-li je tedy na konci zpracování zahrnout, musíme příslušné prvky pole zrušit.

Popsané poskládání URL z vrácených parametrů $args, $filename, $params a $hash se provede, jen pokud funkce Url_Rewrite vrátí jako svou funkční hodnotu false. Jestliže ale vrátí cokoliv jiného, bere se to jako definitivní adresa, která se také ihned použije.

Za předpokladu výše definované funkce Url_Rewrite budou výsledné odkazy v aplikaci vypadat takto:

<?php
AbsUrl
("/main/index");               // http://www.example.com/
AbsUrl("/main/contact");             // http://www.example.com/kontakty
AbsUrl("/main/contact""""x=y");  // http://www.example.com/kontakty?x=y
AbsUrl("/main/contact""""#xy");  // http://www.example.com/kontakty#xy
?>

Nahrazování cílů formulářů

Analogicky výše popsanému nahrazování odkazů existuje i u formulářů možnost nahrazovat URL cílového skriptu, neboli hodnotu atributu action u elementu <form>. Slouží k tomu funkce Form_Start, Form_Script a Form_End.

Ještě předtím, než se vypíše úvodní tag <form>, zjišťuje systém, zda neexistuje programátorem vytvořená funkce Form_Rewrite definující vlastní hodnotu action i ostatních atributů. Pokud ano, využije ji. Opět příklad:

<?php
function Form_Rewrite(&$args$page$script)
{
    
$action "";
    switch (
$args["SCRIPT"]) {
        case 
"admin/user/delete":
            
$action "/admin/deleteuser";
            
$args["method"] = "get";
            UnSet(
$args["SCRIPT"]);
            break;
    }
    return 
$action;
}
?>

V parametru $args se v asociativním poli předávají vstupní parametry volající funkce Form_Start. Jedná se zejména o názvy a hodnoty jednotlivých atributů elementu <form>, ale i jakékoliv jiné potřebné informace, například hodnoty PAGE a SCRIPT. Ty jsou opět (analogicky k předchozí kapitole) volitelně předávány i jako instance třídy CEntity v parametrech $page a $script.

Adresa, kterou funkce vrátí, se použije v tagu <form> jako hodnota atributu action. Ostatní případné atributy tohoto tagu se vytvoří podle vrácené proměnné $args.

Funkce Form_Rewrite uvedená v příkladu ovlivní výpis formulářů v aplikaci takto:

<?php
// <form action="/admin/deleteuser" method="get" id="del">
//     obsah formuláře...
// </form>

Form_Start("""/admin/user/delete""id=del");
    echo 
"obsah formuláře..."
    
Form_Script();
Form_End();
?>