Subscribe to dynamic laravel channel via redis - php

I want to broadcast an event on dynamic channels in larvel.
To broadcast on a specific channel I tried this tutorial and all things worked fine.
This is my event:
class ChatNewMessage implements ShouldBroadcast
{
use InteractsWithSockets, SerializesModels;
public $targetUsers;
public $message;
public function __construct ($message)
{
$this->targetUsers = $message->chat->users->pluck('user_id');
/*This is an array of user that are subscribed to a chat*/
$this->message = $message;
}
public function broadcastOn ()
{
$userChannels = [];
foreach ($this->targetUsers as $id) {
$userChannels[] = 'user-channel.' . $id;
}
return $userChannels;
}
}
And this is js code to get events that occurred for a user by user_id=5 :
var socketURL = 'http://127.0.0.1:3000';
var socket = io(socketURL);
socket.on('user-channel.5:App\\Events\\ChatNewMessage', function (event) {
alert(event.data);
});
And this is js code that runs node :
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
// redis.subscribe('user-channel.5', function (err, count) {});
redis.psubscribe('*', function(err, count) {});
redis.on('message', function (channel, message) {
console.log('Message Received: ' + message);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
http.listen(3000, function () {
console.log('Listening on Port 3000');
});
redis.on("error", function (err) {
console.log(err);
});
As you can see I used redis.psubscribe('*', function(err, count) {}); to subscribe to all channels like user-channel.1 , user-channel.2 , user-channel.3 and so on.
But that does not work.
Of course when I used an static channel name on redis like redis.subscribe('user-channel.5', function (err, count) {}); that all things work.
I do not know what is problem and how can I subscribe to channels that are created dynamically.
I'm using laravel 5.2.

Finally I used below and all things worked fine :
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.psubscribe('*', function(err, count) {});
redis.on('pmessage', function(subscribed, channel, message) {
console.log(channel);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
http.listen(3000, function () {
console.log('Listening on Port 3000');
});
redis.on("error", function (err) {
console.log(err);
});

Related

Laravel socket.io doesn't work, failed socket connect, how to fix?

I am trying to create a live chat. Socket.io and node.js
I can't connect the user to my channel.
https://domen.com:8005/socket.io/?EIO=3&transport=polling&t=NYHmcgH :failed
Here is my server.js
var app = require('express')();
var https = require('https').Server(app);
var io = require('socket.io')(https);
var Redis = require('ioredis');
var redis = new Redis();
var users = [];
https.listen(8005, function () {
console.log('Listening to port 8005');
});
io.on('connection', function (socket) {
socket.on("user_connected", function (user_id) {
console.log("user connected " + user_id);
// users[user_id] = socket.id;
// io.emit('updateUserStatus', users);
// console.log("user connected "+ user_id);
});
and here is my blade template
<script>
$(function () {
let user_id = "{{ auth()->user()->id }}";
console.log(user_id);
let ip_address = 'domen.com';
let socket_port = '8005';
let socket = io(ip_address + ':' + socket_port);
});
socket.on('connect', function () {
socket.emit('user_connected', user_id)
});
</script>
express with https needs some keys
var https = require('https')
var app = express()
https.createServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.cert')
}, app)
.listen(8005, function () {
console.log('Example app listening on port 8005! Go to https://domen.com:3000/')
})
i think you should try first with http, if your code works well then you can upgrade to https
var io = require('socket.io')(http);
http.listen(8005, function () {
console.log('Listening to port 8005');
});
in client side :
let ip_address = 'http://domen.com';

Laravel event broadcasting with socketio and redis is not working in laravel 5.4?

I am trying to show realtime message notification count when user logged in to the website. For this I am using socketio, redis and laravel event. But it is not working. In laravel log I am getting a message. In this message count is showing but socket is null. I think I miss something. My gitbash showing channel subscribed and user is connected.
Gitbash
vagrant#homestead:~/Code/cabinapi$ node socket.js
listening on *:3000
Redis: message-channel subscribed
a user connected
Laravel log
[2017-09-28 07:21:57] local.INFO: Broadcasting [App\Events\MessageEvent] on channels [message-channel] with payload:
{
"count": 9,
"socket": null
}
Controller
public function privateMessageAPICount($id)
{
$count = PrivateMessage::where('receiver_id', new \MongoDB\BSON\ObjectID($id))
->where('read', 0)
->count();
if ($count > 0){
event(new MessageEvent($count));
}
}
routes->web.php
Route::get('/message/count/{id}', 'Cabinowner\DashboardController#privateMessageAPICount');
.env
APP_URL=http://cabinapi.app
BROADCAST_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MessageEvent.php
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class MessageEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $count;
public function __construct($count)
{
$this->count = $count;
}
public function broadcastOn()
{
return ['message-channel'];
}
}
views->owner->layouts->app.blade.php
<body>
<span class="label label-success msgSpan"><span class="msgCountRemove">{!! $miscellaneous->privateMessageCount() !!}</span></span>
<script src="{{ asset('plugins/jQuery/jquery-2.2.3.min.js') }}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.8/socket.io.min.js"></script>
<script>
var socket = io('{{ env("APP_URL") }}:3000');
socket.on('message-channel:App\\Events\\MessageEvent', function(data){
$('.msgCountRemove').remove();
$('.msgSpan').append('<span class="msgCountRemove">'+data.count+'</span>');
});
</script>
</body>
config->app.php
'Redis' => Illuminate\Support\Facades\Redis::class,
socket.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.subscribe('message-channel', function () {
console.log('Redis: message-channel subscribed');
});
redis.on('message', function(channel, message) {
console.log('Redis: Message on ' + channel + ' received!');
console.log(message);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
console.log('user disconnected');
});
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
Not found the exact reason why it is not working in laravel 5.4.
But I have added some changes. Now I am getting realtime message notification count. I have listed below what are the changes I have done.
I fully done realtime notification count with the help of redis and socketio
socket.js (Removed ioredis and included redis)
var redis = require('redis');
io.on('connection', function(socket){
console.log('a user connected');
var redisClient = redis.createClient();
redisClient.subscribe('message');
redisClient.on('message', function(channel, message){
console.log('new message in queue', channel, message);
socket.emit(channel, message);
});
socket.on('disconnect', function(){
redisClient.quit();
console.log('user disconnected');
});
});
Controller
$redis = Redis::connection();
$redis->publish('message', $message);
views->owner->layouts->app.blade.php
<script>
var socket = io('{{ env("APP_URL") }}:3000');
socket.on('message-channel:App\\Events\\MessageEvent', function(data){
$('.msgCountRemove').remove();
$('.msgSpan').append('<span class="msgCountRemove">'+data+'</span>');
});
</script>
Removed MessageEvent.php

How to fetch data from node js with codeigniter's model?

UPDATED.
I'm using codeigniter and i want to fetch data from node js api but i need to be able to get it by codeigniter's models, i also need to be able to pass parameters to node js.
Here is what i've done so far - it's working fine - but i can't figure how to pass parameter to the SQL query - from the model to node js.
If it's not clear up to now - please review the code - i added some explanations there also.
And here is the model (users_model.php)
public function node_get($id)
{
$foo = file_get_contents('http://localhost:3000/'.$id);
return $foo ;
}
Here the node.js app (app.js)
var mysql = require('mysql');
var con = mysql.createConnection({
host: "something",
user: "something",
password: "something",
database: "something"
});
var user_id; <---------------------------How can i pass this in the model?
var user_data;
con.connect(function(err) {
if (err) throw err;
con.query("SELECT * FROM users_data WHERE user_id ="+user_id, function
(err,
result, fields) {
if (err) throw err;
user_data = result;
// console.log(user_data);
});
});
app.get('/', function(req, res){
res.send(JSON.stringify(user_data, null, 3));
});
app.listen(3000);
Got it....
var express = require('express');
var app = express();
var helmet = require('helmet');
var port = process.env.PORT || 3000;
var mysql = require('mysql');
app.use(helmet.hidePoweredBy());
app.use(helmet.xssFilter());
app.use(helmet.frameguard);
app.get('/:user_id', function(req, res){
var user_id = req.params.user_id;
if(user_id == null){
user_id =1;
}
var con = mysql.createConnection({
host: "something",
user: "something",
password: "something",
database: "something"
});
con.connect(function(err) {
if (err) throw err;
con.query("SELECT * FROM users_data WHERE user_id ="+user_id, function (err, result, fields) {
if (err) throw err;
res.send( JSON.stringify(result));
});
});
});
app.listen(port);

How to Sync PHP and NodeJS with Redis and get data in order

I have a example code for make experiments trying to think "how to ""sync"" nodejs and php in a simple chat example.
Here is my NodeJS server:
var redis = require('redis'),
subscriber = redis.createClient(),
publisher = redis.createClient();
//var sckio = require('socket.io').listen(8888);
var http = require('http');
var querystring = require('querystring');
var WebSocketServer = require('ws').Server
var ENCODING = 'utf8';
var tCounter = 0;
/* #################################### */
// Event on "subscribe" to any channel
subscriber.on("subscribe", function (channel, count) {
// Publish to redis server Test Message
publisher.publish("chat", "NODEJS MESSAGE");
});
// Suscrib to redis server
subscriber.on('message', function (channel, json) {
console.log('SUB: ' + channel + ' | ' + json);
console.log('PHP PUSH TO REDIS, AND NODE CAPTURE REDIS PUSH: ' + (getMicrotime(true) - tCounter));
});
subscriber.subscribe('chat'); // Subs to "mysql" channel
/*
var clients = [];
sckio.sockets.on('connection', function (socket) {
clients.push(socket);
publisher.publish("chat", "User connected");
socket.on('message', function (from, msg) {
publisher.publish("chat", msg);
clients.forEach(function (client) {
if (client === socket) return;
client.send(msg);
});
});
socket.on('disconnect', function () {
clients.splice(clients.indexOf(socket), 1);
publisher.publish("chat", "User disconnected");
});
});
*/
var wss = new WebSocketServer({port: 8888, timeout : 500});
var wsClients = [];
wss.on('connection', function(ws) {
ws.AUTH_ID = Math.random();
wsClients.push(ws);
publisher.publish("chat", "User enter");
ws.on('message', function(message) {
wsClients.forEach(function (client) {
client.send(ws.AUTH_ID + ' ' + message);
});
tCounter = getMicrotime(true);
console.log('CALL TO PHP: ' + tCounter);
PostCode('CODE TO PHP FROM NODE', function() {
wsClients.forEach(function (client) {
client.send('PHP SAVE DATA');
});
});
});
ws.on('close', function(message) {
wsClients.splice(wsClients.indexOf(ws), 1);
publisher.publish("chat", "User left");
});
ws.send('HELLO USER!');
});
function getMicrotime(get_as_float) {
var now = new Date().getTime() / 1000;
var s = parseInt(now, 10);
return (get_as_float) ? now : (Math.round((now - s) * 1000) / 1000) + ' ' + s;
}
function PostCode(codestring, callback) {
// Build the post string from an object
var post_data = querystring.stringify({
'output_format': 'json',
'js_code' : codestring
});
// An object of options to indicate where to post to
var post_options = {
host: '127.0.0.1',
port: '80',
path: '/NodeJS/chat_system/php_system.php',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': post_data.length
}
};
// Set up the request
var post_req = http.request(post_options, function(res) {
res.setEncoding(ENCODING);
res.on('data', function (chunk) {
console.log('Response FROM PHP: ' + chunk);
if (typeof callback == 'function') {
callback(chunk);
}
});
});
// post the data
post_req.write(post_data);
post_req.end();
}
Here is my PHP Server
require 'Predis/Autoloader.php';
Predis\Autoloader::register();
function pushToRedis($data) {
try {
$redis = new Predis\Client(array(
'scheme' => 'tcp',
'host' => '127.0.0.1',
'port' => 6379,
));
} catch (Exception $e) {
echo "Couldn't connected to Redis";
echo $e->getMessage();
return false;
}
$json = json_encode($data);
$redis->publish("chat", $json);
return true;
}
pushToRedis('PHP PUSH TO REDIS!');
header('Content-Type: application/json');
echo json_encode(array('response' => print_r(array($_REQUEST, $_SERVER), true)));
And my client:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebSockets - Simple chat</title>
<style>
.chat { width: 400px; height: 250px; overflow-y: scroll; }
</style>
</head>
<body>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
var connection = new WebSocket('ws://127.0.0.1:8888');
$(document).ready(function() {
/*
var socket = io.connect('http://127.0.0.1:8888');
socket.on('message', function (data) {
alert(data);
});
socket.send('HELLO!');
*/
connection.onopen = function () {
console.log('connected!');
};
connection.onerror = function (error) {
};
connection.onmessage = function (message) {
$('.chat').append(message.data + '<br>');
$('.chat').scrollTop($('.chat')[0].scrollHeight);
};
$('input[name="text"]').on('keydown', function(e) {
if (e.keyCode === 13) {
var msg = $(this).val();
connection.send(msg);
$(this).val('').focus();
}
});
});
</script>
<div class="chat">
</div>
<input type="text" name="text">
</body>
</html>
The problem is the order of the PHP return the response to NodeJS via Redis when the network its bussy.
For example: I send more messages from de javascript client, then, NodeJS call to PHP every message, PHP save the data in MYSQL, and call Redis, NodeJS detect the Redis push and update the clients. But, in some cases, if i send from the Javascript client in loop some messages ( for(0-10000)) I dont reply to others clients in the same order, in cases geting numbers like 200,201,300,202,320,203 in the clients.
I think this is for the PHP delay to response.
My question is How i can manage the responses to update the clients, in the correct order? because this problem can cause, to clients receive the chat messages in wrong order.
But why you want to use php you able to send data to mysql directly using mysql package of nodejs
You may install mysql package via :
npm install mysql
Connection made by :
mysql = require('mysql'),
connection = mysql.createConnection({
host: 'localhost',
user: 'username',
password: 'password',
database: 'database name',
port: 3306
}),
And throw query by using :
var q=connection.query('select * from table);

Node.js server sends back EOF block the PHP server

I have a node.js server opening 8000 port. It is a chat server.
I have another PHP server and I use proxy + virtual host so when I go www.phpserver.com/chat It proxies to the node.js server. I did this so I can use ajax to call the node.js server.
Right now, everything works fine when i run the node.js server, however, after a while (a random time frame, not necessarily long or short), the PHP server will crush because it gets an EOF from the node.js server and it's just stuck there until I stop/restart the node.js server.
The error I get is(from php error log):
(70014)End of file found: proxy: error reading status line from remote
server nodeserver.com:8000, referer: https://www.phpserver.com
I asked some professionals and they said it's because of the PHP server sends the request to the node.js server successfully and receives an EOF or fails to receive any response. I don't understand how to fix it tho. What should I do so even the node.js server crushes, it won't crush the PHP server? Should I get rid of the proxy+ajax and starts to use socket.io?
Please advise.
Thank you!
Below is some node codes.
From middleware:
this.events.addListener('update', o_.bind(function(package) {
if(this.clear != 0){
delete this.sessions[this.clear];
}
var _package = package.toJSON();
if(package.type == 'status' && package.status == 'offline') {
var sids = Object.keys(this.sessions), sid, sess;
for(sid in this.sessions) {
sess = this.sessions[sid];
if(sess.data('username') == package.username) {
if(sess.listeners.length)
sess.send(200, {type: 'goodbye'});
delete this.sessions[sid];
break;
}
}
}
}, this));
};
Hub.prototype.destroy = function(sid, fn) {
this.set(sid, null, fn);
};
Hub.prototype.reap = function(ms) {
var threshold = +new Date - ms,
sids = Object.keys(this.sessions);
for(var i = 0, len = sids.length; i < len; ++i) {
var sid = sids[i], sess = this.sessions[sid];
if(sess.lastAccess < threshold) {
this.events.emit('update', new packages.Offline(sess.data('username')));
}
}
};
Hub.prototype.get = function(req, fn) {
if(this.sessions[req.sessionID]) {
fn(null, this.sessions[req.sessionID]);
} else {
this.auth.authenticate(req, o_.bind(function(data) {
if(data) {
var session = new User(req.sessionID, data);
this.set(req.sessionID, session);
this.auth.friends(req, data, o_.bind(function(friends) {
var friends_copy = friends.slice();
o_.values(this.sessions).filter(function(friend) {
return ~friends.indexOf(friend.data('username'));
}).forEach(function(friend) {
var username = friend.data('username');
friends_copy[friends_copy.indexOf(username)] =
[username, friend.status()];
}, this);
session._friends(friends_copy);
console.log("refreshed");
session.events.addListener('status',
o_.bind(function(value, message) {
this.events.emit(
'update',
new packages.Status(session.data('username'),
value,
message)
);
}, this));
this.events.addListener('update',
o_.bind(session.receivedUpdate, session));
this.set(req.sessionID, session);
fn(null, session);
}, this));
} else {
fn();
}
}, this));
}
};
From app.js
#!/usr/bin/env node
var sys = require('sys'),
express = require('express'),
packages = require('./libs/packages'),
fs = require('fs'),
o_ = require('./libs/utils'),
https = require('https');
o_.merge(global, require('./settings'));
try { o_.merge(global, require('./settings.local')); } catch(e) {}
try {
var daemon = require('./libs/daemon/daemon'),
start = function() {
daemon.init({
lock: PID_FILE,
stdin: '/dev/null',
stdout: LOG_FILE,
stderr: LOG_FILE,
umask: 0,
chroot: null,
chdir: '.'
});
},
stop = function() {
process.kill(parseInt(require('fs').readFileSync(PID_FILE)));
};
switch(process.argv[2]) {
case 'stop':
stop();
process.exit(0);
break;
case 'start':
if(process.argv[3])
process.env.EXPRESS_ENV = process.argv[3];
start();
break;
case 'restart':
stop();
start();
process.exit(0);
break;
case 'help':
sys.puts('Usage: node app.js [start|stop|restart]');
process.exit(0);
break;
}
} catch(e) {
sys.puts('Daemon library not found! Please compile ' +
'./libs/daemon/daemon.node if you would like to use it.');
}
var options = {
key: fs.readFileSync('/home/ec2-user/key.pem'),
cert: fs.readFileSync('/home/ec2-user/cert.pem'),
ca: fs.readFileSync('/home/ec2-user/ca.pem'),
};
var app = express();
//app.set('env', 'development');
app.use(express.methodOverride());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(require('./middleware/im')({
maxAge: 30 * 1000,
reapInterval: 20 * 1000,
authentication: require('./libs/authentication/' + AUTH_LIBRARY)
}));
app.set('root', __dirname);
if ('development' == app.get('env')) {
app.set('view engine', 'jade');
app.set('views', __dirname + '/dev/views');
app.stack.unshift({
route: '/dev',
handle: function(req, res, next) {
req.dev = true;
next();
}
});
app.use(express.logger());
require('./dev/app')('/dev', app);
app.use(express.static(
require('path').join(__dirname, '../client')));
app.use(express.errorHandler({dumpExceptions: true, showStack: true}));
}
//app.listen(APP_PORT, APP_HOST);
// Listener endpoint; handled in middleware
app.get('/listen', function(){});
app.post('/message', function(req, res) {
res.find(req.body['to'], function(user) {
if(!user)
return res.send(new packages.Error('not online'));
res.message(user, new packages.Message(
req.session.data('username'),
req.body.body
));
});
});
app.post('/message/typing', function(req, res) {
if(~packages.TYPING_STATES.indexOf('typing' + req.body['state'])) {
res.find(req.body['to'], function(user) {
if(user) {
res.message(user, new packages.Status(
req.session.data('username'),
'typing' + req.body.state
));
}
// Typing updates do not receive confirmations,
// as they are not important enough.
res.send('');
});
} else {
res.send(new packages.Error('invalid state'));
}
});
app.post('/status', function(req, res) {
if(~packages.STATUSES.indexOf(req.body['status'])) {
res.status(req.body.status, req.body.message);
res.send(new packages.Success('status updated'));
} else {
res.send(new packages.Error('invalid status'));
}
});
app.post('/online', function(req, res) {
var d = new Date();
var n = d.getTime() + 30;
req.sessionID.expires = n;
res.status(req.body.status, 'available');
});
app.post('/signoff', function(req, res) {
res.signOff();
res.send(new packages.Success('goodbye'));
});
app.use(function(err, req, res, next){
console.error(err.stack);
res.send(500, 'Error on the node/express server.');
});
https.createServer(options, app).listen(8000);
I can't help you answer your question but I can try to point you in a right direction.
I'm currently working on a Node JS server myself, and I found very useful to have a logger setup.
There are a few of them, but my favorite is Winston so far.
Reference: https://github.com/flatiron/winston
To install Winston for your Node JS server (seems you have already installed a few modules):
npm install winston
Then I have logger module setup as (logger.js):
/**
* Usage:
* - logger.info('sample text');
* - logger.warn('sample text');
* - logger.error('sample text');
*/
// Load modules
var winston = require('winston');
// Create custom logger
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({ json: false, timestamp: true }),
new winston.transports.File({ filename: __dirname + '/debug.log', json: false })
],
exceptionHandlers: [
new (winston.transports.Console)({ json: false, timestamp: true }),
new winston.transports.File({ filename: __dirname + '/exceptions.log', json: false })
],
exitOnError: false
});
// Export logger
module.exports = logger;
Finally I load in Winston logger module into my server scripts by:
var logger = require('./logger');
It will automatically log any exceptions into exceptions.log on your Node JS server location. It helped me out a lot to catch exceptions I haven't noticed before within Node JS unrelated to PHP.
P.S. Also check out socket.io, that may simplify what you are trying to do.

Categories