chevron-left chevron-right

Piotr Nalepa Frontend, architecture and AI

[JS][PHP] Jak zaimplementować powiadomienia typu push w aplikacji JavaScript?

This version
blog.piotrnalepa.pl/2015/06/11/jsphp-jak-zaimplementowac-powiadomienia-typu-push-w-aplikacji-javascript
Published
Author
, Staff Software Engineer
Categories
JavaScript, PHP
Feedback
Write to me · Post on X

This post is 11 years old. Browsers and tools may have changed since then, so check the code against current documentation before you use it.

Spróbuj sobie wyobrazić korzystanie z Twittera, Facebooka czy też GitHuba bez otrzymywania powiadomień o tym co się właśnie wydarzyło. Brak informacji, że pojawiły się nowe wpisy na Twitterze; brak powiadomień, że ktoś do nas napisał na Facebooku czy chociażby brak powiadomień o tym, że ktoś utworzył pull requesta do naszego kodu na Githubie byłby bardzo uciążliwy. W niektórych przypadkach użyteczność takiej aplikacji czy strony znacząco uległaby zmniejszeniu.

Na szczęście, JavaScript API udostępniane przez HTML5 znacząco ułatwia nam implementację tego typu rozwiązań na stronach internetowych i w aplikacjach internetowych. W przypadku powiadomień typu push idealnym rozwiązaniem wydaje się być SSE - Server Sent Events.

Section 1Czym jest SSE?

Jest to mechanizm jednostronnej komunikacji między serwerem a przeglądarką, która odbywa się za pomocą protokołu HTTP. Połączenie jest ustanawiane raz i jest utrzymywane przez serwer aż do momentu w którym twórca aplikacji zdecyduje się zamknąć połączenie. SSE ma wbudowaną funkcjonalność automatycznego wznawiania połączenia w momencie gdy z jakiegoś powodu połączenie zostało zerwane.

Z racji tego, że komunikacja odbywa się po protokole HTTP to wiadomości przesyłane z serwera zawsze będą w postaci tekstu. Czyli danych binarnych nie będziemy już w stanie przesłać, tak jak to ma miejsce w przypadku korzystania WebSockets.

Ponadto, za pomocą SSE jesteśmy w stanie samodzielnie zdefiniować jakie eventy/zdarzenia po stronie serwera będą udostępniane, oprócz standardowych eventów:

  • open,
  • message,
  • error.

Dodatkowo, możemy przesłać ID eventu, które może się okazać przy wznawianiu zerwanego połączenia. W takim przypadku, ID eventu ostatniego otrzymanego, przez przeglądarkę, eventu będzie przesłane w nagłówku żądania (requestu) HTTP: Last-Event-ID.

Section 2Wsparcie SSE w przeglądarkach

Na chwilę obecną wsparcie przeglądarek dla tej funkcjonalności jest bardzo dobre wyłączając przeglądarkę Internet Explorer oraz system Android poniżej wersji 4.4. Jeśli zależy nam na wsparciu brakujących systemów to wystarczy do aplikacji dołączyć odpowiednią bibliotekę dodającą brakującą funkcjonalność, np. to rozwiązanie.

Section 3Implementacja SSE po stronie przeglądarki

Aby zacząć korzystać z SSE po stronie przeglądarki wystarczy, że zastosujemy następujący zapis:

Example 1JavaScript
1
var sse = new EventSource('sse-server.php');

Oczywiście zamiast wpisywać adres sse-server.php możemy wpisać dowolny docleowy adres URL. Jeśli korzystamy ze źródeł danych znajdujących się pod inną domeną, to musimy zastosować odpowiednią politykę CORS na serwerze. W przypadku, gdy backend naszej aplikacji jest napisany w PHP, to wystarczy dodać następujące nagłówki:

Example 2PHP
1
2
3
4
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: X-Requested-With');

Dzięki nim będziemy w stanie się połączyć z serwerem z dowolnej przeglądarki, z aplikacji znajdującej się pod innym adresem internetowym niż serwer z danymi.

Wracając do naszego kodu JavaScript, to aby obsłużyć zdarzenia z serwera, musimy zacząć nasłuchiwać je w następujący sposób:

Example 3JavaScript
1
2
3
var messageCallback = function (event) { console.log('sse:message', event.data); };
 
sse.addEventListener('message', messageCallback, false);

I to jest wszystko co właściwie trzeba zrobić po stronie strony internetowej lub aplikacji internetowej aby móc korzystać z SSE. W przypadku, gdybyśmy chcieli nasłuchiwać innych eventów wysyłanych przez serwer (niestandardowych eventów), to wystarczy dodać następujący kawałek kodu:

