<h3>Strony wyświetlane po podaniu błędnego adresu URL</h3>

<p>
Jeśli w przeglądarce internetowej 
wpiszesz nieistniejący adres URL
to ujrzysz jeden z trzech możliwych komunikatów.
</p>

<p>
Pierwszym rodzajem błędu jest 
podanie niepoprawnego adresu serwera.
Domena <span class="variable">lorem.ipsum.pl</span>,
nie jest opisana w DNS,
a zatem wpisanie w przeglądarce adresu:
</p>

<pre>
http://lorem.ipsum.pl/dolor/sitamet.html
</pre>

<p>
zakończy się błędem.
W takiej sytuacji przeglądarka 
wyświetli komunikat <em>Nie odnaleziono serwera</em>
widoczny na rysunku 1.
</p>

#####{$figures[1]}#####

<p>
Drugim rodzajem błędu jest podanie adresu URL 
zawierającego poprawną nazwę serwera oraz błędną nazwę pliku, np.:
</p>

<pre>
http://example.net/lorem/ipsum/dolor.html
</pre>


<p>
Domena <span class="variable">example.net</span> jest zarejestrowana i posiada stronę WWW,
co możemy stwierdzić odwiedzając witrynę <span class="variable">http://example.net</span>.
Jednak na serwerze nie istnieje plik o nazwie:
</p>



<pre>
/lorem/ipsum/dolor.html
</pre>



<p>
W tej sytuacji serwer <span class="variable">example.net</span> 
wysyła odpowiedź:
</p>

<pre>
HTTP/1.x 404 Not Found
</pre>

<p>
na co przeglądarka reaguje wyświetlając stronę <em>Not found</em> widoczną na rysunku 2.
</p>

#####{$figures[2]}#####

<p>
Trzecim rodzajem stron wyświetlanych 
w odpowiedzi na nieistniejące adresy URL
są dedykowane strony obsługi błędu 404.
Strona taka zazwyczaj zachowuje stylistykę 
serwera, a przy tym zawiera komunikat informujący o zaistniałym błędzie.
Rysunek 3 przedstawia stronę obsługi błędu 404 na serwerze 
<span class="variable">google.pl</span> po podaniu adresu:
</p>

<pre>
http://www.google.pl/lorem/ipsum/dolor.html
</pre>

#####{$figures[3]}#####




<p>
Mamy zatem trzy rodzaje stron wyświetlanych 
po podaniu nieistniejącego adresu URL:
</p>


<ul>
<li>błędny adres serwera,</li>
<li>poprawny adres serwera, błędna nazwa pliku, serwer nie obsługuje błędów 404,</li>
<li>poprawny adres serwera, błędna nazwa pliku, serwer obsługuje błędy 404.</li>
</ul>

<p>
Oczywiście w przypadku pierwszego rodzaju błędu 
za wygląd wyświetlanej strony odpowiada wyłącznie przeglądarka.
Z punktu widzenia webmastera jest to przypadek nieciekawy.
</p>

<p>
W artykule zajmiemy się tym, w jaki sposób 
na serwerze zainstalować obsługę błędów 404 tak,
by zamiast komunikatu z rysunku 2
internauta ujrzał ładną stronę WWW z rysunku 3.
Podane przykłady będą dotyczyły wyłącznie serwera <span class="program">Apache</span> 
i interpretatora PHP.
</p>


<h3>Dyrektywa ErrorDocument</h3>

<p>
Obsługę błędnych adresów URL przez serwer <span class="program">Apache</span> 
ustalamy dyrektywą <span class="variable">ErrorDocument</span>.
Dyrektywa ta może zostać umieszczona w 
ogólnym pliku konfiguracyjnym serwera <span class="program">Apache</span>
<span class="filename">httpd.conf</span> 
lub w plikach <span class="filename">.htaccess</span>.
Ponieważ firmy hostingowe zazwyczaj nie umożliwiają modyfikowania 
pliku <span class="filename">httpd.conf</span>, 
zatem w przykładach wykorzystam pliki <span class="filename">.htaccess</span>.
</p>

