<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <atom:link href="https://jacek.zlydach.pl/blog/tags/php-feed.xml" rel="self" type="application/rss+xml"/>
    <title>Posts tagged: PHP - Jacek Złydach - blog</title>
    <link>https://jacek.zlydach.pl/blog/tags/php.html</link>
    <lastBuildDate>Tue, 25 Jan 2022 12:54:25 +0100</lastBuildDate>
    <description>Blog of Jacek Złydach - a programmer and science enthusiast.</description>
    <generator>Regenerate2</generator>
    <managingEditor>temporal.pl@gmail.com (Jacek Złydach)</managingEditor>
    <webMaster>temporal.pl@gmail.com (Jacek Złydach)</webMaster>
    <ttl>1440</ttl>
    <copyright>© 2017, 2018, 2019, 2020, 2021, 2022, Jacek Złydach</copyright>
    <item>
      <title>Usprawnij pracę z wyrażeniami regularnymi</title>
      <link>https://jacek.zlydach.pl/blog/2013-08-10-usprawnij-prace-z-wyrazeniami-regularnymi.html</link>
      <guid isPermaLink="true">https://jacek.zlydach.pl/blog/2013-08-10-usprawnij-prace-z-wyrazeniami-regularnymi.html</guid>
      <pubDate>Sat, 10 Aug 2013 04:49:35 +0200</pubDate>
      <category domain="https://jacek.zlydach.pl/blog/tags/old-blog.html">Old blog</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/porady.html">porady</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/php.html">PHP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/regexp.html">regexp</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/debuggex.html">Debuggex</category>
      <description><![CDATA[Ah, dawno nic nie wrzucałem ;). Chciałbym się dziś podzielić dwoma poradami związanymi z <a href="https://en.wikipedia.org/wiki/Regular_expression">regex'ami (wyrażeniami regularnymi)</a>.

<strong>Komentarze w wyrażeniach regularnych</strong>
Szybkie pytanie: co dopasowuje poniższe wyrażenie regularne?
<pre><code lang="php">preg_match('/.*(?=.{6,})(?=.*\d)(?=.*[a-zA-Z]).*/', $something);</code></pre>

Myślę, że nawet <a href="http://xkcd.com/208/">wprawionemu w bojach</a> programiście przetworzenie tego wyrażenia zajmie krótką chwilę. W praktyce wyrażenia regularne szybko robią się skomplikowane, ekstremalnie  trudne w czytaniu i jeszcze trudniejsze w debugowaniu. Można jednak te problemy rozwiązać za pomocą odrobiny formatowania i dodania komentarzy.

<pre><code lang="php">preg_match('/^
            .*              # Match any number of characters...
            (?=.{6,})       # ... AND match at least 6 characters (lookahead) ...
            (?=.*\d)        # ... AND match one digit after any number of characters (lookahead) ...
            (?=.*[a-zA-Z])  # ... AND match one letter after any number of characters (lookahead) ...
            .*              # ... AND allow any number of characters later.
            $/x', $password);</code></pre>
Prawda, że czytelniej?

Modyfikator <code inline="inline">x</code> powoduje, że wszystkie niewyescape'owane białe znaki zostaną zignorowane, a wszystko od znaku <code inline="inline">#</code> do końca linii zostanie potraktowane jako komentarz i zignorowane.

O możliwości komentowania wyrażeń regularnych dowiedziałem się z artykułu <a href="http://net.tutsplus.com/tutorials/php/advanced-regular-expression-tips-and-techniques/">Advanced Regular Expression Tips and Techniques</a>. Zachęcam do jego przeczytania - jest tam więcej ciekawych porad, m.in. na temat callbacków czy nadawania nazw grupom.

<strong>Debugger do regexpów</strong>
Istnieje świetne narzędzie do analizy i debugowania regexpów - <a href="http://www.debuggex.com/">Debuggex</a>. Wyświetla on strukturę wyrażenia graficznie, pozwalając na analizę krok po kroku.

Spójrzmy na przykład z poprzedniej części tego posta:
<img src="old-blog/download/posts/regexp/regexp.png">

Prawdziwa potęga tego narzędzia objawia się przy bardzo złożonych wyrażeniach regularnych, jak na przykład:
<pre><code>^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?(?:(?:[-\w]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&amp;(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?$</code></pre>

Polecam skopiowanie powyższego wyrażenia do Debuggexa. Z jednej strony widać wyraźnie, jak ten regexp działa i do czego służy; z drugiej strony, sekcja <em>Some random matches</em> pokazuje, jak dziwne rzeczy przejdą przez nie poprawnie.

<strong>Uwaga o wydajności wyrażeń regularnych</strong>
Jak zapewne wielu programistów, którzy swą przygodę zaczęli od programowania gier, jestem sceptycznie nastawiony do wszelkich rzeczy pracujących na stringach. W szczególności, regexpy mogą na pierwszy rzut oka wydawać się wysoce niewydajne - ot skomplikowane dopasowanie tekstu zapisane w formie tekstu. Z pewnością napisanie tego w formie normalnego kodu - acz dłuższe - musi być wydajniejsze, prawda?

Otóż nie. Wyrażenia regularne są tak na prawdę programem - są (bardzo skondensowanym) opisem <a href="http://pl.wikipedia.org/wiki/Automat_sko%C5%84czony">maszyny stanów</a>, przez którą dopasowywany tekst stopniowo przechodzi, i jeśli uda się doprowadzić tę maszynę do stanu terminalnego, to dopasowanie jest uznawane za udane. Co to dokładnie znaczy? Polecam przyglądnąć się jeszcze raz rysunkom, które tworzy Debuggex - to dokładnie struktura tejże maszyny stanów. Taki system reprezentowany jest w programie w sposób bardzo wydajny; prawdopodobnie dużo wydajniejszy od naiwnego parsera, którego moglibyśmy napisać w niewielkiej liczbie linii kodu.

Co więcej, niektóre języki programowania (jak na przykład <a href="http://weitz.de/cl-ppcre/#create-scanner">Common Lisp</a> lub <a href="http://stackoverflow.com/questions/1720191/java-util-regex-importance-of-pattern-compile">Java</a>) pozwalają <em>prekompilować wyrażenia regularne</em> - jeżeli spodziewamy się, że dane wyrażenie regularne będzie używane wielokrotnie w czasie działania programu, to możemy zbudować maszynę stanów tylko raz (w przeciwieństwie do tworzenia jej na nowo za każdym razem, gdy chcemy dopasować jakiś napis), a następnie używać jej wszędzie tam, gdzie potrzebujemy korzystać z danego wyrażenia regularnego. Są nawet języki, które <a href="http://stackoverflow.com/questions/209906/compile-regex-in-php">kompilują regexpy automatycznie</a>.
]]></description>
    </item>    <item>
      <title>REPL i PHP - czyli Telnetem do rakiety :)</title>
      <link>https://jacek.zlydach.pl/blog/2009-09-15-repl-i-php-czyli-telnetem-do-rakiety.html</link>
      <guid isPermaLink="true">https://jacek.zlydach.pl/blog/2009-09-15-repl-i-php-czyli-telnetem-do-rakiety.html</guid>
      <pubDate>Tue, 15 Sep 2009 16:01:00 +0200</pubDate>
      <category domain="https://jacek.zlydach.pl/blog/tags/old-blog.html">Old blog</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/narzedzia.html">narzędzia</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/php.html">PHP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/webmasterka.html">webmasterka</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/repl.html">REPL</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/lisp.html">Lisp</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/python.html">Python</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/perl.html">Perl</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/ruby.html">Ruby</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/phpa.html">phpa</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/phpsh.html">phpsh</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/php-interactive.html">PHP Interactive</category>
      <description><![CDATA[<blockquote cite="http://www.armadilloaerospace.com/n.x/Armadillo/Home/News?news_id=84">
Telneting into your rocket is sort of fundamentally cool.
</blockquote>
Wiele języków - Lisp, Python, Perl czy Ruby posiadają tzw. <a href="http://pl.wikipedia.org/wiki/REPL">pętlę wczytaj-wykonaj-wypisz</a> - z angielskiego <a href="http://en.wikipedia.org/wiki/REPL">Read-Eval-Print Loop</a>, czyli w skrócie REPL. Chodzi oczywiście o tryb interaktywny, w którym możemy wpisywać wyrażenia w danym języku, które są natychmiast wykonywane a rezultaty działania zwracane. Poza oczywistym zastosowaniem jakim jest mądrzejszy kalkulator :) REPL może przydawać się też do debugowania kodu, często pracującego. Python ma nawet <a href="http://datamech.com/devan/trypython/trypython.py">swojego on-line REPLa</a> do wypróbowania :) .

PHP jest językiem, na którego temat można znaleźć <a href="http://www.devblogi.pl/2009/09/php-to-badziew-ale-to-nie-ma-znaczenia.html">bardzo wiele negatywnych opinii</a> - jednak pomimo tego i tak jest szeroko wykorzystywany do tworzenia aplikacji webowych. Biorąc pod uwagę specyfikę działania skryptów PHP - są one wykonywane od nowa 'na czysto' przy każdym zapytaniu HTTP - REPL mógłby być dla tego języka przydatnym narzędziem; szkoda tylko, że autorzy tego nie uwzględnili (ściślej rzecz ujmując PHP posiada <a href="http://blog.thinkphp.de/archives/44-More-PHP-power-on-the-command-line.html">tryb interaktywny</a> wywoływany parametrem <code inline="true">-a</code>, ale z bliżej mi nieznanych powodów tryb ten jest <a href="http://onwebdevelopment.blogspot.com/2008/03/php-interactive-shell.html">raczej</a> <a href="http://cow.neondragon.net/index.php/945-Php-Interactive-Mode">krytykowany</a>).

Podczas opracowywania <a href="blog/2009-09-14-eksperyment-podsumowanie.html">wyników Eksperymentu</a> powstał problem - jak pracować na danych, których samo wyciągnięcie z bazy i przetworzenie zajmuje (na localhost'cie) ok. 1.5 minuty? Zwłaszcza kiedy jeszcze na początku nie wie się, co się chce osiągnąć? W takich sytuacjach przydaje się REPL - i <strong>na szczęście takowy dla PHP został stworzony</strong>.

<strong><a href="http://www.programmersparadox.com/2008/08/26/php-repl/">phpsh</a></strong> to projekt Facebook'a, będący Open-Source i napisany głównie... w Pythonie. Szczegóły pod adresem:
<a href="http://www.phpsh.org/">http://www.phpsh.org/</a> Całość wykonana jest bardzo ładnie i dobrze wykorzystuje możliwość wyświetlania w konsoli kolorowego tekstu. Po rozpakowaniu na serwerze działa po prostu 'z marszu' :) .

Dalsze poszukiwania pozwoliły znaleźć trzy kolejne projekty: <strong><a href="http://jan.kneschke.de/projects/php-shell/">php shell</a></strong>, <strong><a href="http://david.acz.org/phpa/">phpa</a></strong> oraz pozwalający na zdalną pracę przez przeglądarkę (i potrafiący wyświetlać wyjście w HTMLu) <strong><a href="http://www.hping.org/phpinteractive/">PHP Interactive</a></strong>. Niech więc każdy wybierze co lubi (ja zamierzam raczej zmienić język ;) ).

<a href="http://www.programmersparadox.com/2008/01/22/your-first-programming-language-needs-a-repl/">Przydatność</a> narzędzia jakim jest REPL jest nieoceniona, wypadałoby <a href="http://www.flownet.com/gat/jpl-lisp.html">zacytować</a> pracownika <a href="http://www.jpl.nasa.gov">NASA JPL</a> dotyczący awarii na pokładzie sterowanego zdalnie pojazdu kosmicznego:
<blockquote>
Debugging a program running on a $100M piece of hardware that is 100 million miles away is an interesting experience. Having a read-eval-print loop running on the spacecraft proved invaluable in finding and fixing the problem.
</blockquote>

<em>Edit 15.09.2009 17:39</em>
<a href="http://rav.rootnode.net">Fus</a> donosi, że nawet <a href="http://www.mono-project.com/CsharpRepl">C# ma swojego REPL'a</a>. Podjęte też zostały próby stworzenia takiego narzędzia <a href="http://www.google.pl/search?q=C%2B%2B+repl">dla C i C++</a>.
]]></description>
    </item>    <item>
      <title>Eksperyment - podsumowanie</title>
      <link>https://jacek.zlydach.pl/blog/2009-09-14-eksperyment-podsumowanie.html</link>
      <guid isPermaLink="true">https://jacek.zlydach.pl/blog/2009-09-14-eksperyment-podsumowanie.html</guid>
      <pubDate>Mon, 14 Sep 2009 05:35:30 +0200</pubDate>
      <category domain="https://jacek.zlydach.pl/blog/tags/old-blog.html">Old blog</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/php.html">PHP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/http.html">HTTP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/eksperyment.html">eksperyment</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/dobro-galaktyki.html">dobro Galaktyki</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/html.html">HTML</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/webmasterka.html">webmasterka</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/badania.html">badania</category>
      <description><![CDATA[W tym wpisie znajdują się wyczekiwane przez niektórych wyniki <a href="blog/2009-09-09-http-eksperyment.html">Eksperymentu HTTP</a>. Celem całej tej zabawy było odpowiedzenie sobie na następujące pytanie:
<strong>Czy jest jakiś sposób, żeby odróżnić zapytanie o zasób osadzony na stronie od 'normalnego' zapytania?</strong>
Inaczej - czy da się na przykład rozróżnić, kiedy przeglądarka pyta o obrazek osadzony za pomocą <code escaped="true" lang="HTML" inline="true">&lt;img src=... /&gt;</code>, a kiedy o sam obrazek, którego adres wpisany został bezpośrednio lub wywołany za pomocą linka? Czy da się odróżnić, kiedy ktoś osadza obrazek na swojej stronie, a kiedy tylko do niego linkuje?

Jedyną rzeczą, która mogłaby zawierać takie informacje jest <a href="http://pl.wikipedia.org/wiki/Hypertext_Transfer_Protocol">zapytanie HTTP</a> wysyłane przez przeglądarkę do serwera. We wstępnych badaniach wykluczyliśmy możliwość użycia <a href="http://pl.wikipedia.org/wiki/HTTP_referrer">nagłówka Referer</a> do rozróżnienia zapytań - co prawda Referer przy elementach osadzonych ma zwykle wartość odpowiadającą osadzającej stronie, ale taki sam nagłówek uzyskamy klikając w link znajdujący się na takiej stronie. Z pozostałych nagłówków żaden nie ma na celu informować o rodzaju zapytania. Okazuje się jednak, że takie rozpoznanie jest możliwe - trzeba podejść do sprawy statystycznie. Podejrzenia dość szybko padły na nagłówek Accept...

Eksperyment, w którym w zeszłym tygodniu około 200 (+/-50) osób wzięło udział pozwolił mi zebrać trochę danych statystycznych. Obiektem zainteresowania było zapytanie HTTP wysyłane przez przeglądarkę przy próbie pobierania obrazka JPG i PNG celem osadzenia go na stronie (&lt;img src=...) lub bezpośredniego wyświetlenia. W centrum zainteresowania znalazł się nagłówek Accept - jedyny, który okazuje się przenosić istotne dla nas informacje.

<strong>Wyniki</strong>
<strong>Nagłówek Accept <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">pozwala przeglądarce określić</a>, jakiego typu multimediów spodziewa się w odpowiedzi na swoje zapytanie. Okazuje się, że przeglądarki spodziewając się elementu do osadzenia na stronie często wysyłają w tym nagłówku inne typy, niż w przypadku obrazków pobieranych do wyświetlenia bezpośredniego.</strong> Spójrzmy na przykład na ten wykres dla przeglądarki Firefox:
<a href="old-blog/download/posts/eksperyment/firefox.png">
<img src="old-blog/download/posts/eksperyment/firefox-450px.png" alt="Wykres przedstawiający wyniki Eksperymentu dla przeglądarki Firefox."></a>

Wykres ten przedstawia szansę wyświetlenia danego wpisu w nagłówku Accept w zapytaniu o obrazek osadzony (EMBEDDED) lub do wyświetlenia bezpośredniego (LINK). Różnica w zawartości nagłówków jest wyraźnie widoczna. Wykres jest znormalizowany - przeskalowany tak, że wartość 1 odpowiada sytuacji, gdy dany nagłówek wystąpił w każdym zapytaniu danego typu. Okazuje się, że w przypadku Firefoksa rodzaj zapytania mamy jak na dłoni, wystarczy spojrzeć w nagłówek Accept! Przyglądnijmy się więc wykresom dla innych przeglądarek:

<a href="old-blog/download/posts/eksperyment/opera.png">
<img src="old-blog/download/posts/eksperyment/opera-450px.png" alt="Wykres przedstawiający wyniki Eksperymentu dla przeglądarki Opera."></a>
Opera nie daje nam najmniejszych szans - w każdym zapytaniu wysyła identyczny nagłówek Accept.

<a href="old-blog/download/posts/eksperyment/ie.png">
<img src="old-blog/download/posts/eksperyment/ie-450px.png" alt="Wykres przedstawiający wyniki Eksperymentu dla przeglądarki Internet Explorer."></a>
Internet Explorer jak zawsze sprawia dużo radości - z jednej strony odróżnienie rodzaju zapytania zdaje się być trywialnie proste, z drugiej strony IE ma dziwną <a href="http://www.newmediacampaigns.com/page/browser-rest-http-accept-headers">tendencję zasypywania nas specyficznymi wpisami w nagłówku</a>. Najbardziej zabawny jest <code lang="HTML" inline="true">image/pjpeg</code>, który w jednym zapytaniu potrafi wystąpić <strong>dwa razy obok siebie</strong>. Nie rozumiem przyczyn tego zjawiska, ale występuje w wersjach IE od 6 do 8. Ale obiecano mi już, że zostanie to zgłoszone do Microsoftu :).

<a href="old-blog/download/posts/eksperyment/safari.png">
<img src="old-blog/download/posts/eksperyment/safari-450px.png" alt="Wykres przedstawiający wyniki Eksperymentu dla przeglądarki Safari."></a>
Tutaj bardzo prosto - tak samo jak w IE, zapytania osadzone mają bardzo krótki nagłówek Accept ;)

