poniedziałek, 25 maja 2015

[APACHE][mod_rewrite] Reguły, warunki i atrybuty - tworzymy pierwsze praktyczne przekierowania

TRUE
3468871533248841170
W poprzedniej części, czyli we wstępie określiliśmy sobie czym jest mod_rewrite, na co mniej więcej pozwala i jak wygląda przykładowa reguła przepisywania adresu. W tym odcinku nauczymy się tworzyć bardziej zaawansowane reguły, które pozwolą już na w pełni określone działanie naszych adresów.

Przy okazji opiszę tutaj kilka standardowych problemów wraz z rozwiązaniami jak sobie z nimi radzić, gdyż mod_rewrite do poprawnego działania wymaga kilku "tricków". Nauczymy się też testowania stworzonych reguł oraz przeanalizujemy atrybuty reguł oraz dostępne w mod_rewrite zmienne.

Na początek małe przepomnienie - reguły określone w pliku .htaccess działają na folder, w którym plik ten się znajduje oraz na wszystkie jego podfoldery (o ile w danym podfolderze nie umieścimy kolejnego .htaccess nadpisującego reguły z pliku nadrzędnego). Na potrzeby tego poradnika nasz .htaccess umieszczamy w katalogu głównym naszego serwera. Przy okazji mały tip: nazwę pliku .htaccess można zmienić na inną dowolną, np. na config - określa się to w konfiguracji Apache'a, radzę jednak pozostać przy domyślnej nazwie, żeby trzymać się standardów.

Środowisko testowe

Na potrzeby nauki powinniśmy przygotować sobie jakieś środowisko testowe.
Moją propozycją jest utworzenie wirtualnego hosta na naszym lokalnym serwerze, tak abyśmy do testowanej strony mieli dostęp z poziomu subdomeny:
[code]rewrite.localhost[/code]
Głównym katalogiem (DocumentRoot) dla vhosta niech będzie np.
[code]/NASZ_GŁÓWNY_KATALOG_Z_WWW/rewrite/[/code]
i to na niego przekierujemy subdomenę rewrite.
Jak stworzyć vhosta opisałem w tym artykule.

Dlaczego takie rozwiązanie, a nie np. localhost/rewrite?
Ponieważ w momencie, gdy adres główny będzie miał postać typu serwer/katalog to stworzy nam to nadprogramowy ciąg (katalog/) w adresie, co za tym idzie nasze reguły będą musiały go uwzględniać. Można też oczywiście wszystko wrzucić bezpośrednio na localhost, wygodniej jednak jest tworzyć sobie oddzielne subdomeny (vhosty) do danych eksperymentów. No dobrze, jeśli mamy już vhosta, lub jeśli wybraliśmy katalog główny localhosta, to przystępujemy do pracy - utwórzmy w głównym katalogu plik .htaccess, na nim będziemy pracować.

Tutaj jeszcze jedna wskazówka odnośnie testowania regułek - bardzo przyda się nam strona:
http://htaccess.madewithlove.be/
która zawiera tester online dla takich reguł, wypluwając nam wynik przekierowania w czasie rzeczywistym. Bardzo gorąco polecam do testowania zasad i reguł, gdyż jest to niesamowicie przydatne narzędzie.

Przygotujmy sobie też w głównym katalogu plik index.php, który będzie pokazywał nam na bieżąco parametry pobierane z przepisanego URL-a:
[code]<?php
// index.php
foreach($_GET as $k => $v)
{
  echo $k . ' = ' . $v . '<br />';
}
?>[/code]

Zmienne serwera

