Cookies

Ciasteczka są to małe ilości danych, które są przechowywane na twoim komputerze za pomocą plików tekstowych i wysyłane są do serwera z żądaniami HTTP. Takie pliki zawierają w sobie króciutkie informacje takie jak np. id usera, jakiś zakodowany numer, ostatnią wizytę, numer aktualnej sesji itp.

Bardzo często ciasteczka służą do komunikacji przeglądarka-serwer, ponieważ zarówno serwer, jak i przeglądarka mogą przesyłać takie krótkie informacje między sobą. Dzięki takiej komunikacji strona może przekazywać informacje między podstronami, pamiętać że dany użytkownik właśnie jest zalogowany, przekazywać sobie jakieś dane o sesji itp.

Jak działa taka komunikacja?
Za pomocą odpowiednich nagłówków. Co to takiego i jak to sprawdzić? Wejdź do debugera przeglądarki (F12), i przejdź do zakładki Network. Odśwież stronę.
Zakładka ta pokazuje wszystkie połączenia (żądania i odpowiedzi) jakie się odbywają miedzy przeglądarką a serwerem. Kliknij i podejrzyj sobie np. żądanie pobrania tej strony (powinno być pierwszym żądaniem tej strony).
Przeglądarka wysłała nagłówki do serwera (request headers), że chce wyświetlić ten dokument. W tych nagłówkach jest naście informacji - np. jaka to przeglądarka, jakie obsługuje typy plików, jaki preferuje język itp.
Serwer to żądanie przyjął, przetworzył i wysłał swoje nagłówki (response headers) wraz z treścią odpowiedzi (body). Potem przeglądarka zaczęła czytać odpowiedź którą dostała, natrafiała np. na zagnieżdżone style, skrypty, grafiki i wysłała kolejne requesty wraz z nagłówkami, w odpowiedzi dostając nagłówki wraz z treścią...

Wśród nagłówków response (czyli tych co dostajemy z serwera) często będzie się znajdował nagłówek Set-Cookie - oznacza to, że serwer przesyła do nas ciasteczko.

cookies response

Jeżeli przeglądarka natrafi na taki nagłówek, ustawi sobie ciasteczko jakie przesłał jej serwer, a następnie przy każdym requeście do tego serwera będzie jest wysyłać w nieco skróconej formie posługując się nagłówkiem Cookie.

Sprawdzimy to w debugerze w zakładce Application, wybierając z bocznego menu Cookies:

Zakładka Application w debugerze

Jak widzisz, przeglądarka otrzymała od serwera informację o ciasteczku, a następnie odpowiednio je sobie zapisała.
Od tej pory każde nowe połączenie z tą domeną sprawi, że takie ciasteczko będzie wysyłane wraz z requestem:

cookies request

Większość skryptów z tego działu będziesz musiał odpalić z serwera lokalnego.
Obostrzenia względem ciasteczek są bardzo podobne do same-origin policy, czyli zabezpieczeń, które nie pozwalają łączyć się asynchronicznie (ajax) gdzie tylko chcemy. Najczęściej ograniczeni jesteśmy do własnej domeny, chyba, że domena z którą się komunikujemy, ustawi odpowiednie nagłówki.

Tak samo jak po stronie serwera możemy tworzyć ciasteczka (np. w PHP za pomocą $_COOKIES), tak i Javascript umożliwia nam tworzyć takie ciastka. Poniżej omówimy jak to robić.

Postać ciasteczka

Zanim przyjrzymy się ciasteczkom w Javascript, spójrzmy na postać ciasteczek, które trafiają do nas w nagłówku Set-Cookie


Set-Cookie: value;max-age=seconds;domain=domena;path=sciezka;secure;HttpOnly
Parametr Wymagane Co oznacza Przykładowa wartość
value Wymagane Wartość i nazwa ciasteczka username=Marcin
max-age Opcjonalne czas w sekundach 6050050
domain Opcjonalne domena na której będzie działać do ciasteczko kurspl.pl
path Opcjonalne sciezka do domeny, albo do podkatalogu /
secure Opcjonalne Zabezpieczenia ciasteczka. Czy ma ono się odwoływać tylko do https secure
HttpOnly Opcjonalne Czy będzeimy mogli się odwoływać do ciasteczek z poziomu JavaScript HttpOnly