<a href="old-blog/download/posts/eksperyment/mozilla.png">
<img src="old-blog/download/posts/eksperyment/mozilla-450px.png" alt="Wykres przedstawiający wyniki Eksperymentu dla przeglądarki Mozilla."></a>
Ten wykres przedstawia przeglądarki przedstawiające się jako Mozilla, które nie zaklasyfikowały się nigdzie indziej. Wykres jest analogiczny do tego od Firefoksa (tak na prawdę część z przeglądarek na tym wykresie to źle rozpoznane Firefoksy - przyczyny tego zjawiska omawiam dalej). Podobała mi się w tym wszystkim Mozilla, która przedstawiła się jako &quot;<em>ROBOT DO WYKRADANIA KONTENTU</em>&quot; :) .

<a href="old-blog/download/posts/eksperyment/chrome.png">
<img src="old-blog/download/posts/eksperyment/chrome-450px.png" alt="Wykres przedstawiający wyniki Eksperymentu dla przeglądarki Chrome."></a>
Google Chrome także nie robi problemów przy rozpoznaniu typu zapytania.

<a href="old-blog/download/posts/eksperyment/inne.png">
<img src="old-blog/download/posts/eksperyment/inne-450px.png" alt="Wykres przedstawiający wyniki Eksperymentu dla nie sklasyfikowanych przeglądarek."></a>
W przypadku nierozpoznanych przeglądarek sprawa troszkę się zaciemnia, ale wciąż można zidentyfikować przeważające tendencje, które pozwalają rozróżnić rodzaj zapytania.

