niedziela, 24 maja 2015

[APACHE][mod_rewrite] Wstęp do przepisywania URL-i

TRUE
5422605052919904069
Mechanizm przepisywania URL-i za pomocą modułu rewrite z początku może wydawać się nieco skomplikowany. O ile nauczenie się podstawowych regułek przepisywania i wykorzystanie ich w praktyce nie stanowi zbyt dużego problemu, to niestety, ale prędzej, czy później natrafimy na kilka problemów związanych z regułami przekierowań, na pewno też nie raz i nie dwa "zapętlimy się" przy co bardziej rozbudowanej hierarchi regułek.

W artykułach tutaj postaram się przedstawić najczęstsze problemy z jakimi prawie na pewno spotkamy się podczas tworzenia plików .htaccess. Omówię też same podstawy tworzenia takich przekierowań. Zacznijmy więc może od początku - czym jest mod_rewrite? Jest to moduł serwera Apache, ktory za pomocą odpowiednio określonych warunków (conditions) wywołuje zadaną przez nas regułę (rule), która w zadany, określony sposób dokonuje przekierowania z jednego URL-a na inny, zdefiniowany przez nas. Reguły takie określamy w specjalnym pliku o nazwie .htaccess, który umieszczamy w folderze, dla którego zawarte w nim reguły mają obowiązywać (będą też obowiązywać dla jego podfolderów). Plik .htacccess ma ściśle określoną strukturę, składającą się z kilku sekcji. Omówimy je kilka akapitów niżej. Na razie omówmy samą zasadę przepisywania URL-i, a więc - na czym to wszystko polega?

Wyobraźmy sobie, że mamy przygotowaną w PHP aplikację, np. sklep internetowy.
W aplikacji tej posiadamy system kategorii i podkategorii naszych produktów, listę tych produktów, oraz odpowiednie sekcje odpowiadające za wyświetlanie informacji o zadanym produkcie.
Po sklepie tym poruszamy się w jakiś określony sposób definiujący nam np. gdzie obecnie się znajdujemy (czy np. w widoku listy kategorii, czy produktów) oraz jaką kategorię lub produkt obecnie wyświetlamy. Informacje takie przekazujemy zazwyczaj jako parametry w adresie, np.
[code]http://nasz_sklep.com/index.php?action=category&id=20[/code]
lub
[code]http://nasz_sklep.com/index.php?action=product_info&id=45[/code]
Brzydkie prawda? A i niezbyt chętnie indeksowane przez wyszukiwarki.
O wiele ładniej wygladałoby to, gdyby linki przedstawiały się np. tak:
[code]http://nasz_sklep/kategorie/telewizory[/code]
lub
[code]http://nasz_sklep/produkt/Samsung_Smart_LED_J6200.html[/code]
...albo jeszcze lepiej:
[code]http://nasz_sklep/telewizory[/code]
lub
[code]http://nasz_sklep/telewizory/LED/Samsung_Smart_LED_J6200.html[/code]
Dzięki mod_rewrite możemy uzyskać właśnie takie linki i wiele wiele więcej.
Na czym polega takie zamiana? Otóż, zauważmy, iż każdy z naszych wewnętrznych linków ma jakąś określoną strukturę, np. w linku
[code]?action=category&category_id=20[/code]
parametr action przechowuje nam informacje o rodzaju oglądanej sekcji (akcji), w tym przypadku jest to widok kategorii produktów, natomiast parametr category_id mówi naszej aplikacji o numerze id kategorii jaką wlaśnie przeglądamy. Zarazem parametr action jest tutaj niejako stałym elementem takiego linka. Zmienia się jedynie to co występuje w jego wartości, np.:
[code]index.php?action=category
index.php?action=product_info
index.php?action=order_info[/code]
itd...

Analogicznie jest z parametrem id, który przechowuje nam numer id oglądanego zasobu:
[code]index.php?action=product_info&id=1
index.php?action=product_info&id=54
index.php?action=product_info&id=437[/code]
itd...
Struktura naszego linka przedstawia się więc następująco:
[code]http://nasz_sklep.com/index.php?action=NAZWA_WYŚWIETLANEJ_AKCJI&id=ID_POKAZYWANEGO_ELEMENTU[/code]
Załóżmy teraz, że chcemy taki link sprowadzić do bardziej ładnej wersji, np. do takiej formy:
[code]http://nasz_sklep.com/NAZWA_WYŚWIETLANEJ_AKCJI/ID_POKAZYWANEGO_ELEMENTU[/code]
Jak tego dokonać? Otóż musimy utworzyć:

