Przyjazne adresy URL a wydajność

11 listopada 2011

Jakiś czas temu (kilka lat temu?) ktoś w komentarzu poprosił mnie o wytłumaczenie jak zniwelować dodatkowe obciążenie jakie generuje użycie przyjaznych adresów URL. Zacząłem o tym pisać a potem, z jakiegoś powodu, przerwałem i do tej pory nie znalazło się trochę czasu aby skończyć... Wiele artykułów mi w ten sposób przepadło. Tym razem dokończę dzieła :) A raczej napiszę od nowa bo wcześniej niepotrzebnie się rozpisałem...

Główne założenia: musisz napisać wydajną aplikację z przyjaznymi adresami URL w których nie przekazujesz żadnych identyfikatorów.

Przyjazne adresy to dzisiaj już konieczność. Ale wymagania wciąż rosną i teraz adresy muszą być jeszcze bardziej przyjazne niż kiedyś :) Nie tak dawno każdemu wystarczały adresy w postaci example.com/artykuly/1,jakis-adres/ czyli z przekazanym identyfikatorem zasobu. Te jednak okazały się zbyt brzydkie. Dzisiaj już nie chcemy identyfikatorów, chcemy czystych adresów czyli example.com/artykuly/jakis-adres/.

Oczywiście z punktu widzenia trudności zakodowania takiego rozwiązania, różnica jest niewielka. Jedno małe zapytanie do bazy danych gdzie wyszukamy identyfikator/dane artykułu na podstawie jego adresu. Czas wykonania się kodu też jakoś specjalnie się nie wydłuży. W czasach rozbudowanych frameworków z ich prostymi w użyciu routerami mało kto w ogóle sobie takimi rzeczami zawraca głowę. Być może jednak potrzebujesz ekstremalnej wydajności lub jesteś jej zwykłym fanatykiem (tak jak ja swego czasu :) ). Jest sposób na zlikwidowanie dodatkowego obciążenia.

Kluczem do sukcesu jest fakt, że przekierowanie w .htaccess, którego używasz (zakładam, że używasz bo inaczej nie uzyskałbyś nawet adresu example.com/artykuly/1,jakis-adres/ a co najwyżej jakieś example.com/artykuly.php/1,jakis-adres/) aby mieć przyjazne URL'e, działa tylko wtedy jeżeli nie istnieje fizycznie na dysku katalog/plik odpowiadający wpisanemu adresowi. Innymi słowy, jeżeli w publicznym katalogu głównym naszej aplikacji istnieje katalog artykuly a w nim katalog jakis-adres to żadne przekierowanie z .htaccess nie zadziała tylko Apache zacznie szukać w tym katalogu jakiegoś pliku index do wyświetlenia/uruchomienia.

Od tej wiedzy do sukcesu już krótki odcinek. Wystarczy podczas tworzenia artykułu (ale nie zapomnij potem o edycji i usuwaniu) tworzyć katalogi, które będą odpowiednikami adresów na stronie. W każdym z takich katalogów wystarczy umieścić plik index.php. Jego zawartość może wyglądać tak:

PHP:
  1. <?php
  2. $id = 1;
  3.  
  4. require '../../../start.php';
  5. start($id);

Oczywiście kod jest zupełnie przykładowy, chodzi o samą logikę. W wygenerowanym pliku mamy informację o identyfikatorze. Dzięki temu nie trzeba robić dodatkowego zapytania do bazy danych. Następnie startujemy naszą aplikację i przekazujemy dalej informację o identyfikatorze.

A więc mamy katalog /artykuly/jakis-adres/ w którym znajduje się plik index.php z powyższą zawartością. Działa. Osobiście przenoszę jeszcze wszystkie wygenerowane katalogi do jakiegoś wspólnego miejsca np. katalogu /cache. To wprowadza mi pewien porządek na dysku serwera, zwłaszcza gdy elementów korzystających z tego mechanizmu jest więcej (miałbym wtedy katalogi artykuly, galeria, imprezy itd. co wprowadza niepotrzebny bałagan). Robię to za pomocą .htaccess. Przykład:

CODE:
  1. RewriteRule ^artykuly/([^/]+)/?$ cache/artykuly/$1/

I w ten oto prosty sposób mamy trochę więcej katalogów i plików na dysku ale:
- zyskaliśmy piękne adresy URL
- cały mechanizm nie generuje zbędnego obciążenia
- sposób nie jest zbyt kłopotliwy bo cały cache siedzi sobie w jednym katalogu i nikomu nie przeszkadza
- nasza strona jest o jeden krok bliżej do osiągnięcia sytuacji gdy artykuły są dostępne nawet podczas awarii bazy danych