Na początek warto poznać podstawowe zmienne, które udostępniane są nam do wykorzystania w pliku .htaccess.
Załóżmy, że wywołujemy w adresie następujący URL (a właściwie to URI, ale na potrzeby poradnika używajmy bardziej przyjemnej nazwy URL):
[code]http://rewrite.localhost/kategorie/21[/code]
Serwer widzi to wywołanie jako zmienną %{REQUEST_URI}
Nie jest to jedyna zmienna jaką serwer ustawia, zmiennych serwerowych jest dużo, poniżej kilka z nich wraz z krótkim opisem co dana zmienna zawiera:

  • %{REQUEST_FILENAME} - ścieżka i nazwa do wywoływanego w adresie pliku/katalogu,  tutaj: kategorie/21
  • %{REQUEST_URI} - pełny adres zapytania z jakiego nastąpiło wywołanie, tutaj: rewrite.localhost/kategorie/21
  • %{REQUEST_TIME} - obecny czas wywołania adresu
  • %{HTTP_HOST} - adres hosta do jakiego nastąpiło w adresie wywołanie, tutaj: rewrite.localhost
  • %{REQUEST_METHOD} - rodzaj wywołania (GET/POST/PUT...itp), tutaj: GET
  • %{HTTP_REFERER} - adres, z którego nastąpiło wywołanie adresu
  • %{QUERY_STRING} - wartość parametrów z adresu, np. wywołując http://localhost/kategorie/lista.html?sort=desc zawierać będzie sort=desc
  • %{DOCUMENT_ROOT} - katalog główny (pełna ścieżka) 
  • %{AUTH_TYPE} - rodzaj autoryzacji dla katalogu (Digest/Basic...)
  • %{THE_REQUEST} - pełna treść wywołania, tutaj: GET /kategorie/21/ HTTP/1.1

To tylko kilka główniejszych zmiennych, które pozwolą na standardową pracę z mod_rewrite.
Pełna lista zmiennych, wraz z opisami (a jest ich dużo) znajduje się pod adresem:
http://www.askapache.com/htaccess/mod_rewrite-variables-cheatsheet.html, natomiast lista najczęściej używanych zmiennych - pod koniec artykułu.

Przypomnijmy sobie przekierowanie jakie utworzyliśmy w poprzednim artykule:
[code]
# .htaccess
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/.]+)/?$ index.php?action=$1
[/code]
Tworzyło ono przekierowanie z:
[code]http://nazwa_hosta/AKCJA[/code]
do
[code]index.php?action=AKCJA[/code]
uprzednio sprawdzając, czy w katalogu nie istnieje plik, lub folder o nazwie AKCJA.

Pierwszy problem - końcowy slash
I w tym momencie napotykamy na pierwszy z problemów do rozwiązania, mianowicie na fakt występowania końcowego slasha. Na czym polega nasz problem?
Na tym - adres nasz może wyglądać tak:
[code]http://rewrite.localhost/kategorie[/code]
ale moze i tak:
[code]http://rewrite.localhost/kategorie/[/code]
/dla celów testowych, będziemy tutaj wykorzystywać umownie nazwę naszego vhosta - rewrite.localhost/

Akurat w naszej regułce obie formy wykonają poprawne przekierowanie, ale żeby ustandaryzować sobie formę adresu, na jakim będziemy dalej operować powinniśmy sprawić, aby:
- albo wszystkie adresy kończyły się slashem
- albo żeby żaden z adresów nie kończył się slashem

Rozwiązanie takie jest ponadto dobrą praktyką jeśli chodzi o roboty wyszukiwarek - w przypadku linku z końcowym slashem i linku bez slasha mamy DWA różne linki, a więc zdublowaną zawartość, co nie jest mile widziane przez wyszukiwarki.

Dodamy slasha na końcu do każdego adresu, zrobimy to warunkiem:
[code]RewriteCond %{REQUEST_URI} !(.*)/$[/code]
Powyższy warunek sprawdza, czy adres URL (REQUEST_URI) nie równa się wyrażeniu ze slashem na końcu (negacja - !)
jeśli nie, to tworzymy przekierowanie dla takiego adresu, za pomocą:
[code]RewriteRule ^(.*)$ http://%{HTTP_HOST}/$1/ [L,R=302][/code]
Powyższa reguła przepisuje każdy adres na taki sam, ale ze slashem na końcu.
Następnie mamy tutaj podane dwa argumenty objęte w nawiasy kwadratowe.
Argumenty te wymagają wyjaśnienia.

Argumenty reguł