Dla podsumowania, oto zapytania dla wszystkich przeglądarek na raz:
<a href="old-blog/download/posts/eksperyment/wszystkie.png">
<img src="old-blog/download/posts/eksperyment/wszystkie-450px.png" alt="Wykres przedstawiający wyniki Eksperymentu zbiorczo dla wszystkich przeglądarek."></a>

<strong>O samym Eksperymencie</strong>
<a href="old-blog/download/posts/eksperyment/przegladarki.png">
<img src="old-blog/download/posts/eksperyment/przegladarki-450px.png" alt="Wykres przedstawiający przeglądarki, które wzięły udział w Eksperymencie."></a>
Powyższy wykres przedstawia wszystkie główne rodziny przeglądarek zidentyfikowane przez skrypty (proszę nie zwracać uwagi na wartości numeryczne - odnoszą się ilości zapytań wygenerowanych przez daną przeglądarkę). Identyfikacji przeglądarek dokonywała funkcja <a href="http://pl.php.net/manual/pl/function.get-browser.php">get_browser()</a> w PHP. Co prawda funkcja ta dość skutecznie rozdzieliła przeglądarki, jednak mimo wszystko pozostawia wiele do życzenia (mówiąc mniej formalnie - jest skopana) - nie radzi sobie z niektórymi wariacjami Firefoksa,  Safari czy Chrome na systemach Apple. Dużo lepiej z deszyfrowaniem nagłówka User-Agent zdaje się radzić sobie jedna <a href="http://www.useragentstring.com/index.php">ze stron w Internecie</a>.

Serdecznie dziękuję wszystkim, którzy poświęcili swój czas i wzięli udział. Bardzo miłym akcentem było wrzucenie linku do Eksperymentu na Wykop. Ponadto dziękuję wszystkim tym, którzy zachęcali znajomych do wzięcia udziału. Pozdrawiam też użytkowników Linksa i Lynxa ;). Szczególne podziękowania dla kolegów ze Stosowanej oraz wszystkich gości z Blip'a!

<strong>Zastosowania</strong>
Mając możliwość rozpoznania celu zapytania o dany zasób możemy odpowiednio zareagować. Przykładowe zastosowania tej wiedzy to:
<ul>
	<li>Lepsze sprawdzanie, czy ktoś nie <a href="http://pl.wikipedia.org/wiki/Hotlink">hotlinkuje</a> naszych zdjęć/obrazków - klasyczne używanie nagłówka Referer jest nieskuteczne i denerwujące dla osób korzystających z Google Images.</li>
	<li>Serwowanie różnej treści zależnie od rodzaju zapytania - chyba głównie dla zabawy :)</li>
	<li>Wybiórcze stosowanie przekierowań - serwowanie <a href="blog/2009-09-10-przekierowania-skracanie-i-ukrywanie-adresow-co-i-jak.html">ramek maskujących adres</a> w przypadku zapytania bezpośredniego oraz używanie np. przekierowania proxy w przypadku zapytań osadzonych.</li>