Pierwszą część składni ciasteczka zajmuje nazwa ciasteczka oraz jego wartość. Są to jedyne wymagane parametry ciasteczka. Jeśli przeglądarka nie znajdzie cookie o danej nazwie w aktualnej domenie/ścieżce, utworzy nowe. Jeśli ciasteczko o podanej nazwie już istnieje, jego stara wartość zostanie zastąpiona przez nową.

Parametr expires określa datę wygaśnięcia. W ciasteczku data wygaśnięcia jest przechowywana w sekundach, ale my w celu jej ustalenia musimy ją podawać w formacie GTM. Po upływie daty wygaśnięcia ciasteczko zostaje usunięte z systemu.
Jeżeli nie podamy tego parametru, wówczas ciastko zostanie utworzone do czasu trwania sesji czyli do czasu wyłączenia przeglądarki
Gdy podasz datę wygaśnięcia ciasteczka na wcześniejszą od aktualnej - wówczas takie ciasteczko zostanie usunięte.

Parametr domain określa domenę ciasteczka. Jeżeli nie ustawisz tego parametru, wówczas przeglądarka ustawi go jako nazwę serwera skąd zostało utworzone ciasteczko. Jeżeli będziesz chciał wyświetlić wszystkie ciasteczka z dokumentu cookie wówczas zostaną ci wyświetlone tylko te ciasteczka, które mają parametr domain taki jak dokument z którego wysyłasz żądanie wyświetlenia ciasteczek. Adresy domeny możesz podawać na różne sposoby - jest tylko jedno zastrzeżenie - adres taki musi zawierać przynajmniej 2 kropki np.
.pckurier.pl
.digimer.pl
Możesz też podać cały URL (<http://taka.sobie.domena/ścieżka>).

Parametr path ustawia ścieżkę skąd zostało utworzone ciasteczko. Najlepszym rozwiązaniem jest tutaj pozostawienie domyślnej wartości czyli "/".

Parametry domain i path wcale nie są takie opcjonalne. Jeżeli zakładasz ciastko dla całej witryny (tak by działało także na podstronach) to wtedy będziesz musiał użyć parametru path=/, bo może się okazać, że założyłeś ciastko tylko dla jakiejś postrony.
Parametru domain będziesz musiał użyć, gdy w swoim serwisie równocześnie korzystasz z linków rozpoczynających się od http:// i http://www (chociaż to zależy od ustawień serwera).

Kolejny parametr ciasteczka to secure. Jeśli pominiesz ten parametr, przeglądarka automatycznie przyjmuje, że cookie dostępne jest dla każdej domeny lub programu CGI, jeśli tylko zgadzają się odpowiednie własności. Jeżeli ustawisz go na true, wtedy ciastka będą wysyłane tylko w zabezpieczonych połączeniach (https).


Jeżeli przeglądarka dostanie powyższą informację o ciastku, ustawi sobie takie ciasteczko u siebie. Od tej pory do każdego żądania do danej domeny nasza strona dołączy takie ciasteczko w nagłówku Cookie, ale w skróconej formie (po stronie przeglądarki jego wielkość to max 4kb).

Postać takiego nagłówka może wyglądać tak:


Cookie: username=Marcin; userid=123345; session=dasdkljasd82213213

W powyższym nagłówku przeglądarka wysłała 3 informacje: nazwa użytkownika, jego id i numer sesji.

To co powyżej się stało, działo się automatycznie. Przeglądarka dostała informację, że ma sobie utworzyć ciasteczko więc je utworzyła i w razie czego odesłała do serwera.

My jako programiści także możemy ręcznie tworzyć własne ciasteczka. Aby to zrobić, musimy użyć odpowiedniej konstrukcji.

Tworzymy pierwsze ciasteczko

Aby utworzyć ciasteczko musimy je utworzyć w specyficzny sposób wykorzystując do tego właściwość document.cookie:


document.cookie = "nazwaCookie=wartoscCookie; expires=dataWygasniecia; path=/; secure"

Jak widzisz składa się ona ze składowych, które przysyła nam serwer w nagłówku Set-Cookie

Najprostszą wersją utworzenia swojego ciastka jest użycie konstrukcji:


document.cookie = "nazwaCookie=wartoscCookie"

Żeby obsłużyć resztę parametrów, utworzmy funkcję służącą do tworzenia ciasteczek.
Będzie ona miała następującą postać:


function setCookie(name, val, days, path, domain, secure) {
    if (navigator.cookieEnabled) { //czy ciasteczka są włączone
        const cookieName = encodeURIComponent(name);
        const cookieVal = encodeURIComponent(val);
        let cookieText = cookieName + "=" + cookieVal;

        if (typeof days === "number") {
            const data = new Date();
            data.setTime(data.getTime() + (days * 24*60*60*1000));
            cookieText += "; expires=" + data.toGMTString();
        }

        if (path) {
            cookieText += "; path=" + path;
        }
        if (domain) {
            cookieText += "; domain=" + domain;
        }
        if (secure) {
            cookieText += "; secure";
        }

        document.cookie = cookieText;
    }
}

Na początku tworzymy nazwę i wartość ciastka kodując je za pomocą encodeURI. W przeciwnym razie ktoś podał by nazwę ciastka np. "Moje ciasteczko", co jest niedopuszczalne - tutaj działają takie same zasady jak przy adresach url.
Kolejnymi czynnościami jest wyliczenie składowych tekstu zapisu ciastka.
Na samym końcu po prostu ustawiamy ten tekst dla document.cookie.


document.querySelector('#addTest').addEventListener('click', function() {
    setCookie('mojeCiasteczko', 'jegoWartosc');
});
(po utworzeniu odśwież stronę i sprawdź w debugerze w zakładce Aplication -> Cookies)
Utworzone nowe ciastko

Odczyt ciasteczka

Aby odczytać dane ciasteczko, także skorzystamy z document.cookie. Jak widziałeś powyżej właściwość ta zawiera ciąg składający się z par: nazwaCiastka=wartośćCiastka.


nazwacookie1=wartosccookie1; nazwacookie2=wartosccookie2; nazwacookie3=wartosccookie3;

Aby wydzielić poszczególne cookie do tablicy skorzystamy z funkcji split, która podzieli powyższy ciąg na części i zróci je w formie tablicy. Aby zabezpieczyć się przed sytuacją, gdyby miedzy ciastkami nie było by spacji (a któż to wie...), użyjemy tutaj wyrażenia regularnego:


const cookies = document.cookie.split(/; */); //dopasuje "; " ale też ";"

Jeżeli przykładowo wydrukujemy teraz cookies[1] to wyświetli nam się 2 ciasteczko na naszej stronie (jeżeli takowe jest) czyli np. nazwacookie2=wartoscookie2. Dla takiego ciastka ponownie skorzystamy z metody split aby wydzielić nazwę i wartość ciasteczka:


console.log(cookies[0]); //wypisze nazwacookie1=wartosccookie1
console.log(cookies[0].split("=")[0]) //nazwa pierwszego ciastka
console.log(cookies[0].split("=")[1]) //wartość pierwszego ciastka

Możemy oczywiście odczytywać wartości poszczególnych ciasteczek jak to jest pokazane powyżej, lepiej jednak napisać funkcję, w której będziemy pobierali ciastka za pomocą nazwy:


function showCookie(name) {
    if (document.cookie !== "") {
        const cookies = document.cookie.split(/; */);

        for (let i=0; i<cookies.length; i++) {
            const cookieName = cookies[i].split("=")[0];
            const cookieVal = cookies[i].split("=")[1];
            if (cookieName === decodeURIComponent(name)) {
                return decodeURIComponent(cookieVal);
            }
        }
    }
}

//czytamy ciastko
console.log(showCookie("Przedmiot"));

Skoro wcześniej encodowaliśmy nazwy ciastek, w powyższej funkcji musieliśmy je dekodować.


document.querySelector('#readTest').addEventListener('click', function() {
    console.log('Wartość ciastka mojeCiasteczko: ', showCookie("mojeCiasteczko"));
});

Usuwanie ciasteczka

Aby usunąć dane ciasteczko musimy ustawić jego parametr expires na wcześniejszy od aktualnej daty. Możemy to zrobić korzystając z poniższej funkcji służącej do usuwania ciastek.


function deleteCookie(name) {
    const data = new Date();
    data.setTime(date.getMonth()-1);
    const name = encodeURIComponent(name);
    document.cookie = name + "=; expires=" + data.toGMTString();
}

Możemy też użyć pewnej sztuczki ustawiając datę wygaśnięcia danego ciastka na najniższą komputerową możliwą datę (to właśnie od tej daty komputery liczą czas).
Jakby nie patrzeć datę więc ustawimy na wcześniejszą niż obecna :)


