I am a beginner when it comes to web development. Recently i have been working on a real time chat website based completely on PHP and JS/jQuery (i'm not using any frameworks). Currently, my setup is just simple AJAX polling, which obviously isn't as good as i'd like it to be. My database is a MYSQL database.
I have read about websockets and my new initial plan was to create a NodeJS server with Socket.io which will handle messages (How to integrate nodeJS + Socket.IO and PHP?), and i thought about storing those messages in a MySQL database (MySQL with Node.js).
Here is what i have currently (not much, i'd like to clarify how to progress before i actually do progress). This is my test setup, the HTML used in actual chat is a bit different obviously.
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 requires a bit explanation - At the moment when someone connects to my website i run session_start(). This is because i want to have something like anonymous sessions. I distinguish between logged in and anonymous users through $_SESSION variables. An anon user will have $_SESSION['anon'] set to true, as well as will NOT have $_SESSION['username'] set. Logged in user will obviously have it inverted.
When it comes to the chat - it's available to both logged in users as well as anonymous users. When user is anonymous, a random username is generated from a database or random names. When user is logged in, his own username is chosen. Right now my system with Ajax polling works like this:
User inputs the message (in the current chat solution, not the testing HTML i sent above) and presses enter and an AJAX call is made to 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 any official project). This function inputs users message to the database.
To recieve new messages, i use setTimeout() function of JS, to run an AJAX check every 1s after new messages. I save the ID of last message that is displayed in JS, and send that ID as a parameter to this PHP function (and it's ran 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 to implement something similar, but with my earlier mentioned setup of node.js server and websockets? I need to distinguish between logged in and anonymous users somehow. My first idea was to just run an ajax call from node.js server to PHP and pass message data, and PHP will insert it into DB exactly as it does right now. But the problem in this case is how to send the message out to the clients again? Usernames are applied while the message is being input into database, that means i'd have to call AJAX to save to the DB, and then call another AJAX to extract the newly input message and emit it to the clients, or make a function that inserts and extracts and returns extracted message. However, won't that cause problems when 2 messages are input at the exactly same time?
Is it somehow possible to access PHP session variables in Node.js? Then i could rewrite all DB querying to work in the Node.js server instead of PHP.
I apologize once more if my code or explanation is messy.
SO, for everyone that is wondering and will find this thread in the future: I DID NOT FIND AN ANSWER WITH THE SOLUTION I WANTED TO USE, HOWEVER I CAME UP WITH SOMETHING ELSE, AND HERE IS A DESCRIPTION:
Instead of making Node.js server send the AJAX request, i left it as i had before, the jQuery $.post() request from the client, to a PHP function.
What i did next was to implement a MySQL listener, that checked the MySQL binlog for changes. I used mysql-eventsmodule. It retrieves the newly added row with all data and then uses socket.io emit function to send it to connected clients. I also had to drop SSL because it apparently hates me. It's a small hobby project, so i don't really have to bother that much with SSL.
Best solution would be obviously to program the whole webserver in Node.js and just drop Apache completely. Node.js is awesome for real time applications, and it's a very easy language to learn and use.
My setup of Node.js + Socket.io + mysql-events: (ignore the unused requires)
// 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's not a complete JSON array w/o them, and that's 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');
});
Client JavaScript 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 string, so to clean it up i had to replace a bit of the string itself.
PHP DB INSERT 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();
}
I hope this helps someone at least a bit. Feel free to use this code, as i probably copied most of it from the internet already anyway :) I will be checking this thread from time to time, so if you have any questions leave a comment.
Related
Guys m working on my first live project and i am stuck at a point, where i need help with ajax jquery. i can do this with PHP but i wanna do this with ajax.
Here if user enter a product code ,so i want to compare this product code value into my database and show product name in my other form ,which will open after user input value:
Here in first field i want product name:
Here in my table you can see product code and product name:
ok so here is my html code in last option when user enter product code
Here is jquery i am sending user data to 8transectiondata.php to compare
And this is php file and i want $data['product_name']; to show
Here's a generic answer.
JS FILE:
$(document).ready(function () {
$('#myButtonId').on('click', function () {
var code = $('#myCodeInputId').val();
if (code !== '') { // checking if input is not empty
$.ajax({
url: './my/php/file.php', // php file that communicate with your DB
method: 'GET', // it could be 'POST' too
data: {code: code},
// code that will be used to find your product name
// you can call it in your php file by "$_GET['code']" if you specified GET method
dataType: 'json' // it could be 'text' too in this case
})
.done(function (response) { // on success
$('#myProductNameInput').val(response.product_name);
})
.fail(function (response) { // on error
// Handle error
});
}
});
});
PHP FILE:
// I assumed you use pdo method to communicate with your DB
try {
$dbh = new PDO('mysql:dbname=myDbName;host=myHost;charset=utf8', 'myLogin', 'myPassword');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e) {
exit('ERROR: ' . $e->getMessage());
}
$sql = "SELECT `product_name` FROM `products` WHERE `product_code` = :code";
$result = $dbh->prepare($sql);
$result->bindValue('code', $_GET['code'], PDO::PARAM_INT);
$result->execute();
if($result->rowCount()) { // if you got a row from your DB
$row = $result->fetchObject();
echo json_encode($row, JSON_UNESCAPED_UNICODE); // as we use json method in ajax you've got to output your data this way
// if we use text method in ajax, we simply echo $row
}
else {
// handle no result case
}
I know what you want to do, but without specific code the best I can do is give you a generalized answer.
When a user fills out a field, you want to post that field to the server, look up a product and return some stuff.
The basics are going to look like this.
$(document).ready( function(){
//rolling timeout
var timeout;
$('#field').on('keyup', function(e){
if(timeout) clearTimeout(timeout);
timeout = setTimeout( function(){
var data = {
"field" : $('#field').val()
};
$.post( '{url}', data, function(response){
if(response.debug) console.log(response.debug);
if(response.success){
//open other form
$('{otherFormProductField}').val(response.product);
}
}); //end post
},450); //end timeout
});//end onKeyup
}); //end onReady
Then in PHP, you have to process the request. Pull the field from the $_POST array, look it up in the Database. Then build a response array and send it back to the client as JSON. I like to build responses in a structure something like this.
{
success : "message", //or error : "message"
debug : "",
item : ""
}
Then in PHP I will do this.
ob_start();
..code..
$response['debug'] = ob_get_clean();
header("Content-type:application/json");
echo json_encode($response);
This way, you can still print out debug info (in side the output buffer calls ) when developing it and don't have to worry about it messing up the Json or the header call.
-note- Use a timeout, that you reset on each key press (a rolling timeout). What it does is reset the previous timeout each time the key is released. That way it only sends the request once the user quits typing (instead of sending request on every keypress). I have found 450 milliseconds to be about the perfect value for this. Not too long not too short. Basically once they stop typing for 450ms it will trigger the $.post
I am developing a website which uses a private messaging system using php + socket.io.
From the beginning i passed the sender_id, recipient_id and text to socket.io using socket.emit but later realized that this could be easily tampered with and wanted to use my php sessions in some way to be sure that the sender_id is indeed the sender_id.
I have the following setup right now but i dont really understand how to pass the session from index.php to app.js and then connect to redis-server in app.js to get the PHPSESSID which holds the user_id.
Server 1 running nginx + php-fpm (index.php)
Server 2 running node.js with socket.io (app.js)
Server 3 running redis for session management
My code right now looks like the following but is obviously missing the redis part right now which i would really appriciate some help with.
Thanks!
index.php
<?php
session_start();
if ($_SESSION['user_id'] == false){
header("Location:login.php");die;
}
?>
<script>
var socket = io('https://app01.dev.domain.com:8895');
socket.on('connect', function(){
console.log("Connected to websockets");
});
socket.on('event', function(data){});
socket.on('disconnect', function(){});
$('.chat-message').keypress(function (e) {
if (e.which == 13) {
console.log("send message");
var friend_id = $(this).attr('id');
friend_id = friend_id.split("-");
friend_id = friend_id[3];
var obj = {
recipient_id: friend_id,
text: $(this).val()
};
socket.emit('chat_message', obj);
$(this).val('');
return false;
}
});
</script>
app.js
var https = require("https"), fs = require("fs");
var options = {
key: fs.readFileSync('/etc/letsencrypt/live/domain/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/domain/cert.pem'),
ca: fs.readFileSync('/etc/letsencrypt/live/domain/chain.pem')
};
var app = https.createServer(options);
var io = require("socket.io")(app);
var redis = require("redis");
// This i want to fill with for example PHPSESSION:user_id that i get from redis and later use it as sender
// var all_clients = {};
io.set("transports", ["websocket", "polling"]);
io.on("connection", function(client){
console.log("Client connected");
// Here i would like to connect to redis in some way and get the user_id but dont really understand how
//all_clients[USER_ID_FROM_REDIS] = client.id;
//var user_id = USER_ID_FROM_REDIS;
client.on("chat_message", function(data){
var obj = {
to: data.recipient_id,
text: data.text
};
console.log("Message inbound from socket: "+client.id+" from: "+data.user_id+" to: "+data.recipient_id+" with text: "+data.text);
});
client.on("disconnect", function(){
console.log("Client disconnected ");
//delete all_clients[USER_ID_FROM_REDIS];
});
});
app.listen(8895, function(){
console.log("listening on *:8895");
});
var recursive = function () {
//console.log("Connected clients: "+Object.keys(all_clients).length);
//console.log(JSON.stringify(all_clients));
setTimeout(recursive,2000);
}
recursive();
HTTP in itself does not protect against MITM attacks, to protect against MITM the server certificate needs to be pined.
To protect against a user being spoofed you need authentication such as logging-in or a secret token like Dropbox.
Add certificate pinning, that is just jargon for validating that you are connecting to the correct server and not a MITM by verifying the certificate that is sent by the server. MITM used to be harder but WiFi has made it easy to connect to the wrong end-point at Hot Sports, even at home I have seen this.
My application stack:
On my server runs a Redis server. The PHP backend communicates with Predis library with the Redis server. It will publish messages. These messages will be fetched by my Redis client (node.js) and pushed to the connected websocket clients (with SockJS).
My problem:
It runs well. At least for broadcast messages. Now I came to the point I need to send a unicast message and I'm stuck... How to connect the user on the backend side (sender of messages) with the connected client of the websocket?
Code snippets:
PHP
$redis = new Client();
$redis->publish('updates', Random::getUniqueString());
Redis client on node.js server
redis.subscribe('updates');
redis.on('message', function(channel, data) {
for (var id in sockets) {
if (sockets.hasOwnProperty(id)) {
sockets[id].write(data);
}
}
});
SockJS client
mySocketFactory.setHandler('message', function(event) {
console.log(event.data);
});
Like I said. Working well but the id used for the socket connection is not known by the PHP backend.
Edit: One idea I got in mind is to use cookies.
I found a way to solve my problem. When the socket connection is established I sent a request to my PHP backend and ask for the user id. This is stored on the node.js server. When messages are incoming there is a check if they are for specific user and handle them only for them.
So, what do I store exactly on my node server?
var sockets = {}; // {connection_id: socket_connection}
var connIdToUser = {}; // {connection_id: user_id}
var connIdsForUser = {}; // {user_id: [connection_id_1, connection_id_2 ,...]}
socketServer.on('connection', function(conn) {
sockets[conn.id] = conn;
var options = {
host: os.hostname(),
port: 80,
path: '/user/id',
method: 'GET'
};
var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
var userId = JSON.parse(chunk).id;
connIdToUser[conn.id] = userId;
if (!connIdsForUser.hasOwnProperty(userId)) {
connIdsForUser[userId] = [];
}
connIdsForUser[userId].push(conn.id);
console.log('connection id ' + conn.id + ' related to user id ' + userId);
});
});
req.end();
conn.on('close', function() {
console.log('connection lost ' + conn.id);
// remove connection id from stack for user
var connections = connIdsForUser[connIdToUser[conn.id]];
var index = connections.indexOf(conn.id);
if (index > -1) {
connections.splice(index, 1);
}
// remove connection at all
delete sockets[conn.id];
// remove relation between connection id and user
delete connIdToUser[conn.id];
});
});
The reason for storing the relation between user id an connection id twice is the different use case I need either for sending a message or deleting the connection for the close event. Otherwise I would have to use a nested loop.
As you can see deleting a socket is fairly easy. Although deleting the connection from the connection stack of an user is a little bit complicated.
Let's continue with the sending of a message. Here I defined a structure of the message I get from the Redis server:
{
targets: [], // array of unit ids (can be empty)
data: <mixed> // the real data
}
Sending the data to the sockets looks like:
redis.on('message', function(channel, message) {
message = JSON.parse(message);
// unicast/multicast
if (message.targets.length > 0) {
message.targets.forEach(function(userId) {
if (connIdsForUser[userId] !== undefined) {
connIdsForUser[userId].forEach(function(connId) {
sockets[connId].write(message.data);
});
}
});
// broadcast
} else {
for (var id in sockets) {
if (sockets.hasOwnProperty(id)) {
sockets[id].write(message.data);
}
}
}
});
Since I store the connection stack per user it is quite easy to send the data to all sockets related to a specific user. So what I can do now is unicast (array with one user id), multicast (array with more than one user id) and broadcast (empty array).
It's working well for my use case.
I'm writing a webapp which successfully allows me to login to Facebook (I'm using Phonegap and the Phonegap Facebook plugin). I then want to store the logged in users name and ID. To start with as a simple test I wanted to get the following controller to run collect the ID, display it in the xcode console to confirm it was there and then send it to the php code below to then store in a mysql table. I can't seem to get it working and I think it's possibly the format of my data in the {}'s within the $http.post but it's a bit beyond my current knowledge to figure this one out. Any ideas?
function FacebookCtrl($scope) {
FB.api('/me', function(response) {
var fbid=response.id;
console.log('Testing, ' + fbid + '.');
$http.post('http://somedomain.co.uk/php/users.php', {uid: fbid})
console.log('Complete');
});
}
The php code at the receiving end is:
<?php
$data = file_get_contents("php://input");
$objData = json_decode($data);
$uid = $objData->uid;
try {
include 'database.php';
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $conn->prepare('INSERT INTO Userdata (oauth_uid) VALUES (:userid)');
$stmt->execute(array(
':userid' => $uid,
));
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
?>
The same php code works with another one of my controllers however the difference is that the other controller captures the data passed from a form so the http.post looks like this:
$http.post('http://somedomain.co.uk/php/submitdata.php', {Position1: $scope.position1}
And the code in the php that captures this data is:
$Position1 = $objData->Position1->Name;
As the code all works on another controller, I'm assuming that the issue is with how I'm formatting the data I'm passing between the {}'s?
Try to define success \ error callbacks
$http.post("http://somedomain.co.uk/php/users.php", {uid: fbid})
.success(function(data, status, headers, config) {
$scope.data = data;
}).error(function(data, status, headers, config) {
$scope.status = status;
});
What will it say then?
i used multi room chat application example for node.js writed by mike in this article.and changed it to use session data which grabed from php session handler until now
this is the part of code which i wrote until now
var express = require('express'),
app = express(),
memcache = require("memcache"),
http = require('http'),
server = http.createServer(app),
io = require('socket.io').listen(server),
co = require("./cookie.js"),
php = require('phpjs'),
codein = require("node-codein");
//check if user loggedin
// routing
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
var cookieManager = new co.cookie(req.headers.cookie);
var client = new memcache.Client(11211, "localhost");
client.connect();
user = client.get("sessions/"+cookieManager.get("sec_session_id"), function(error, result){
var session = JSON.parse(result);
user = JSON.parse(session.name);
user = user.username;
storeUsername(user);
});
});
function storeUsername(user){
// usernames which are currently connected to the chat
var usernames = {};
io.of('/private').authorization(function (handshakeData, callback) {
console.dir(handshakeData);
handshakeData.foo = 'baz';
callback(null, true);
}).io.sockets.on('connection', function (socket) {
usernames[socket.id] = socket;
// when the client emits 'sendchat', this listens and executes
socket.on('sendchat', function (data) {
// we tell the client to execute 'updatechat' with 2 parameters
io.sockets.emit('updatechat', socket.username, data);
});
// when the client emits 'adduser', this listens and executes
socket.on('adduser', function(username){
// we store the username in the socket session for this client
socket.username = user;
// add the client's username to the global list
// echo to client they've connected
if(php.in_array(socket.username,usernames)){
delete usernames[socket.username];
}else{
usernames[user] = user;
console.log('not exist');
socket.emit('updatechat', 'SERVER', 'you have connected');
// echo globally (all clients) that a person has connected
socket.broadcast.emit('updatechat', 'SERVER', username + ' has connected');
// update the list of users in chat, client-side
io.sockets.emit('updateusers', usernames);
}
});
// when the user disconnects.. perform this
socket.on('disconnect', function(){
// remove the username from global usernames list
delete usernames[socket.username];
// update list of users in chat, client-side
io.sockets.emit('updateusers', usernames);
// echo globally that this client has left
socket.broadcast.emit('updatechat', 'SERVER', socket.username + ' has disconnected');
});
});
}
server.listen(3000);
for example user master will connect to our chatroom and he will have his username which stored from php based application.but where is the problem now?when user master connect from 2 or 3 tab of our browser he will connect to socket server 3 or 4 times and if he post some data we have this result
master : hello
master : hello
master : hello
i want users to connect to my application just once and can post data just once.now how should i achieve that?
how should i access this users in case of private message to each other
i am so new in node.js.my bad.sorry
thanks for help in advance.+1 for all teachers
1) You could you (seems to), var app = require('express').express();
2) On first app.get, you don't need to put 2 times JSON.parse, maybe the second JSON.parse is not what you want (are you trying to retrieve user threw that field ?)
3) MOST IMPORTANT : to make usage of room, you must use socket.join to join a room, if you don't
do it, the socket.broadcast will have no special effect...
To remove a user from a room, use socket.leave