1) jakiś warunek sprawdzający jak nasz URL wygląda
2) jakąś regułę, która na podstawie tego warunku dokona odpowiedniej zamiany

Budowa pliku .htaccess

Zacznijmy od zapoznania się z przykładową budową pliku .htaccess, który będzie przepisywać nasze URL-e. Plik taki składa się z kilku sekcji, które po koleji sobie omówimy. Zacznijmy więc, poniżej przykładowy plik .htaccess:
[code]
RewriteEngine on //1
RewriteCond %{REQUEST_FILENAME} !-f //2
RewriteRule ^([^/.]+)/?$ index.php?action=$1  //3[/code]

Ten prosty plik dokonuje po koleji następujących działań:
1) włącza moduł rewrite dla tego folderu i jego podfolderów
2) sprawdza warunek, czy nie odwołujemy się do istniejącego na serwerze pliku
3) jeśli powyższy warunek jest spełniony to przepisuje URL z postaci:
[code]nasz_sklep.com/NAZWA_AKCJI/[/code]
na
[code]index.php?action=NAZWA_AKCJI[/code]

RewriteCond

O ile pierwsza linijka nie wymaga zbytniego tłumaczenia, tak dalej sprawa robi się już bardziej skomplikowana. Przeanalizujmy wiec wszystko po koleji, na początek warunek zawarty w linijce drugiej:
[code]RewriteCond %{REQUEST_FILENAME} !-f[/code]
warunki dla danej reguły definiujemy na zasadzie:
[code]RewriteCond NASZ_WARUNEK1
RewriteCond NASZ_WARUNEK2
RewriteCond NASZ_WARUNEK3
itd...[/code]
Każdy z warunków musi znaleźć się w oddzielnej linijce.
Jeśli dany warunek zostaje spełniony, to silnik przeskakuje do następnej linijki i albo spradza kolejny warunek, albo natrafia na blok z regułą, tak jak w naszym przykładzie, gdzie warunek mamy tylko jeden.

W przypadku podania większej ilości warunków - wszystkie one muszą zostać spełnione, aby wykonała się reguła po nich następująca. Poprzedzające regułę warunki obowiązują tylko dla pierszej reguły po nich występującej!

Ogólna zasada działania prezentuje się więc tak:
[code]WARUNEK1
WARUNEK2
WARUNEK3
REGUŁA1 (jeśli WARUNKI 1,2, i 3 spełnione)

WARUNEK4
REGUŁA2 (jeśli WARUNEK4 spełniony)
REGUŁA3 (kolejna reguła, tutaj już nie obowiązuje WARUNEK4)[/code]
itd...
gdzie dana reguła wykona się jedynie wtedy, gdy określone przed nią warunki zostaną dla danej reguły spełnione.
W naszym przykładzie mamy na początek jedynie jedną regułę:
[code]RewriteCond %{REQUEST_FILENAME} !-f[/code]
która sprawdza, czy przypadkiem nie odwołujemy się do istniejącego pliku.
Dlaczego taka reguła? Już wyjaśniam. Załóżmy, że nasz plik .htaccess znajduje się w katalogu głównym naszej strony, przy czym w tym samym katalogu znajduje się plik o nazwie kategorie. Mało prawdopodone, ale wyobraźmy sobie, że jednak plik http://nasz_sklep.com/kategorie istnieje realnie w tym miejscu. Zarazem słowo kategorie jest tutaj jednym z parametrów jakie przyjmuje nasza zmienna $_GET['action'].
Mamy więc tutaj kolizję, gdyż adres http://nasz_sklep.com/kategorie może odwoływac się zarówno do realnie istniejącego pliku o nazwie:
[code]http://nasz_sklep.com/kategorie[/code]
jak i do przepisanego URL-a:
[code]http://nasz_sklep.com/index.php?action=kategorie[/code]
Jak więc rozwiązać ten problem? Od tego właśnie mamy tutaj nasz warunek.
Sprawdza on, czy plik o zadanej w adresie nazwie nie istnieje fizycznie w obecnym katalogu i dopiero jeśli takowego pliku nie ma, to wykonywana jest reguła przepisująca adres:
[code]http://nasz_sklep.com/kategorie[/code]
na
[code]http://nasz_sklep.com/index.php?action=kategorie[/code]
Rozłóżmy ten warunek na czynniki pierwsze:
[code]%{REQUEST_FILENAME} !-f[/code]
%{REQUEST_FILENAME} - jest to zmienna środowiskowa przechowująca wywoływany właśnie adres, w naszym przypadku np. http://nasz_sklep.com/kategorie
-f - tóż po niej podany jest parametr -f, który oznacza lokalny plik.
Podany jest on jednak ze znakiem negacji (wykrzyknik - !), a więc tłumacząc na zrozumiały język warunek brzmi następująco:
[code]WYWOŁYWANY ADRES != REALNIE ISTNIEJACY PLIK O TAKIEJ NAZWIE[/code]
a więc: jeśli nie istnieje plik o nazwie takiej jak wywoływany URL, to warunek zostaje spełniony i lecimy dalej, wywołać regułę w linijce trzeciej.
W zasadzie zawsze jako startowe reguły podaje się właśnie sprawdzenie, czy wywoływany adres nie prowadzi do istniejącego pliku lub folderu:
[code]%{REQUEST_FILENAME} !-f
%{REQUEST_FILENAME} !-d[/code]
Parametr -f oznacza plik, parametr -d oznacza folder.
Wzbogaćmy więc nasz .htaccess o sprawdzenie folderu:
[code]
RewriteEngine on //1
RewriteCond %{REQUEST_FILENAME} !-f //2
RewriteCond %{REQUEST_FILENAME} !-d //3
RewriteRule ^([^/.]+)/?$ index.php?action=$1  //4
[/code]
Tego typu zmiennych jest więcej, np.

  • %{HTTP_HOST} - przechowywuje część z adresu z nazwą hosta, przykładowo: po wywołaniu http://nasz_sklep.com/kategorie w zmiennej tej znajdzie się nasz_sklep.com.
  • %{REQUEST_URI} - przechowuje cały URL z jakiego nastąpiło wywołanie.