<p>
Utwórz trzy pliki:
<span class="filename">.htaccess</span>,
<span class="filename">error-404.html</span>,
<span class="filename">error-404.css</span>
i umieść je w folderze głównym <span class="filename">htdocs/</span> 
serwera <span class="program">Apache</span>.
</p>

<p>
W pliku <span class="filename">.htaccess</span> wprowadź jedną linijkę:
</p>


<pre>
ErrorDocument 404 /error-404.html
</pre>

<p>
W pliku <span class="filename">error-404.html</span> 
wprowadź dowolny komunikat, zaś w stylach
<span class="filename">error-404.css</span> dowolnie ustal wygląd witryny.
Zwróć uwagę na to, by użyte adresy URL były bezwzględne.
Adresy występują w dwóch miejscach:
</p>

<ul>
<li>w dyrektywie <span class="variable">ErrorDocument</span> (<span class="filename">/error-404.html</span>),</li>
<li>w elemencie <span class="variable">link</span> (<span class="filename">/error-404.css</span>).</li>
</ul>



<p>
Jeśli teraz podasz w przeglądarce adres:
</p>


<pre>
http://localhost/lorem/ipsum/dolor.html
</pre>


<p>
ujrzysz wykonaną przed chwilą stronę błędu.
</p>

<p>
Jeśli użyjesz względnych adresów URL, obsługa błędu 404 nie
będzie wykonana poprawnie.
Dyrektywa <span class="variable">ErrorDocument</span> 
zawierająca adres względny:
</p>


<pre>
ErrorDocument 404 error-404.html
</pre>


<p>
nie działa, zaś użycie względnego adresu stylów:
</p>



<pre>
&lt;link rel="stylesheet" type="text/css" href="error-404.css" /&gt;
</pre>



<p>
spowoduje, że wyłącznie adresy URL dotyczące foldera głównego, czyli np.
</p>




<pre>
http://localhost/a.html
</pre>


<p>
będą sformatowane zgodnie z podanymi stylami.
W przypadku strony pochodzącej z podfolderu, np.
</p>

<pre>
http://localhost/a/b/c/d.html
</pre>


<p>
style nie zostaną odnalezione.
</p>

<p>
Oczywiście strona obsługi błędu 404 może zostać wykonana w języku PHP.
Utwórz plik <span class="filename">error-404.php</span> z dowolnym komunikatem
po czym zmień dyrektywę <span class="variable">ErrorDocument</span>:
</p>

<pre>
ErrorDocument 404 /error-404.php
</pre>

<h3>Kody błędów protokołu HTTP</h3>

<p>
Liczba 404 pojawiająca się w dyrektywie <span class="variable">ErrorDocument</span> 
jest jednym z tzw. kodów błędów protokołu HTTP.
Przeglądarka WWW wysyła do serwera żądania 
(zdefiniowane protokołem HTTP). W odpowiedzi, serwer 
wysyła do przeglądarki odpowiedź HTTP.
Każda odpowiedź serwera musi rozpoczynać się od kodu. 
Jeśli serwer nie może zrealizować żądań klienta, 
wówczas przeglądarka może &mdash;
na podstawie kodu dołączonego do odpowiedzi &mdash;
częściowo poznać przyczyny niepowodzenia.
Kody błędów protokołu HTTP mają wartości od 400 do 417.

Pełną listę błędów znajdziemy w specyfikacji 
RFC 2616 pod adresem 
<a href="http://www.w3.org/protocols/rfc2616/rfc2616-sec10.html#sec10.4">http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4</a>.
</p>

<p>
Przykładowymi błędami są:
</p>

<ul>
<li><span class="variable">400 Bad Request</span> &mdash; błędne zapytanie HTTP,</li>
<li><span class="variable">401 Unauthorized</span> &mdash; strona wymaga autoryzacji,</li>
<li><span class="variable">403 Forbidden</span> &mdash; dostęp zabroniony,</li>
<li><span class="variable">404 Not Found</span> &mdash; plik nie został odnaleziony.</li>
</ul>