Tóż po wyrażeniu obsługującym regułę przekierowania w nawiasach kwadratowych [ ] możemy podać listę dodatkowych argumentów/parametrów/znaczników/flag - spotkamy się z różnymi nazwami - my przyjmijmy określenie argumenty.
Argumenty takie mówią serwerowi co dodatkowo ma zrobić po wykonaniu reguły je poprzedzającej. Argument możemy podać jeden, możemy też kilka - w przypadku większej ich ilości podajemy je wszystkie w obrębie jednego nawiasu, oddzielając je przecinkiem. Lista możliwych argumentow jest długa i zostanie opisana na końcu artykułu, na chwilę obecną przedstawię jedynie te najczęściej wykorzystywane:

[L] - skrót od Last. Określa, iż reguła zawarta w linijce z tym argumentem jest ostatnią regułą przekierowania.
Na czym to polega? Otóż mod_rewrite przelatuje po koleji po wszystkich pasujących regułach, aż do samego końca. Parametr [L] mówi serwerowi, że obecna regułka jest ostatnią i że w tym miejscu, jeśli nastąpi jej wykonanie nakazuje zignorowanie następujących po niej reguł. Wykorzystywane to jest głównie przy określaniu "sztywnych" przekierowań.

[R=] - skrót od Redirect, czyli przekierowanie. Po znaku = przyjmuje kod przekierowania, a więc kod statusu z grup 300 i 400.
Przykładowo: [R=301] określa przekierowanie trwałe (301 Moved Permamently), natomiast [R=302] nakazuje przekierowanie tymczasowe (302 Moved Temporaly).
W celach testowych powinniśmy zamiast przekierowań 301 używać tych o statusie 302, czyli czasowych. Dlaczego? Dlatego, iż np. Firefox zapamiętuje permamentne przekierowania w cache'u, co może czasem utrudnić testowanie naszych regułek. W ostatecznej wersji natomiast powinniśmy używać 301.

[NC] - skrót od No Case. Sprawia, że ignorowana jest wielkość liter podczas dopasowania i tak np. reguła dla index.php w adresie bedze obowiązywać również dla InDeX.pHp.

[N] - skrót od Next. Powoduje rozpoczęcie przepisywania reguł od początku, z tym, że każde kolejne dopasowania wykonywane są dla już obecnie przepisanego URL-a, nie dla tego bazowego

[C] - skrót od Chain, czyli łańcuch. Powoduje zawarcie kilku reguł w jeden łańcuch (grupę). Jeśli nie wystąpi żadna z reguł w takim łańcuchu, to wykonywana jest reguła po nich występująca.

[T=] - skrót od Mime Type. Pozwala na ustawienie własnego typu MIME. Np. wykonanie [T=image/png] spowoduje wywołanie przepisanego URL-a z typem image/png.

Argumentów tych jest o wiele więcej. Opisałem w skrócie te najczęściej używane.
Pełna lista argumentów znajduje się na samym dole artykułu.

Istnieją także 2 argumenty dla dyrektywy RewriteCond:

[NC] - jak wyżej, ignorujący wielkość znaków
[OR] - domyślnie warunki są łączone za pomocą operatora logicznego AND, tj. muszą być spełnione wszystkie, aby wykonać regułę. Argument [OR] jak sama nazwa wskazuje wstawia pomiędzy dwa warunki operator logiczny OR zamiast AND.

Wiemy już co nieco o argumentach, wwróćmy więc do naszej regułki:
[code]
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://%{HTTP_HOST}/$1/ [L,R=302]
[/code]

Reguła jak widzimy dodaje nam slash do końca adresu, po czym informuje, że dopasowanie jest ostatnim i nie przetwarzamy już dalej w tym cyklu [L] oraz przekierowujemy na adres ze slashem w koncówce [R=302] (tutaj finalnie powinniśmy dać 301, dla testów pozostawmy 302). Mając już pewność, że adres kończy się slashem na końcu, część wyrażenia:
[code]RewriteRule ^([^/.]+)/?$ index.php?action=$1[/code]
sprawdzająca wystąpienie, lub nie końcowego slasha może zostać zamieniona na:
[code]RewriteRule ^([^/.]+)/$ index.php?action=$1[/code]
Na razie jednak pozostawy to w niezmienionej formie.
Finalnie więc nasz .htaccess wygląda tak:
[code]
# .htaccess
RewriteEngine on