Podsumowując: gdy ktoś wpisze adres example.com/artykuly/jakis-adres/ to zadziała przekierowanie na katalog /cache/artykuly/jakis-adres/ gdzie wygenerowany plik index.php posiada informację o identyfikatorze tego katalogu, uruchomi naszą aplikację i przekaże jej ID.

Można jeszcze dopisać mechanizm, który zablokuje bezpośredni dostęp do plików cache na serwerze (aby nie działały adresy example.com/cache/artykuly/jakis-adres/). I na tym koniec :)

PS
Pamiętaj aby pilnować przy adresach bez identyfikatorów aby adresy się nie powtarzały.


Autoryzacja gdy PHP jest zainstalowane jako CGI w Apache

06 maja 2010

Czas najwyższy na jakiś wpis z konkretnymi poradami bo ostatnio jest tutaj z tym kiepsko... :)

Jak wiemy (albo i nie :P ) autoryzacja w PHP działa tylko wtedy gdy PHP jest zainstalowane jako moduł Apache (o innych serwerach dzisiaj pisać nie będę). Tak przynajmniej oficjalnie głosi dokumentacja. Jest jednak światełko w tunelu dla tych, którzy mają hosting z PHP postawionym jako CGI.

Poniżej opisane jest jedno z rozwiązań. Nie ma pewności, że będzie u Ciebie działało, zależy to od możliwości/ustawień Twojego konta hostingowego. U mnie działa ;) W komentarzach do wyżej podlinkowanej dokumentacji znajdziesz inne rozwiązania więc próbuj do skutku.

Pierwszy wymagany krok to ustawienie w pliku .htaccess odpowiedniej regułki. Wygląda ona tak:

CODE:
  1. RewriteEngine on
  2. RewriteRule \.php$ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]

W ten sposób ustawiamy w PHP specjalną wartość w tablicy $_SERVER z kluczem HTTP_AUTHORIZATION i danymi autoryzacyjnymi. Teraz wystarczy w PHP odczytać to co zaserwuje nam Apache takim kodem:

PHP:
  1. if(isset($_SERVER['HTTP_AUTHORIZATION']) AND $_SERVER['HTTP_AUTHORIZATION'] != '') {
  2.   list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));
  3. }

Przemyśl dokładnie gdzie to wstawić w swój kod. Dzięki takiej sztuczce PHP dostanie dwie wartości, Twój login i hasło, tak jakbyś korzystał z modułu Apache.

Gdyby powyższe nie działało to można spróbować jeszcze takiego kodu w .htaccess (zamiennie do powyższego):

CODE:
  1. SetEnvIf Authorization "^(Basic .+)$" HTTP_AUTHORIZATION=$1

U siebie akurat stosuje ten drugi kod bo to było pierwsze rozwiązanie na które natrafiłem. Jednak na większości serwerów bardziej dostępny jest mod_rewrite niż mod_setenvif dlatego wybrałem taką kolejność podawanych rozwiązań.


Uniwersalne przekierowanie 301

30 stycznia 2009

Na temat przekierowań 301 napisałem już sporo (tu i tu) ale tym razem mam coś bardzo smakowitego ;)

Przekierowania 301 najczęściej używa się gdy chcemy aby nasza strona zawsze pojawiała się z lub bez "www". Oczywiście podawałem już rozwiązania tego problemu w .htaccess (patrz linki wyżej) ale miały one jedną wadę: trzeba było wpisać adres swojej witryny. A może da się prościej? Czytaj dalej »


Generowanie bezpiecznych adresów www dla SEO

08 listopada 2008

Standardem stają się ładne, oparte o .htaccess, adresy stron internetowych, tworzone według zasad SEO (Search Engine Optimization - Pozycjonowanie stron internetowych). Przykład to chociażby ten blog. Adres tego wpisu wygląda tak:

CODE:
  1. http://www.tarnaski.eu/blog/generowanie-bezpiecznych-adresow-www-dla-seo

Ale to tylko "przykrywka". Sprytny sposób ukrycia prawdziwego adresu, który prezentuje się tak:

CODE:
  1. http://www.tarnaski.eu/blog/?p=458

Który link wygląda ładniej? :) No właśnie... Wyszukiwarki typu google też wolą ten pierwszy :)