<h3>Podglądanie odpowiedzi serwera WWW</h3>

<p>
Do analizy zapytań i odpowiedzi HTTP wymienianych przez 
przeglądarkę i serwer możemy użyć wtyczki <span class="program">LiveHTTPHeaders</span> 
przeglądarki <span class="program">Firefox</span>
dostępnej pod adresem 
<a href="http://livehttpheaders.mozdev.org">http://livehttpheaders.mozdev.org</a>
lub dodatku 
<span class="program">ieHTTPHeaders</span>
<span class="filename">Internet Explorera</span>
dostępnego pod adresem 
<a href="http://www.blunck.se/iehttpheaders/iehttpheaders.html">http://www.blunck.se/iehttpheaders/iehttpheaders.html</a>.
</p>




<p>
Po zainstalowaniu 
wtyczki
<span class="program">LiveHTTPHeaders</span>
wybierz opcję
menu głównego <span class="option">Narzędzia &rarr; Live HTTP Headers</span>.
Ujrzysz zakładkę wyświetlającą zapytania i odpowiedzi HTTP wysyłane i odbierane
przez przeglądarkę.
</p>




<p>
Po podaniu adresu:
</p>

<pre>
http://google.pl
</pre>

<p>
serwer <span class="variable">google.pl</span> wysyła do przeglądarki odpowiedź
</p>

<pre>
HTTP/1.x 200 OK
</pre>

<p>
widoczną na rysunku 4.
</p>

#####{$figures[4]}#####

<p>
Odpowiedź wysyłana przez serwer <span class="variable">google.pl</span> 
po podaniu adresu
(strona z rysunku 3):
</p>

<pre>
http://www.google.pl/lorem/ipsum/dolor.html
</pre>

<p>
zawiera kod 404 widoczny na rysunku 5.
</p>

#####{$figures[5]}#####

<p>
W ten sposób możesz sprawdzić kod odpowiedzi HTTP
wysyłany przez serwer WWW.
</p>

<h3>Dyrektywa ErrorDocument i pozostałe błędy HTTP</h3>

<p>
Poznana wcześniej dyrektywa <span class="variable">ErrorDocument</span> zawiera dwa parametry:
kod błędu oraz adres URL:
</p>


<pre>
ErrorDocument  kod-błędu  url
</pre>

<p>
Po wystąpieniu podanego błędu serwer <span class="program">Apache</span>
wyśle w odpowiedzi do przeglądarki stronę o podanym adresie URL.
Na przykład dyrektywa:
</p>

<pre>
ErrorDocument  403 /brak-dostepu.html
</pre>

<p>
będzie powodowała wysyłanie dokumentu <span class="filename">brak-dostepu.html</span>
w odpowiedzi na błędy 403.
</p>


<p>
Przygotuj cztery pliki:
</p>


<ul>
<li><span class="filename">a/.htaccess</span></li>
<li><span class="filename">.htaccess</span></li>
<li><span class="filename">brak-dostepu.html</span></li>
<li><span class="filename">brak-dostepu.css</span></li>
</ul>

<p>
i umieść je w folderze głównym serwera <span class="program">Apache</span> zachowując strukturę
widoczną na rysunku 6.
</p>

#####{$figures[6]}#####



<p>
W pliku <span class="filename">a/.htaccess</span> umieść jedną linijkę:
</p>


<pre>
Options -Indexes
</pre>

<p>
Podana dyrektywa powoduje, że odwiedzenie przeglądarką adresu:
</p>


<pre>
http://localhost/a/
</pre>

<p>
zakończy się komunikatem 403.
</p>


<p>
W pliku <span class="filename">.htaccess</span> wprowadź natomiast  dyrektywę:
</p>



<pre>
ErrorDocument 403 /brak-dostepu.html
</pre>




