NorthPole.pl - przyjazne linki

Przyjazne linki, przyjazne adresy stron, albo inaczej dostępne url-e

Są przynajmniej dwa powody, dla których warto zrezygnować z kodowania w adresach url parametrów w bezpośredni sposób. Mam na myśli linki typu: /index.php?id=galeria&sid=2006&ssid=ciechocinek

Po pierwsze takie adresy są niestrawne dla robotów indeksujących strony, przez co podstrony mogą być niedodane do indeksu. Więcej na ten temat można znaleźć na forach i stronach zajmujących się pozycjonowaniem SEO.

Drugim, wydaje mi się, równie ważnym powodem jest ograniczona czytelność takich linków, czyli temat dotyczący usability. Internauta odwiedzając nasz serwis na pewno nie zapamięta takiego niezrozumiałego ciągu znaków i kiedy ponownie będzie chciał przeczytać interesujący go artykuł czy pooglądać ciekawą galerię będzie musiał rozpoczynać poszukiwania w naszej witrynie od początku. Nawet, jeśli doda link do ulubionych to przy niedostatecznie dobrym opisie strony (w elemencie <title/>) za jakiś czas nie domyśli się, co kryje się za skomplikowanym adresem i może go o zgrozo sfrustrowany usunąć.

Jak temu zaradzić?

Powinniśmy się postarać, aby zakodować parametry w linkach przyjaźnie na przykład tak: /galeria/2006/Ciechocinek/

Tak zakodowany adres jest czytelny dla ludzi i robotów indeksujących strony. Wiadomo od razu, że chodzi o galerię zdjęć z 2006 roku wykonanych w Ciechocinku. Dodatkowo dociekliwy internauta może usunąć z adresu url fragment Ciechocinek/ i przejdzie do spisu galerii z 2006 roku, a wycinając 2006/Ciechocinek/ przejdzie do spisu wszystkich galerii nie korzystając przy tym z nawigacji na stronie.

Przypomnij sobie ile razy sam tak postąpiłeś szczególnie, gdy nawigacja nie grzeszyła dostępnością (accessibility), przez co była mało użyteczna (usability). Wydaje mi się, że jest to często intuicyjne zachowanie i warto zaimplementować odpowiednią akcję z nim związaną. Rozwiązanie takie może poprawić wizerunek naszego portalu czy strony.

Jak to osiągnąć?

Najprostszym rozwiązaniem jest stworzenie na serwerze struktury, drzewa katalogów odzwierciedlającej zawartość serwisu gdzie w każdym katalogu lokujemy index.php/html.

Jest to jednak rozwiązanie obarczone wszelkimi wadami kodowania stron przy pomocy struktury opartej na wielu plikach. Problemy z późniejszą modyfikacją, wielokrotnie zagłębione ścieżki no i ilość kodu, który bezużytecznie zalega na serwerze. Poza tym rozwiązanie jest tylko częściowe, bo adresy będą wyglądały tak: /galeria/2006/ciechocinek/index.php albo tak /galeria/2006/ciechocinek.php a po co internauta ma wiedzieć jak nazywa się plik który jego klient (np. przeglądarka) pobiera z serwera? Czym mniej o tym wie tym możemy czuć się bezpieczniej.

Dlatego spróbujemy zrobić to inaczej.

Mod_rewrite

To jest magiczne zaklęcie, z którego skorzystamy do poprawienia urody linków. Mod_rewrite, to moduł serwera posiadający funkcję rewrite zajmującą się przepisywaniem adresów.

Więc zanim coś konkretnego zrobimy trzeba sprawdzić czy nasz serwer obsługuje to zaklęcie.

Na własnym serwerze (localhost) sprawdzamy po prostu plik konfiguracyjny Apache-a httpd.conf (np. apache/conf/ httpd.conf ) czy linijka: LoadModule rewrite_module modules/mod_rewrite.so jest odhashowana (nie ma przed nią znaku #). Jeśli jest to po prostu go usuwamy, zapisujemy plik konfiguracyjny i uruchamiamy Apache-a ponownie.

Na serwerze, na którym nie mamy dostępu do plików konfiguracyjnych, czyli na wszystkich dzierżawionych komercyjnych i darmowych, na których przyszło nam składować nasze strony uruchamiamy funkcję phpinfo().

Jak to zrobić? Pewnie wiesz ale jakby co ;-) to proponuję zapisać na serwerze plik test.php w którym umieścisz poniższy kod:

  1. <?PHP
  2. phpinfo();
  3. ?>