Jednak aby mieć takie adresy trzeba spełnić kilka warunków. Potrzebujemy między innymi funkcji, która zamieni dowolny ciąg znaków na coś co będzie bezpieczne i akceptowalne przez wszystkie wyszukiwarki i przeglądarki Internetowe. Czyli coś co zamieni "Generowanie bezpiecznych adresów www dla SEO" na "generowanie-bezpiecznych-adresow-www-dla-seo". I tym właśnie się zajmiemy :P Czytaj dalej »


Przekierowanie 301 raz jeszcze

07 czerwca 2008

Dzisiaj ktoś prosił mnie o pomoc w związku z moim wpisem Przekierowanie 301. Postanowiłem więc uzupełnić informacje :) To będzie krótka notka :P

Może zdarzyć się, że mamy dwie domeny, obie kierują na tą samą stronę jednak nie chcemy aby strona była w dwóch kopiach pod dwoma adresami tylko żeby jeden adres przekierowywał na drugi. Oto gotowa reguła:

CODE:
  1. RewriteEngine On
  2. RewriteCond %{HTTP_HOST} ^(www\.)?domena1.pl(.*) [NC]
  3. RewriteRule ^(.*)$ http://www.domena2.pl/$1 [R=301,L]

Powyższy kod umieszczony w pliku .htaccess przekieruje wszystko co jest pod domena1.pl na domena2.pl :) Bez względu na to czy dodamy przedrostek www.

Wersja dla PHP wygląda tak:

PHP:
  1. <?php
  2. if($_SERVER['HTTP_HOST']=='domena1.pl' || $_SERVER['HTTP_HOST']=='www.domena1.pl')
  3. {
  4. header("HTTP/1.1 301 Moved Permanently");
  5. header("Location: http://www.domena2.pl".$_SERVER['REQUEST_URI']);
  6. header("Connection: close");
  7. }
  8. ?>

Cała sprawa polega na tym, że trzeba sprawdzić jaka domena jest wywołana ponieważ obie domeny kierują na te same pliki. Gdybyśmy wsadzili zwykłe przekierowanie z poprzedniego wpisu na temat przekierowań 301 to po wejściu na domena2.pl przekierowywałoby nas na... domena2.pl :) Wszystko by się zapętliło i mielibyśmy błąd. Zresztą domena1.pl też by nie działała bo najpierw by nas przekierowało na domena2.pl a potem znowu zapętlenie.

Uwaga! (30.01.2008)
Jeżeli interesuje Cię przekierowanie w .htaccess z "bez www" na "z www" lub odwrotnie to nie pożałujesz jeżeli przeczytasz mój wpis na temat uniwersalnego przekierowania 301.


Przekierowanie 301

22 kwietnia 2008

Zdaję sobie sprawę, że informacje o przekierowaniu 301 to nic nowego. Ale nadal niektórzy szukają odpowiedzi na pytanie "Co to jest?" więc zamieszczam krótką ściągawkę.

"Przekierowanie 301: Moved Permanently" przydaje się do pozycjonowania stron Internetowych. Służy ono do przekierowania z jednego adresu na drugi. Niby nic nadzwyczajnego ale... To konkretne przekierowanie ma pewne zalety. Otóż przenosi ono "siłę" jednego adresu na drugi. Czyli przekazywany jest PageRank adresu, jego pozycja w wyszukiwarkach na wybrane frazy itd. Czytaj dalej »


Jak wyłączyć PHP w wybranym katalogu?

07 kwietnia 2008

Mam specyficzną potrzebę. Piszę sobie oprogramowanie typu Multisite CMS. Od zwykłego CMS'a różni się to tym, że na jednym serwerze, na jednych plikach i jednej bazie danych mogę postawić nieskończenie wiele stron :) Bardzo to wygodne chociaż skomplikowane przy projektowaniu. Ale ma ogromną przewagę nad innymi CMS'ami bo przy stawianiu kolejnych stron nie muszę za każdym razem instalować kolejnych plików, tworzyć kolejnych tabel w bazie danych itd.

Muszę pamiętać również o bezpieczeństwie. Z oprogramowania będą korzystały różne osoby. Z jednej strony, muszą mieć bardzo szerokie możliwości zmian i dodawania elementów do swoich witryn. Z drugiej, nie mogą mieć dostępu do PHP, nie mogą poznać kodu źródłowego, nie mogą mieć możliwości żadnych zmian silnika oprogramowania itd.

Każda strona ma swój osobny katalog w którym można zamieszczać potrzebne pliki np. grafikę, skrypty JavaScript itd. Dostęp do tego katalogu będzie poprzez panel administratora. Ale co zrobić aby nikt mi tam nie wsadził podejrzanych skryptów, które mogą narozrabiać na serwerze?