Example 4JavaScript
1
2
3
var postUpdatedCallback = function (event) { console.log('post:updated', event);
 
sse.addEventListener('postupdated', postUpdatedCallback, false);

Section 4Implementacja mechanizmu SSE po stronie serwera z wykorzystaniem PHP

Teraz pora na kawałek kodu, który na tym blogu od dawna nie pojawiał. Aplikacja na serwerze została napisana w prostym PHP, dzięki czemu można szybko sprawdzić działanie SSE w przeglądarce:

Example 5PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: X-Requested-With');
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
 
$counter = rand(1, 10);
$events = ['update', 'delete', 'create'];
$ids = [1, 12, 34, 65, 234, 18, 5];
 
while (1) {
    $counter--;
 
    if (!$counter) {
        echo 'event: ' . $events[array_rand($events)] . "\n";
        echo 'data: {"id": "' . $ids[array_rand($ids)] . '"}' . "\n\n";
 
        $counter = rand(1, 10);
    } else {
        echo 'event: ping' . "\n";
        echo 'data: {"time": "' . date(DATE_ISO8601) . '"}' . "\n\n";
    }
 
    ob_flush();
    flush();
    sleep(1);
}

Powyższy kod ma na celu losowe generowanie eventów z tablicy 3-ch elementów: update, create, delete z losowo dobranymi ID jakiejkolwiek treści (umówmy się, że będą to ID artykułów w bazie danych). Powyższy kod domyślnie wysyła event ping z danymi dotyczącymi czasu. W momencie gdy upłynie określony czas - zmienna $counter, to wysyła jeden z eventów o których wspomniałem wcześniej. Proste i łatwe do testowania.

Oczywiście, powyższe rozwiązanie ma na celu pokazanie jak SSE działa po stronie serwera. Kodu w postaci jak wyżej raczej nie będziesz pisać, aby obsłużyć swoją aplikację webową. Ze swojej strony mogę podpowiedzieć, że do nasłuchiwania zmiany stanu treści (artykułów, postów na blogu, stron) warto wykorzystać mechanizmy kolejek, np. RabbitMQ.

Section 5Postać danych przesyłanych z serwera za pomocą SSE

Jak wspomniałem wcześniej, dane są przesyłane w postaci tekstu i mogą przyjąć następujący format:

Example 6PHP
id: 1234\n
event: update\n
retry: 18000\n
data: Jakieś dane\n
data: {"title":"Tytuł artykułu","author":"Piotr Nalepa"}\n\n

Wyjaśnię, co oznaczają kolejne przedrostki:

id
ID eventu, który może być wykorzystany przy wznawianiu połączenia.
event
Nazwa eventu który jest wysyłany przez serwer.
retry
Odpowiedzialny za ustawienie opóźnienia wznawiania połączenia. Domyślnie jego wartość wynosi 3 sekundy. Stosujemy zapis w milisekundach.
data
Dane jakie chcemy przesłać do przeglądarki. Należy pamiętać aby dane przesyłać w postaci tekstu, czyli obiekty, tablice, klasy. Przykład sformatowanego JSONa został przedstawiony powyżej.

Section 6Podsumowanie

Mam nadzieję, że udało mi się w sposób wyczerpujący wyjaśnić czym jest SSE i jak z tego korzystać. Dzięki SSE można stworzyć wiele ciekawych funkcjonalności na stronach internetowych czy też wewnątrz aplikacji internetowych. Dzięki SSE można w łatwy sposób stworzyć aplikacje powiadamiające o jakichkolwiek zdarzeniach, np. o nowych wpisach na Twitterze czy też przesłać informacje o zmianie wyniku meczu piłkarskiego. Generalnie, to w jaki sposób można wykorzystać zaprezentowaną funkcjonalność zależy od pomysłu jaki ma dana osoba, np. Ty.

O budowaniu aplikacji javascriptowych z wykorzystaniem SSE, systemu CMS eZ Platform i tworzeniu natywnych aplikacji Windows wykorzystujących JavaScript, SSE i eZ Platform mówiłem na konferencji InfoShare 2015 w Gdańsku.

Share
Feedback

About the author

Staff Software Engineer

I've been writing this blog since 2009 and building for the web professionally for 14 years. Today I lead the frontend of the Cosmos platform at Bishop Fox, where I've been since day one of the project. Before that I worked at OLX, KUDO, Auto1 and eZ Systems. Here I write about what I've learned in practice: JavaScript, TypeScript, React, CSS and frontend architecture. I've also spoken at conferences across Poland and Europe. Got a question or an idea for a post? Let me know!