irek86pl / 12.03.2020

Przyjazne adresy - mod_rewrite - instalacja i konfiguracja modułu

Przyjazne adresy mod_rewrite

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}{/html} oraz {html}{/html} wprowadzamy dyrektywy:

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.