Przekierowania, skracanie i ukrywanie adresów - co i jak?

W ostatnim okresie upowszechniły się serwisy skracające adresy, takie jak tnij.org, tinyurl.com czy działający wewnątrz Blipa 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ę Eksperymencie HTTP. mod_rewrite i przepisywanie URL Kluczem do stworzenia systemu skracania adresów jest możliwość 'przepisywania adresów' - dzięki temu nasz system będzie umiał obsłużyć adresy postaci: http://moj.system.skrotow.pl/skroconyurl. Do tego celu można wykorzystać na przykład bardzo popularny moduł mod_rewrite serwera Apache. Krótko i konkretnie temat 'ładnych linków' omówił na swojej stornie porneL. Przykład .htaccess, którego obecnie używam (nazwy związane z serwisem zmienione ;) ):
#<IfModule mod_rewrite.c>

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]

#</IfModule>
Występuje tu kilka rzeczy, których nie omówił jeszcze porneL. Po pierwsze, dyrektywa RewriteBase - 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 RewriteBase mamy dwie linie dokonujące usuwania slashy z URI w ramach procesu przekształcania adresu do postaci kanonicznej. Istotne (zwłaszcza dla Google) 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 bardzo dużo materiałów w sieci - 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: Przekierowania HTTP Przekierowania te dość dokładnie omawiałem w jednym z ostatnich postów. 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:
  • Takiego przekierowania można używać także do elementów osadzanych, np. w tagach <img>
  • Nie można zamaskować adresu docelowego - jest on widoczny w pasku adresu przeglądarki.
  • Zwłaszcza w przypadku użycia kodu 301 PageRank przelewa się na adres docelowy (ale ostrożnie z tym).
  • Opiera się w całości na protokole HTTP, więc musi być wspierana przez wszystkie przeglądarki.
Ciekawostka - serwis tnij.org zdaje się odsyłać nagłówek 301 Moved Permanently, a nie 302 Found lub 307 Temporary Redirect. Przekierowania "meta refresh" Innym rodzajem przekierowania, wykonywanym całkowicie po stronie klienta, jest wymuszenie odświeżenia adresu przez przeglądarkę przy użyciu znacznika <meta>. Przykładowy kod wygląda następująco:
<html>
 <head>
  <meta http-equiv="Refresh" content="0; url=http://www.innastrona.com/">
 </head>
 <body>
  <p>Proszę, kliknij w <a href="http://www.innastrona.com/">link</a>!</p>
 </body>
</html>
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:
  • Nie można zamaskować adresu docelowego - jest on widoczny w pasku adresu przeglądarki.
  • Chodzą wieści, że Google umie czytać ten typ przekierowania z kodu HTML - ale na rewelacyjny przelew PageRank bym nie liczył.
  • 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.
  • Znikoma przydatność.
Ponieważ w tym przypadku znacznik <meta> 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:
<?php
Header('Refresh: 0; url=http://www.innastrona.com');
echo 'Proszę kliknąć w <a href="http://www.innastrona.con">link</a>.';
?>
Osobiście ciekawi mnie, jak przeglądarka zareagowałaby w przypadku zapytania o osadzony element (<img src="... >), gdyby odesłać jej obrazek razem z nagłówkiem Refresh. Być może sprawdzę to przy najbliższej okazji. Przekierowania Javascript Metoda podobna do meta refresh - wysyłamy stronę ze spreparowanym JavaScript'em, który automatycznie przekierowuje użytkownika pod inny adres. Cechy techniki:
  • Nie działa z elementami osadzonymi.
  • Wiele crawlerów nie umie obsługiwać JavaScript'u - wyszukiwarki nie zobaczą przekierowań.
  • Niektórzy nie posiadają lub celowo wyłączają obsługę JavaScript'u w przeglądarce.
  • 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.
Maskowanie adresu - metoda ramek Użycie znienawidzonego znacznika <frameset> pozwala bardzo skutecznie zamaskować adres docelowy strony, na którą przekierowujemy (i przy okazji zmienić jej tytuł). Wystarczy wygenerować taki oto HTML:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"
   "http://www.w3.org/TR/html4/frameset.dtd">
<html>
	<head>
	</head>
	
	<frameset cols="100%">
			<frame src="http://www.innastrona.com" name="moja_ramka" />
	</frameset>
</html>
Własności tej metody:
  • Pozwala zamaskować adres docelowy i wymienić tytuł.
  • Nie działa z elementami osadzonymi - z oczywistych przyczyn.
  • Standardem są już skrypty pozwalające stronie automatycznie 'wyskoczyć z ramki' - i wtedy nici z maskowania adresu.
  • Może być przyczyną kontrowersji i ataków ze strony niedouczonych webmasterów stron docelowych.
Tę technikę spotykamy się wbrew pozorom dość często - na stronach takich jak Wykop.pl czy nawet Google Images. Takie strony po prostu dodają dodatkową ramkę z własnymi informacjami :). Przekierowania 'proxy' 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 readfile(). Metoda jest bardzo prosta w teorii i gwarantuje zamaskowanie adresu, jednak rodzi też szereg trudności technicznych:
  • 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.
  • Jeżeli oddajemy coś innego niż zwykły tekst to musimy ręcznie określić typ MIME 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.
  • Cała treść przechodzi przez nasz serwer, skutecznie go spowalniając (już nie mówiąc o ewentualnych limitach downloadu).
Wspominam o tej technice, gdyż ma ona także dużo dobrych właściwości: 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... Eksperyment HTTP 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 zapraszam, 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 :).