Ajax w oparciu o cookies

Mimo, że często mówiąc Ajax ma się na myśli nierozerwalnie obiekt XMLHttpRequest, to wcale nie jedyna technika asynchronicznego łączenia się z serwerem przez JavaScript.

Niedawno opisywałem przykład zastosowania pływającej ramki jako medium wymiany danych. W tym artykule pokażę, jak wykorzystać do tego celu ciasteczka (ang. cookies).

Zasada działania

  1. Wyślij na serwer dane za pomocą obrazka (obiekt Image odwołujący się do skryptu PHP [server-side]).
  2. Wynik działania skryptu zapisz w ciasteczku.
  3. Sprawdzaj w JS co pewien czas, czy już ustawiono wartość ciasteczka.

Proponowany przeze mnie kod próbuje zachować intefejs obiektu XMLHttpRequest.

Kod

Aktualny kod znajdziesz na google code:

[javascript]// obiekt symulujacy XMLHttpRequest oparty o cookies
// autor: Patryk yarpo Jar
// data : 08-IV-2011

function XMLHttpCookieRequest()
{
var oSelf = { readyState : CONST(‚UNSET’) };

var nAttemptCounter = 0
, sUrl = ”
, bAsync = true
, aHeaders = []
, bXml = false;

function CONST(k)
{
var c = { UNSET : 0, OPENED : 1, LOADING : 3, READY : 4,
COOKIE_NAME : ‚yXMLHttpCookieRequest’,
ERROR : 500, ERROR_TXT : ‚Server Error’,
MAX_ATTEMPTS : 10, INTERVAL : 100 };
return c[k];
}
function fOpen(method, url, async)
{
// only GET method
sUrl = url;
bAsync = (false === async) ? false : true;
oSelf.readyState = CONST(‚OPENED’);
}
function fSend(data)
{
fReset();

var i = new Image();
i.src = sUrl + ‚?’ + data;
oSelf.readyState = CONST(‚LOADING’);

if (bAsync)
{
setTimeout(fRead, CONST(‚INTERVAL’));
}
else
{
alert(‚Brak wsparcia dla żądań synchronicznych’);
}
}
function fStr2Xml(text)
{
if (window.ActiveXObject)
{
var doc = new ActiveXObject(‚Microsoft.XMLDOM’);
doc.async = ‚false’;
doc.loadXML(text);
}
else
{
var parser = new DOMParser()
, doc = parser.parseFromString(text,’text/xml’);
}
return doc;
}
function fReadData(data)
{
if (bXml)
{
oSelf.responseXml = fStr2Xml(data);
}
else
{
oSelf.responseText = data;
}
oSelf.readyState = CONST(‚READY’);
}
function fRead()
{
nAttemptCounter++;
var data = fGetCookie();
if (false !== data)
{
fReadData(data);
oSelf.onreadystatechange.apply(oSelf);
return;
}

if (nAttemptCounter > CONST(‚MAX_ATTEMPTS’))
{
oSelf.status = CONST(‚ERROR’);
oSelf.statusText = CONST(‚ERROR_TXT’);
oSelf.onreadystatechange.apply(oSelf);
}
else
{
setTimeout(fRead, CONST(‚INTERVAL’));
}
}
function fReset()
{
oSelf.readyState = CONST(‚UNSET’);
nAttemptCounter = 0;
fDelCookie();
delete oSelf.responseText;
delete oSelf.responseXml;
}
function fAbort()
{
sUrl = ”;
bAsync = true;
bXml = false;
fReset();
}
function fDelCookie()
{
document.cookie = CONST(‚COOKIE_NAME’) +
‚=; expires=Thu, 01-Jan-70 00:00:01 GMT;’;
}
function fGetCookie()
{
var docCookie = document.cookie.split("; ");
for (var i=0; i < docCookie.length; i++)
{
var piece = docCookie[i].split("=");
if (piece[0] === CONST(‚COOKIE_NAME’))
{
return unescape(String(piece[1]).replace(/\+/g, " "));
}
}
return false;
}
function fSetRequestHeader(k, v)
{
aHeaders.push({‚header’ : k, ‚value’ : v});
bXml = (‚content-type’ === k.toLowerCase() &&
-1 !== v.toLowerCase().indexOf(‚xml’));
}
function fGetRequestHeader()
{
if (0 === arguments.length)
{
var headCont = ”;
for(var i = 0; i < aHeaders.length; i++)
{
headCont += aHeaders[i].header + ‚: ‚ + aHeaders[i].value + ‚\n’;
}
return headCont;
}
else if (1 === arguments.length)
{
for(var header in aHeaders)
{
if (header.header.toLowerCase() === arguments[0].toLowerCase())
{
return header.header + ‚: ‚ + header.value + ‚\n’;
}
}
}
}
return oSelf = {
open : fOpen,
send : fSend,
abort : fAbort,
setRequestHeader : fSetRequestHeader,
getResponseHeader : fGetRequestHeader,
getAllResponseHeaders : fGetRequestHeader,
onreadystatechange : null,
status : 200,
statusText : ‚OK’
};
};[/javascript]

Użycie:

Przykłady na svn:

[javascript]function testXml()
{
var client = new XMLHttpCookieRequest();
client.open(‚GET’, ‚xml.php’);
client.setRequestHeader(‚Content-type’, ‚text/xml’);
client.onreadystatechange = function()
{
if (200 === this.status && 4 === this.readyState)
{
var xml = this.responseXml
, info = xml.getElementsByTagName(‚imie’)[0].firstChild.nodeValue + ‚ ‚ +
xml.getElementsByTagName(‚nazwisko’)[0].firstChild.nodeValue;
alert(info);
}
}
client.send(‚imie=Patryk&nazwisko=Jar’);
}[/javascript]

Zmiany po stronie serwera

Niestety, taka technika wymaga zmiany po stronie serwera :/

Zamiast wyświetlać dane, trzeba je przekazywać do ciasteczka. W przypadku PHP służy do tego `setcookie‚.

Przykładowy kod:

[php]<?php
define(‚EXPIRES_IN_2_MINUTES’, time()+60*2);
$data = $_GET[‚imie’] . ‚ ‚ . $_GET[‚nazwisko’];

setcookie(‚yXMLHttpCookieRequest’, $data, EXPIRES_IN_2_MINUTES);
echo $data;[/php]

Ograniczenia

Nie wszystko działa jak w obiekcie XMLHttpRequest. Oto lista ograniczeń:

  1. Można używać tylko metody GET – trudno za pomocą obrazka przesyłać POSTem ;). Można to rozwiązać używając pływającej ramki i formularza, ale o tym pisałem w osobnym artykule.
  2. Nie implementowałem obsługi żądań synchronicznych. Sądzę, że dałoby się to zrobić, ale czy ktokolwiek będzie tego używał? Chyba nie, to raczej ciekawostka 🙂
  3. Nie do końca prawidłowe statusy HTTP. Po 10 próbach odczytania z ciasteczka ustawiany jest status 500 – server internal error. Choć wcale nie musi to być prawdą.
  4. Konieczność zmian po stronie serwera. Można to obejść robić coś takiego:
    [php]<?php

    echo $data;
    setcookie(‚ciastko’, $data, $exp);[/php]
  5. Ciasteczka mają ograniczoną pojemność, a niektórzy nawet je wyłączają.

Sądzę, że są to wystarczające powody, dla których jednak lepiej jest stosować XMLHttpRequest. Warto jednak wiedzieć i pamiętać, bo czasem może się okazać niezastąpione, że istnieją inne sposoby na komunikację z serwerem.

Warto przeczytać:

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *