Tutorial node.js i socket.io – system komentarzy „real-time”

udontsay-128

Poziom zaawansowany

Poprzednio pisaliśmy o tym jak pracować z node.js i socket.io. Dziś rozwiniemy temat przykładem praktycznym. Stworzymy prosty, wydajny system komentarzy między użytkownikami, który będzie przetwarzał i odświeżał listę komentarzy wszystkim użytkownikom, którzy mają otwartą naszą aplikację web.

Tutorial node.js i socket.io – real-time comments

Krok po kroku, od podstaw. Oczywiście sama strona i funkcjonalność komentarzy będzie uproszczona – każdy może łatwo dostosować je do własnych potrzeb, począwszy od danych, jakie chce trzymać w bazie danych.

Skupiamy się tutaj na wykorzystaniu node.js i socket.io. Zakładamy że nasz system komentowania podpięty będzie do artykułów.

Krok 1 – start

Stwórzmy folder na nasz projekt, otwórzmy konsolę i przejdźmy do tego folderu. Z poziomu wiersza poleceń instalujemy potrzebne moduły node.js:

$ npm install socket.io

oraz

$ npm install mysql

Nie konieczne gdy mamy już zainstalowane te moduły globalnie.

Krok 2 – schemat bazy MySQL

Stwórzmy bazę danych (np. node_tests) oraz prostą tabelę (np. tbl_comments). Przykładowy kod SQL zaprezentowany poniżej.