<p>
W ten sposób skonfigurujesz obsługę błędów 403. 
Jeśli teraz odwiedzisz stronę:
</p>


<pre>
http://localhost/a/
</pre>

<p>
to ujrzysz treść dokumentu <span class="filename">brak-dostepu.html</span>.
</p>



<p>
Oczywiście jeden plik <span class="filename">.htaccess</span> może zawierać wiele dyrektyw
<span class="variable">ErrorDocument</span>:
</p>


<pre>
ErrorDocument 403 /brak-dostepu.html
ErrorDocument 404 /error-404.html
...
</pre>

<p>
Ponadto obsługa wielu różnych błędów może być wykonywana przez
jeden dokument:
</p>


<pre>
ErrorDocument 403 /blad.html
ErrorDocument 404 /blad.html
...
</pre>

<h3>Ustalanie odpowiedzi HTTP w PHP</h3>


<p>
W języku PHP kod odpowiedzi wysyłanej przez serwer <span class="program">Apache</span> 
możemy zmienić wywołując funkcję <span class="variable">header()</span>:
</p>

<pre>
header('HTTP/1.x 404 Not Found');
</pre>


<p>
Należy pamiętać, że przed wywołaniem funkcji <span class="variable">header()</span>
nie może pojawić się żadna instrukcja wysyłająca dane
(np. <span class="variable">echo</span> lub <span class="variable">print</span>).
Skrypt:
</p>


<pre>
&lt;?php
echo 'Błąd! Podana strona nie istnieje!';
header('HTTP/1.x 404 Not Found');
?&gt;
</pre>

<p>
jest niepoprawny.
</p>

<p>
Niektóre witryny stosują połowiczną obsługę błędu 404 nazywaną <em>soft 404</em>.
Polega to na tym, że strona błędu 404 jest atrakcyjnie wizualna,
ale opatrzona nagłówkiem poprawnej odpowiedzi HTTP, czyli
</p>

<pre>
HTTP/1.x 200 OK
</pre>

<h3>Usuwanie dostępu do plików</h3>

<p>
Jeśli skrypt składa się z kilku plików PHP dołączanych 
instrukcją <span class="variable">require_once</span>, 
to należy pamiętać o tym, by żadne skrypty zawierające
tajne informacje nie były dostępne w ramach usługi WWW.
Jeśli w pliku <span class="filename">db.inc</span>
zapiszemy hasło dostępu do bazy danych:
</p>

<pre>
$link = mysql_connect('localhost', 'appadm', 'tajnehaslo');
</pre>

<p>
a plik umieścimy w folderze <span class="filename">include/</span>,
to wystarczy użyć adresu:
</p>

<pre>
http://example.net/include/db.inc
</pre>

<p>
by poznać dane konta <span class="variable">appadm</span>.
</p>


<p>
Jeśli to tylko możliwe, to wszystkie biblioteki, szablony 
i pliki z danymi należy przenieść poza drzewo katalogów widoczne 
w ramach usługi WWW (np. poza folder <span class="filename">public_html/</span>).
Niestety wielu dostawców hostingu nie umożliwia tego:
wszystkie pliki nagrywane na serwerze znajdują się w folderze dostępnym 
poprzez WWW.
W takiej sytuacji należy wykorzystać plik <span class="filename">.htaccess</span>.
</p>


<p>
Dyrektywa:
</p>

<pre>
&lt;Files ~ "\.php$"&gt;
    Order allow,deny
    Deny from all
&lt;/Files&gt;
</pre>

<p>
wyłącza dostęp do plików, których 
nazwa kończy się rozszerzeniem <span class="filename">.php</span>.
</p>


<p>
W podobny sposób możemy wyłączyć:
</p>


<pre>
&lt;Files ~ "\.txt$"&gt;
    Order allow,deny
    Deny from all
&lt;/Files&gt;
</pre>




<p>
lub włączyć
</p>