function deleteCookie(name) {
    const cookieName = encodeURIComponent(name);
    document.cookie = cookieName + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}

document.querySelector('#deleteTest').addEventListener('click', function() {
    deleteCookie("mojeCiasteczko");
});
Po usunięciu, odśwież stronę i sprawdź w debugerze w zakładce Aplication -> Cookies

Przykład praktyczny

Załóżmy, że chcemy odczytać od użytkownika jakieś dane - np. jego imię i nazwisko, żeby móc potem z nich korzystać na innych stronach.
Tworzymy więc odpowiedni formularz:


<form name="formularz">
    <fieldset>
        <div>
            <label for="name">Podaj imię:</label>
            <input id="name" name="name" type="text" maxlength="20" />
        </div>

        <div>
            <label for="surname">Podaj nazwisko:</label>
            <input id="surname" name="surname" type="text" maxlength="20" />
        </div>

        <div>
            <button class="button" type="button" id="save">Zapisz </button>
            <button class="button" type="button" id="show">Pokaz </button>
        </div>
    </fieldset>
</form>

Następnie tworzymy odpowiedni skrypt, który utworzy nam nasze ciasteczka (1 dla imienia i 2 dla nazwiska).
Funkcje setCookie, deleteCookie i showCookie już znamy. Pozostaje je właściwie wykorzystać. Podpinamy wiec zdarzenia click pod odpowiednie guziki i w nich albo ustawiamy, albo pobieramy odpowiednie ciastka:


