chevron-left chevron-right

Jak zacząć korzystać z serwera WebSockets w rozwoju swojej aplikacji?

W nowoczesnych aplikacjach i na interaktywnych stronach internetowych niebagatelną rolę odgrywa szybka, nieprzerwana komunikacja między przeglądarką a serwerem. Do tej pory najczęściej korzystanym sposobem komunikacji było wysyłanie zapytań za pomocą AJAX-a i reagowanie na odpowiedź z serwera.

Kiedyś już pisałem na temat komunikacji pomiędzy przeglądarką a serwerem poruszając zagadnienie Server Sent Events. W tym artykule poruszę zagadnienia związane z implementacją WebSockets.

Co to jest WebSockets?

WebSockets jest to protokół komunikacyjny między przeglądarką (lub innym klientem webowym) a serwerem, który pozwala na dwustronną komunikację podczas jednego połączenia. Co to oznacza? To oznacza tyle, że w momencie gdy przeglądarka zainicjuje połączenie serwerem, a następnie otrzyma dane z serwera to połączenie nie jest przerywane. Jest utrzymywane do momentu gdy zamkniemy kartę przeglądarki bądź do momentu w którym programista, za pomocą kodu JS, sam zamknie połączenie.

Diagram połączenia przeglądarki z serwerem za pomocą protokołu WebSockets

Taki, dwustronny sposób komunikacji na jednym połączeniu jest możliwy dzięki obsłudze eventów udostępnianych przez API WebSockets.

Inne sposoby komunikacji przegląrka-serwer

Oprócz WebSockets istnieją inne sposoby komunikacji pomiędzy przeglądarką a serwerem. Takimi sposobami komunikacji są między innymi:

  • AJAX - czyli wysyłanie kolejnych zapytań do serwera w celu uzyskania odświeżonego zestawu danych,
  • Server Sent Events - czyli ustanowienie jednostronnego połączenia komunikacyjnego pomiędzy przeglądarką a serwerem, gdzie serwer wysyła eventy do przeglądarki, ale przeglądarka nie może na nie odpowiedzieć używając tego samego połączenia.

Czy technologia WebSockets ma jakieś wady?

Jak każda technologia, WebSockets ma rzeczy które można uznać za wadę. Do nich możemy zaliczyć:

  • Nie można używać CDN-ów aby zapewnić komunikację za pomocą WebSockets,
  • Danych z WebSockets nie można cache'ować ani przepuszczać przez różnego rodzaju proxy,
  • Komunikacja za pomocą WebSockets musi być prowadzona przez HTTPS,
  • Zwiększa obciążenie serwera ze względu na fakt, że trzeba utrzymać połączenie z każdym połączonym klientem/przeglądarką.

Tak naprawdę, te wady stają się rzeczywistymi wadami w momencie gdy zaczynamy korzystać z WebSockets w projektach do których ta technologia nie została stworzona.

