Live chat, message handling - Socket.io, PHP, MySQL, Apache

I am starting to do web development. I recently worked on a live chat site entirely based on PHP and JS / jQuery (I don't use any frameworks). Currently my setup is just a simple AJAX poll which is obviously not as good as I would like. My database is a MYSQL database.

I read about websites and my new initial plan was to create a NodeJS server with Socket.io that will handle messages ( How do I integrate nodeJS + Socket.IO and PHP? ) And I thought about keeping those messages in a MySQL database ( MySQL with Node.js ).

Here's what I currently have (not much, I would like to be clear on how to advance before I actually make progress). This is my test setup, the HTML used in live chat is slightly different.

Node.js Server:

// NODE
var socket = require( 'socket.io' );
var express = require( 'express' );
var https = require( 'https' );
var http = require( 'http'); //Old
var fs = require( 'fs' );

var app = express();

//Working HTTPS server 
var server = https.createServer({ 
               key: fs.readFileSync('/etc/letsencrypt/live/%site%/privkey.pem'),
               cert: fs.readFileSync('/etc/letsencrypt/live/%site%/fullchain.pem')
             },app);

// var server = https.createServer( app ); Won't work cause no cert. 

var io = socket.listen( server );
console.log("Server Started"); 
io.sockets.on( 'connection', function( client ) {
    console.log( "New client !" );

    client.on( 'message', function( data ) {
        console.log( 'Message received ' + data); //Logs recieved data
        io.sockets.emit( 'message', data); //Emits recieved data to client.
    });
});
server.listen(8080, function() {
    console.log('Listening');
});

      

JS Client script:

var socket = io.connect('https://%site%:8080');



document.getElementById("sbmt").onclick = function () {

socket.emit('message', "My Name is: " + document.getElementById('nameInput').value + " i say: " + document.getElementById('messageInput').value); 

};

socket.on( 'message', function( data ) {
    alert(data); 
    });

      

My super simple test HTML:

<form id="messageForm">
<input type="text" id="nameInput"></input>
<input type="text" id="messageInput"></input>
<button type="button" id="sbmt">Submits</button>
</form>

      

PHP needs a little explanation. The moment someone connects to my site, I run session_start()

. This is because I want to have something like anonymous sessions. I distinguish between logged in and anonymous users using variables $_SESSION

. The user anon will be $_SESSION['anon']

set to true and also not set $_SESSION['username']

. The recorded user will obviously be inverted.

When it comes to chat, it is available to both registered users and anonymous users. When a user is anonymous, a random username is generated from the database or random names. When a user logs in, their own username is selected. Right now my system with Ajax polling works like this:

The user enters a message (in the current chat solution, not testing the HTML posted above) and hits enter and the AJAX call is made to the following function:

  function sendMessage($msg, $col) {
    GLOBAL $db;
      $un = "";


    if (!isset($_SESSION['username'])) {

        $un = self::generateRandomUsername();

    } else {
    $un = $_SESSION['username'];
    }

    try {
      $stmt = $db->prepare('INSERT INTO chat (id, username, timestamp, message, color) VALUES (null, :un, NOW(), :msg, :col)');
      $stmt->bindParam(':un', $un, PDO::PARAM_STR);
      $stmt->bindValue(':msg', strip_tags(stripslashes($msg)), PDO::PARAM_STR); //Stripslashes cuz it saved \\\ to the DB before quotes, strip_tags to prevent malicious scripts. TODO: Whitelist some tags. 
      $stmt->bindParam(':col', $col, PDO::PARAM_STR);
        } catch (Exception $e) {
            var_dump($e->getMessage());
    }
      $stmt->execute();
  }

      

(Please don't hate my bad code and crappy exception handling, this is not an official project). This function enters a custom message into the database.

In order to receive new posts, I am using setTimeout()

JS function to check AJAX every 1s after new posts. I am storing the id of the last post that is displayed in JS and it sends that id as a parameter to this PHP function (and it runs every 1s):

  /* Recieve new messages, ran every 1s by Ajax call */
  function recieveMessage($msgid) {
    //msgid is latest msg id in this case
    GLOBAL $db;
    $stmt = $db->prepare('SELECT * FROM chat WHERE id > :id');
    $stmt->bindParam(':id', $msgid, PDO::PARAM_INT);
    $stmt->execute(); 
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    return json_encode($result);

  }

      

The question is, how do I implement something like this, but with my previously mentioned Node.js and websockets server setup? I need to differentiate between registered and anonymous users somehow. My first idea was to just run an ajax call from the Node.js server to PHP and pass the post data, and PHP will inject it into the DB just like it does now. But the problem in this case is how to send the message to clients again? Usernames are applied when a message is entered into the database, which means I will need to call AJAX to save to the database and then call another AJAX to retrieve the new input message and pass it to clients, or create a function that inserts and retrieves and returns the retrieved message. However, will there be problemswhen 2 messages are entered at the same time?

How can one access PHP session variables in Node.js? Then I could rewrite all DB queries to run on a Node.js server instead of PHP.

I apologize again if my code or explanation is messy.

+3


source to share


1 answer


SO, for anyone wondering and finding this thread in the future: I DON'T FIND AN ANSWER WITH A SOLUTION THAT I WANT TO USE, VERY I AM ACCEPTING WITH SOMETHING SOMETHING, AND THE DESCRIPTION IS HERE:

Instead of making the Node.js server send an AJAX request, I left it as before, a jQuery $ .post () request from the client to a PHP function.

What I did next was to implement a MySQL listener that checked the MySQL binary for changes. I have used mysql-events

. It extracts the newly added row with all the data and then uses the socket.io emit function to send it to the connected clients. I also had to give up SSL because it seems to hate me. This is a small hobby project, so I don't have to worry too much about SSL.

A better solution would obviously be to program the entire web server in Node.js and just uninstall Apache entirely. Node.js is great for real-time applications and it is a very simple language to learn and use.

My Node.js + Socket.io + mysql-events setup : (ignore unused)

// NODE
var socket = require( 'socket.io' );
var express = require( 'express' );
var https = require( 'https' );
var http = require( 'http');
var fs = require( 'fs' );
var request = require( 'request' );
var qs = require( 'qs' );
var MySQLEvents = require('mysql-events');

var app = express();


/*Correct way of supplying certificates.
var server = https.createServer({
               key: fs.readFileSync('/etc/letsencrypt/live/x/privkey.pem'),
               cert: fs.readFileSync('/etc/letsencrypt/live/x/cert.pem'),
               ca: fs.readFileSync('/etc/letsencrypt/live/x/chain.pem')
       },app); */

var server = http.createServer( app ); // Won't work without cert.

var io = socket.listen( server );
console.log("Server Started");

//DB credentials
var dsn = {
  host:     'x',
  user:     'x',
  password: 'x',
};
var mysqlEventWatcher = MySQLEvents(dsn);

//Watcher magic, waits for mysql events.
var watcher = mysqlEventWatcher.add(
  'newage_db.chat',
  function (oldRow, newRow, event) {

     //row inserted
    if (oldRow === null) {
      //insert code goes here
      var res = JSON.stringify(newRow.fields); //Gets only the newly inserted row data
    res.charset = 'utf-8'; //Not sure if needed but i had some charset trouble so i'm leaving this. 
      console.log("Row has updated " + res);
      io.sockets.emit('message', "[" + res + "]"); //Emits to all clients. Square brackets because it not a complete JSON array w/o them, and that what i need. 
    }

     //row deleted
    if (newRow === null) {
      //delete code goes here
    }

     //row updated
    if (oldRow !== null && newRow !== null) {
      //update code goes here
    }

    //detailed event information
    //console.log(event)
  });

io.sockets.on( 'connection', function( client ) {
    console.log( "New client !" );



    client.on( 'message', function( data ) {
        //PHP Handles DB insertion with POST requests as it used to.
    });
});
server.listen(8080, function() {
    console.log('Listening');
});

      

JavaScript client SEND MESSAGE:



$('#txtArea').keypress(function (e) {

  if (e.which == 13 && ! e.shiftKey) {

      var emptyValue = $('#txtArea').val();
      if (!emptyValue.replace(/\s/g, '').length) { /*Do nothing, only spaces*/ }
      else {
            $.post("/shana/?p=execPOST", $("#msgTextarea").serialize(), function(data) {

            });


  }

  $('#txtArea').val('');
  e.preventDefault();
}


});

      

Cliend JavaScript RECIEVE MESSAGE:

socket.on( 'message', function( data ) {
          var obj = JSON.parse(data);

          obj.forEach(function(ob) {
          //Execute appends

          var timestamp = ob.timestamp.replace('T', ' ').replace('.000Z', '');
          $('#messages').append("<div class='msgdiv'><span class='spn1'>"+ob.username+"</span><span class='spn2'style='float: right;'>"+timestamp+"</span><div class='txtmsg'>"+ob.message+"</div>");
          $('#messages').append("<div class='dashed-line'>- - - - - - - - - - - - - - - - - - - - - - - - - - -</div>"); //ADD SCROLL TO BOTTOM
          $("#messages").animate({ scrollTop: $('#messages').prop("scrollHeight")}, 1000);
        });
    });

      

Somehow the binlog magic destroys the timestamp line, so I had to replace a bit of the line itself to clean it up.

INSERT PHP DB FUNCTION:

  function sendMessage($msg, $col) {
    GLOBAL $db;
      $un = "";


    if (!isset($_SESSION['username'])) {

        $un = self::generateRandomUsername();

    } else {
    $un = $_SESSION['username'];
    }
    try {
      $stmt = $db->prepare('INSERT INTO chat (id, username, timestamp, message, color) VALUES (null, :un, NOW(), :msg, :col)');
      $stmt->bindParam(':un', $un, PDO::PARAM_STR);
      $stmt->bindValue(':msg', strip_tags(stripslashes($msg)), PDO::PARAM_LOB); //Stripslashes cuz it saved \\\ to the DB before quotes, strip_tags to prevent malicious scripts. TODO: Whitelist some tags.
      $stmt->bindParam(':col', $col, PDO::PARAM_STR);
        } catch (Exception $e) {
            var_dump($e->getMessage());
    }
      $stmt->execute();
  }

      

Hope this helps someone at least a little. Feel free to use this code as I probably copied most of this from the internet anyway :) I will check this thread from time to time, so if you have any questions please leave a comment.

+3


source







All Articles