//te funkcje już znamy
function setCookie(name, val, days, path, domain, secure) {
    if (navigator.cookiesEnabled) {
        const cookieName = encodeURIComponent(name);
        const cookieVal = encodeURIComponent(val);
        let cookieText = cookieName + "=" + cookieVal;

        if (typeof days === "number") {
            const data = new Date();
            data.setTime(data.getTime() + (days * 24*60*60*1000));
            cookieText += "; expires=" + data.toGMTString();
        }

        if (path) {
            cookieText += "; path=" + path;
        }
        if (domain) {
            cookieText += "; domain=" + domain;
        }
        if (secure) {
            cookieText += "; secure";
        }

        document.cookie = cookieText;
    }
}

function deleteCookie(name) {
    const cookieName = encodeURIComponent(name);
    document.cookie = cookieName + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}

function showCookie(name) {
    if (document.cookie != "") {
        const cookies = document.cookie.split(/; */);

        for (let i=0; i<cookies.length; i++) {
            const cookieName = cookies[i].split("=")[0];
            const cookieVal = cookies[i].split("=")[1];
            if (cookieName === decodeURIComponent(name)) {
                return decodeURIComponent(cookieVal);
            }
        }
    }
}

document.addEventListener("DOMContentLoaded", function() {
    document.querySelector('#save').addEventListener('click', function(e) {
        e.preventDefault();

        const nameVal = document.querySelector('#name').value;
        const surnameVal = document.querySelector('#surname').value;

        setCookie('name', nameVal);
        setCookie('surname', surnameVal);
    });

    document.querySelector('#show').addEventListener('click', function(e) {
        e.preventDefault();
        alert(showCookie('name') + ' ' + showCookie('surname'))
    });
});