<pre>
&lt;Files ~ "\.png$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;
</pre>


<p>
dostęp do dowolnego rodzaju plików.
</p>



<h3>Przykład praktyczny</h3>

<p>
Jako przykład praktyczny przeanalizujmy dwie witryny WWW.
Jedna z nich będzie 
stosowała zwykłe adresy URL, a druga &mdash; adresy przyjazne.
Witryny te są tak wykonane, by mogły zostać umieszczone
na serwerze udostępnianym przez zewnętrzną firmę.
Zakładamy, że:
</p>

<ul>
<li>serwer obsługuje pliki <span class="filename">.htaccess</span>,</li>
<li>wszystkie pliki umieszczane na serwerze są dostępne poprzez WWW.</li>
</ul>

<h4>Przykładowa witryna <em>Lorem ipsum</em></h4>


<p>
Wygląd witryny jest przedstawiony na rysunku 7.
Jest ona wykonana w PHP przy wykorzystaniu szablonów 
<span class="program">Smarty</span> i plików tekstowych.
Poziome menu witryny powstaje na podstawie pliku tekstowego
<span class="filename">dane/menu.txt</span> o zawartości:
</p>


<pre>
Lorem|lorem.txt|2
Ipsum|ipsum.txt|3
Dolor|dolor.txt|4
Sit amet|sit-amet.txt|5
</pre>


<p>
Każda linijka pliku opisuje jedną opcję. Kolejne kolumny
to:
</p>


<ul>
<li>tytuł opcji umieszczany w menu strony,</li>
<li>nazwa pliku tekstowego z zawartością strony,</li>
<li>wartość zmiennej <span class="variable">id</span> 
użytej w adresie <span class="variable">index.php?id=XXX</span>.</li>
</ul>



<p>
Cała witryna stosuje następujące adresy URL:
</p>