</ul>

<strong>Proof of concept</strong>
Miał być, ale nie ma - próbując z Paszczakiem zaktualizować browsercaps.ini na serwerze coś zepsuliśmy ;). Z tego powodu na razie przykładu działania nie będzie - dość powiedzieć, że u mnie lokalnie działa :). Na podstawie zebranych danych <a href="old-blog/code-snippets/#CODE_SNIPPETS_HTTP_EKSPERYMENT_DATA">wygenerowałem tabelkę w PHP</a> pozwalającą napisać sobie skrypt rozpoznający rodzaj zapytania.

<strong>Co dalej?</strong>
Powstają coraz to nowe wersje przeglądarek, a wraz z nimi prawdopodobnie będzie zmieniać się zawartość nagłówka Accept. Można spróbować stworzyć system rozpoznawania zapytania oparty o <a href="http://en.wikipedia.org/wiki/Naive_Bayesian_classifier">filtr bayesowski</a> lub wykorzystać inne metody pozwalające na bieżąco aktualizować stan wiedzy na temat używanych wartości nagłówków.
Danych, które zebrałem nie było strasznie dużo, ale jeżeli ktoś chce poddać je dalszej analizie to chętnie je udostępnię :) .

<em>Edit</em>
Dla wytrwałych bajka o <a href="http://webaim.org/blog/user-agent-string-history/">zdecydowanie pokręconych stringach User Agent</a> :)

<em>Edit 2</em>
Wykresy, o które prosił dr_bonzo:
<del datetime="2010-07-27T08:34:15+00:00"><a href="random/blog/materialy/posty/eksperyment/odwrocone">http://temporal.pr0.pl/blog/materialy/posty/eksperyment/odwrocone</a>
Czytelniej ? :)</del><ins datetime="2010-07-27T08:34:15+00:00">zginęły przy zmianie serwera, powinny być gdzieś w backupie.</ins>
]]></description>
    </item>    <item>
      <title>Przekierowania, skracanie i ukrywanie adresów - co i jak?</title>
      <link>https://jacek.zlydach.pl/blog/2009-09-10-przekierowania-skracanie-i-ukrywanie-adresow-co-i-jak.html</link>
      <guid isPermaLink="true">https://jacek.zlydach.pl/blog/2009-09-10-przekierowania-skracanie-i-ukrywanie-adresow-co-i-jak.html</guid>
      <pubDate>Thu, 10 Sep 2009 11:30:48 +0200</pubDate>
      <category domain="https://jacek.zlydach.pl/blog/tags/old-blog.html">Old blog</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/porady.html">porady</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/php.html">PHP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/google.html">Google</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/http.html">HTTP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/mod-rewrite.html">mod_rewrite</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/seo.html">SEO</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/eksperyment.html">eksperyment</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/html.html">HTML</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/javascript.html">JavaScript</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/skracanie-adresu.html">skracanie adresu</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/webmasterka.html">webmasterka</category>
      <description><![CDATA[W ostatnim okresie upowszechniły się serwisy skracające adresy, takie jak <a href="http://tnij.org/">tnij.org</a>, <a href="http://tinyurl.com/">tinyurl.com</a> czy działający wewnątrz <a href="http://blip.pl/">Blipa</a> rdir.pl. W tym wpisie prezentuję techniki leżące u podstaw takich usług - metody przekierowywania przeglądarek i ukrywania adresów. Ponieważ 'serwis skrótowy' jest jednym z elementów projektu, nad którym pracuję, poniższy opis jest de-facto spojrzenie z perspektywy budowania usługi skracania adresów. Na koniec wspomnę też kilka słów o toczącym się <a href="blog/2009-09-09-http-eksperyment.html">Eksperymencie HTTP</a>.

<strong>mod_rewrite i przepisywanie URL</strong>
Kluczem do stworzenia systemu <a href="http://pl.wikipedia.org/wiki/Skracanie_adres%C3%B3w_internetowych">skracania adresów</a> jest możliwość 'przepisywania adresów' - dzięki temu nasz system będzie umiał obsłużyć adresy postaci: <code inline="true">http://moj.system.skrotow.pl/skroconyurl</code>. Do tego celu można wykorzystać na przykład bardzo popularny moduł mod_rewrite serwera Apache. Krótko i konkretnie temat 'ładnych linków' <a href="http://pornel.net/dobrelinki">omówił na swojej stornie porneL</a>. Przykład .htaccess, którego obecnie używam <em>(nazwy związane z serwisem zmienione ;) )</em>:
<pre><code lang="text">#&lt;IfModule mod_rewrite.c&gt;

RewriteEngine On
RewriteBase /ACME/skrypcik/

#add trailing slash (url canonicalization)
RewriteCond	%{REQUEST_FILENAME}		-d
RewriteRule		^(.+[^/])$		$1/		[R=301,L]

RewriteCond	 %{REQUEST_FILENAME} !-f
RewriteCond	 %{REQUEST_FILENAME} !-d
RewriteRule 	^(.*)$		index.php?adres=$1		[NC,QSA,L]

#&lt;/IfModule&gt;</code></pre>
Występuje tu kilka rzeczy, których nie omówił jeszcze porneL. Po pierwsze, dyrektywa <code inline="true">RewriteBase</code> - pomaga ona w poprawnym działaniu przepisywania adresów w przypadku, gdy nasza strona znajduje się w podkatalogu na serwerze. Z tą dyrektywą jest zwykle trochę zabawy, więc proszę okazywać cierpliwość ;). Nie znam prostego (w kilku zdaniach) wyjaśnienia jej roli - jeżeli ktoś umie takowe sformułować, to zachęcam do uczynienia tego w komentarzu!

Po <code inline="true">RewriteBase</code> mamy dwie linie dokonujące usuwania slashy z URI w ramach procesu przekształcania adresu do postaci kanonicznej. Istotne (<a href="http://www.mattcutts.com/blog/seo-advice-url-canonicalization/">zwłaszcza dla Google</a>) jest tutaj przekierowanie 301. Kolejne trzy linijki sprawdzają, czy żądany adres nie jest fizycznie istniejącym plikiem lub katalogiem. Podobny kod można na przykład znaleźć w .htaccess Wordpressa. Jeżeli żądany adres nie pasuje do żadnego istniejącego pliku lub katalogu na serwerze, to wywoływany jest nasz skrypt (index.php) z parametrem adres zawierającym całą końcówkę wpisaną przez użytkownika.

To tyle tytułem wprowadzenia do .htaccess na praktycznym przykładzie - na temat tego modułu jest <a href="http://home.pl/dokumentacja/funkcjeserwera/htaccess/modrewrite">bardzo</a> <a href="http://sf.jogger.pl/2007/05/02/mod-rewrite-w-przykladach/">dużo</a> <a href="http://httpd.apache.org/docs/1.3/mod/mod_rewrite.html">materiałów</a> <a href="http://nnplaya.pl/wpisy/mod_rewrite_a_sciezki_do_plikow">w sieci</a> - myślę, że każdy sobie poradzi. Mając już adres wpisany przez użytkownika należy jakoś odesłać mu właściwą treść. Możemy to zrobić na kilka sposobów:

<strong>Przekierowania HTTP</strong>
Przekierowania te <a href="blog/2009-09-08-przekierowania-http.html">dość dokładnie omawiałem w jednym z ostatnich postów</a>. Jest to najbardziej podstawowy sposób oddania komuś żądanego zasobu ze 'skróconego' (przez termin 'skrócony adres' w tym tekście rozumiem każdej długości alias w serwisie skrótowym - nawet, jeśli jest dłuższy od oryginalnego URL :) ) adresu. Wybór kodu powinien zależeć od tego, jaką rolę pełni skrócony adres - 301 gdy zdecydujemy się przypisać go na stałe do miejsca docelowego, 302 lub 307, gdy adres docelowy może ulec zmianie.
Właściwości tej metody to:
<ul>
	<li>Takiego przekierowania można używać także do elementów osadzanych, np. w tagach &lt;img&gt;</li>
	<li>Nie można zamaskować adresu docelowego - jest on widoczny w pasku adresu przeglądarki.</li>
	<li>Zwłaszcza w przypadku użycia kodu 301 PageRank przelewa się na adres docelowy (<a href="http://www.google.com/support/webmasters/bin/answer.py?hl=en&amp;answer=93633">ale ostrożnie z tym</a>).</li>
	<li>Opiera się w całości na protokole HTTP, więc musi być wspierana przez wszystkie przeglądarki.</li>