# blok1
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://%{HTTP_HOST}/$1/ [L,R=302]

# blok2
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/.]+)/?$ index.php?action=$1
[/code]
Należy tutaj zrozumieć zasadę działania mechanizmu rewrite.
Otóż silnik rewrite leci po pliku .htaccess po koleji.
Polega to na tym, iż reguły z pliku przetwarzane są do skutku od początku do końca, aż do wystąpienia ostatniego pasującego dopasowania.
W naszym przypadku najpierw sprawdzany jest slash na końcu (blok 1), po czym po znalezieniu go, wyłaczamy dalsze przetwarzanie [L], następnie następuje przekierowanie na adres ze slashem [R=301] po czym cały proces przetwarzania pliku .htaccess zaczyna się od początku, ale tym razem mając już slash na końcu pierwsza reguła nie wykona się (nie dopasuje), wykona się jedynie ta z bloku 2, następnie po dopasowaniu jej (lub nie) przetwarzanie .htaccess zakończy się. Bardzo ważne jest zrozumienie tego mechanizmu, gdyż bez zrozumienia go prawie na pewno zdarzy się nam popadnięcie w błędną pętlę przy bardziej złożonych regułach.

Powyższy .htaccess w przypadku wpisania adresu:
[code]http://rewrite.localhost/kategorie[/code]
działa tak:
[code]
URL http://rewrite.localhost/kategorie
PIERWSZY PRZEBIEG .htaccess >> http://rewrite.localhost/kategorie/
DRUGI PRZEBIEG .htaccess >> http://rewrite.localhost/index.php?action=kategorie
[/code]

Diagram przedstawiający działanie mechanizmu Rewrite:
źródło: apache.org

I jego dokładniejsza wersja:




Dopasowania

Poki co zrobiliśmy jedno dopasowanie, do jednego parametru, dodajmy więc teraz więcej, podając np. id kategorii:
Najpierw musimy obmyśleć jak skonstruowane będą nasze URL-e i wybrać jakąś logikę.
Chcemy sporządzić URL zawierający w sobie dwa elementy:


  1. nazwę akcji (przykładowo: kategoria)
  2. id przeglądanej kategorii

Adres taki może mieć więc taką postać, gdzie kategoria to nazwa akcji $action, 41 to $id przeglądanej kategorii:
[code]http://rewrite.localhost/kategoria/41[/code]
Adres taki tłumaczyć będziemy na adres wewnętrzny o postaci:
[code]http://rewrite.localhost/index.php?action=kategoria&id=41[/code]
Pierwszy problem: nasz URL może przybrać dwie możliwe formy:
[code]http://rewrite.localhost/kategorie[/code]
lub
[code]http://rewrite.localhost/kategoria/41[/code]
Musimy więc w jakiś sposób uwzględnić obie formy.
Jak tego dokonamy? Za pomocą dwóch reguł.
Zrobimy to tak:
[code]
# .htaccess
RewriteEngine on

# blok1 - dodajemy slash na końcu
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://%{HTTP_HOST}/$1/ [L,R=301]

