LinkedIn YouTube Facebook
Szukaj

Wstecz
SoM / SBC

Serwer WWW z elementami grafiki 3D – praktyczne wykorzystanie pakietów Node.js oraz Three.js w systemach wbudowanych (część 1)

Serwer WWW z podziałem na funkcje front-end/back-end

Bezpośrednie umieszczenie „kodu strony” – w postaci napisu „Hello World!” – w funkcji response.end() nie wpływa znacząco na czytelność kodu, jednak nietrudno wyobrazić sobie sytuację, że budowany przez nas serwis zaczyna się rozrastać, a wprowadzane znaczniki HTML znacznie zwiększają objętość kodu strony, powodując że skrypt main.js może stać się mało czytelny i trudny w zarządzaniu. W takiej sytuacji niezbędne jest wprowadzenie jasnego podziału na front-end (czyli właściwą stronę udostępnianą użytkownikowi) oraz back-end (czyli kod realizujący zadania stawiane przed serwerem). Wprowadzenie podziału na front-end i back-end wymaga jedynie kosmetycznych zmian w skrypcie main.js z listingu 1. Zmodyfikowany skrypt przedstawiono na listingu 2.

var http = require ('http');
var fs = require ('fs');

var index = fs.readFileSync (__dirname + '/index.html');

var PORT = 8080;

var server = http.createServer (function handler (request, response) {
  response.writeHead (200, {'Content-Type': 'text/html'});
  response.end (index);
});

server.listen (PORT);

Listing 2. Serwer WWW z podziałem na funkcje fron-end/back-end

Z wykorzystaniem wbudowanego modułu fs (pozwalającego na przeprowadzanie szeregu operacji I/O na plikach) w sposób synchroniczny wczytujemy zawartość pliku index.html, umieszczonego w tym samym folderze co skrypt main.js. Ponieważ wczytany plik jest prostą stroną HTML, zmieniamy zawartość pola Content-Type na text/html. W wywołaniu response.end() przesyłamy użytkownikowi zawartość pliku index.html.

Dla kompletności zadania, utwórzmy również najprostszy plik HTML – index.html – jak przedstawiono to na listingu 3.

<!DOCTYPE html>
<html>

  <head>
  </head>

  <body>
    <h1>Hello World!</h1>
  </body>
</html>

Listing 3. Zawartość pliku index.html

Po zakończonej edycji plików main.js oraz index.html, sprawdźmy poprawność naszego kodu poprzez ponowne uruchomienie serwera:

nodejs main.js
Komunikacja front-end – back-end z wykorzystaniem socket.io

Wprowadzenie wyraźnego podziału na sekcje front-end i back-end stawia przed nami kolejne zadanie do wykonania – zapewnienie sprawnej komunikacji i wymiany danych w „czasie rzeczywistym” pomiędzy tymi modułami. Dlaczego w czasie rzeczywistym? Protokół HTTP jest typowym protokołem typu żądanie-odpowiedź, w którym to rolę żądającego pełni  klient/przeglądarka internetowa. Rozwiązanie to spełnia swoje zadanie w przypadku gdy to klient chce przesłać dane do serwera. Niestety w sytuacji gdy serwer chce poinformować odbiorcę o aktualizacji danych (np. nowych odczytach z czujników temperatury, których wyniki powinny być wyświetlane w czasie rzeczywistym w interfejsie przeglądarkowym), nie może on zainicjować połączenia z klientem. Modyfikacja „w locie” pliku index.html przez kod serwera oraz cykliczne odświeżanie strony przez klienta nie brzmią jak idealne rozwiązanie problemu. W takiej sytuacji pomocną dłoń wyciąga do nas biblioteka socket.io [5] zapewniająca połączenie pomiędzy stroną WWW (front-endem) a skryptem uruchomionym na serwerze (back-endem). Socket.io jest biblioteką języka JavaScript, której zadaniem jest ułatwienie pracy z protokołem WebSocket (który to natomiast jest częścią specyfikacji HTML5, umożliwiającą dwustronną komunikację klient-serwer w czasie rzeczywistym). Biblioteka socket.io składa się z tzw. części serwerowej (będącej modułem dla platformy Node.js) oraz klienckiej (dla przeglądarek  internetowych). Bazując na kodzie skryptu main.js oraz strony index.html z poprzedniego podrozdziału, przejdźmy do praktycznej implementacji.

Rozbudowę skryptu main.js rozpoczynamy od zaimportowania modułu socket.io (szczegóły dotyczące instalacji zewnętrznego pakietu socket.io przedstawiono
w ramce poniżej):

var io = require ('socket.io').listen(server);