</ul>
Ciekawostka - serwis tnij.org zdaje się odsyłać nagłówek 301 Moved Permanently, a nie 302 Found lub 307 Temporary Redirect.

<strong>Przekierowania &quot;meta refresh&quot;</strong>
Innym rodzajem przekierowania, wykonywanym całkowicie po stronie klienta, jest wymuszenie odświeżenia adresu przez przeglądarkę przy użyciu znacznika <code lang="HTML" inline="true" escaped="true">&lt;meta&gt;</code>. Przykładowy kod wygląda następująco:
<pre><code lang="HTML">&lt;html&gt;
 &lt;head&gt;
  &lt;meta http-equiv=&quot;Refresh&quot; content=&quot;0; url=http://www.innastrona.com/&quot;&gt;
 &lt;/head&gt;
 &lt;body&gt;
  &lt;p&gt;Proszę, kliknij w &lt;a href=&quot;http://www.innastrona.com/&quot;&gt;link&lt;/a&gt;!&lt;/p&gt;
 &lt;/body&gt;
&lt;/html&gt;</code></pre>
Generujemy taki kod, podstawiając odpowiedni adres. Cyfra 0 może zostać zastąpiona inną - określa ona czas w sekundach, po jakim przeglądarka powinna dokonać odświeżenia strony z nowym adresem.
Własności tej metody:
<ul>
	<li>Nie można zamaskować adresu docelowego - jest on widoczny w pasku adresu przeglądarki.</li>
	<li><a href="http://sebastians-pamphlets.com/google-and-yahoo-treat-undelayed-meta-refresh-as-301-redirect/">Chodzą wieści</a>, że Google umie czytać ten typ przekierowania z kodu HTML - ale na rewelacyjny przelew PageRank bym nie liczył.</li>
	<li>Może nie być wspierane przez wszystkie przeglądarki. Dlatego też powinno się umieścić w dokumencie informację o linku dla przeglądarek nie wspierających tej metody.</li>
	<li>Znikoma przydatność.</li>
</ul>

Ponieważ w tym przypadku znacznik <code inline="true" lang="HTML" escaped="true">&lt;meta&gt;</code> nadpisuje istniejący w HTTP nagłówek Refresh, istnieje też wariant tej metody nie wymagający umieszczania dodatkowego znacznika w dokumencie HTML - możemy zamiast tego wysłać dowolny HTML i wygenerować odpowiedni nagłówek HTTP w skrypcie. Przykład:
<pre><code lang="PHP">&lt;?php
Header('Refresh: 0; url=http://www.innastrona.com');
echo 'Proszę kliknąć w &lt;a href=&quot;http://www.innastrona.con&quot;&gt;link&lt;/a&gt;.';
?&gt;</code></pre>
Osobiście ciekawi mnie, jak przeglądarka zareagowałaby w przypadku zapytania o osadzony element <code lang="HTML" inline="true" escaped="true">(&lt;img src=&quot;... &gt;)</code>, gdyby odesłać jej obrazek razem z nagłówkiem Refresh. Być może sprawdzę to przy najbliższej okazji.

<strong>Przekierowania Javascript</strong>
Metoda podobna do meta refresh - wysyłamy stronę ze spreparowanym JavaScript'em, który automatycznie przekierowuje użytkownika pod inny adres.
Cechy techniki:
<ul>
	<li>Nie działa z elementami osadzonymi.</li>
	<li>Wiele <a href="http://pl.wikipedia.org/wiki/Robot_internetowy">crawlerów</a> nie umie obsługiwać JavaScript'u - wyszukiwarki nie zobaczą przekierowań.</li>
	<li>Niektórzy <a href="http://pl.wikipedia.org/wiki/Lynx_(przegl%C4%85darka)">nie posiadają</a> lub <a href="http://en.wikipedia.org/wiki/JavaScript#Security">celowo wyłączają</a> obsługę JavaScript'u w przeglądarce.</li>
	<li>Teoretycznie można próbować użyć jej do zamaskowania adresu docelowego zasobu w pasku adresu - niestety nie znam się aż tak na JavaScript, żeby podać przykład.</li>
</ul>

<strong>Maskowanie adresu - metoda ramek</strong>
Użycie <a href="http://pornel.net/ramki">znienawidzonego</a> znacznika &lt;frameset&gt; pozwala bardzo skutecznie zamaskować adres docelowy strony, na którą przekierowujemy (i przy okazji zmienić jej tytuł). Wystarczy wygenerować taki oto HTML:
<pre><code lang="HTML">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Frameset//EN&quot;
   &quot;http://www.w3.org/TR/html4/frameset.dtd&quot;&gt;
&lt;html&gt;
	&lt;head&gt;
	&lt;/head&gt;
	
	&lt;frameset cols=&quot;100%&quot;&gt;
			&lt;frame src=&quot;http://www.innastrona.com&quot; name=&quot;moja_ramka&quot; /&gt;
	&lt;/frameset&gt;
&lt;/html&gt;</code></pre>
Własności tej metody:
<ul>
	<li>Pozwala zamaskować adres docelowy i wymienić tytuł.</li>
	<li>Nie działa z elementami osadzonymi - z oczywistych przyczyn.</li>
	<li>Standardem są już skrypty pozwalające stronie automatycznie 'wyskoczyć z ramki' - i wtedy nici z maskowania adresu.</li>
	<li>Może być przyczyną kontrowersji i ataków ze strony niedouczonych webmasterów stron docelowych.</li>
</ul>
Tę technikę spotykamy się wbrew pozorom dość często - na stronach takich jak <a href="http://www.wykop.pl/ramka/231015/internet-skonczyl-40-lat">Wykop.pl</a> czy nawet <a href="http://images.google.pl/images?q=s%C5%82oneczko">Google Images</a>. Takie strony po prostu dodają dodatkową ramkę z własnymi informacjami :).