RewriteRule

Skoro oba warunki mamy spełnione (nie istnieje plik http://nasz_sklep.com/kategorie oraz nie istnieje folder http://nasz_sklep.com/kategorie) to wykonywana jest następująca po warunku reguła:
[code]RewriteRule ^([^/.]+)/?$ index.php?action=$1[/code]
Rozłóżmy ją na czynniki pierwsze.
W pierwszym członie:
[code]^([^/.]+)/?$[/code]
znajduje się wyrażenie, z którego będziemy przepisywać URL, tutaj np.:
[code]http://nasz_sklep.com/kategorie[/code]
w drugim natomiast forma do jakiej przepisanie nastąpi:
[code]index.php?action=$1[/code]
Regułka ma postać:
[code]RewriteRule ADRES_WYWOŁANY PRAWDZIWY_ADRES [atrybuty/flagi][/code]
Pierwszy człon reguły jest wyrażeniem regularnym.
Aby więc móc umiejętnie wykorzystywać reguły przypisań nieobca musi nam być wiedza dotycząca stosowania wyrażeń regularnych. Na potrzeby tego poradnika zakładam, że jesteś z wyrażeniami regularnymi zapoznany i umiesz używać ich w praktyce.

Przyjrzyjmy się dopasowywanemu ciągowi:
[code]^([^/.]+)/?$[/code]
Po koleji:
  • ^ - początek adresu, wystąpi on zaraz po http://nasza_strona.com
  • /?$ - nasz adres może, ale nie musi kończyć się slashem (znak zapytania określa, że znak przed nim może wystąpić lub nie)
  • [^/.]+ - mamy tutaj dopasowywany zakres znaków, w którym na początku nie może wystąpić slash (znak negacji ^), następnie może wystąpić dowolna ilość znaków (kropka .), a całość takiej konstrukcji musi wystąpić conajmniej raz (znak + zastosowany dla podanego zakresu)
  • ([^/.]+) - dopasowane wyrażenie jest następnie wzięte w nawiasy okrągłe. Zawartość dopasowania objęta w nawiasy zostanie przypisana do zmiennej $1 w następnym członie.
Pamiętajmy, że dopasowywany zawsze w RewriteRule ciąg jest jedynie tą częścią adresu, która występuje po nazwie hosta, a więc w naszym przypadku wszystko to co występuje po http://nasz_sklep.com/. Jeśli chcemy dostać się do części z nazwą hosta wykorzystajmy zmienną %{HTTP_HOST}.
Takich dopasowań możemy zrobić więcej, w przypadku takim każde kolejne dopasowanie wzięte w nawias będzie tworzyło odpowiednio kolejną zmienną, czyli $1, $2, $3 itd.

Czas na drugą część wyrażenia:
[code]index.php?action=$1[/code]
określa ono co robimy z uzyskanym w pierwszej części dopasowaniem.
W naszym przypadku przepisujemy adres na:
[code]index.php?action=$1[/code]
co jest równoważne z:
[code]index.php?action=DOPASOWANE_W_NAWIASACH_WYRAŻENIE[/code]
czyli z:
[code]index.php?action=([^/.]+)[/code]
Wywołując więc adres:
[code]http://nasz_sklep.com/kategorie[/code]
nasza reguła za pomocą wyrażenia regularnego dopasowywuje z niego ciąg kategorie, umieszcza go w zmiennej $1, a następne zmienną tą przekazuje jako parametr do:
[code]index.php?action=$1[/code]
generując tym samym adres:
[code]index.php?action=kategorie[/code]
Finalnie, efektem naszego działania będzie to, iż użytkownik wpisujący w przeglądarkę adres:
[code]http://nasz_sklep.com/kategorie[/code]
tak naprawdę trafia do lokacji:
[code]index.php?action=kategorie[/code]
Takie przekierowanie jest oczywiście "przezroczyste" dla użytkownika, w pasku adresu pozostaje wciąż adres http://nasz_sklep.com/kategorie.
Przepisywany URL jest widoczny jedynie wewnętrznie dla serwera oraz dla naszej aplikacji w PHP.

To tyle tytułem krótkiego wstępu do silnika rewrite.
Wiemy już mniej więcej na czym polega działanie mod_rewrite i do czego służy.
W nastepnych artykułach przeanalizujemy bardziej złożone reguły i nauczymy się trochę bardziej skomplikowanych aspektów przepisywania URL-i.

Tester online reguł mod_rewrite znajduje się tutaj:
http://htaccess.madewithlove.be/

2 komentarze:

  1. Witam, mam takie pytanie odnośnie przepisywania adresów i ich przekierowania żeby nie było duplikowania treści. Przykładowo po przepisaniu adresu mam stronę "http://przykladowa-strona/kontakt" i taka jest w hiperłączach na stronie ale też pozostaje przecież strona oryginalna "http://przykladowa-strona/kontakt.php" i co wtedy? Czy to nie będzie wtedy duplikacja treści? Czy może wtedy trzeba oprócz przepisania adresu "kontakt.php" na "kontakt" dodatkowo zrobić jeszcze przekierowanie 301 "kontakt.php" na "kontakt". Tylko wtedy pojawia się błąd: pętla przekierowań. Czy jest jakiejś rozwiązanie tego problemu?

    OdpowiedzUsuń
  2. Bardzo fajny artykuł. Właśnie czegoś takiego szukałem. Jest jednak jedna rzecz, która nie powinna mieć miejsca... Wydaje mi się, że zupełnie pokręciłeś wyjaśnienie wyrażenia regularnego. Napisałeś:
    "[^/.]+ - mamy tutaj dopasowywany zakres znaków, w którym na początku nie może wystąpić slash (znak negacji ^), następnie może wystąpić dowolna ilość znaków (kropka .), a całość takiej konstrukcji musi wystąpić conajmniej raz (znak + zastosowany dla podanego zakresu)"
    Opisujesz chyba inne wyrażenie, bo większość tego co napisałeś nie jest prawdą. Poprawne wyjaśnienie powinno brzmieć:
    "[^/.]+ - mamy tutaj ciąg znaków o długości co najmniej 1 (znak +). W ciągu tym NIE MOGĄ wystąpić znaki slash (/) oraz kropka (.)."
    Tak więc slash nie może wystąpić wcale, a nie tylko na początku, a kropka w tym wyrażeniu nie oznacza dowolnej ilość dowolnych znaków, tylko "normalny" znak kropki (.). Po prostu aby to wyrażenie nie pasowało do czegoś takiego: "abc.html". Nie będzie pasować, bo fraza posiada kropkę. Znak negacji (^) wewnątrz kwadratowych nawiasów odnosi się do wszystkich znaków w środku. [kl]ot pasuje do "kot" i "lot", a [^kl]ot pasuje do "aot", "bot", "cot" itd. za wyjątkiem kot i lot.
    Mimo to artykuł bardzo pomocny :-)

    OdpowiedzUsuń

Masz sugestię? Znalazłeś błąd? Napisz komentarz! :)

webmaester.pl - profesjonalne projektowanie WWW i webaplikacji
webmaester.pl - profesjonalne projektowanie WWW i webaplikacji