htaccess – mod_rewrite, ładne URL, przekierowanie

  htaccess, Webhosting

Apache to najpopularniejszy serwer WWW. Dostępnych jest wiele dodatków (modułów), które rozszerzają jego możliwości i funkcjonalność. Poniższy tekst zawiera opis jednego z nich – mod_rewrite.

Mod_rewrite umożliwia nadpisywanie adresu URL żądań przychodzących podczas pracy serwera. Tłumaczenie odbywa się poprzez przetworzenie reguł zapisanych wyrażeniami regularnymi. Poza regułami tłumaczenie może być zależne od innych warunków.

Tłumaczenie adresu URL może być następujące:
www.example.com/clanek/pocasi -> www.example.com/clanek.php?id=12342

Nadpisywanie

Adres URL może być nadpisany dwoma sposobami – przekierowaniem lub podstawieniem.

  • Przekierowanie – przeglądarka klienta otrzyma odpowiedź HTTP redirect (HTTP kod 301 lub 302) do nowej lokalizacji. Użytkownik zorientuje się, że adres URL został zmieniony.
  • Podstawienie – serwer zwraca przeglądarce „nowy” adres URL, ale nie informuje jej o tym. Użytkownik nie zorientuje się, że doszło do zmiany. Zawartość może pochodzić z tej samej strony, z innej strony na tym samym serwerze lub z zupełnie innego serwera.

Ważne jest, kiedy dochodzi do nadpisania adresu URL. Następuje to we chwili przyjęcia żądania HTTP przez serwer (po parsowaniu żądania, nagłówka, itp.), ale jeszcze przed rozpoczęciem interpretacji adresu URL (tj. przed wyszukaniem pliku, który ma zostać wysłany użytkownikowi, lub przed wywołaniem silnika skryptu, który plik uruchomi). Interpretowane są tylko nadpisane adresy URL. Wynika z tego, że w dowolny sposób możemy wpłynąć na to, co ostatecznie dotrze do przeglądarki użytkownika.

Na webhostingu WEDOS reguły mod_rewrite można zapisywać w pliku .htaccess, tj. w pliku konfiguracyjnym serwera WWW. Znajdziesz go na FTP w folderze /www (może ich tam być kilka) – Twoja strona będzie miała najprawdopodobniej swój własny .htaccess. Ten w folderze /www odnosi się do całego webhostingu, ten w folderze Twojej strony będzie dotyczył tylko jej. To właśnie w tym folderze zalecamy wprowadzać zmiany. Jeśli natomiast Twoja strona znajduje się w folderze /www, plik .htaccess będzie dostępny właśnie tutaj.

Nie widzisz pliku .htaccess? Spróbuj włączyć wyświetlanie ukrytych plików w swoim kliencie FTP lub skorzystaj z naszego WebFTP. Kropka na początku nazwy pliku oznacza, że jest to plik ukryty.

Zanim zaczniemy pisać reguły nadpisywania, konieczne jest włączenie mod_rewrite za pomocą:

RewriteEngine on

RewriteBase

Za pomocą tej dyrektywy możliwe jest ustawienie domyślnego katalogu dla celów przekierowania, z którego będą pochodzić ścieżki względne.

RewriteBase path

RewriteRule

Ta dyrektywa określa samą regułę nadpisywania i ma następującą składnię:

RewriteRule Pattern Substitution [flags]

Pattern jest wyrażeniem regularnym określającym, kiedy reguła powinna zostać wykonana. Składnia wyrażenia regularnego jest podobna do tej w Perlu, dodatkowo możemy użyć negacji (znak „!” na początku). Pattern jest porównywany z adresem URL, pożądanym przez przeglądarkę klienta. Jeśli pattern pasuje,  nastąpi nadpisanie na substitution.