<strong>Przekierowania 'proxy'</strong>
Najbardziej bezpośrednią metodą wyświetlenia cudzego contentu pod swoim adresem :) jest pobranie jej przez nasz serwer i oddanie użytkownikowi. W PHP pomóc nam w tym może funkcja <code lang="PHP" inline="true">readfile()</code>. Metoda jest bardzo prosta w teorii i gwarantuje zamaskowanie adresu, jednak rodzi też szereg trudności technicznych:
<ul>
	<li>Jeżeli oddajemy w ten sposób stronę internetową to najprawdopodobniej posypią się wszystkie relatywne odnośniki do obrazków, skryptów i CSS - dostaniemy goły, nieużyteczny dokument HTML.</li>
	<li>Jeżeli oddajemy coś innego niż zwykły tekst to musimy ręcznie określić <a href="http://pl.wikipedia.org/wiki/Typ_MIME">typ MIME</a> zwracanego zasobu. A ponieważ zasób jest nie u nas, to możemy albo zgadywać ten typ (np. po rozszerzeniu), albo odczytywać dopiero po ściągnięciu. Innymi słowy - dodatkowa robota.</li>
	<li>Cała treść przechodzi przez nasz serwer, skutecznie go spowalniając (już nie mówiąc o ewentualnych limitach downloadu).</li>
</ul>

Wspominam o tej technice, gdyż ma ona także dużo dobrych właściwości:
<ul>
	<li>Kompletne zamaskowanie adresu zasobu.</li>
	<li>Działa z elementami osadzonymi.</li>
	<li>Pozwala nam modyfikować zasób i sposób jego przetwarzania (np. można <a href="http://www.google.pl/search?q=content-disposition+attachment">wymusić na przeglądarce wyświetlenie okienka &quot;Zapisz jako...&quot;</a>).</li>
</ul>
Wspomniane przeze mnie problemy techniczne można oczywiście obejść, ale pytanie czy nie jest to przerost formy nad treścią :).

Trochę ten post wyszedł przydługi... ;). A teraz obiecany na początku...

<strong>Eksperyment HTTP</strong>
Chciałbym w tym miejscu podziękować wszystkim osobom, które już wzięły udział i rozgłaszały Eksperyment wśród innych. Ruch pierwszego dnia był spory; jakaś dobra dusza nawet wrzuciła link na Wykop :). Zebrałem już dość dużo danych, jakieś 85% tego, co założyłem jako minimum. Dlatego jeżeli ktoś jeszcze nie brał udziału, to <strong><a href="blog/2009-09-09-http-eksperyment.html">zapraszam</a></strong>, a jeżeli już brał to zachęcam o podrzucenie linka innym osobom! Myślę, że wyniki opracuję w najbliższych dniach. Dla zaostrzenia apetytu powiem tylko, że eksperyment ma duży związek z tematem tego posta :).
]]></description>
    </item>    <item>
      <title>HTTP - Eksperyment</title>
      <link>https://jacek.zlydach.pl/blog/2009-09-09-http-eksperyment.html</link>
      <guid isPermaLink="true">https://jacek.zlydach.pl/blog/2009-09-09-http-eksperyment.html</guid>
      <pubDate>Wed, 09 Sep 2009 10:48:31 +0200</pubDate>
      <category domain="https://jacek.zlydach.pl/blog/tags/old-blog.html">Old blog</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/php.html">PHP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/http.html">HTTP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/eksperyment.html">eksperyment</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/dobro-galaktyki.html">dobro Galaktyki</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/webmasterka.html">webmasterka</category>
      <description><![CDATA[<em>Aktualizacja - 12.09.2009</em>
Eksperyment został zakończony, trwa przetwarzanie zebranych danych. Zapraszam - wyniki oraz cel samych badań już wkrótce! :)

<em>Aktualizacja - 14.09.2009</em>
<a href="blog/2009-09-14-eksperyment-podsumowanie.html">Wyniki już są </a> :) .

Drodzy Czytelnicy, w ramach prac nad pewnym projektem i <a href="blog/2009-09-08-przekierowania-http.html">poszerzaniem wiedzy n/t HTTP</a> przeprowadzam mały eksperyment statystyczny. <strong>Każdy z Was może wziąć w nim udział i tym samym zrobić coś dla dobra Galaktyki!</strong> :). Wystarczy jedynie wejść na podaną niżej stronę eksperymentu i po załadowaniu kliknąć w oba obrazki (mają podłożone linki).

<strong><a href="random/eksperyment/eksperyment.html" rel="nofollow">Strona Eksperymentu</a></strong>

W eksperymencie tym zbieram tylko troszkę danych na temat zapytania HTTP wysyłanego przez przeglądarkę. Wyniki i opracowanie podam po zebraniu odpowiedniej ilości danych, czyli - miejmy nadzieję - w ciągu kilku najbliższych dni. Jednocześnie bardzo prosiłbym o nie znęcanie się nade mną przez fałszowanie wyników lub hackowanie strony. Wystarczy wejść, kliknąć dwa razy i wyjść :).

Z góry dziękuję wszystkim, którzy zechcą wziąć udział w eksperymencie! Wyniki wkrótce!