Po wykonaniu tego skryptu powinniśmy zobaczyć informacje na temat konfiguracji serwera. Odszukujemy wśród nich załadowane moduły Loaded Modules i jeśli jest tam moduł mod_rewrite to serwer obsługuje przepisywanie.

Niekiedy funkcja phpinfo() może być zablokowana (oczywiście nie mówię o localhostach) wtedy pozostaje zapytać administratora dostępność tego modułu.

Jeśli jednak z jakiegoś powodu moduł jest niedostępny to pozostaje nam zaimplementować przepisywanie na poziomie skryptów php. O takim rozwiązaniu tutaj nie będę jednak pisał.

Co to jest .htaccess?

OK. Nasz serwer obsługuje przepisywanie więc aby nakazać mu specjalne traktowanie spreparowanych url-i musimy stworzyć plik .htaccess czyli plik konfiguracyjny serwera Apache umożliwiający zarządzanie zawartością katalogu w którym się znajduje.

Plik ten posiadać powinien dyrektywy, które wykorzystamy do ustawienia przekierowań. Muszą znajdować się one w nowych liniach a na końcu, rzecz bardzo ważna, konieczna jest pusta nowa linia .

Baza testowa

Abyśmy jednak mogli pobawić się z Apachem w przepisywanie, musimy przygotować testową stronę. Proponuję umieścić ją w nowym katalogu, u mnie url-test. Katalog ten powinien znajdować się fizycznie w głównym katalogu dokumentów Apachea, czyli tym zdefiniowanym w DocumentRoot w pliku konfiguracyjnym Apachea (httpd.conf), domyślnie w htdocs lub public_html. Do tego katalogu z poziomu przeglądarki odwołujemy się przy pomocy ukośnika / a do naszego przez /url-test/ czyli na localhost url powinien wyglądać tak: http://localhost/url-test/ a na zarejestrowanej domenie np tak: http://www.moja-strona.pl/url-test/

Czemu piszę o takich podstawach? Powód jest prosty będziemy serwerowi nakazywać szukanie plików nie tam i nie takich, jakich by się tego mógł spodziewać po adresie url, więc istotne jest abyśmy sami wiedzieli co gdzie mamy. To pozwoli dobrnąć do celu bez nadmiernej frustracji naszej i serwera.

No dobra katalog już mamy uff, oczywiście nie zapominamy na Linuxsowych maszynach ustawić chmod-ów na 755. Zabieramy się, więc do zapisania w nim pliku testowego np index.php ;-) ze skromną zawartością:

  1. <?PHP
  2. echo '<h1>Udało się!</h1>';
  3. ?>

Zawartość naszego .htaccess

Wreszcie coś istotnego. Otwieramy, więc nasz ulubiony edytor i wpisujemy w nim kolejno:

  1. RewriteEngine on
  2. DirectoryIndex index.php
  3. RewriteBase /url-test/
  4. RewriteRule ^(.*)$ index.php

Dyrektywy te oznaczają, że:

  1. włączamy funkcje przepisywanie RewriteEngine on;
  2. ustawiamy domyślny plik, który zostanie wykonany w naszym katalogu DirectoryIndex index.php;
  3. definiujemy „ścieżkę bazową” do katalogu z naszą stroną - na jej podstawie będzie tworzone przepisywanie RewriteBase /url-test/;
  4. i wreszcie właściwa dyrektywa, której składnia wymaga podania ciągu szukanego i ciągu przepisywanego – u nas RewriteRule ^(.*)$ index.php co oznacza że wszystko co wystąpi w adresie (.*) po ścieżce bazowej będzie przepisywane jako index.php;
  5. a na końcu nowa linia ;

To oczywiście jest tylko szablon, który dalej będziemy rozwijać.

Plik ten zapisujemy oczywiście w naszym testowym katalogu pod nazwą .htaccess. Jeśli wasz edytor tego nie potrafi to można zapisać go pod dowolną nazwą a potem ją zmienić (ale radzę zmienić edytor ;-) to przecież skandal że nie potrafi zrobić takiej prostej rzeczy).

Testy

Wszystko jest gotowe otwieramy przeglądarkę (oczywiście NIE IE) i wpisujemy adres: http://localhost/url-test/ lub http://www.moja-strona.pl/url-test/

