[JS][PHP] Jak zaimplementować powiadomienia typu push w aplikacji JavaScript?
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.
Czym 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
.
Wsparcie 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.
Implementacja SSE po stronie przeglądarki
Aby zacząć korzystać z SSE po stronie przeglądarki wystarczy, że zastosujemy następujący zapis:
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:
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:
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:
1 2 3 | var postUpdatedCallback = function (event) { console.log('post:updated', event); sse.addEventListener('postupdated', postUpdatedCallback, false); |
Implementacja 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:
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.
Postać 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:
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.
Podsumowanie
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.