Pakiet socket.io nie jest częścią platformy Node.js i wymaga dodatkowej instalacji. Do instalacji dodatkowych pakietów można wykorzystać dystrybuowany wraz z Node.js, manager pakietów npm:

npm install socket.io

W następnym kroku utwórzmy event-handler dla zdarzenia connection (które jest wywoływane każdorazowo, gdy do serwera podłączony zostanie nowy klient), wyświetlający krótki komunikat na standardowym wyjściu:

io.on ('connection', function (socket) {
  console.log ('We have new connection!');
});

W docelowym rozwiązaniu aplikacja serwera będzie przesyłała do przeglądarki użytkownika informacje odczytane z modułu żyroskopu. Sposób w jaki zostanie zrealizowana komunikacja pomiędzy procesem obsługującym żyroskop a serwerem WWW, zostanie omówiony w kolejnym podrozdziale artykułu. Na potrzeby obecnego etapu prac, przygotujmy prostą funkcję send_time(), która z interwałem jednej sekundy, prześle do wszystkich podłączonych klientów aktualny czas:

function send_time() {
  io.emit ('time', {message: new Date().toISOString()});
}
setInterval (send_time, 1000);

W ciele funkcji send_time() wysyłamy rozgłoszeniową wiadomość time z aktualnym czasem serwera, skierowaną do wszystkich aktualnie podłączonych klientów. Pełny kod skryptu main.js został przedstawiony na listingu 4.

var http = require ('http');
var fs = require ('fs');

var index = fs.readFileSync (__dirname + '/index.html');

var PORT = 8080;

var server = http.createServer (function handler (request, response) {
  response.writeHead (200, {'Content-Type': 'text/html'});
  response.end (index);
});

var io = require ('socket.io').listen(server);

io.on ('connection', function (socket) {
  console.log ('We have new connection!');
});

function send_time() {
  io.emit ('time', {message: new Date().toISOString()});
}
setInterval (send_time, 1000);

server.listen (PORT);

Listing 4. Skrypt main.js zintegrowany z biblioteką socket.io

Ostatnim etapem zadania jest integracja biblioteki socket.io z udostępnianą przez serwer stroną index.html. Integrację biblioteki rozpoczniemy od dołączenia w sekcji <head> biblioteki socket.io:

<script src='/socket.io/socket.io.js'></script>

Również w sekcji <head> utwórzmy prosty skrypt realizujący nawiązanie połączenia z serwerem oraz odbiór komunikatów (należy pamiętać, że kod zawarty w tagach <script> </script> zostanie uruchomiony przez przeglądarkę, a więc komputer PC użytkownika):

var socket = io();

socket.on ('time', function (data) {
  /* TODO */
});

Zanim przystąpimy do uzupełnienia kodu event-handler’a dla zdefiniowanego przez nas zdarzenia time, w sekcji <body> strony HTML utwórzmy akapit z identyfikatorem test, w miejscu którego wyświetlone zostaną dane otrzymane z serwera WWW:

<p id="test">JavaScript can change HTML content.</p>

Mając określone pole w którym otrzymane dane będą wyświetlane, możemy uzupełnić implementację event-handler’a dla zdarzenia time:

socket.on ('time', function (data) {
  document.getElementById("test").innerHTML = data.message;
});

Pełna zawartość pliku index.html została przedstawiona na listingu 5.

<!DOCTYPE html>
<html>

  <head>
    <script src='/socket.io/socket.io.js'></script>
    <script>

      var socket = io();

      socket.on ('time', function (data) {
        document.getElementById("test").innerHTML = data.message;
      });

    </script>
  </head>

  <body>
    <h1>Hello World!</h1>
    <p id="test">JavaScript can change HTML content.</p>
  </body>

</html>

Listing 5. Strona index.html zintegrowana z biblioteką socket.io

Przy ponownym uruchomieniu serwera poleceniem:

nodejs main.js

oraz odświeżeniu zawartości strony WWW, powinniśmy uzyskać efekt przedstawiony na rysunku 2.

Rys. 2. Przykład komunikacji między serwer WWW a przeglądarką internetową

Łukasz Skalski - absolwent Politechniki Gdańskiej, miłośnik FLOSS, autor książki "Linux. Podstawy i aplikacje dla systemów embedded" oraz szeregu artykułów dotyczących programowania systemów wbudowanych. Zawodowo związany z firmą Samsung. Wszystkie wolne chwile poświęca projektowaniu i programowaniu urządzeń wyposażonych w mikroprocesory 8-/16- i 32-bitowe. Szczególnym zainteresowaniem obejmuje tematykę systemu Linux w aplikacjach na urządzenia embedded.