Substitution – nowy adres URL strony, która faktycznie wyświetli się klientowi. Adres może być bezwzględny (zaczynający się od http lub https) lub względny. Ścieżka względna rozpoczynająca się od ukośnika pochodzi od roota virtualhosta, w przeciwnym razie z bieżącego katalogu (lub z RewriteBase).

substitution możemy się także odwoływać do „załatanych” części regexpu (części ograniczonych nawiasami), zmiennych systemowych lub funkcji odwzorowujących (wszystko zostanie wyjaśnione w dalszej części tego tekstu). Odwołania do n-tej części patternu dokonamy wywołaniem $N. Można również odwołać się do części patternu z reguły RewriteCond (patrz poniżej) przy użyciu %N. Odwołanie do zmiennej systemowej jest możliwe przy użyciu %{VARNAME} i funkcji za pomocą ${mapname:key|default}.

substitution można też pracować z parametrami adresu URL. Jeśli substitution nie zawiera znaku zapytania, pierwotne parametry zostaną dodane na końcu zmienionego adresu. Umieszczenie znaku zapytania na końcu spowoduje usunięcie wszystkich parametrów. Po znaku zapytania możemy podać nowe parametry. Tutaj także możemy skorzystać z załatanych części z patternu ($N i %N). Ponadto, jeśli wprowadzimy flagę QSA (patrz poniżej), do nowych parametrów zostaną i te pierwotne.

Jeśli w konfiguracji wprowadzono wiele reguł RewriteRule, kolejność przetwarzania będzie zgodna z kolejnością dodania. Jeśli reguła będzie odpowiadać adresowi URL, dojdzie do nadpisu. Kolejne reguły będą pracować z już nadpisanym adresem (nie z pierwotnym). Może więc dojść do większej liczby nadpisań, w wyniku których adres URL będzie stopniowo przekształcany. Trzeba tu uważać na zapętlenie. Reguły są oceniane od początku do końca, dopóki nie dojdzie do nadpisu. 

[flags] – parametry opcjonalne. No najczęściej używanych należą:

  • F – zakaże adres URL, zwróci odpowiedź HTTP  – 403 (forbidden)
  • L – to jest ostatnia reguła, kolejna nie zostanie już wykonana
  • NC – wyrażenie case-insensitive
  • P – force proxy, wymuszenie przetworzenia celów przez mod_proxy
  • QSA – dodać na końcu pierwotne parametry URL
  • R[=code] – przekierować na nowy URL
    • 301 – Moved Permanently
    • 302 – Found (Moved Temporarily) – domyślny

Przykłady podstawowe

Przykład 1 – przekierowanie standardowe

RewriteRule pierwotna-strona1\.html nowa-strona1.html [R]

Proste przekierowanie adresu URL jednej strony na inną stronę w ramach jednej witryny. Najczęściej używane w przypadku, gdy zmienia się adres konkretnej strony, a my chcemy, aby klienci zaczęli używać nowego adresu. Należy jednak pamiętać, że kropki i inne znaki w wyrażeniu regularnym muszą być zmienione, ponieważ kropka oznacza dowolny znak w wyrażeniu regularnym. Drugi parametr nie jest już wyrażeniem regularnym, w tym przypadku nie może dojść do pominięcia kropki. W tym przypadku konieczne jest także określenie flagi R, w przeciwnym razie doszłoby do podstawienia, a nie przekierowania (w tym przypadku chcemy, aby użytkownik poznał nowy adres).

Przykład 2 – podstawienie standardowe

RewriteRule pierwotna-strona2\.html nowa-strona2.html

Podobna sytuacja, ale w tym przypadku brak jest flagi R dla przekierowania. Ponieważ drugi parametr jest adresem względnym, domyślnie wykonywane jest podstawienie. Adres URL w przeglądarce użytkownika nie ulegnie zmianie, ale zostaną mu wyświetlone treści z nowego adresu.

Příklad 3 – przekierowanie gdzie indziej

RewriteRule stronka\.html http://www.example.com/gdzie-indziej.html