WebSockets najlepiej się sprawdza wszędzie tam, gdzie potrzebna jest dwukierunkowa komunikacja między przeglądarką a serwerem, które wymagają aktualizacji swojego stanu na żywo. Do przykładowych aplikacji, gdzie WebSockets może znaleźć swoje zastosowanie, możemy zaliczyć:

  • Czaty na żywo,
  • Streamowanie mediów,
  • Gry multiplayer,
  • Interaktywne systemy powiadomień (do zwykłych powiadomień na żywo z serwera wystarczy technologia Server Sent Events lub zwykłe odpytywanie AJAX'em).

Przykładowy kod serwera WebSockets

Zanim zaczniemy pisać kod serwera WebSockets musimy się upewnić że mamy zainstalowaną bibliotekę do obsługi tej technologii. Można ją zainstalować następująco:

npm install websocket

lub

yarn add websocket

Gdy mamy tą paczkę dostępną możemy przystąpić do pisania kodu serwera WebSockets. Kod który uruchamia serwer wygląda następująco:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// server.js
// ładujemy potrzebne zależności
// serwer HTTP
const http = require('http');
// serwer WebSockets
const WebSocketServer = require('websocket').server;
// biblioteka do czytania plików z serwera
const fs = require('fs');
// biblioteka do obsługi ścieżek do plików na serwerze
const path = require('path');
const readFile = (fileName) => fs.readFileSync(
    path.resolve(path.join(__dirname, '.', 'data', fileName))
);
const INTERVAL_BASE = 1000;
const SOCKET_PORT = 8181;
const PROCESS_PORT = process.env.PORT || SOCKET_PORT;
const PROCESS_HOST = process.env.HOST || 'localhost';
const OTHER_ROUTE = /^\/other\/?$/;
const OTHER_DATA = readFile('other.json');
const OTHER = JSON.parse(OTHER_DATA);
const MESSAGES_DATA = readFile('messages.json');
const MESSAGES = JSON.parse(MESSAGES_DATA);
 
/**
    Tworzymy serwer HTTP, który będzie obsługiwał zwykłe zapytania AJAX
    i zwracał inne dane niż wiadomości na żywo.
*/
const httpServer = http.createServer((request, response) => {
    if (OTHER_ROUTE.test(request.url)) {
        response.writeHead(200, {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
            'Content-Type': 'application/json',
            'Content-Length': OTHER_DATA.length,
        });
 
        response.write(OTHER_DATA);
        response.end();
 
        return;
    }
 
    response.writeHead(404);
    response.end();
});
let INTERVAL;
 
// informujemy serwer HTTP aby nasłuchiwał na konkretnych portach
httpServer.listen(PROCESS_PORT, PROCESS_HOST, () => {
    const { address, port } = httpServer.address();
 
    console.log(`Server listening on http://${address}:${port}`);
});
 
// inicjalizujemy serwer WebSockets, który będzie zawierał w sobie serwer HTTP
const webSocketServer = new WebSocketServer({
    httpServer,
    // wskazujemy ścieżkę na której ma nasłuchiwać połączeń WebSockets
    path: '/messages',
    // wskazujemy port do obsługi WebSockets
    port: SOCKET_PORT,
});
 
// nasłuchujemy prób połączenia z serwerem WebSockets
webSocketServer.on('request', (request) => {
    const connection = request.accept('echo-protocol', request.origin);
    const queue = [...MESSAGES];
    const time = (Math.random() * INTERVAL_BASE) | 250;
 
    /**
        Konfigurujemy interwał który będzie nam wysyłał
        wiadomości do aplikacji otworzonej w przeglądarce.
        Jest to przykładowe wykorzystanie
    */
    INTERVAL = setInterval(() => connection.send(JSON.stringify(queue.shift())), time);
 
    connection.on('message', () => console.log('Obsłuż dane wysłane z aplikacji'));
 
    /**
        w momencie gdy nastąpi zdarzenie `close` to czyścimy interwał
        i przestajemy wysyłać dane do przeglądarki.
    */
    connection.on('close', () => clearInterval(INTERVAL));
});

Powyższy kod umieściłem w katalogu głównym projektu pod nazwą server.js. Aby uruchomić serwer i móc z niego korzystać podczas tworzenia własnych rozwiązań internetowych należy uruchomić komendę w terminalu:

node server.js

Wtedy serwer HTTP będzie działał pod adresem: http://127.0.0.1:8181 a serwer WebSockets zostanie uruchomiony pod adresem: ws://127.0.0.1:8181/messages.

To co jest ważne, to protokół jaki należy podać przy adresie zasobu. W przypadku standardowego serwera HTTP dodajemy przedrostek: http:// a w przypadku dostępu do serwera WebSockets należy użyć przedrostka: ws://.

Przykładowe użycie klienta WebSockets w przeglądarce

Kod który jest potrzebny do obsługi WebSockets w przeglądarce jest zdecydowanie prostszy i tak naprawdę ogranicza się do następującego kodu:

1
2
3
4
5
// scripts.js
 
const client = new WebSocket('ws://127.0.0.1:8181/messages', 'echo-protocol');
 
client.onmessage = (message) => console.log('Wiadomość z serwera', message);

Tylko tyle wystarczy aby być w stanie uruchomić i móc korzystać z dobrodziejstw serwera WebSockets.

Gdybyśmy dodatkowo chcieli mieć możliwość zakończenia połączenia z serwerem to wystarczy odpalić następujący kawałek kodu:

1
client.close();

Podsumowanie

Ostatnio przy okazji prac nad jednym z moich projektów miałem sposobność pobawić się w tworzenie serwera WebSockets. Potrzebowałem go aby zasymulować pobieranie danych na żywo w celu zaktualizowania tabeli ligowej rozgrywek o mistrzostwo Anglii.

Uznałem, że jest to na tyle ciekawe zagadnienie iż warto się tym podzielić z Tobą. Dzięki temu możesz mieć tzw. gotowca do wykorzystania w swoim projekcie, jeśli chcesz mieć możliwość do aktualizowania stanu aplikacji na żywo.