Znalazłem kilka sposobów ale zależało mi na tym, żeby zawartość plików PHP po prostu się wyświetlała w przeglądarce a większość rozwiązań powodowała rozpoczęcie ściągania pliku na dysk.

Co zrobić? Utworzyć plik .htaccess z taką zawartością:

AddType text/plain .php .php3 .php4 .php5 .inc .phtml .cgi .pl

Oczywiście listę rozszerzeń można dowolnie modyfikować. Ta linijka spowoduje, że zawartość wszystkich plików o wypisanych rozszerzeniach będzie traktowana jak zwykły tekst. Wszystko działa począwszy od katalogu w którym znajduje się plik .htaccess oraz głębiej w strukturze katalogów. Pamiętajcie o tym bo w skrajnym wypadku nasz kochany użytkownik usunie plik .htaccess i będzie robił co mu się podoba :)


Sesja dostępna pod wszystkimi subdomenami naszej strony

02 października 2007

Sesje to bardzo przydatny mechanizm i chyba nikogo nie trzeba do tego przekonywać. Opisywany dzisiaj problem tak naprawdę nie dotyczy bezpośrednio sesji. Mimo to, to właśnie podczas pracy z sesjami ludzie najczęściej wykrywają problem.

Logując użytkownika na naszej stronie jesteśmy przekonani, że na każdej podstronie trzeba wystartować sesję i już mamy załatwiony problem przenoszenia informacji o użytkownikach podczas poruszania się po naszej witrynie. Niestety, nie zawsze.

Identyfikator sesji można przekazywać między stronami na dwa sposoby: poprzez plik cookie lub za pomocą adresu URL. Pierwszy jest wygodny ale zapisywanie ciasteczek może być wyłączone przez użytkownika. Drugi jest kłopotliwy. PHP samo dba o doklejanie identyfikatora do wszystkich adresów ale ten mechanizm nie jest w stanie wykryć wszystkich dziwnych sytuacji i czasami sami musimy mu dopomóc. Ponadto metoda ta zwiększa zagrożenie przejęcia aktywnej sesji przez inną osobę.

Na szczęście niewiele osób wyłącza ciasteczka i raczej wszyscy programiści skłaniają się do pierwszej metody.

Problem pojawia się gdy nasza strona ma adresy w subdomenach np. abc.tarnaski.eu. Dla naszej przeglądarki to jest zupełnie inny adres od samego tarnaski.eu (nawet www.tarnaski.eu i tarnaski.eu to tak naprawdę dwa różne adresy) i nie przekazuje dalej ciasteczka z identyfikatorem sesji. Można jednak temu zaradzić.

Każdemu ciasteczku możemy ustawić parametr, który podpowiada przeglądarce dla jakiej domeny dane ciasteczko jest widoczne. Oczywiście przeglądarki zabezpieczają się przed ustawieniem ciasteczka dla zupełnie obcej domeny. Nie możemy zmieniać ciastek na komputerze użytkownika, które zostały ustawione przez inne strony www. To byłaby straszna luka bezpieczeństwa. Możemy natomiast rozporządzać własną domeną.

Domyślnie każde ciastko jest dostępne tylko i wyłącznie dla adresu domeny z której to ciastko zostało wywołane. Jeżeli więc ustawimy ciastko z adresu tarnaski.eu to wchodząc na adres abc.tarnaski.eu przeglądarka nie przekaże naszemu serwerowi informacji o ciasteczku. W przypadku sesji, identyfikator nie będzie znany i PHP uzna, że ten użytkownik dopiero wszedł na naszą stronę i wygeneruje nowy SID.

Możemy to jednak zmienić. Dla sesji istnieje specjalna funkcja, która pozwala nam ustawić parametry naszego ciasteczka sesyjnego. Nazywa się session_set_cookie_params(). Poniżej prezentuję przykład użycia:

PHP:
  1. session_set_cookie_params(0, '/', '.tarnaski.eu', false, false);

Dokładniejsze informacje o tej funkcji znajdziesz w manualu. Nas w tej chwili interesuje trzeci parametr. Oto rozwiązanie zagadki. Należy ustawić tu naszą domenę z kropką na początku. To jasna informacja dla przeglądarki, że ciastko ma być dostępne dla domeny głównej i dla wszystkich subdomen. Od teraz Twoi użytkownicy nie będą wylogowywani gdy będą poruszali się między subdomenami Twojej witryny.