W tym przypadku dojdzie do przekierowania, ponieważ drugim parametrem jest bezwzględny adres URL. Nie ma potrzeby podawania [R].

Przykład 4 – zakaz strony

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

Flaga F jest używana do blokowania dostępu do niektórych zasobów na stronie. W tym przypadku nie chcemy, aby ktokolwiek dostał się do zawartości katalogów narzędzi CVS lub Subversion. Nie ma znaczenia, czy taki katalog lub taki plik tam istnieje. Ważne jest, aby adres URL był zgodny z podanym wyrażeniem regularnym. W przypadku tej flagi dodanie adresu URL w drugim parametrze nie ma sensu. Niemniej jednak, aby zachować liczbę parametrów, konieczne jest dodanie myślnika.

Przykład 5 – ustawienie dokumentu MIME-type

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

Jest to trik umożliwiający odwiedzającym przeglądanie kodu źródłowego wszystkich plików z końcówką php. Przy żądaniu o plik z końcówką phps, do przeglądarki użytkownika zostanie wysłany plik php o takiej samej nazwie ze specjalnym typem MIME (dzieje się tak głównie po to, aby plik php nie był interpretowany, tj. nie był wykonywany, ale nastąpiło wyświetlenie jego zawartości).

Przykład 6 – język ukryty w URL

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

W ten sposób możemy przenosić ważne parametry w adresie URL (w tym przypadku kod języka), o ile nie chcemy przekazać go jako rzeczywistego parametru na końcu adresu. Aby za parametrem z językiem zostały dołączone pozostałe parametry, które znajdowały się w pierwotnym adresie URL, nie możemy zapomnieć o fladze [QSA].

Przykład 7 – przekierowanie wszystkiego

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

RewriteCond

Korzystając z dyrektyw RewriteCond, możemy określić jeden lub więcej warunków, które muszą być spełnione, aby następująca reguła RewriteRule została zastosowana.

RewriteCond TestString CondPattern [flags]

TestString – łańcuch testowy, który będziemy dopasowywać do CondPattern. Może zawierać zmienne systemowe lub funkcje mapujące.

CondPattern – może być standardowym wyrażeniem regularnym z negacją. Druga opcja to zwykły ciąg liter o specjalnym znaczeniu. Gdy następuje porównanie leksykograficzne z TestString, możemy użyć porównania (<,>, =). Możemy również użyć następujących symptomów, które zawsze testują TestString.

  • -d – czy TestString jest katalogiem?
  • -f – czy TestString jest plikiem?
  • -s – czy TestString jest niepustym plikiem?
  • -l – czy TestString linkiem symbolicznym?
  • -x – czy TestString ma prawo executable?

Warunek jest więc spełniony, jeśli TestString pasuje do wyrażenia regularnego lub innego warunku w  CondPattern. Jeśli przed RewriteRule określono więcej niż jedną klauzulę RewriteCond, to każda z nich musi zostać spełniona, aby doszło do nadpisu, tak jakby miały między sobą łącznik AND (można to zmienić flagą OR).

[Flags] – u warunków mamy możliwość użycia:

RewriteRule (w Substitution) i RewriteCond (w TestString) można korzystać ze zmiennych systemowych, zapisywanych w formacie %NAZWA_ZMIENNEJ}. Do dyspozycji są np.:

  • nagłówki HTTP – HTTP_USER_AGENT, HTTP_REFERER, HTTP_HOST, …
  • informacje o połączeniu i żądaniu – REMOTE_ADDR, REQUEST_METHOD, QUERY_STRING, …
  • zmienne serwerowe – DOCUMENT_ROOT, SERVER_NAME, …
  • data i czas – TIME, TIME_YEAR, TIME_HOUR, TIME_WDAY, …
  • %{ENV:variable} – zmienne środowiska
  • %{SSL:variable} – parametry połączenia SSL
  • %{HTTP:header} – dowolny nagłówek HTTP