<strong>Ważny Edit!</strong>
Dziękuję tym, którzy tak ochoczo zareagowali na wezwanie do wzięcia udziału! Jednocześnie mam prośbę - <strong>jeżeli ktoś ma zainstalowane w systemie kilka przeglądarek WWW, to zachęcam do wejścia na stronę Eksperymentu każdą z nich z osobna</strong>! Dzięki temu zebrane zostanie więcej istotnych danych :).
]]></description>
    </item>    <item>
      <title>Przekierowania HTTP</title>
      <link>https://jacek.zlydach.pl/blog/2009-09-08-przekierowania-http.html</link>
      <guid isPermaLink="true">https://jacek.zlydach.pl/blog/2009-09-08-przekierowania-http.html</guid>
      <pubDate>Tue, 08 Sep 2009 11:48:21 +0200</pubDate>
      <category domain="https://jacek.zlydach.pl/blog/tags/old-blog.html">Old blog</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/porady.html">porady</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/php.html">PHP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/google.html">Google</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/http.html">HTTP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/mod-rewrite.html">mod_rewrite</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/apache.html">Apache</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/seo.html">SEO</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/webmasterka.html">webmasterka</category>
      <description><![CDATA[W ostatnim okresie czasu intensywnie pracuję nad pewnym projektem w <a href="http://pl.wikipedia.org/wiki/PHP"><abbr title="PHP Hypertext Preprocessor">PHP</abbr></a>. Prace te pozwoliły mi się zapoznać dokładnie z różnymi technikami przekierowywania przeglądarek do żądanych zasobów. W tym poście chciałbym omówić główne przekierowania zdefiniowane w protokole HTTP; przynajmniej jedno z nich powinno być powszechnie znane programistom PHP. Wpis ten oparty jest o post <a href="http://sebastians-pamphlets.com/the-anatomy-of-http-redirects-301-302-307/">&quot;The anatomy of server sided redirect&quot;</a> na <a href="http://sebastians-pamphlets.com/">blogu Sebastian's Pamphlets</a>, specyfikacji HTTP/1.1, której wydrukowany fragment trzymam obok siebie oraz osobistych doświadczeniach.

<strong>Przekierowania HTTP</strong>
Jak <a href="http://kurs.browsehappy.pl/HTTP/Wprowadzenie">wiadomo</a>, fundamentalny dla WWW protokół <abbr title="HyperText Transfer Protocol">HTTP</abbr> zawiera kody odpowiedzi (opisane <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10">w sekcji 10 specyfikacji RFC2616</a>), którymi serwer WWW informuje przeglądarki o tym, co przeglądarki mają dalej robić. Nas w tym miejscu interesują kody serii 3xx (sekcja 10.3 RFC). Zadaniem tych kodów jest przekierowanie przeglądarki pod inny adres docelowy. Kody te mają też wpływ na <a href="http://pl.wikipedia.org/wiki/Robot_internetowy">crawlery wyszukiwarek</a>, które przecież starają się udawać ludzi. Powiemy sobie o:
<ul>
	<li><strong>HTTP 301 Moved Permanently</strong></li>
	<li><strong>HTTP 302 Found</strong></li>
	<li><strong>HTTP 303 See Other</strong></li>
	<li><strong>HTTP 307 Temporary Redirect</strong></li>
</ul>

Gdy serwer dokonuje przekierowania to oprócz odpowiedniego kodu statusu i opisu wysyła także nagłówek <em>Location</em> zawierający adres, pod który ma udać się przeglądarka. Przykładowa odpowiedź-przekierowanie HTTP, które przeniesie przeglądarkę na stronę Google, może wyglądać następująco:

<pre><code>HTTP/1.1 307 Temporary Redirect
Location: http://www.google.pl</code></pre>

<strong>HTTP/1.1 301 Moved Permanently</strong>
Kod ten tworzy permanentne przekierowanie pod adres docelowy. To tak, jakbyśmy powiedzieli przeglądarce: <em>&quot;URI, o które prosiłaś znikło. Być może nigdy go nie było, a <strong>na pewno nigdy nie będzie</strong>. Nie będę dostarczać żadnej treści dla zapytania o to URI, więc zaktualizuj swoje zakładki podanym niżej adresem i nie zawracaj mi już więcej głowy. Nigdy więcej.&quot;</em>.

Przekierowania 301 używamy najczęściej przy przeniesieniu całej strony, gdy chcemy poinstruować ludzi i crawlery o zmianie lokalizacji. W szczególności powinniśmy to zrobić, jeżeli zależy nam na utrzymaniu dotychczasowej pozycji strony w wyszukiwarkach. <a href="http://www.google.com/support/webmasters/bin/answer.py?answer=93633">Google rozumie kod 301</a>, ale stronę trzeba <a href="http://www.google.com/support/webmasters/bin/answer.py?hl=pl&amp;answer=83105">przenosić</a> <a href="http://sebastians-pamphlets.com/the-anatomy-of-http-redirects-301-302-307/#moving-sites-301">z głową</a>.

<strong>Kod 301 jest przekierowaniem 'na zawsze' - URI raz oznaczone tym numerem nie powinno być nigdy więcej używane w innym celu</strong>. To znaczy, że mamy obowiązek go utrzymywać - przynajmniej jeśli zależy nam na <a href="http://pl.wikipedia.org/wiki/PageRank">Page Rank</a> - tak długo, jak to tylko możliwe (tak, trzeba płacić i za starą domenę ;)). Nie powinno się też nigdy zmieniać miejsca, do którego odsyła to przekierowanie. Kodu 301 powinniśmy używać też do tzw. <a href="http://en.wikipedia.org/wiki/Canonicalization#Canonicalization_in_Search_Engines_and_SEO">sprowadzenia adresu do postaci kanonicznej</a> (<a href="http://www.mattcutts.com/blog/seo-advice-url-canonicalization/">pozwala uniknąć problemów z wyszukiwarkami</a>, w tym podwójnego widzenia treści dla adresów z www i bez www). Przykład użycia pliku <a href="http://pl.wikipedia.org/wiki/Htaccess">.htacccess</a> i <a href="http://en.wikipedia.org/wiki/Rewrite_engine">mod_rewrite</a> do zapewnienia postaci kanonicznej adresu:
<pre><code>RewriteEngine On
RewriteCond %{HTTP_HOST} !^example\.com [NC]
RewriteRule (.*) http://example.com/$1 [R=301,L]</code></pre>
Należy zwrócić uwagę na fragment <em>R=301</em> w ostatniej linijce - oznacza to, że do zmiany adresu zostanie użyte przekierowanie 301.

Aby przekierowanie 301 zostało wykonane poprawnie musimy wysłać przeglądarce tekst <strong>z kodem i statusem</strong> - w przeciwnym razie wykonane zostanie domyślne przekierowanie 302 Found, o którym niżej. Tyczy się to szczególnie PHP, gdzie już chyba zwyczajem stało się pisanie<a name="POST_PRZEKIEROWANIA_HTTP_1_NIECHLUJSTWO">:</a>
<pre><code lang="PHP">Header('Location: '.$adres);</code></pre>
Taki kod wykona przekierowanie 302. Poprawnie sformułowane w PHP przekierowanie 301:
<pre><code lang="PHP">Header('HTTP/1.1 301 Moved Permanently');
Header('Location: '.$adres);</code></pre>

<strong>HTTP/1.1 302 Found</strong>
Przekierowanie 302 jest chyba najbardziej <a href="http://sebastians-pamphlets.com/the-anatomy-of-http-redirects-301-302-307/#301-moved-permanently">nadużywanym przekierowaniem</a> w sieci - głównie z powodu tego, że jest to przekierowanie domyślne, które zostanie wykonane jeżeli samodzielnie nie wykonaliśmy innego przekierowania. Problemy dotyczą głównie niewłaściwego używania 302 w miejscu, gdzie powinno być 301 - częściowo <a href="blog/2009-09-08-przekierowania-http.html#POST_PRZEKIEROWANIA_HTTP_1_NIECHLUJSTWO">z niechlujstwa</a>, a częściowo - <a href="http://sebastians-pamphlets.com/the-anatomy-of-http-redirects-301-302-307/#302-found-elsewhere">wg Simon's Pamphlets</a> - z różnych mitów rozpowszechnianych w środowisku <a href="http://pl.wikipedia.org/wiki/Optymalizacja_dla_wyszukiwarek_internetowych"><abbr title="Search Engine Optimization">SEO</abbr></a>. Pomyłkowe użycie 302 zamiast 301 jest niegroźne zarówno dla człowieka jak i przeglądarki, ale może wyrządzić krzywdę pozycji strony w wyszukiwarce.

Kod 302 Found (w HTTP/1.0 zwany 302 Moved Temporarily) służy do wskazania tymczasowej lokalizacji zasobu. Ponieważ jednak przekierowanie może się zmieniać, specyfikacja sugeruje, by klienci dalej używali tego adresu, którym przyszli, a nie tego, który dostaną z przekierowania. Jeżeli chce się z jakiegoś powodu odesłać użytkownika na inną stronę - czy to wewnętrzną, czy zewnętrzną w stosunku do naszego serwisu - to możemy użyć właśnie tego kodu. W PHP wygląda to tak:
<pre><code lang="PHP">//Wersja HTTP/1.0
Header('HTTP/1.0 302 Moved Temporarily');
Header('Location: '.$adres);

//Wersja HTTP/1.1
Header('HTTP/1.1 302 Found');
Header('Location: '.$adres);</code></pre>
Poza niewielkimi różnicami w specyfikacji dotyczącymi cache'ingu kody 302 obu wersji robią w zasadzie to samo. I chociaż <a href="http://www.ietf.org/rfc/rfc1945.txt">dotychczasowe (9.3)</a> <a href="http://www.ietf.org/rfc/rfc2068.txt">RFC (10.3.3)</a> nie pozwalały zmieniać przy przekierowaniu metody zapytania (np. z POST na GET), większość przeglądarek to właśnie robi (przeważyły prawdopodobnie względy bezpieczeństwa) - kod 302 jest interpretowany tak jak 303. Aby można było rozróżnić, kiedy serwer chce wykonać przekierowanie ze zmianą metody zapytania na GET a kiedy bez zmiany, wprowadzono w HTTP/1.1 dwa dodatkowe kody - <strong>303 See Other</strong> i <strong>307 Temporary Redirect</strong>. Ponieważ przeglądarki oraz crawlery potrafią już 'rozmawiać' protokołem HTTP/1.1, to poza szczególnymi przypadkami gdy z jakiegoś powodu musimy 'bawić się' w HTTP/1.0 możemy spokojnie korzystać z nowych kodów.

<strong>HTTP/1.1 303 See Other</strong>
Według <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4">RFC2616 (10.3.4)</a> celem istnienia tego kodu jest przede wszystkim umożliwienie wynikom zapytania POST możliwości przekierowania do innych zasobów. W momencie otrzymania kodu 303 przeglądarka powinna zmienić metodę zapytania na GET. Poprawne użycie w PHP:
<pre><code lang="PHP">Header('HTTP/1.1 303 See Other');
Header('Location: '.$adres);</code></pre>
Kodu tego powinniśmy używać w skryptach wywoływanych zapytaniem POST - po przetworzeniu żądania powinny one wykonywać przekierowanie 303 na stronę, która wyświetli nam wyniki. Dzięki temu, gdy użytkownik odświeży stronę, zapytanie POST (mogące mieć przecież jakieś skutki uboczne!) nie zostanie wykonane ponownie (Firefox w takich sytuacjach pyta, czy ponownie wysłać dane). Jest to o tyle istotne, że <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html">zgodnie ze specyfikacją</a> do wprowadzania zmian (edycja, usuwanie, itp.) w zasobach strony powinniśmy używać metody POST, nie GET (celowo pomijam tutaj <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6">PUT</a> i <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7">DELETE</a> - architektura <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">REST</a>, która je wykorzystuje, nie jest jeszcze aż tak popularna).

<strong>HTTP/1.1 307 Temporary Redirect</strong>
Kod 307 to nasz najlepszy przyjaciel, gdy potrzebujemy wykonywać przekierowania do tymczasowych zasobów. W przeciwieństwie do 303 nie zmienia metody zapytania - kod 307 zachowuje się tak, jak powinien był się zachowywać w przeglądarkach 302. To właśnie tym kodem chcemy się posługiwać, więc warto zadbać o jego poprawne wysłanie, żeby przypadkiem nie wyszedł nam 302:
<pre><code lang="PHP">Header('HTTP/1.1 307 Temporary Redirect');
Header('Location: '.$adres);</code></pre>
Jednym z zastosowań przekierowania 307 jest przekierowywanie kanałów <a href="http://pl.wikipedia.org/wiki/RSS">RSS</a>/<a href="http://pl.wikipedia.org/wiki/Atom_(standard)">Atom</a> do usługi <a href="http://pl.wikipedia.org/wiki/FeedBurner">FeedBurner</a>.

<strong>Podsumowanie - kiedy używamy którego kodu?</strong>
<ul>
	<li><strong>HTTP 301 Moved Permanently</strong> - gdy zasób został na stałe przeniesiony pod inny adres. Przekierowania 301 powinniśmy utrzymywać włączone tak długo, jak to możliwe i nigdy nie poddawać ich recyclingowi.</li>
	<li><strong>HTTP 302 Found</strong> - gdy musimy borykać się z protokołem HTTP/1.0.</li>
	<li><strong>HTTP 303 See Other</strong> - gdy skrypt, który otrzymał zapytanie POST chce wyświetlić jakieś wyniki - powinien wtedy wykonać przekierowanie 303 na stronę z wynikami. Unikniemy dzięki temu ponownego wysłania zapytania POST przy odświeżeniu strony.</li>
	<li><strong>HTTP 307 Temporary Redirect</strong> - wszystkie sytuacje, gdy robimy tymczasowe przekierowania. Skracanie nazw URI, odesłanie do innej strony, przekierowanie RSS/Atom na FeedBurnerm itd.</li>
</ul>

<strong>Dodatkowe informacje</strong>
<ul>
	<li>Omówione przekierowania operują bezpośrednio na poziomie protokołu HTTP. Oznacza to, że możemy ich użyć do przekierowywania np. obrazków osadzanych w tagach &lt;img&gt;.</li>
	<li>Osobom używającym .htaccess do sprowadzania adresu do postaci kanonicznej (w tym np. usuwanie bądź dodawanie 'www' przed nazwą) polecam upewnić się, że używają przekierowania 301 a nie domyślnego 302.</li>
</ul>

Zainspirowany <a href="http://gynvael.coldwind.pl/">Gynvael'em</a> i innymi postanowiłem utworzyć też dział <a href="old-blog/code-snippets/">Code Snippets</a>, w którym znajdować się będą różne drobne i - miejmy nadzieję - przydatne fragmenty kodu. Jako pierwszy umieszczam <a href="old-blog/code-snippets/#CODE_SNIPPETS_PHP_HTTP_TOOLS">drobną klasę PHP</a> realizującą u mnie w projekcie przekierowania oraz obsługę wybranych kodów 4xx i 5xx.
]]></description>
    </item>    <item>
      <title>O tworzeniu stron słów kilka...</title>
      <link>https://jacek.zlydach.pl/blog/2007-07-12-o-tworzeniu-stron-slow-kilka.html</link>
      <guid isPermaLink="true">https://jacek.zlydach.pl/blog/2007-07-12-o-tworzeniu-stron-slow-kilka.html</guid>
      <pubDate>Thu, 12 Jul 2007 12:29:14 +0200</pubDate>
      <category domain="https://jacek.zlydach.pl/blog/tags/old-blog.html">Old blog</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/php.html">PHP</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/cms.html">CMS</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/xml.html">XML</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/rss.html">RSS</category>
      <category domain="https://jacek.zlydach.pl/blog/tags/xsl.html">XSL</category>
      <description><![CDATA[Przez ostatnie kilka dni intensywnie tworzę sobie <abbr title="Content Management System">CMS</abbr> w oparciu o PHP5 i MySQL. devBlog'a na niego raczej nie przenoszę, ale chcę na jego podstawie stworzyć kilka stron - dla siebie i nie tylko. CMS piszę w oparciu o funkcjonalność 'object-oriented' w języku PHP5. Dzięki temu, zgodnie z założeniami projektowymi, jest on już teraz w miarę rozszerzalny i przenośny. Jak postawię na nim pierwszą stronę, to opis projektu pojawi się i tutaj.

W trakcie tworzenia zacząłem się zastanawiać nad sensownością używania CMSów do budowy stron internetowych wszelkiego rodzaju. W sieci do niedawna panowała taka moda, że każdy kto chce mieć dobrą stronę musi użyć jakiegoś CMSa i obowiązkowo dodać do tego forum dyskusyjne. A co bardziej ambitni tworzyli własne systemy zarządzania treścią. Ale czy kompleksowy, rozbudowany silnik strony to zawsze jest dobra droga?

Weźmy dla przykładu <a href="http://wawszczak.pr0.pl">Liryczną Galeryjkę Stanisława Wawszczaka</a>. Strona składa się z layout'u opartego na XHTML/CSS oraz kilku narzędziowych funkcji w PHP. Zapis i odczyt komentarzy z bazy i obsługa parsera <a href="http://pl.wikipedia.org/wiki/XSL">XSL</a>. Wiersze zapisane są w plikach XML a krótki kod, oparty o parser XSL, generuje na tej podstawie zarówno wygląd strony jak i zawartość <a href="http://pl.wikipedia.org/wiki/RSS">kanału RSS</a>. Mamy więc garść prostych funkcji narzędziowych + mały parser i ładnie wyglądającą, funkcjonalną stronę, której niczego nie brakuje.

Jak widać, czasem cel można osiągnąć prościej. Nie zawsze strona domowa musi obsługiwać wszystkie języki świata, cztery rodzaje baz danych, mieć forum, użytkowników, shoutbox'a i 101 innych features'ów.
]]></description>
    </item>
  </channel>
</rss>