<pre>
index.php?id=1 - strona błędu 404
index.php?id=2 - strona lorem
index.php?id=3 - strona ipsum
index.php?id=4 - strona dolor
index.php?id=5 - strona sit amet
img/*.png      - obrazy użyte na stronie
style.css      - style strony
</pre>

<p>
Dodatkowo adres <span class="variable">index.php</span>
jest przekierowaniem
do strony domyślnej <em>lorem</em> o adresie 
<span class="variable">index.php?id=2</span>.
</p>


#####{$figures[7]}#####

<p>
Witryna składa się z plików i folderów przedstawionych na rysunku 8.
W ramach usługi WWW dostępnymi plikami powinny być wyłącznie:
</p>


<pre>
index.php
style.css
img/*.png
</pre>




<p>
żadne pliki z folderów:
</p>



<pre>
dane/
include/
template/
template_c/
</pre>



<p>
nie powinny być dostępne poprzez WWW.
</p>


#####{$figures[8]}#####

<h4>Rozwiązanie pierwsze: witryna bez przyjaznych URL-i</h4>

<p>
W przypadku witryny, która nie stosuje przyjaznych adresów URL
w pliku <span class="filename">.htaccess</span> 
umieszczamy następujące dyrektywy:
</p>

<ul>
<li>ustalamy stronę domyślną,</li>
<li>definiujemy strony błędów,</li>
<li>wyłączamy dostęp do plików.</li>
</ul>


<p>
Do ustalenia strony domyślnej stosujemy dyrektywy
<span class="variable">Options</span> oraz <span class="variable">DirectoryIndex</span>:
</p>



<pre>
Options -Indexes
DirectoryIndex index.php
</pre>



<p>
Pierwsza z nich wyłącza możliwość listowania zawartości katalogu,
a druga ustala, że adres kończący się nazwą folderu, np.
</p>


<pre>
http://localhost/przyklad/
</pre>



<p>
będzie odwoływał się do pliku:
</p>

<pre>
http://localhost/przyklad/index.php
</pre>


<p>
Następnie ustalamy strony błędów:
</p>



<pre>
ErrorDocument 404 /index.php?id=1
ErrorDocument 403 /index.php?id=1
</pre>



<p>
pamiętając o tym, by w skrypcie <span class="filename">index.php</span> 
użyć funkcji 
<span class="variable">header()</span> do generowania nagłówka <em>Not found</em>:
</p>



<pre>
if ($akcja == 1) {
    header('HTTP/1.x 404 Not Found');    
} else {
    ...
}
</pre>




<p>
Na zakończenie blokujemy dostęp do wszystkich plików:
</p>


<pre>
&lt;Files ~ ".*$"&gt;
    Order allow,deny
    Deny from all
&lt;/Files&gt;
</pre>



<p>
po czym odblokowujemy dostęp do plików <span class="filename">index.php</span>,
<span class="filename">.css</span>, <span class="filename">.png</span>
oraz do folderu:
</p>

<pre>
&lt;Files ~ "index\.php$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;

&lt;Files ~ "\.css$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;

&lt;Files ~ "\.png$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;

&lt;Files ~ "^$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;
</pre>



<p>
Pełna zawartość pliku <span class="filename">.htaccess</span> jest przedstawiona na listingu 1.
</p>

#####{$listings[1]}#####


<h4>Rozwiązanie drugie: witryna z przyjaznymi adresami URL-i</h4>


<p>
Przypadek, w którym witryna stosuje przyjazne adresy URL jest nieco bardziej
skomplikowany.
Tym razem stosujemy adresy <span class="filename">.html</span>,
które będą przekierowane na skrypty PHP:
</p>

<pre>
error.html       -&gt;  index.php?id=1
lorem.html       -&gt;  index.php?id=2
ipsum.html       -&gt;  index.php?id=3
dolor.html       -&gt;  index.php?id=4
sit-amet.html    -&gt;  index.php?id=5
</pre>



<p>
Podobnie jak poprzednio, plik <span class="filename">.htaccess</span> 
rozpoczynamy od ustalenia strony domyślnej:
</p>



<pre>
Options -Indexes
DirectoryIndex index.php
</pre>



<p>
Następnie dołączamy dyrektywy odpowiedzialne za translacje adresów:
</p>



<pre>
RewriteEngine on
RewriteRule ^error\.html$       index.php?id=1
RewriteRule ^lorem\.html$       index.php?id=2
RewriteRule ^ipsum\.html$       index.php?id=3
RewriteRule ^dolor\.html$       index.php?id=4
RewriteRule ^sit-amet\.html$    index.php?id=5
</pre>




<p>
Po czym ustalamy strony błędów:
</p>


<pre>
ErrorDocument 404 /error.html
ErrorDocument 403 /error.html
</pre>




<p>
i blokujemy dostęp do plików:
</p>



<pre>
&lt;Files ~ ".*$"&gt;
    Order allow,deny
    Deny from all
&lt;/Files&gt;

&lt;Files ~ "index\.php$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;

&lt;Files ~ "\.html$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;

&lt;Files ~ "\.css$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;

&lt;Files ~ "\.png$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;

&lt;Files ~ "^$"&gt;
    Order allow,deny
    Allow from all
&lt;/Files&gt;
</pre>



<p>
Kompletny plik <span class="filename">.htaccess</span> 
jest przedstawiony na listingu 2.
</p>

#####{$listings[2]}#####



<p>
W kodzie skryptu PHP możemy dodatkowo zablokować dostęp 
do witryny adresami <span class="filename">index.php</span>:
</p>



<pre>
if (preg_match('/index\.php/', $_SERVER['REQUEST_URI'])) {
    header('Location: http://localhost/lorem.html');
    header('HTTP/1.x 301 Moved Permanently');
}
</pre>




<p>
Dzięki temu odwołania do stron:
</p>


<pre>
index.php
index.php?id=2
index.php?id=3
...
</pre>


<p>
będą przekierowywane na stronę główną.
</p>

#####{$frames[1]}#####


#####{$tables[1]}#####

#####{$tables[2]}#####