Przykłady zaawansowane

Przykład 8 – istnienie pliku

# tylko jeśli pożądany plik naprawdę nie istnieje 
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^forum/topic-([0-9]+)\.html$ forum-topic.php?id=$1 [QSA,L]

Podstawianie innej strony tylko w przypadku, gdy pierwotnie pożądany plik nie istnieje. Jest to także przykład tworzenia „ładnych” adresów URL, gdzie parametr (np. numer tematu dyskusji) jest ukryty bezpośrednio w nazwie pliku i nie pojawia się jako parametr adresu URL. W tym przypadku konieczne jest umieszczenie w regule QSA, która na końcu nowego adresu doda pierwotne parametry URL.

Przykład 9 – skrypt domyślny

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

Jeśli pożądany plik naprawdę istnieje, reguła jest pomijana, a plik zostanie wysłany do przeglądarki. Jeśli plik nie istnieje, żądanie zostanie przekazane do skryptu uniwersalnego, który na podstawie adresu URL może znaleźć odpowiednie treści np. w bazie danych, lub wyświetli błąd (sposób na wyłapywanie i rozwiązywanie błędów 404).

Przykład 10 – uruchomienie pliku .html jako skryptu PHP

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

Reguła pozwala na wprowadzanie w adresie URL plików z rozszerzeniem .html, które w rzeczywistości mają na serwerze rozszerzenie .php. Dzięki temu plik będzie uruchamiał się jako skrypt PHP, ale odwiedzającym będzie wyświetlany jako statyczny plik .html. Reguła nie będzie miała zastosowania w przypadku, gdy dany plik .html nie istnieje. Istnienie pliku .php nie ma znaczenia, jeśli nie jest dostępny, zostanie wyświetlony błąd 404. 

Przykład 11 – przekierowanie wg domeny

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

Ten warunek i reguła zadbają o to, aby odwiedzający, który wejdzie na stronę przez domenę „www”, zostanie natychmiast przekierowany na tę samą stronę, ale na domenie z „www”. Jest to pożądane, ponieważ niektóre wyszukiwarki nakładają kary za udostępnianie tej samej treści pod różnymi adresami URL. Podany tutaj kod HTTP 301 oznacza stałe przekierowanie.

Przykład 12 – przekierowanie na HTTPS

RewriteEngine On 
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{SERVER_NAME}/$1 [R=301]
Header set Content-Security-Policy "upgrade-insecure-requests;"   

Kod zapewni nam wczytywanie strony przy użyciu bezpiecznego połączenia. Upewnij się, że masz poprawnie skonfigurowany certyfikat SSL. Nie musisz edytować reguł, zostaw tam zmienną {SERVER_NAME}. Reguły będą uniwersalne dla każdej strony. 

Przykład 13 – różne wersje strony wg daty/czasu

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

Wiele można zdziałać na podstawie aktualnej daty i czasu. Wykorzystujemy fakt, że w RewriteCond wartości można porównywać leksykograficznie i że składowe daty i czasu są wyrównane do tej samej długości (uzupełnione zerami od lewej). W tym przykładzie odwiedzającemu zostanie udostępniona wersję strony zgodna z porą dnia. Istotne jest, że oba wymienione warunki odnoszą się do następnej reguły i występuje między nimi połączenie „AND”. Druga reguła nie zawiera żadnego warunku. Jeśli pierwsza reguła zostanie wykonana, druga nie zostanie uruchomiona, ponieważ będzie się pracować z już zmienionym adresem URL.

Przykład 14 – różne wersje strony wg przeglądarki

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]

Podobny przykład podsuwania różnych wersji strony w oparciu o jakąś funkcję przeglądarki użytkownika. W tym przypadku mamy prostszą wersję strony dla starszych przeglądarek. Warunki mają między sobą łącznik „OR”. Druga reguła działa jako domyślna, jeśli warunki pierwszej reguły nie zostaną spełnione.