# blok2 - nasze regułki
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2 [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/?$ index.php?action=$1
[/code]

Pomijając resztę, nasze reguły wyglądają tak:
[code]
RewriteRule ^([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2 [L]
RewriteRule ^([^/.]+)/?$ index.php?action=$1
[/code]

Co się tutaj dzieje?
1) Najpierw dopasowujemy regułę dla dłuższego ciągu, z wystąpieniem numeru id w adresie, jeśli dopasowanie zostanie wykonane, to parametr [L] mówi serwerowi, że jest to ostatnie przetwarzanie i żeby nie dopasowywał już kolejnej reguły,
2) Dopiero po sprawdzeniu powyższego sprawdzamy krótszą wersję, z podaniem samej akcji. Dlaczego ważna jest taka kolejność?
Akurat w tym przypadku wykonanie reguł odwrotnie nie sprawi problemu, ale przy bardziej złożonych regułach mogłoby to powodować problemy, gdyż pamiętajmy, że każda kolejna reguła pracuje na wyniku poprzedniej, a więc dopasowanie początkowe niejako określa wszystko to co poleci dalej do przetwarzania.
Dowód? Wykonajmy coś takiego:
[code]
RewriteRule ^(.+)/$ index.php?action=$1
RewriteRule ^([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2
[/code]
Usunęliśmy sprawdzenie wystąpienie slasha w środku adresu w pierwszej linijce w związku z czym pierwsza reguła zostanie dopasowana do:
[code]http://rewrite.localhost/kategoria/41[/code]
Do reguły kolejnej przekazany zostanie wynik dopasowanej pierwszej:
[code]index.php?action=kategorie/41[/code]
w związku z czym druga reguła nie wykona się nigdy!
Powróćmy więc do wersji poprawnej:
[code]
RewriteRule ^([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2 [L]
RewriteRule ^([^/.]+)/?$ index.php?action=$1
[/code]
Wynikiem w przypadku adresu:
[code]http://rewrite.localhost/kategoria/123[/code]
będzie wewnętrzne przekierowania na:
[code]index.php?action=kategoria&id=123[/code]
...natomiast w przypadku:
[code]http://rewrite.localhost/kategorie[/code]
będzie to:
[code]index.php?action=kategorie[/code]
Analogicznie zrobimy z np. 3 i więcej parametrami:
[code]
RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2&parametr3=$3&parametr4=$4 [L]
RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2&parametr3=$3 [L]
RewriteRule ^([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2 [L]
RewriteRule ^([^/.]+)/?$ index.php?action=$1 [L]
[/code]
Całość naszego .htaccess wyglądać więc może tak:
[code]
# .htaccess
RewriteEngine on

# blok1 - dodajemy slash na końcu
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://%{HTTP_HOST}/$1/ [L,R=301]

# blok2 - nasze regułki
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2&parametr3=$3&parametr4=$4 [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2&parametr3=$3 [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2 [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/?$ index.php?action=$1 [L]
[/code]

Sprawdźmy teraz co się stanie, gdy wywołamy adres:
[code]http://rewrite.localhost/kategoria/123/aaa/bbb/ccc/ddd/eeee[/code]
Nie mamy żadnego wyrażenia dopasowującego taką ilość parametrów, serwer zwróci nam 404 Not Found.

Możemy jednak tego uniknąć, ignorując po prostu większą ilość parametrów podawanych między nadprogramowymi slashami:
Zmieńmy pierwszą regułę, zmieniając końcówkę jej dopasowania na:
[code].*/$[/code]
a więc na:
[code]RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/([^/.]+).*/?$ index.php?action=$1&id=$2&parametr3=$3&parametr4=$4 [L][/code]
.* oznaczać będzie nam tutaj dowolny ciąg znaków, który może, ale nie musi nadprogramowo na końcu wystąpić.
Jest to bardziej eleganckie rozwiązanie, niż rzucenie błędem 404.
Możemy też pobrać cały ciąg, przekazać go jako parametr do index.php, następnie przetworzyć przez PHP, albo stworzyć do obsłużenia tego oddzielne reguły, ja podałem jedynie przykład na jeden ze sposóbów radzenia sobie z niepasującymi do wzorca URL-ami. W praktyce całe żądanie przekazuje się do PHP i dopiero tam je przetwarza. Takich reguł jak powyżej się nie tworzy, pokazałem tutaj jedynie na przykładzie pewne zagadnienie.

QSA - parametry GET w zapytaniu

A co jeśli wywołamy np. taki URL?
[code]http://rewrite.localhost/kategorie/?sort=desc[/code]
Po wykonaniu naszych przepisań, otrzymamy jedynie:
[code]index.php?action=kategorie[/code]
natomiast:
[code]?sort=desc[/code]
zostanie pominięte.

Jak więc przekazać parametry podane po znaku zapytania?
Służy do tego argument [QSA] - skrót od Query String Append.
Zmodyfikujmy teraz nasze reguły w .htaccess do takiej postaci:
[code]
# .htaccess
RewriteEngine on

# blok1 - dodajemy slash na końcu
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://%{HTTP_HOST}/$1/ [L,R=301]

# blok2 - nasze regułki, tym razem z uwzględnieniem QSA
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2&parametr3=$3&parametr4=$4 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2&parametr3=$3 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/?$ index.php?action=$1 [L,QSA]
[/code]

Voila! Wynikiem przepisania:
[code]http://rewrite.localhost/kategorie/?sort=desc[/code]
jest:
[code]index.php?action=kategorie&sort=desc[/code]

Problem z prefixem "www."

Nieszczęsna pozostałość po starych czasach, czyli prefix, a właściwie subdomena www może, ale nie musi wystąpić na początku naszego adresu.
Wszystko zależy jaki adres wywoła użytkownik. Niestety tak to już jest, że:
[code]http://www.rewrite.localhost[/code]
i
[code]http://rewrite.localhost[/code]
to (pomimo że prowadzą w to samo miesjce) dwa rożne adresy, zarówno dla plików cookies, czy polityki Same Domain Origin, a nawet dla robotów wyszukiwarek.
Co możemy z tym zrobić? Opcje są dwie:

1) usuwamy wszędzie www. jeśli takowe wystąpi w adresie
2) dodajemy wszędzie www. o ile nie występuje na początku adresu.

Wybor należy do nas, podaję oba rozwiązania:

1) usuwa www. z każdego adresu:
[code]
RewriteCond %{HTTP_HOST} ^www. [NC]
RewriteRule ^(.*)$ http://rewrite.localhost/$1 [R=302,L]
[/code]

2) dodaje www. do każdego adresu:
[code]
RewriteCond %{HTTP_HOST} !^www. [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=302,L]
[/code]

Uwaga: pamiętajmy, że jedynie na czas testów dajemy tutaj przekierowanie 302, w finalnej wersji dajemy tutaj 301, czyli Moved Permamently.
Reguły te umieszczamy na początku, przed regułami dodającymi slasha do końcówki adresu:
[code]
# .htaccess
RewriteEngine on

# blok0 - usuwamy "www." z początku adresu
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{HTTP_HOST} ^www. [NC]
RewriteRule ^(.*)$ http://rewrite.localhost/$1 [R=302,L]

# blok1 - dodajemy slash na końcu
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://%{HTTP_HOST}/$1/ [L,R=302]

# blok2 - nasze regułki, tym razem z uwzględnieniem QSA
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2&parametr3=$3&parametr4=$4 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2&parametr3=$3 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/([^/.]+)/?$ index.php?action=$1&id=$2 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^/.]+)/?$ index.php?action=$1 [L,QSA]
[/code]

Jak wspomniałem wcześniej - konstrukcji takich jak powyżej nie tworzy się w praktyce, zamiast tego przekierowywuje się każdego requesta do jednego pliku PHP, który już sam dalej zajmuje się przeparsowaniem otrzymanego żądania:
[code]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule .? index.php [L]
[/code]

No i to tyle w dzisiejszym odcinku.
Wiemy już jak przepisywać większą ilość parametrów w URL-u, jak używać atrybutów oraz na co zwracać uwagę przy tworzeniu regułek. W następnej części wykorzystamy to trochę bardziej praktycznie, a także poznamy kilka innych rozwiązań.

Poniżej jeszcze lista wszystkich argumentów jakie stosować można do regułek oraz najczęściej używanych zmiennych. Lista zaczerpnięta z http://4programmers.net/Z_pogranicza/Mod_rewrite

Argumenty - lista:

  • Znacznik [NC] (nocase)
    Sprawia, że w danym wzorcu pomijane są różnice w wielkościach znaków. Dla przykładu, dwa poniższe wzorce mają identyczne działanie:
    RewriteRule ^([A-Za-z])/?$ web.php?q=$1
    RewriteRule ^([a-z])/?$ web.php?q=$1 [NC]
  • Znacznik R[=kod] (redirect)
    Znacznik ten powoduje formalne przekierowanie wraz z podaniem kodu stanu HTTP, domyślnie powoduje on wysłanie kodu HTTP 302 Moved Temporarily. Przykład:
    RewriteRule ^art/(\d+)/?$ art.php?id=$1 [R=301]
    Kod w powyższym przykładzie to 301 Moved Permanently.
    Używając tego znacznika można zwracać dowolny kod HTTP z zakresu od 300 do 400.
  • Znacznik F (forbidden)
    Działa podobnie do znacznika redirect, ale od razu wysyła odpowiedź HTTP 403. Jeżeli w ramach jednej reguły znajdzie się ten znacznik oraz redirect, to pierwszeństwa na znacznik forbidden, co oznacza, że zostanie wysłany kod 403 niezależnie od wartości kodu redirect.
  • Znacznik G (gone)
    Znacznik ten wysyła kod HTTP 410 - Usunięto, który informuje użytkownika o tym, że żądana strona została usunięta. Ma on pierwszeństwo przed znacznikiem redirect, ale ważniejszym od niego jest forbidden.
  • Znacznik L (last)
    Znacznik ten pozwala na zatrzymanie przetwarzania reguł RewriteRule po skutecznym dopasowaniu adresu URL do reguły.
  • Znacznik N (next)
    Znacznik next ponownie rozpoczyna proces przepisywania adresu od samego początku listy reguł. W momencie rozpoczęcia ponownego przetwarzania reguł obsługują one juz nie oryginalny adres URL, ale jego postać przepisano przez wszystkie reguły do momentu pojawienia się znacznika next.
  • Znacznik C (chain)
    Jeżeli chcielibyśmy traktować blok reguł jako jednostkę, to powinniśmy posłużyć się znacznikiem chain. Wytłumaczmy to na przykładzie:
    Rewrite Rule ^art/(\d+)/?$ art.php?id=$1 [C]
    RewriteRule ^cat/(\w+)/?$ cat.php?cat=$1 [C]
    RewriteRule ^art/[^\d+]/?$ error.php
    Jeżeli żadna z reguł związanych znacznikiem chain nie pasuje to przechodzimy do ostaniej.
  • Znacznik S=liczba (skip)
    Ten znacznik powoduje pominięcie podanej liczby reguł. Znacznik ów zachowuje podobnie się do chain, z tą różnicą, że pozwala pominąć reguły w przypadku udanego dopasowania.
  • Znacznik T=typ-mime (type)
    W przypadku kiedy chcemy zmienić typ MIME wysyłanego dokumentu, to możemy posłużyć się tym znacznikiem. Na przykład jeżeli mamy pliki xhtml i chcielibyśmy wysłać je za pomocą poprawnego typu MIME (application/xhtml+xml) to możemy wykorzystać tą regułę:
    RewriteRule ^.+\.xhtml$ - [T=application/xhtml+xml]
  • Znacznik CO=nazwa:wartość:domena[:czaszycia[:sciezka]] (cookie)
    Znacznik ten służy do utworzenia ciasteczka w przeglądarce użytkownika . Wymagane są pola nazwa, wartość i domena. Możemy na przykład utworzyć ciasteczko o nazwie bylemtu, istnienie którego będzie oznaczało że użytkownik już odwiedzał naszą stronę.
    RewriteRule ^index\.php$ - [CO=bylemtu:true:domena.pl]
  • Znacznik E=zmienna:wartosc (env)
    Znacznik ten pozwala nadać wartość zmiennej środowiskowej zmienna. W jednej grupie znaczników można przypisać wartości do wielu zmiennych środowiskowych.
  • Znacznik QSA (qsappend)
    W przypadku obecności tego znacznika mechanizm przepisywania pobierze oryginalny ciąg znaków zapytania i dopisze do niego ciąg znaków wygenerowany przez regułę.
    RewriteRule ^index\.php$ index.php?zmienna=wartosc [QSA]
    Jeżeli tej regule podamy adres postaci index.php?cos=bla to w wyniku jej działania otrzymamy index.php?zmienna=wartosc&cos=bla.
  • Znacznik NE (noescape)
    Znacznik ten pomaga unikać automatycznego wyróżniania znaków specjalnych.
  • Znacznik PT (passtrough)
    Używamy go w momencie kiedy chcemy połączyć moduł mod_rewrite z innymi moduła, które również zajmują się obsługą adresó URL.
  • Znacznik NS (nosubreq)
    Znacznik ten używamy w momencie jeżeli chcemy aby mechanizm przepisywania adresów pominął regułę, jeżeli żądanie jest wewnętrznym żądaniem podrzędnym. Bardzo rzadko jest używany, jezeli korzystamy z Apache i PHP, przydaje się w momencie dołączenia skryptów CGI.
  • Znacznik P (proxy)
    Nakazuje on zakończenie przetwarzania reguł i przekazanie danych żądania do modułu mod_proxy.

Zmienne serwera:

  • HTTP_USER_AGENT Ciąg znaków z informacja identyfikującą przeglądarke.
  • HTTP_REFERER Dokładnie "polecający", adres strony, która nas skierowała, na konkretną stronę. Nie wszystkie przeglądarki wysyłają, również firewall'e często wycinają to.
  • HTTP_COOKIE Ciąg znaków ciasteczka.
  • HTTP_FORWARDER Jeżeli żądanie jest obsługiwane przez serwer proxy, to zawiera dowolne przekazywane informacje.
  • HTTP_HOST Nazwa komputera wymieniana w rządaniu.
  • HTTP_PROXY_CONNECTION Zwraca zawartość nagłówka Proxy-Connection.
  • REMOTE_ADDR Adres ip komputera wysłającego żądanie.
  • REMOTE_HOST Nazwa komputera wysyłającego żądanie.
  • REMOTE_USER Nazwa użytkownika komputera wysyłającego żądanie. Nie musi być podana.
  • REMOTE_IDENT Zmienne przeznaczone do celów indentyfikacji. Może zawierać nazwę użytkownika.
  • REMOTE_METHOD Metoda żądania pliku (POST lub GET).
  • SCRIPT_FILENAME Pełna lokalna ścieżka do żądanego pliku.
  • PATH_INFO Dodatkowe informacje o ścieżce.
  • QUERY_STRING Ciąg zapytania albo parametry zapytania metodą GET.
  • AUTH_TYPE Rodzaj uwierzytelniania.
  • DOCUMENT_ROOT Katalog główny strony.
  • SERVER_ADMIN Adres email administratora serwera.
  • SERVER_ADDR Adres ip lub komputera serwera.
  • SERVER_PORT Podany w żądaniu port serwera.
  • SERVER_PROTOCOL Nazwa i wersja protokołu żądania.
  • SERVER_SOFTWARE Nazwa oprogramowania serwera.
  • TIME_YEAR Rok na serwerze.
  • TIME_MON Numer miesiąca.
  • TIME_DAY Numer dnia.
  • TIME_HOUR Godzina żądania.
  • TIME_MIN Minuta żądania.
  • TIME_SEC Sekunda żądania.
  • TIME_WDAY Dzień tygodnia żądania.
  • TIME Bierząca godzina.
  • API_VERSION Numer wersji interfejsu API serwera Apache.
  • THE_REQUEST Pełne żądanie HTTP.
  • REQUEST_URI Żądany zasób.
  • REQUEST_FILENAME Pełna ścieżka w lokalnym systemie plików do żądanego pliku.
  • IS_SUBREQ Informacja, czy żądanie jest wewnętrznym żądaniem podrzędnym.
  • HTTPS Wartość on oznacza, że połączenie jest szyfrowane.

Pełna lista tutaj: 

3 komentarze:

  1. Ciężko to ogarnąć laikowi. Czyli jeżeli dobrze rozumiem, żeby pozbyć się problemu adresów url ze sleshem na końcu należy wpisać do .httacces

    RewriteCond %{REQUEST_URI} !(.*)/$
    RewriteRule ^(.*)$ http://%{HTTP_HOST}/$1/ [L,R=302]

    ??
    Czy lepiej dać 301 jak to w zasadzie na stałe?

    OdpowiedzUsuń
  2. błąd:

    jest: %{REQUEST_URI} - pełny adres zapytania z jakiego nastąpiło wywołanie, tutaj: rewrite.localhost/kategorie/21

    a powinno być to co zostaje po adresie hosta, tutaj: /kategorie/21

    OdpowiedzUsuń
  3. Dzień dobry. W związku z tematem która forma przekierowania będzie bardziej poprawna:

    opcja 01:
    RewriteEngine on
    RewriteCond %{HTTPS} !=on [NC]
    RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

    opcja02:
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

    dziękuję i pozdrawiam

    OdpowiedzUsuń

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

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