Jeśli zrobiliśmy wszystko bezbłędnie to ujrzymy piękny nagłówek poziomu pierwszego (<h1/>) z napisem Udało się! To oczywiście nic wielkiego, ale jeśli na końcu naszego adresu dodamy kilka znaków np qwerty (http://localhost/url-test/qwerty) to po odświeżeniu zawartości okna dalej powinniśmy oglądać nasze optymistyczne hasło. Co zrobiliśmy – nic szczególnego to zwykłe przekierowanie, odszukany ciąg qwerty został przez serwer przepisany jako index.php który później został wykonany.

Jeśli wszystko działa to możemy zabierać się dalej za główny temat naszych prac, jakim jest wykorzystanie mod_rewrite do stworzenia przyjaznych, dostępnych adresów url w naszej witrynie.

Modyfikacje

W tym celu do naszego pliku index.php dopisujemy kolejne linie, tak że całość powinna wyglądać następująco:

  1. <html>
  2. <head>
  3. <title>Przyjazne linki</title>
  4. </head>
  5. <body>
  6. <?PHP
  7. $root_dir="/url-test/";
  8. if(isset($_GET['id'])){
  9. $id=$_GET['id'];
  10. }else{
  11. $id='startowa';
  12. }
  13. echo '<h1>Udało się!</h1>';
  14. echo "<p>Jestem na stronie: $id</p>";
  15. ?>
  16. <ul>
  17. <li><a href="<?php echo "$root_dir"; ?> pierwszy-link/">Pierwszy link</a></li>
  18. <li><a href="<?php echo "$root_dir"; ?> drugi-link/">Drugi link</a></li>
  19. <li><a href="<?php echo "$root_dir"; ?> trzeci-link/">Trzeci link</a></li>
  20. </ul>
  21. </body>
  22. </html>

Co tu się dzieje? Po sekcji <head/> na początku skryptu ustawiamy zmienną $root_dir zawierającą ścieżkę do katalogu z naszą stroną, czyli dokładnie to samo, co zawiera dyrektywa RewriteBase zapisana w pliku .htaccess. Dalej sprawdzamy czy zmienna id została wysłana jako GET i jeśli tak to ją pobieramy $id=$_GET['id'] a jeśli nie to ustawiamy ją jako 'startowa'. W dalszej części kodu chwalimy się przechwyconą lub ustawioną zmienną: echo "<p>Jestem na stronie: $id</p>";

Na końcu budujemy menu w którym w linkach doklejamy $root_dir przez co jeśli zapragniemy w przyszłości przenieść naszą stronę do innej lokalizacji to wystarczy że zmienimy wartość tej zmiennej i wpis w .htaccess w dyrektywie RewriteBase.

Musimy również poprawić nasz plik .htaccess. Zmieniamy w nim regułę RewriteRule i teraz wygląda on tak:

  1. RewriteEngine on
  2. DirectoryIndex index.php
  3. RewriteBase /url-test/
  4. RewriteRule ^([a-zA-Z-_0-9]+)/$ index.php?id=$1

Co to znaczy! Wyrażenia regularne? Tak, pierwsza część reguły to klasyczny wzorzec oparty na wyrażeniach regularnych, którym będziemy dopasowywali adres url. Oznacza on, że szukamy ciągu składającego się z małej lub wielkiej litery, myślnika, podkreślenia lub cyfry, [a-zA-Z-_0-9], który musi wystąpić co najmniej raz + i musi być zakończony ukośnikiem /. Ta część ciągu, zgrupowana w nawiasie (…), stanowić będzie wartość parametru $1, który wstawiamy do żądania jako id=$1. Dzięki temu serwer będzie próbował wykonać instrukcje zawarte w pliku index.php z parametrem id który przyjmie wartość $1.

No, więc testujemy. Wpisujemy adres: http://localhost/url-test/ Jeśli wszystko dokładnie zrobiliśmy powinniśmy zobaczyć naszą stronę testową z informacją że Jestem na stronie: startowa. Po wybraniu dowolnego linku nasza strona raczy nas o tym poinformować wypisując odpowiedni komunikat np Jestem na stronie: drugi-link

Dodatkowo, jeśli w adresie dodamy dowolny ciąg składający się z dozwolonych znaków ([a-zA-Z-_0-9]), to serwer potulnie przechwyci je i ustawi w zmiennej $id którą skrypt z radością wyświetli w komunikacie Jestem na stronie:qwerty_qwerty-qwerty.

Jest tylko jeden mały problem. Jeśli w adresie na końcu zabraknie ukośnika to serwer poinformuje nas o tym wyświetlając komunikat w rodzaju: Not Found. The requested URL /url-test/qwerty was not found on this server.

Jednak to jest naprawdę mały problem, który usuniemy dodając do RewriteRule jeden znak – znak zapytania ?, który poinformuje serwer że poprzedzający go ukośnik nie musi występować w dopasowywanym ciągu. Cała poprawiona dyrektywa wygląda tak:

RewriteRule ^([a-zA-Z-_0-9]+)/?$ index.php?id=$1

Kodowanie wielu parametrów w linku

Co osiągnęliśmy do tej pory? Mamy skrypt który koduje jeden parametr w adresie url, na serwerze czeka .htaccess z dyrektywami pozwalającymi na odpowiednią interpretację przesyłanych zapytań, więc co jeszcze możemy zrobić? Oczywiście zakodować kolejne parametry. Zazwyczaj ilość przesyłanych zmiennych w linku jest większa, nawet w przytoczonym na wstępie przykładzie galerii zdjęć z Ciechocinka są trzy.

Więc ponownie zabieramy się do pracy. Po pierwsze trzeba przerobić naszą testową stronę, aby wysyłała więcej parametrów no i zmodyfikować odpowiednio dyrektywę RewriteRule.

Modyfikację skryptu dla kilku parametrów przesyłanych w linku rozpoczniemy od napisania funkcji odczytującej te parametry:

  1. function pobierz_parametr($id){
  2. if(isset($_GET["$id"])){
  3. $id=$_GET["$id"];
  4. }else{
  5. $id=false;
  6. }
  7. return $id;
  8. }

Co ona robi? Z grubsza to samo, co fragment kodu w pierwotnym skrypcie odpowiedzialny za pobieranie zmiennych wysyłanych przez GET. Mając, więc napisaną „wyspecjalizowaną” funkcję możemy wykorzystać ją w naszym kodzie:

  1. <?PHP
  2. //ustawianie bazowego adresu
  3. $root_dir="/url-test/";
  4. //pobieranie parametrów
  5. $id1= pobierz_parametr('id1');
  6. $id2= pobierz_parametr('id2');
  7. $id3=pobierz_parametr('id3');
  8. if($id1!=false){
  9. echo "<h1>Udało się!</h1>
  10. <p>Jestem na stronie: $id1</p>";
  11. echo "<p>Dodatkowe parametry zebrane z urla:</p>";
  12. echo "<ul><li>$id2</li><li>$id3</li></ul>";
  13. }else{
  14. echo "<h1>Udało się!</h1>
  15. <p>Jestem na stronie startowej</p>";
  16. }
  17. ?>

Kod jest prosty, więc powinien być zrozumiały, ale dla porządku opiszę go.

Po zebraniu parametrów $id1, $id2, $id3 sprawdzany jest warunek czy $id1 różny jest od false, co oznacza, że w linku wysłane zostały jakieś parametry następnie wyświetlana jest strona, na której możemy odczytać wartości wszystkich zebranych parametrów. Nie chodzi tutaj o praktyczne ich wykorzystanie do sterowania zawartością tylko o oznajmienie, że zostały one pobrane. Dlatego kod został napisany tak, aby w jak najprostszy sposób można było testować wyniki naszej pracy.

Pozostaje jeszcze zmodyfikować „menu” tak, aby można było wysyłać więcej parametrów:

  1. <h2>Menu</h2>
  2. <ul>
  3. <li><a href="<?php echo "$root_dir"; ?> pierwszy-link/">Pierwszy link 1</a></li>
  4. <li>
  5. <ul>
  6. <li><a href="<?php echo "$root_dir"; ?> pierwszy-link1/parametr1_1">podstrona 1.1</a></li>
  7. <li><a href="<?php echo "$root_dir"; ?> pierwszy-link1/parametr1_2">podstrona 1.2</a></li>
  8. </ul>
  9. </li>
  10. <li><a href="<?php echo "$root_dir"; ?> drugi-link2/">Drugi link 2</a></li>
  11. <li><a href="<?php echo "$root_dir"; ?> trzeci-link3/">Trzeci link 3</a></li>
  12. <li>
  13. <ul>
  14. <li><a href="<?php echo "$root_dir"; ?> trzeci-link3/parametr3_1">podstrona 3.1</a></li>
  15. <li>
  16. <ul>
  17. <li><a href="<?php echo "$root_dir"; ?> trzeci-link3/parametr3_1/parametr3_1_1"> podstrona 3.1.1</a></li>
  18. <li><a href="<?php echo "$root_dir"; ?> trzeci-link3/parametr3_1/parametr3_1_2"> podstrona 3.1.2</a></li>
  19. </ul>
  20. </li>
  21. </ul>
  22. </li>
  23. </ul>

Zrobiło się tego trochę, ale po wyświetleniu otrzymamy menu zawierające trzy poziomy zagłębienia.

Kolejna część dotyczy modyfikacji zawartości pliku .htaccess.

Dodamy w nim dwie reguły:

  1. RewriteEngine on
  2. DirectoryIndex index.php
  3. RewriteBase /url-test/
  4. RewriteCond %{REQUEST_FILENAME} !-f
  5. RewriteCond %{REQUEST_FILENAME} !-d
  6. RewriteRule ^([a-zA-Z-_0-9]+)/([a-zA-Z-_0-9]+)/([a-zA-Z-_0-9]+)/?$ index.php?id1=$1&id2=$2&id3=$3 [L]
  7. RewriteRule ^([a-zA-Z-_0-9]+)/([a-zA-Z-_0-9]+)/?$ index.php?id1=$1&id2=$2 [L]
  8. RewriteRule ^([a-zA-Z-_0-9]+)/?$ index.php?id1=$1 [L]

Dyrektywa RewriteCond z parametrem %{REQUEST_FILENAME} !-f oraz !-d zajmuje się sprawdzaniem czy przepisana reguła nie odnosi się do istniejącego pliku (-f) albo katalogu (-d), umożliwia to dostęp do plików istniejących na serwerze np. do pliku css czy do obrazków.

Reguły zapisane są w kolejności od najbardziej rozbudowanej, aby kolejno wprowadzać reguły prostsze.

^([a-zA-Z-_0-9]+)/([a-zA-Z-_0-9]+)/([a-zA-Z-_0-9]+)/?$ index.php?id1=$1&id2=$2&id3=$3 [L]

Dopuszczamy tutaj trzy parametry zamknięte w nawiasach, których wartości wstawione zostaną do żądania jako id1=$1, id2=$2, id3=$3.

Dodatkowo na końcu każdej reguły pojawiła się flaga [L] (od last), która nakazuje przerwać dalsze przepisywanie, jeśli napotkany ciąg można dopasować bieżącym wyrażeniem. Zapobiega to przed zapętlaniem kolejnych reguł.

Podsumowanie

I to właściwie wszystko. Można zabrać się za testowanie i dalsze modyfikacje. Cały listing kodu dla plików index.php oraz .htaccess znajduje się na końcu artykułu. Ale zanim przejdziesz do własnych zajęć pragnę dodać, iż przedstawione tutaj rozwiązanie jest bardzo proste, ponieważ miało na celu jedynie zapoznanie z możliwościami, jakie dają dyrektywy Apachea. Pomimo to dzięki zastosowaniu zaprezentowanego kodu uzyskaliśmy oczekiwane „przyjazne linki” pozbawione nieczytelnych parametrów. Dla tych, którzy znają „wyrażenia regularne” jasne jest, iż w dyrektywie RewriteRule można wiele zmienić. Ja wybrałem taki sposób, ponieważ zależało mi na maksymalnej prostocie, która pozwolić miała na zrozumienie zasad.

Ponadto w naszym przekładzie przewidzieliśmy możliwość wprowadzania jedynie trzech parametrów, co może być wystarczające jedynie dla niewielkich witryn. Jest to rozwiązanie zupełnie nieelastyczne, ponieważ gdy pojawi się potrzeba większego zagłębienia struktury serwisu niezbędna będzie modyfikacja pliku .htaccess o kolejny poziom.

Znacznie bardziej uniwersalne rozwiązanie bazuje na rozbijaniu przepisanego url-a na poziomie skryptu php, to jednak jest już zupełnie inna bajka…

Pobierz pliki z pełnym kodem opisywanym w artykule