Stacja telewizyjna A nakręciła materiał z koncertu super gwiazdy i udostępnia go na swoim kanale. Jednocześnie stacja ta pozwala innym stacjom na używanie tego materiału na swoich kanałach (zwykle za opłatą licencyjną). I właśnie takie udostępnianie innym nazywamy syndykacją.
W świecie internetu gro procesu syndykacji zostało uproszczone do generowania przez właściciela treści pliku z jej zawartością, który to umieszcza na swoim serwerze. Rzadko też spotykamy się z odpłatnymi kanałami, a jeśli już to są one konsekwencją płatnego dostępu do całego serwisu. Dość po słowie, że świat idzie na przód i syndykacja w obecnym obliczu jest jednym z elementów, które powodują odejście starej wizji mediów w niebyt.
JavaScript i AJAX
Ale wracając do tematu wpisu, czyli wyświetlania kanału treści na swojej witrynie. Niestety najprostsze i najkorzystniejsze dla właściciela hostingu (transfer, czas procesora) podejście wykorzystujące JavaScript w wielu przypadkach nie sprawdzi się z racji polityki pochodzenia z tej samej domeny. Oczywiście jeśli chcemy agregować treści znajdujące się w serwisach tej samej domeny ten problem nas nie dotyczy i bez problemu możemy wykorzystać AJAX (Tak kanały w standardzie RSS jak i w Atom korzystają z XML) choćby z poniższym kodem:function GetXHR() { var xmlHttp = null; try { // Firefox, Opera 8.0+, Safari xmlHttp=new XMLHttpRequest(); } catch (e) { // Internet Explorer try { xmlHttp=new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { xmlHttp=new ActiveXObject("Microsoft.XMLHTTP"); } } if(xmlHttp == null) alert("Browser does not support HTTP Request"); return xmlHttp; } window.addEventListener('load', function() { var chk=setInterval(function() { doc = document.getElementsByTagName("body"); if(doc.length == 0 || doc[0].innerHTML.length < 500) { return; } clearInterval(chk); var xhr = GetXHR(); xhr.onreadystatechange=function() { if(this.readyState == 4 && this.status == 200) { document.getElementById("articleStuffShowRSSHere").innerHTML = "<a href='"+ this.responseXML.getElementsByTagName("entry")[0].getElementsByTagName("link")[4].getAttribute("href")+"'>"+ this.responseXML.getElementsByTagName("entry")[0].getElementsByTagName("title")[0].textContent+ "</a>"; } }; xhr.open("GET" , '/feeds/posts/default', true); xhr.send(); },100); }, false);Przykład ten wyświetla obok link do najnowszego artykułu z tego bloga:
PHP i cache po stronie serwera
Jednak jeśli nasz serwis znajduje się w innej domenie to z usług JavaScript skorzystać nie będzie nam dane. Oczywiście możemy próbować korzystać z różnego rodzaju sztuczek lub zewnętrznych serwisów pobierających pliki syndykacji za nas... ale raczej nie o półśrodki tu chodzi, a o mniej lub bardziej profesjonalne i autonomiczne rozwiązanie. PHP na szczęście umożliwia nam pobranie plików z zewnętrznych serwisów i swobodną manipulację na nich, tak więc problem wspólnej domeny odpada. Pojawia się za to inny problem, a mianowicie problem z wydajnością - w przypadku JavaScript cały proces de facto zrzucaliśmy na użytkownika i z punktu widzenia serwera nie odczulibyśmy różnicy przy syndykacji z 2 kanałów lub z 200 kanałów (Choć użytkownik by zapewne odczuł). Przy korzystaniu z PHP natomiast jeśli każda wizyta użytkownika miałaby uruchamiać mechanizm pobierania plików, ich parsowania i transformacji do finalnej postaci prezentowanej użytkownikowi to nie potrzeba agregacji z 200 kanałów, ale wystarczy 1 kanał i 200 użytkowników naszego serwisu byśmy odczuli ten sam efekt. Co więcej istnieje realna groźba, że właściciel serwisu, z którego dane pobieramy uzna nas za źródło ataku DoS (ang. Denial-of-service), - bo wszak wszystkie żądania pobrania będą pochodzić z naszego serwera - i zablokuje nam dostęp do kanału.Stąd też konieczny jest mechanizm zapisu pobranych danych w taki sposób aby odwiedziny nowych użytkowników nie generowały transferu danych pomiędzy serwisami, a korzystały z już ściągniętych danych. Tu należy się zastanowić na którym etapie procesu przygotowywania do prezentacji należy dane zapisać, bo w zależności od tego jak chcemy je wykorzystać inny moment tego procesu może okazać się najefektywniejszy. W poniższym przykładzie zapis do pliku na serwerze odbywa się po przygotowaniu całości prezentowanego komponentu, a podobny mechanizm można z powodzeniem wykorzystać do zapisu całych stron, których wygenerowanie wymaga wielu obliczeń lub zapytań do baz danych, a których treści nie ulegają częstej zmianie.
Generalnie kod składa się z dwóch części, pierwsza - dłuższa to funkcja przeprowadzająca pobranie, obróbkę i zapis treści na dysk serwera:
<?php function prepare($ident) { // download data and parse them with SimleXmlElement $content = file_get_contents('http://runaurufu.blogspot.com/feeds/posts/default'); $x = new SimpleXmlElement($content); // get data which we want to use $entryName = $x->entry[0]->title.""; $entryUrl = ""; foreach ($x->entry[0]->link as $value) { $aux = $value->attributes(); if($aux['rel']."" == 'alternate') { $entryUrl = $aux['href'].""; } } $blogTitle = $x->title.""; $blogSubTitle = $x->subtitle.""; $blogUrl = ""; foreach ($x->link as $value) { $aux = $value->attributes(); if($aux['rel']."" == 'alternate') { $blogUrl = $aux['href'].""; } } // prepare for cacheing //start output buffering ob_start(); // here do entire presentation generation ?> <a href="<?php echo $entryUrl; ?>"><?php echo $entryName; ?></a> @ <a href="<?php echo $blogUrl; ?>" title="<?php echo $blogSubTitle; ?>"><?php echo $blogTitle; ?></a> <?php // save output buffer to $data $data = ob_get_contents(); // clear output buffer and stop buffering // (ob_end_flush(); can be used to stop buffering and display buffer content) ob_end_clean(); $handle = fopen($ident, 'wb'); if(flock($handle, LOCK_EX)) { $bytes = fwrite($handle, $data); flock($handle, LOCK_UN); } fclose($handle); return $data; } ?>
Druga część jest odpowiedzialna za sam proces wyboru, czy chcemy użyć dane zapisane na dysku, lub czy mimo wszystko pobieramy je od nowa:
<?php $cacheTime = 86400; $ident = "myFeed.cache"; if(!is_file($ident)) { echo prepare($ident); } else { if(filemtime($ident) + $cacheTime > time()) { // show content of file echo file_get_contents($ident); } else { echo prepare($ident); } } ?>Oczywiście kod ten można w znacznej mierze zmodyfikować i sprawić, aby cały proces obsługi naszego cache wyglądał tak np:
<?php$cache = new Chors_Cache($identifier, $time); if($cache->check()) { return $cache->fetchRender(); } else { $cache->startOutputCaching(); // generate cached content begin ... // generate cached content end return $cache->endOutputCachingClean(); }?>
Słowo na koniec
Jak widać obsługa kanałów syndykacji nie jest czymś trudnym a jedynym problemem na jaki możemy natrafić to konieczność stworzenia parsera wyłuskującego te dane, które nas interesują. Warto jeszcze wspomnieć, że o ile używanie AJAXa nie obciąża naszego serwera, to generuje zbyteczny ruch na serwerze udostępniającym kanał treści i z tego względu będąc "miłym sąsiadem" należałoby mimo wszystko zaprzęgnąć swój serwer do obsługi tych danych.* Słowem wyjaśnienia: RSS i Atom o ile bywają synonimem udostępniania zawartości witryny to w rzeczywistości są tego udostępniania standardami, a nie samą nazwą procesu.