Moduł mod_rewrite to nie tylko „przyjazne” adresy URL. mod_rewrite jest modułem Apache, którego funkcjonalność jest przeogromna – o czym często nawet administratorzy serwerów nie wiedzą. A jeszcze częściej wiedzą i wykorzystują tam, gdzie nie powinni. Trzeba jednak pamiętać, że mod_rewrite to nie lekarstwo na każdy problem!
W poście tym zamierzam przedstawić kilka alternatywnych rozwiązań, które będą wygodniejsze w użyciu, bardziej optymalne oraz skalowalne.
Przepisywanie adresów URL
Pewnie trochę Cię to zdziwi, ale dlaczego nie zaleca się przepisywania linków za pośrednictwem tego modułu? Sposób ten jest prosty i pozwala w bardzo krótkim czasie stworzyć „przyjazne” linki dla całego serwisu.
Jednak uzależnianie się od jednego serwera (w tym przypadku Apache) jest błędem. Twój serwis powinien być skalowalny, a to znaczy, że powinno być możliwe bezproblemowe przeniesienie serwisu na inny serwer HTTP (np. IIS, nginx czy Lighttpd). Wystarczy, że nowy serwer nie będzie obsługiwał mod_rewrite i zabawa przepisywania linków zaczyna się od nowa :)
Wykorzystywanym przeze mnie rozwiązaniem jest stosowanie ustawień podobnych do tych oferowanych przez WordPress, tzn.:
1 2 3 4 5 6 7 | <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> |
W ten sposób każde odwołanie trafia do pliku index.php, w którym następnie specjalna klasa tłumaczy i przyporządkowuje „przyjazne” linki do rzeczywistych im adresów. Efekt taki sam jak w przypadku mod_rewrite, ale skalowalność dużo większa.
Zwykłe przekierowania
W wielu miejscach w sieci można poczytać o przekierowaniach za pośrednictwem .htaccess. Prezentowane przykłady opierają się na mod_rewrite i dyrektywie RewriteRule z flagą R.
Dlaczego ten sposób jest niezalecany? Gdyż od przekierowywania są specjalne reguły rozpoznawane przez serwer Apache, typu Redirect, RedirectMatch czy ProxyPass.
- Redirect
Pozwala na wykonywanie prostych przekierowań – metoda ta jest najszybsza i pozwala na wykonywanie przekierowań typu jeden-do-jednego. Przykładowe zastosowania:1Redirect 301 / http://nowy.adres.pl/1Redirect pliki/ http://nowy.adres.pl/pliki/ - RedirectMatch
Reguła podobna do powyższej, lecz powala na stosowanie bardziej skomplikowanych przekierowań, budowanych przy pomocy wyrażeń regularnych. Przykładowe zastosowanie:1RedirectMatch 301 (.*)\.jpg http://nowy.adres.pl/zdjecia/$1 - ProxyPass
Reguła ta pozwala na wykonywanie przekierowań z wykorzystaniem serwera proxy. Przekierowania takie wykorzystują moduł mod_proxy i mają zastosowanie między innymi przy rozdzielaniu obciążenia na kilka serwerów. Przykładowe zastosowanie:1
2
3
4
5
6
7
8<IfModule mod_proxy.c>
# bezpieczeństwo - wyłaczenie pośrednictwa zwrotnego
ProxyRequests Off
# właściwe przekierowanie niewidoczne dla użytkownika
ProxyPass / http://nowy.adres.pl/
ProxyPassReverse / http://nowy.adres.pl/
</IfModule>Chcąc wykorzystać mod_proxy do bardziej skomplikowanych operacji (np. pobranie obrazków z innego serwera niewidoczne dla klienta) można połączyć mod_proxy z mod_rewrite. Wygląda to następująco:
1
2
3RewriteEngine On
RewriteRule (.*\.jpg) http://zdjecia.strona.pl/$1 [P]
ProxyPassReverse / http://zdjecia.strona.pl/
Serwery wirtualne
Moduł mod_rewrite można zastosować także do dynamicznego tworzenia serwerów wirtualnych. Jednak przed takim wykorzystaniem modułu warto rozważyć alternatywne rozwiązania. Istnieją bowiem dedykowane moduły utworzone specjalnie na cele serwerów wirtualnych. Mowa tutaj o mod_vhost_alias. Oczywiście warto wspomnieć o standardowych serwerach wirtualnych, domyślnie obsługiwanych przez serwer Apache.
Na korzyść mod_vhost_alias przemawia między innymi fakt, iż moduł ten pozwala odwzorowywać nazwy domenowe na nazwy katalogów. Umożliwia to dodawanie nowych nazw domenowych bez zmian w pliku konfiguracyjnym – czego nikt nie lubi.
Kontrola dostępu na podstawie adresu
Moduł mod_rewrite dzięki swojej warunkowej dyrektywie RewriteCond pozwala efektywnie blokować dostęp wybranym adresom IP, między określonymi godzinami czy dla różnych agentów przeglądarek. Jednak rzadko kiedy faktycznie wykorzystywane są powyższe sposoby blokowania treści, a najczęściej zabezpieczana jest jedynie wybrana część serwisu.
W tym celu nie należy korzystać z tego modułu. Wystarczającym rozwiązaniem jest zastosowanie serwerowych dyrektyw allow i deny dla danych folderów i miejsc na serwerze.
1 2 3 4 5 | <Directory /www/admin/> Order allow,deny Allow from 121.0. Deny from all </Directory> |
Lub w bardziej skomplikowanym przypadku (z wykorzystaniem wyrażeń regularnych):
1 2 3 4 | <DirectoryMatch "^/www/.*/[0-9]{3}"> Order allow,deny Allow from all </Directory> |
Podsumowanie
Warto pamiętać, że moduł mod_rewrite został napisany w 1996 roku i nie zawsze może być najlepszym rozwiązaniem. Wiele innych rozwiązań pozwala na efektywniejsze zarządzanie serwerem i rzadko kiedy tak naprawdę potrzebujesz tego modułu.
Moja rada jest więc następująca: zamiast upatrywać się wszelkich miejsc, gdzie tylko możesz zastosować mod_rewrite, zacznij korzystać z tego modułu jedynie w sytuacjach bez wyjścia – jako ostatnią deskę ratunku. Powodów jest kilka, między innymi wykorzystywanie niewydajnych wyrażeń regularnych czy niepotrzebne aktywowanie kolejnego modułu, co również wydłuża działanie serwera Apache.
Używam podobnego rozwiązania z pierwszego przykładu – różnica jest znikoma.
2
3
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ ?furl=$1&%{QUERY_STRING}
Jednym if’em załatwiam sprawdzane czy jest friendly czy nie.
Nie chcę siać fermentu – ale część rzeczy nie jest realizowana przez ModRewrite – po prostu możliwości .htaccess są przeogromne.
I jeszcze taka mała uwaga – RewriteBase nie jest wymagany, czasem przydatny, czasem szkodzi.
Właśnie w tej notce chciałem opisać miejsca, w których nadużywa się modułu mod_rewrite. Jest wiele innych modułów i dyrektyw Apache, które działają optymalniej (choćby przez niewykorzystywanie niewydajnych wyrażeń regularnych). Z tego co zauważyłem, zwłaszcza wśród pozycjonerów i przekierowań – ich problemy możnaby w dużo prostszy sposób rozwiązać (Redirect, RedirectMatch).
Co do Twojej regułki mam pewną uwagę: wzorzec o wartości ^(.*)$ wymusza na mechanizmie wyrażenia regularnego przeanalizowanie całego ciągu tekstowego i porównanie każdego znaku z symbolem wieloznaczności . w celu sprawdzenia, czy znak pasuje do wzorca. Oczywiście każdy znak pasuje do wzorca . – jednak ciąg o n znakach będzie powodował wykonanie n operacji porównania.
W przypadku wzorca z WordPress sprawa wygląda następująco:
Czyli od razu cała treść adresu URL jest przekazana do pliku index.php :)
Różnice są w pobieraniu wywołanego adresu
2
3
4
5
6
7
8
9
10
11
12
13
14
// $_GET['furl'] - moja wersja
// dla adresu http://domena.com/katalog/pl/artykuly/tytul.html
$HTTP_SERVER_VARS['PATH_INFO'] # nic
$_SERVER['REQUEST_URI'] # /katalog/pl/artykuly/tytul.html
$_SERVER['PATH_INFO'] # nic
$_GET['furl'] # /pl/artykuly/tytul.html
// dla adresu http://domena.com/katalog/index.php/pl/artykuly/tytul.html
$HTTP_SERVER_VARS['PATH_INFO'] # /pl/artykuly/tytul.html
$_SERVER['REQUEST_URI'] # /katalog/index.php/pl/artykuly/tytul.html
$_SERVER['PATH_INFO'] # /pl/artykuly/tytul.html
$_GET['furl'] # nic
Możliwe, że coś pominąłem…
Enyłej, po wpisaniu ‚php frienldy url’ w googlach, wyniki są … masakrujące.
Tutoriale jak stworzyć potworki pokroju index.php/pl/artykul/tytul.html, albo przygotowywanie wyrażenia regularnego dla każdego wariantu adresu…
Heh! Że też im się chce…
Cześć,
a co wiesz na temat tego jak mod_rewrite wpływa na obciążenie serwera?
pytam dlatego że chce zrealizować takie zadanie: za pomocą m_r zablokować możliwość pobierania z serwera plików tj. jpg, js, gif itd.
Aktualnie używam do tego takiej regułki:
RewriteRule \.(gif|jpg|jpeg|png|js|)$ /dev/null
no i pytanie, jest to optymalne czy nie?
Pozdrawiam :)
@piochu: Jak nie mod_rewrite to czym innym chciałbyś blokować? Na pewno dużo więcej transferu zaoszczędzisz w ten sposób niż gdybyś zezwalał na pobieranie tych plików graficznych.
Jeśli wydajność jest tutaj mega istotna to grafiki wydzielić do jednego folderu (grupy folderów / serwera) i na ten folder dać .htaccess z regułką jak powyższa.
Może wyjaśnie o co chodzi, tak będzie prościej.
Pisze kawałek kodu który będzie analizował ruch na mojej stronie. Jeśli liczba odsłon na minute/godzinę (jak tam sobie ustale) wzrośnie o ustalony wskaźnik skrypt przełączy się w „tryb oszczędności transferu” ;), ma to być coś w rodzaju zabezpieczenia przed efektem wykopu.
Czyli, jeśli link do mojej strony dostanie się na jakiś popularny serwis algorytm „zauważy to” i odpowiednio zareaguje.
Myślę że dobrym wyjściem w takiej sytuacji (jeśli korzysta się z taniego, współdzielonego hostingu o ograniczonym transferze i chce się aby strona możliwie jak najdłużej przetrwała online) jest nie serwowanie ciężkiego contentu (zdjęcia, filmy itd.).
Dlatego właśnie pytam czy regułka którą podałem nadawała by się do zastosowania w takiej sytuacji?
Jeśli bredzę to powiedź ;)
Niby dobre rozwiązanie, choć często JS w 80% jest pobierane z CDN Google czy innej serwerowni (mowa o głównej bibliotece, jak jquery czy prototype, które często ważą najwięcej). To samo z obrazkami – jeśli spodziewasz się tak wielkiego ruchu to można przerzucić na jakiś CDN i po problemie.
Wyłączenie grafiki i innych bajerów też jest dobrym rozwiązaniem, choć przy większym natężeniu ruchu serwer i tak padnie :-) mod_rewrite też nie należy do najwydajniejszych rozwiązań i warto by było wyłączyć całkowicie przepisywanie adresów na ten czas. Zamiast tego mógłbyś w folderze z grafiką i plikami JavaScript umieścić plik .htaccess o treści:
2
Deny from all
ooo „mod_rewrite też nie należy do najwydajniejszych rozwiązań i warto by było wyłączyć całkowicie przepisywanie adresów na ten czas.” ooo takie stwierdzenie mi chodziło :)
ogólnie, thx za info :)
Właśnie odnalazłem Twój art w sieci. Na pewno się przydadzą Twoje informacje.
Pozdrawiam
Robiłeś coś może z przekierowaniami https?
Usilnie staram się zrobić za pomocą mod_rewrite’a przekierowanie na panel admina po https i jakość kiszka :)
@Marek: nie robiłem takiego przekierowania, ale nie widzę w tym większych problemów. Poza tym sieć jest pełna materiałów o tym jak robić takie przekierowania. Może sprecyzuj swój problem oraz poprzyj przykładowym kodem, który nie działa? :)
mod_rewrite.so bo tak się ta biblioteka nazywa pod Linuksem i pod Windows również (nie .dll) to program napisany w języku c, skompilowany z opcją optymalizacji pod nowoczesne procesory i korzystający z pliku .htaccess (lub odpowiednio innego) jako plik konfiguracyjny a nie interpretowany język programowania.
Co to oznacza?
Ano to że plik konfiguracyjny modułu jest parsowany w jednym przebiegu a jego wartości podstawiane są pod konkretne miejsca w pamięci RAM bloku danych modułu mod_write.so nie jak w PHP gdzie składnia jest rozpoznawana i parsowana w 4-12 przelotach stosownie do zagnieżdżeń.
Na procesorach pentium 3 ta operacja jest szybsza od jakiegokolwiek skryptu PHP o jakieś 200 razy a na i3 w trybie 64bit około 500 mimo że i parsowanie PHP przyspiesza. Niestety programy skompilowane do języka maszynowego tak mają że są szybsze od języków interpretowanych jakim jest np PHP.
Skrypt PHP będzie na 100% wolniejszy przynajmniej o rząd wielkości od działania modułu mod_rewrite.so a zajętość pamięci będzie 1000 razy większa przy tworzeniu sterty wywołania kodu PHP niż 64KB pamięci które zajmuje moduł.
To tyle. Czekam na konstruktywną krytykę.
Serdecznie pozdrawiam!
@at0mic: bardzo słuszne uwagi – im więcej rzeczy przerzucimy na mod_rewrite tym szybciej nasza strona zadziała.
Problem w tym, że regułki z .htaccess są przerabiane przy każdym żądaniu, niezależnie czy jest to plik graficzny czy czcionka, styl CSS czy żądanie zwykłej strony HTML. Zatem przy stronie zawierającej kilkadziesiąt plików (obrazki, css, javascript, czcionki) mod_rewrite sprawdzi każde żądanie z osobna pod kątem regułek zawartych w ustawieniach serwera. A jeśli do .htaccess władujemy kilkaset wyrażeń regularnych dla obsługi wszystkich podstron to trochę się nam mnoży tych zadań :-)
Nie mówiąc o sytuacji, w której później zechcemy zmienić serwer Apache na nginx czy inny. Albo gdy klient zarządza możliwości ustawiania adresów URL z poziomu CMS.
Jak zmienić re_write znak „_” na „-„?