Tak jak napisałem na początku, problem dotyczy nie tyle sesji co mechanizmu ciasteczek. Pamiętaj o tym bo być może w przyszłości będziesz chciał przekazać między subdomenami jakieś własne ciastko. Mechanizm zachowania jest identyczny tylko domenę ustawiasz bezpośrednio w funkcji setcookie().

Ciekawostka
Istnieją inne sposoby ustawienia domeny dla ciastka sesyjnego. Jeżeli masz dostęp do uruchamiania funkcji ini_set() to możesz użyć takiego kodu:

PHP:
  1. ni_set('session.cookie_domain', '.tarnaski.eu');

Natomiast z poziomu pliku .htaccess (oczywiście potrzebne są odpowiednie uprawnienia, nie na każdym hostingu to zadziała) możesz zrobić to:

CODE:
  1. php_value session.cookie_domain .tarnaski.eu


Strona na hasło poprzez .htaccess i .htpasswd

05 maja 2007

Czasami zdarza się, że chcemy ustawić dostęp na hasło do wybranego katalogu i podkatalogów lub do całej strony www. Można się bawić w PHP ale nie zawsze to jest proste (zależy od budowy serwisu) i nie zablokujemy w ten sposób dostępu do plików graficznych, CSS itd. czyli do wszystkich, których nie interpretuje PHP przy ich wywołaniu.

Jest jednak prosta metoda dla użytkowników Apache. W pliku .htaccess wstaw gdzieś taką treść (jeżeli nie masz tego pliku to go stwórz):
AuthName "Podaj haslo"
AuthType Basic
AuthUserFile /SCIEZKA_DO_KATALOGU/.htpasswd
Require valid-user

SCIEZKA_DO_KATALOGU zamień na poprawną ścieżkę do katalogu w którym będzie plik .htpasswd, który zaraz utworzymy. Pamiętaj aby ten plik, o ile to możliwe, był poza głównym katalogiem aplikacji czyli aby nie był dostępny bezpośrednio z przeglądarki. Z doświadczenia wiem, że z tą ścieżką mogą być problemy bo ludzie ustawiają ją względem swojego najwyższego katalogu do którego mają dostęp a to najczęściej błąd. Najłatwiej w jakimś pliku PHP wywołać taką linijkę:

PHP:
  1. echo $_SERVER['DOCUMENT_ROOT'];

To podpowie Ci gdzie tak naprawdę znajduje się Twoje konto w strukturze katalogów serwera i na podstawie tej informacji ustaw ścieżkę. Można też próbować zabawy ze ścieżkami względnymi (np. ../.htpasswd) ale nie zawsze to działa.

Plik .htpasswd to nic innego jak zwykły plik tekstowy z zapisanymi hasłami. W jednej linijce mieści się informacja o jednym użytkowniku, w takiej formie:
uzytkownik:haslo

Hasła w .htpasswd są specjalnie zakodowane. Pod tym adresem znajdziesz generator haseł. Dla loginu MariuszT i hasła haslo może to wyglądać w ten sposób:
MariuszT:$apr1$53lRO/..$KTQqShXUgmYUotpueXfyg0

Po zapisaniu wszystkiego i wejściu na adres, który ma być chroniony, wyświetli nam się monit o podanie hasła. Nie zostaniemy wpuszczeni dalej póki nie podamy prawidłowych danych. Jeżeli anulujemy monit to dostaniemy błąd 401: wymagana autoryzacja. Jeżeli wpiszemy poprawne login i hasło to wyświetli się nam docelowa strona i przechodząc na inne podstrony nie musimy już ponownie podawać danych autoryzacyjnych.

Ciekawostki:

  • Generowanie haseł jest w taki sposób zaprojektowane, że dla jednego loginu i hasła będą Ci się generować inne hashe.
  • W systemach Windows domyślnie do generowania haseł używa się MD5 natomiast w Linuxach Crypt. Tylko hasła zahashowane przez MD5 działają na obu systemach.
  • Nazwę pliku .htpasswd możemy ustawić na inną w konfiguracji serwera. Stanowczo jednak odradza się to robić ponieważ pliki zaczynające się od .ht są domyślnie chronione przez Apache i dostęp do nich jest blokowany przy każdej próbie wywołania z zewnątrz (np. z przeglądarki). Jeżeli zmienimy nazwę pliku na inny to sami będziemy musieli się zatroszczyć o to aby ten plik był chroniony. Oczywiście hasła są zakodowane więc nawet jeżeli ktoś dostanie się do tego pliku to nadal ich nie odczyta ale może użyć metody brute force do poznania hasła.