Przyjazne adresy URL wyglądają jak nazwy statycznych plików HTML, np. lorem.html, ipsum.html. W istocie są to odwołania do skryptów PHP, które bardzo często zawierają zmienne URL, np. index.php?id=13&tresc=artykul.
Artykuł opisuje, w jaki sposób przyjazne URL-e zaimplementować wykorzystując moduł serwera Apache mod_rewrite. Dodatkowo w otrzymanym rozwiązaniu adresy odwołujące się bezpośrednio do skryptów PHP zostają wyeliminowane. Jedynymi poprawnymi adresami są adresy przyjazne.
Instalacja modułu mod_rewrite
Instalacja modułu {stala}mod_rewrite{/stala} sprowadza się do zmian w konfiguracji serwera Apache. Instalowanie dodatkowych pakietów oprogramowania nie jest konieczne.
W pliku {stala}httpd.conf{/stala} zawartym w folderze {stala}c:\\Program files\\Apache Group\\Apache2\\conf{/stala} należy znaleźć dyrektywę {stala}Direcotry{/stala}:
Pomiędzy dwoma znacznikami {html}
Options Indexes FollowSymLinks
Order allow,deny
Allow from all
RewriteEngine on
AllowOverride All
Ponadto w linijce:
#LoadModule rewrite_module modules/mod_rewrite.so
należy usunąć znak komentarza {stala}#{/stala}:
LoadModule rewrite_module modules/mod_rewrite.so
Ostatnim krokiem jest restart serwera Apache.
Przykład pierwszy: translacja adresu
Moduł {stala}mod_rewrite{/stala} wykonuje translacje adresów URL. Przeglądarka wysyła żądanie dotyczące pliku o nazwie adres.html. Moduł {stala}mod_rewrite{/stala} powoduje, że serwer Apache w odpowiedzi na żądanie o plik adres{stala}.html {/stala}wyśle plik {stala}index.php{/stala}. Na serwerze nie ma pliku o nazwie {stala}adres.html{/stala}.
Uruchomienie przykładu rozpoczynamy od przygotowania skryptu PHP o nazwie {stala}index.php{/stala}:
Witaj\';
?>
Jedynym zadaniem skryptu {stala}index.php{/stala} jest wydrukowanie komunikatu powitalnego.
Następnie w tym samym folderze tworzymy plik o nazwie {stala}.htaccess{/stala}. W pliku tym umieszczamy dwie dyrektywy konfiguracyjne {stala}RewriteEngine{/stala} oraz {stala}RewriteRule{/stala}:
RewriteEngine on
RewriteRule adres.html index.php
Pierwsza z nich włącza działanie modułu {stala}mod_rewrite{/stala}, a druga ustala wykonywaną translację. Podana reguła powoduje, że żądanie HTTP:
GET /adres.html HTTP/1.1
będzie obsługiwane identycznie, jak żądanie:
GET /index.php HTTP/1.1
W odpowiedzi na żądanie o plik index.html serwer Apache wyśle plik{stala} index.php {/stala}(dokładniej: kod HTML wygenerowany przez skrypt index.php).
W celu sprawdzenia działania powyższego rozwiązania należy uruchomić przeglądarkę i odnaleźć plik index.php. Ujrzymy wtedy stronę taką, jak na rys. 1.
Teraz w polu Adres przeglądarki wprowadzamy – w miejsce{stala} index.php{/stala} – nazwę pliku {stala}adres.html{/stala}. Przeglądarka wyświetli ten sam dokument, co poprzednio. Ilustruje to rys. 2.
Zwróćmy uwagę na dwa szczegóły. Po pierwsze plik {stala}.htaccess{/stala} nie jest widoczny w przeglądarce (rys. 3). Odpowiada za to wpis:
Order allow,deny
Deny from all
w pliku konfiguracyjnym serwera Apache {stala}httpd.conf{/stala}. Zauważymy, że ta sama strona WWW przedstawiona na rys. 1 oraz 2 jest dostępna pod dwoma różnymi adresami:{stala}index.php{/stala} oraz {stala}adres.htm{/stala}.
Wyrażenia regularne w dyrektywie RewriteRule
Dyrektywa {stala}RewriteRule{/stala} wykorzystuje wyrażenia regularne. W zapisie:
RewriteRule adres.html index.php
wyrażeniem regularnym jest nazwa pliku adres.html. Wyrażenie to będzie pasowało m.in. do adresów:
adres/html
adresXhtml
nowy/adres.html
nowy/adres/html
moj/nowy/adresXhtml
moj/nowy/adres-html/strona
moj/nowy/adres-html/strona/www
Po wpisaniu dowolnego z powyższych adresów w przeglądarce, ujrzymy stronę z rys. 1, co ilustruje rys. 4.
Adres wprowadzony w przeglądarce, np. {stala}nowy/adres/html{/stala}, jest dopasowywany do wyrażenia regularnego {stala}adres.html{/stala} podanego w dyrektywie {stala}Rewrite-Rule{/stala}. Ponieważ w wyrażeniu regularnym {stala}adres.html{/stala} kropka oznacza dowolny znak, więc fragment {stala}adres/html{/stala} pasuje do wyrażenia regularnego. W odpowiedzi na żądanie
GET /nowy/adres/html HTTP/1.1
Przykład drugi: dokładne dopasowanie nazwy pliku
Wyrażenie regularne{stala}adres.html{/stala} użyte w pierwszym przykładzie powoduje, że ta sama strona WWW jest dostępna pod nieskończoną liczbą adresów. Jeśli chcemy, by poprawnym adresem był wyłącznie napis {stala}adres.html{/stala}, należy użyć wyrażenia regularnego {stala}^adres\\.html${/stala}:
RewriteEngine on
RewriteRule ^adres\.html$ index.php
Przykład trzeci: wykluczanie adresu index.php
W celu wykluczenia adresu {stala}index.php{/stala} wykorzystujemy zmienną {stala}REQUEST_URI{/stala}, która jest dostępna wewnątrz skryptu PHP.
Plik {stala}.htacces{/stala} pozostawiamy niezmieniony:
RewriteEngine on
RewriteRule ^adres\.html$ index.php
zaś w pliku {stala}index.php{/stala} najpierw pobieramy zmienną {stala}REQUEST_URI{/stala}:
$adr = $_SERVER[\'REQUEST_URI\'];
Następnie usuwamy z niej wszelkie niedopuszczalne znaki:
$adr = preg_replace(
\'/[^a-zA-Z0-9_\-\/.]/\',
\'\',
$adr
);
Po czym usuwamy nazwę folderu, w którym znajduje się skrypt:
$f = dirname($_SERVER[\'PHP_SELF\']) . \'/\';
$adr = preg_replace(
\'/^\' . preg_quote($f, \'/\') . \'/\',
\'\',
$adr
);
Otrzymana zmienna {stala}$adr{/stala} zawiera adres, który został użyty do odwiedzenia strony. Na jej podstawie możemy wyświetlić treść lub komunikat o błędzie:
if ($adr == \'adres.html\') {
echo \'Witaj
\';
} else {
echo \'Niepoprawny adres\';
}
W przykładzie tym jedynym poprawnym adresem jest {stala}adres.html{/stala}. W przypadku użycia adresu index.php (lub jakiegokolwiek adresu innego niż {stala}adres.html{/stala}) wyświetlany będzie komunikat Niepoprawny adres.
W wyrażeniu tym odwrócony ukośnik \ powoduje, że kropka ma znaczenie dosłowne (tzn. pasuje wyłącznie do kropki, a nie do dowolnego znaku), zaś kotwice ^ (początek wyrażenia) oraz $ (koniec wyrażenia) wykluczają dodanie czegokolwiek przed słowem adres lub po rozszerzeniu html.
W ten sposób strona będzie dostępna wyłącznie pod dwoma adresami {stala}adres.html{/stala} oraz {stala}index. php{/stala}. Ilustruje to rys. 5.
Przykład czwarty: ustalanie zmiennych URL
Adresy URL mogą zawierać zmienne, dołączone po znaku zapytania, np.:
index.php?imie=Jan&nazwisko=Nowak&wiek=57
W takim przypadku wyrażenie regularne użyte w dyrektywie {stala}RewriteRule{/stala} może stosować przechwytywanie. Jeśli w pliku {stala}.htaccess{/stala} umieścimy dyrektywę:
RewriteEngine on
RewriteRule ^adres-(.+)\.html$ index.php?id=
wówczas adres:
adres-lorem.html
będzie traktowany tak jak adres:
index.php?id=lorem
Podobnie adresy:
adres-a.html
adres-ipsum.html
adres-nieco-inny.html
zostaną potraktowane tak jak adresy:
index.php?id=a
index.php?id=ipsum
index.php?id=nieco-inny
Nawiasy okrągłe użyte w wyrażeniu:
^adres-(.+)\.html$
przechwytują dopasowany fragment. Fragment ten jest użyty w dalszej części dyrektywy {stala}RewriteRule{/stala} jako {stala}$1{/stala}:
index.php?id=
W skrypcie PHP badamy zmienną {stala}$_GET[‘id\’]{/stala}. Zawiera ona informacje potrzebne do ustalenia treści witryny:
Niestety w rozwiązaniu takim poprawnymi adresami są zarówno:
adres-a.html
adres-ipsum.html
adres-nieco-inny.html
jak i:
index.php?id=a
index.php?id=ipsum
index.php?id=nieco-inny
Przykład piąty: eliminacja adresów o rozszerzeniu .php
W celu wyeliminowania adresów o rozszerzeniu {stala}.php{/stala} z poprzedniego rozwiązania stosujemy zmienną {stala}REQUEST_URI{/stala}. Najpierw w pliku {stala}.htaccess{/stala} ustalamy regułę translacji:
RewriteEngine on
RewriteRule ^adres-.+\.html$ index.php
Zwróćmy uwagę, że reguła ta nie wykorzystuje przechwytywania.
W skrypcie PHP, w identyczny sposób, tak jak w przykładzie trzecim, ustalamy adres pobieranego dokumentu:
$adr = $_SERVER[\'REQUEST_URI\'];
$adr = preg_replace(
\'/[^a-zA-Z0-9_\-\/.]/\',
\'\',
$adr
);
$f = dirname($_SERVER[\'PHP_SELF\']) . \'/\';
$adr = preg_replace(
\'/^\' . preg_quote($f, \'/\') . \'/\',
\'\',
$adr
);
Następnie adres zawarty w zmiennej {stala}$adr{/stala} dopasowujemy do wyrażenia regularnego:
^adres-(.+)\.html$
Tym razem stosujemy przechwytywanie. Przechwycony fragment dostępny w zmiennej {stala}$m[1]{/stala} pozwala na ustalenie treści witryny:
W powyższym rozwiązaniu poprawnymi adresami są wyłącznie adresy zakończone rozszerzeniem {stala}.html{/stala}, np.:
adres-a.html
adres-lorem-ipsum.html
Wszelkie inne adresy, w szczególności adresy zakończone rozszerzeniem {stala}.php{/stala}, są niepoprawne i nie będą działały. Zwróćmy uwagę, że adresy zawierające znak zapytania i zmienne URL nie są w ogóle wykorzystywane. Cała aplikacja stosuje wyłącznie przyjazne URL-e o rozszerzeniu {stala}.html{stala}, co w znacznym stopniu uprości zarządzanie przestrzenią adresową.
Przykład szósty: strona z menu stosująca zmienne URL
Wykorzystajmy opisane rozwiązania do implementacji przyjaznych URL-i na stronie zawierającej menu. Rys. 6 przedstawia witrynę z piosenkami. Wybranie tytułu z lewego menu powoduje załadowanie tekstu piosenki po prawej stronie.
Strona wykorzystuje pliki tekstowe. Tekst każdej piosenki jest zapisany w osobnym pliku tekstowym. Dodatkowy plik o nazwie {stala}00lista.log{/stala} zawiera zestawienie wszystkich piosenek:
Krasnoludki|krasnoludki.txt|krasnoludki.html
Stary niedźwiedź|stary_niedzwiedz.txt|niedzwiedz.html
Ta Dorotka|ta_dorotka.txt|dorotka.html
...
Separatorem w pliku jest znak |. Kolejne kolumny to tytuł piosenki, nazwa pliku tekstowego z treścią piosenki oraz przyjazny URL.
Na przykład tekst piosenki zatytułowanej Stary niedźwiedź jest zawarty w pliku {stala}stary_niedzwiedz.txt{/stala}. Piosenka ta będzie dostępna pod adresem {stala}niedziwiedz.html{/stala}.
W pliku {stala}.htaccess{/stala} przedstawionym na listingu 1 ustalamy reguły translacji adresów (dyrektywa {stala}RewriteRule{/stala}) oraz adres strony głównej (dyrektywa {stala}DirectoryIndex{/stala}).
DirectoryIndex index.php?id=chodzi-lisek.html
RewriteEngine on
RewriteRule ^(.+\.html)$ index.php?id=
Skrypt {stala}index.php{/stala} jest przedstawiony na listingu 2. Na podstawie zmiennej {stala}$_GET[\’id\’]{/stala} ustalamy treść, która będzie widoczna na stronie.
assign(\'tekst\', $tekst);
}
$s->assign(\'menu\', $d[2]);
$s->assign(\'akcja\', $akcja);
$s->display(\'sz.tpl\');
?>
W rozwiązaniu tym poprawnymi adresami są:
- adres pusty (po wejściu do folderu z przykładem ujrzymy pierwszą piosenkę; odpowiada za to dyrektywa {stala}DirectoryIndex{/stala}),
- przyjazne adresy URL postaci {stala}niedzwiedz.html{/stala}, {stala}praczki.html {/stala}(ich dokładna postać wynika z zawartości pliku {stala}00lista.log{/stala}),
- adresy stosujące zmienne URL, np.{stala} index.php?id=niedzwiedz.html{/stala}, {stala}index.php?id=praczki.html{/stala}.
Przykład siódmy: eliminacja adresów .php?id= na stronie z jednym menu
Ostatni przykład demonstruje eliminację adresów odwołujących się do skryptu {stala}index.php{/stala} w przykładzie z piosenkami. Podane poniżej rozwiązanie wykorzystuje zmienną {stala}REQUEST_URI{/stala}.
W pliku {stala}.htaccess{/stala} ustalamy reguły translacji, tak by dokumentem domyślnym w folderze (dyrektywa {stala}DirectoryIndex{/stala}) był skrypt index.php. Ponadto przyjazne URL-e (np. niedzwiedz.html, praczki. html) przekierowujemy dyrektywą RewriteRule również do skryptu index.php. Treść pliku {stala}.htaccess{/stala} jest przedstawiona na listingu 3.
DirectoryIndex index.php
RewriteEngine on
RewriteRule ^[a-zA-Z0-9_\-]+\.html$ index.php
W skrypcie {stala}index.php{/stala} w zmiennej {stala}$adr{/stala} ustalamy adres bieżąco obsługiwanego żądania HTTP. Otrzymany adres może być pusty (gdy wchodzimy do folderu) lub może być przyjaznym adresem piosenki (np. {stala}niedzwiedz.html{/stala}). Instrukcja {stala}if{/stala} wybiera jeden z przypadków. Skrypt {stala}index.php{/stala} jest przedstawiony na listingu 4.
assign(\'tekst\', $tekst);
}
$s->assign(\'akcja\', $akcja);
$s->assign(\'menu\', $d[2]);
$s->display(\'sz.tpl\');
?>
W otrzymanym rozwiązaniu poprawnymi adresami będą:
- adres pusty,
- przyjazne adresy URL zawarte w pliku {stala}00lista.log{/stala}.
Adresy, które zawierają nazwę skryptu index.php nie są w ogóle stosowane.