CREATE TABLE IF NOT EXISTS `tbl_comments` (
  `comment_id` int(11) NOT NULL AUTO_INCREMENT,
  `article_id` int(11) DEFAULT '0',
  `sender` varchar(100) NOT NULL,
  `body` TEXT NOT NULL,
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`comment_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Krok 3 – konfiguracja

Tak jak w poprzednich artykułach, nasze aplikacje node.js mają swój plik konfiguracyjny. Dostosujmy go do potrzeb projektu:

var config = {};
config.db = {};
config.general = {};

config.db.type = 'mysql';
config.db.charset = 'utf8';
 
config.db.username = 'user';
config.db.password = 'pass';
config.db.host = 'localhost';
config.db.dbname = 'node_tests'; // DB name

config.db.comments_tbl = 'tbl_comments';

config.general.host = '//localhost'; // http://my.host.com etc
config.general.port = 1350;
config.general.debug = false; // debug mode on/off

// export
module.exports = config;

Należy tutaj zwrócić uwagę głownie na konfigurację tabeli DB oraz portu do komunikacji i hosta.

Możemy przejść do implementacji elementów bazujących na node.js i gniazdkach.

Krok 4 – część client-side

Właściwą implementację zaczniemy od prostej strony HTML5, w której umieścimy kod wysyłający dane do skryptu po stronie serwera.

Aby zadziałał on w przeglądarce potrzebujemy dołączyć bibliotekę socket.io.js.

Plik index.php

<?php
// ...
?>
<!DOCTYPE html>
<html>
  <head>
      <title>Socket.IO Tutorial</title>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">

      <script 
        src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
      <script 
        src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.2/socket.io.min.js">
      </script>

      <style>
        ul li {
          list-style-type: none;
          border: solid 1px;
          margin: 4px; 
          padding: 4px;
        }

        input {
          width: 200px;
        }

        textarea {
          width: 400px;
          height: 100px;
        }
      </style>
  </head>
  <body>

  <h2>Welcome to our comment system</h2>

  <p>Write something... </p>

  <ul id="messages">
  </ul>

  <br>
  Your name:
  <p><input type="text" id="nick"/></p>

  Message:
  <p><textarea id="comment"></textarea></p>

  <script>
  // our script will be here...
  </script>
</body>
</html>

Plik ten musimy zaserwować np. poprzez PHP. Zwykle uruchomienie pliku index.html nie zadziałało by.

Wydawnictwo Strefa Kursów

W naszym przypadku strona jest w PHP, są tam zaślepki do pobierania np. ID komentowanego właśnie artykułu, pobieranie historii komentarzy z DB, czy konfiguracji. Można to napisać błyskawicznie używając ulubionego frameworku PHP, pominęliśmy więc tutaj implementacje tych elementów.

Dodaliśmy podstawowe elementy, teraz możemy napisać właściwy skrypt JS:


var socket = io.connect('//localhost:1350', {reconnection: false});

// this value can be generated by e.g. PHP
// var article_id = <?php echo $article_id ?>;
var article_id = 48;

// say hello to the server
socket.emit("new_conn", {
  mydata: {msg: "New connection established!"}
});

// process
$("#send").click(function () {
  var nick = $('#nick').val();
  var comment = $('#comment').val();

  if (nick.length === 0 || comment.length === 0) {
    alert('Fields cannot be empty');

    return false;
  }

  socket.emit("send", {
    c_data: {
      nick: nick,
      comment: comment,
      article_id: article_id
    }
  });
});

// handle data from server
socket.on('add_comment', function (data) {

  // display ONLY for the current article 
  // if (data.comment_data.article_id !== article_id) { 
     // return false;
  // }
  var content = '<li>Comment ID: ' + data.comment_data.comment_id;
  content += '<br> Author: ' + data.comment_data.nick;
  content += '<br> Time: ' + data.comment_data.time;
  content += '<p>' + data.comment_data.comment + '</p></li>';

  $('#messages').append(content);

  // emit more data ...
  // socket.emit("something", { data: 'New comment added' });
});

socket.on('error', function () {
  console.error(arguments);
});

Kod nawiązuje połączenie oraz wysyła „powitanie”. Następnie jest funkcja przetwarzająca wysyłanie komentarza. Jeśli wymagane pola są wypełnione, dane zostają wysłane do serwera:

...
  socket.emit("send", {
    c_data: {
      nick: nick,
      comment: comment,
      article_id: article_id
    }
  });
...

Dalsza część kodu zajmuje się obsługą zdarzenia „add_comment”, które następuje po wysłaniu danych przez serwer; zostają one wyświetlone wszystkim podłączonym użytkownikom.

W realnej aplikacji oczywiście zaprogramujemy także filtrowanie takie jak:

if (data.comment_data.article_id !== article_id) { return false; }

które zapobiega wyświetlaniu informacji o komentarzu pod artykułami innymi niż ten, którego komentarz dotyczy(!).

Pamiętajmy także o historii komentarzy. Po odświeżeniu okna komentarze znikną z widoku. Są one oczywiście zapisane w bazie danych (tbl_comments), musimy jedynie pobrać je i wygenerować (jako elementy listy „messages”) z pomocą np. naszego frameworku PHP.

Do już pokazanych komentarzy, nowe, przychodzące z node.js komentarze będą „doklejane” do listy.

Krok 5 – część server-side


var http = require('http'),
        fs = require('fs'),
        index = "<html><body>Listening</body></html>";

// setup
var config = require('./config.js');
var tools = require('./tools.js');

var PORT = config.general.port;
var HOST = config.general.host;

// DB connection
var mysql = require('mysql');

var db_access = {
    host: config.db.host,
    user: config.db.username,
    password: config.db.password,
    database: config.db.dbname
};

var tbl_comments = config.db.comments_tbl;

// send html content to all requests
var app = http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end(index);
});

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

io.sockets.on('connection', function (socket) {
    socket.nick = '';
    socket.comment = '';
    socket.article_id = 0;

    socket.on('send', function (data) {

        data.c_data.comment = tools.sanitize(data.c_data.comment);

        socket.nick = data.c_data.nick;
        socket.comment = data.c_data.comment;
        socket.article_id = data.c_data.article_id;

        if (config.general.debug) {
            console.log('New comment to article: ' + socket.article_id);
        }

        // insert data to DB and emit back to all connected sockets
        insertComment(socket, data.c_data);
    });

    socket.on('disconnect', function () {
        console.log('User disconnected');
    });

});

// todo: insertComment() 

app.listen(PORT);

console.log('Server running at ' + HOST + ':' + PORT + '/');

Początek części serwerowej to nic nowego – wczytanie modułów, konfiguracji. Od linijki:

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

zaczyna się magia socket.io. W io.sockets.on(‚connection’) obsługujemy dane przychodzące z podłączonych gniazdek (użytkowników).

Wewnątrz tego kodu obsługujemy zdarzenie socket.on(‚send’) – komentarz przychodzący z przeglądarki użytkownika. W tym miejscu pobieramy / przetwarzamy dane.

Dodatkowo czyścimy je z elementów potencjalnie niebezpiecznych na stronie:

data.c_data.comment = tools.sanitize(data.c_data.comment);

Następnie wywołujemy funkcję insertComment(), która zapisuje dane w bazie. Parametrami są: aktualne gniazdko z potrzebnymi danymi, oraz nasza przetworzona tablica, aby łatwo odesłać ją do kodu po stronie klienta.

Implementacja funkcji insertComment():


function insertComment(socket, c_data) {
    
    var connection = mysql.createConnection(db_access);
    connection.connect();

    var clean_comment = tools.addslashes(socket.comment);

    var q = "INSERT INTO " + tbl_comments + " (article_id, sender, body, created_at) ";
    q += "VALUES (" + socket.article_id + ", '" + socket.nick + "', '" + clean_comment + "', NOW() )";
    // console.log(q);
    
    connection.query(q, function (qe, qr) {
        if (qe && config.general.debug) { console.log(qe); }

        // add to array the last insert ID (new comment ID)
        c_data.comment_id = qr.insertId;
        c_data.time = tools.getNow();

        // io.sockets = emit to all
        io.sockets.emit("add_comment", {
            comment_data: c_data
        });

        connection.end();
    });
}

Wykonujemy zapytanie i jeśli się ono powiodło, pobieramy ID wstawionego komentarza (insertId) oraz obecną datę i czas.

Dodajemy te dane do odsyłanej tablicy:

c_data.comment_id = qr.insertId;
c_data.time = tools.getNow();

po czym wysyłamy kompletne dane do podłączonych gniazdek:

// io.sockets = emit to all
io.sockets.emit("add_comment", {
  comment_data: c_data
});

Plik tools.js z funkcjami pomocniczymi znajduje się w źródłach przykładu.

Możemy teraz uruchomić to co stworzyliśmy!


Server:

– przechodzimy w konsoli do katalogu z naszym kodem i uruchamiamy:

$ node comment.js

Client:

– uruchamiamy index.php w przeglądarce:

http://localhost/tests/node/index.php

Nie ważne ile uruchomimy okien z tym adresem i w ilu przeglądarkach. Komentarz wysłany w jednym pojawi się od razu we wszystkich innych!

comment

To jest piękno socket.io.

Przykład dostępny na GitHub:

https://github.com/dominik-w/js_html5_com/tree/master/node-socket-io-rt-comments

Podsumowanie

Mamy tu więc napisany i omówiony w ramach naszego tutorialu node.js i socket.io, prosty system komentarzy real-time. Dzięki node i socket.io dość niedużym nakładem uzyskaliśmy świetnie działający efekt, który łatwo rozbudowywać i dostosowywać do naszych potrzeb.

Enjoy.js, have_fun.io

Programista WWW i aplikacji mobilnych z wieloletnim doświadczeniem. Bloger 🙂 Anty-lewak 🙂 Pasjonat programowania, nowych technologii, a także sportu i motoryzacji.

Twitter LinkedIn Google+ Skype Xing 

Podaj dalej: Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0Share on Tumblr0Digg thisEmail this to someonePin on Pinterest0
Możesz skomentować leave a response, lub podać trackback z własnej strony.