I've successfully used nodejs and socket.io to send a large json file from a server to a client, but I'm stumped on the next step: I need to analyse the json, and only send changes to the client, so that I have very fast real-time updates on the client-side, without having to send the entire json every second. I fear I'm missing something really basic. It's currently sending the entire json over and over. I see where that's happening, I just don't see how to send, instead, only the CHANGES. Ideas?
Server:
/*************************** Require modules ********************************/
var app = require('express')()
, request = require('request')
, fs = require('fs')
, http = require('http')
, server = require('http').createServer(app)
, io = require('socket.io').listen(server);
/************************* Start socket server ******************************/
server.listen(8127);
// socket.io
io.sockets.on('connection', function(socket){
var options = {
host: 'host.com',
port: 80,
path: '/api/tomyjson.json',
headers: {
'Authorization': 'Basic ' + new Buffer('username' + ':' + 'password').toString('base64')
}
};
function getStreams() {
http.get(options, function(response){
var data = "";
response.on('data', function(chunk) {
data += chunk;
});
response.on('end', function() {
socket.emit('news', JSON.parse(data));
});
});
}
setInterval(getStreams, 5000);
socket.on('message', function(data){
console.log(data)
})
socket.on('disconnect', function(){
})
});
Client JS:
var socket = io.connect('host.com:8127/');
socket.on('news', function (json) {
$.each(json.data, function(i, x) {
console.log(x.json.element);
$('#stream-container').prepend(x.json.element);
})
socket.emit('my other event', { my: 'data' });
});
socket.on('message', function(data){
// Do some stuff when you get a message
oldData += data;
document.getElementById('stream-container').innerHTML = oldData;
});
socket.on('disconnect', function(){
// Do some stuff when disconnected
});
Related
I'll try to make this as simple as possible so i'm not having to post a ton of code. Heres what my app does right now:
User uploads an audio file from the browser
That file is processed on my server, this process takes some time and has about 8 or so steps to complete.
Once everything is finished, the user gets feedback in the browser that the process is complete.
What I want to add to this, is after every step in the process that is completed, send some data back to the server. For example: "Your file is uploaded", "Meta data processed", "image extracted" etc etc so the user gets incremental feedback about what is happening and I believe Server Sent Events can help me do this.
Currently, the file is POSTed to the server with app.post('/api/track', upload.single('track'), audio.process). audio.process is where all the magic happens and sends the data back to the browser with res.send(). Pretty typical.
While trying to get the events working, I have implemented this function
app.get('/stream', function(req, res) {
res.sseSetup()
for (var i = 0; i < 5; i++) {
res.sseSend({count: i})
}
})
and when the user uploads a file from the server I just make a call to this route and register all the necessary events with this function on the client side:
progress : () => {
if (!!window.EventSource) {
const source = new EventSource('/stream')
source.addEventListener('message', function(e) {
let data = JSON.parse(e.data)
console.log(e);
}, false)
source.addEventListener('open', function(e) {
console.log("Connected to /stream");
}, false)
source.addEventListener('error', function(e) {
if (e.target.readyState == EventSource.CLOSED) {
console.log("Disconnected from /stream");
} else if (e.target.readyState == EventSource.CONNECTING) {
console.log('Connecting to /stream');
}
}, false)
} else {
console.log("Your browser doesn't support SSE")
}
}
this works as expected, when I upload a track, i get a stream of events counting from 0-4. So thats great!
My Problem/Question: How do i send relevant messages from the audio.process route, to the /stream route so that the messages can be related to whats happening. audio.process has to be a POST, and /stream has to be a GET with the header 'Content-Type': 'text/event-stream'. It seems kind of weird to make GET requests from within audio.process but is this the best way?
Any and all advice/tips are appreciated! Let me know if you need any more info.
New Answer:
Just use socket.io, it's so much easier and better!
https://www.npmjs.com/package/socket.io#in-conjunction-with-express
basic setup:
const express = require('express');
const PORT = process.env.PORT || 5000;
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
// listen to socket connections
io.on('connection', function(socket){
// get that socket and listen to events
socket.on('chat message', function(msg){
// emit data from the server
io.emit('chat message', msg);
});
});
// Tip: add the `io` reference to the request object through a middleware like so:
app.use(function(request, response, next){
request.io = io;
next();
});
server.listen(PORT);
console.log(`Listening on port ${PORT}...`);
and in any route handler, you can use socket.io:
app.post('/post/:post_id/like/:user_id', function likePost(request, response) {
//...
request.io.emit('action', 'user liked your post');
})
client side:
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
$(function () {
var socket = io();
$('form').submit(function(e){
e.preventDefault(); // prevents page reloading
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
});
</script>
full example: https://socket.io/get-started/chat/
Original Answer
Someone (user: https://stackoverflow.com/users/451634/benny-neugebauer | from this article: addEventListener on custom object) literally gave me a hint on how to implement this without any other package except express! I have it working!
First, import Node's EventEmitter:
const EventEmitter = require('events');
Then create an instance:
const Stream = new EventEmitter();
Then create a GET route for event streaming:
app.get('/stream', function(request, response){
response.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
Stream.on("push", function(event, data) {
response.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
});
});
In this GET route, you are writing back that the request is 200 OK, content-type is text/event-stream, no cache, and to keep-alive.
You are also going to call the .on method of your EventEmitter instance, which takes 2 parameters: a string of the event to listen for and a function to handle that event(that function can take as much params as it is given)
Now.... all you have to do to send a server event is to call the .emit method of your EventEmitter instance:
Stream.emit("push", "test", { msg: "admit one" });
The first parameter is a string of the event you want to trigger (make sure that it is the same as the one in the GET route). Every subsequent parameter to the .emit method will be passed to the listener's callback!
That is it!
Since your instance was defined in a scope above your route definitions, you can call the .emit method from any other route:
app.get('/', function(request, response){
Stream.emit("push", "test", { msg: "admit one" });
response.render("welcome.html", {});
});
Thanks to how JavaScript scoping works, you can even pass that EventEmitter instance around to other function, even from other modules:
const someModule = require('./someModule');
app.get('/', function(request, response){
someModule.someMethod(request, Stream)
.then(obj => { return response.json({}) });
});
In someModule:
function someMethod(request, Stream) {
return new Promise((resolve, reject) => {
Stream.emit("push", "test", { data: 'some data' });
return resolve();
})
}
That easy! No other package needed!
Here is a link to Node's EventEmitter Class: https://nodejs.org/api/events.html#events_class_eventemitter
My example:
const EventEmitter = require('events');
const express = require('express');
const app = express();
const Stream = new EventEmitter(); // my event emitter instance
app.get('/stream', function(request, response){
response.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
Stream.on("push", function(event, data) {
response.write("event: " + String(event) + "\n" + "data: " + JSON.stringify(data) + "\n\n");
});
});
setInterval(function(){
Stream.emit("push", "test", { msg: "admit one" });
}, 10000)
UPDATE:
i created a module/file that is easier to use and doesn't cause memory leaks!
const Stream = function() {
var self = this;
// object literal of connections; IP addresses as the key
self.connections = {};
self.enable = function() {
return function(req, res, next) {
res.sseSetup = function() {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
}
res.sseSend = function(id, event, data) {
var stream = "id: " + String(id) + "\n" +
"event: " + String(event) + "\n" +
"data: " + JSON.stringify(data) +
"\n\n";
// console.log(id, event, data, stream);
res.write(stream);
}
next()
}
}
self.add = function(request, response) {
response.sseSetup();
var ip = String(request.ip);
self.connections[ip] = response;
}.bind(self);
self.push_sse = function(id, type, obj) {
Object.keys(self.connections).forEach(function(key){
self.connections[key].sseSend(id, type, obj);
});
}.bind(self);
}
/*
Usage:
---
const express = require('express');
const Stream = require('./express-eventstream');
const app = express();
const stream = new Stream();
app.use(stream.enable());
app.get('/stream', function(request, response) {
stream.add(request, response);
stream.push_sse(1, "opened", { msg: 'connection opened!' });
});
app.get('/test_route', function(request, response){
stream.push_sse(2, "new_event", { event: true });
return response.json({ msg: 'admit one' });
});
*/
module.exports = Stream;
Script located here - https://github.com/ryanwaite28/script-store/blob/master/js/express-eventstream.js
i have code for server
server.js
var socket = require( 'socket.io' );
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = socket.listen( server );
var port = process.env.PORT || 3000;
var nik = {};
server.listen(port, function () {
console.log('Server listening at port %d', port);
});
io.on('connection', function (socket) {
socket.on( 'new_count_message', function( data ) {
io.sockets.emit( 'new_count_message', {
new_count_message: data.new_count_message
});
});
socket.on( 'update_count_message', function( data ) {
io.sockets.emit( 'update_count_message', {
update_count_message: data.update_count_message
});
});
});
and this is how i use that
$.ajax({
type: "POST",
url: "(some_url)",
data: $("id_form").serialize(),
dataType: "json",
beforeSend:function(){
alert('bla..bla..');
},
success: function (result) {
if (result.status) {
var socket = io.connect('http://' + window.location.hostname + ':3000');
socket.emit('new_count_message', {
new_count_message: result.new_count_message
});
} else if (result.status == false) {
alert(error);
return false;
}
},
error: function(xhr, Status, error) {
alert(error);
}
});
that function is working perfectly, but it send to all. how to send notif to specific user? i have the ID user that i want to send the notif
Thanks
Well,
With io.sockets.emit you emit a message to all sockets. Instead use io.sockets.in("roomname").emit("message").
As well if you have the socket ID where you want to send the message you can use io.sockets.connected["socketid"].emit("message").
If you are inside the io.on('connection') function and you want to send a message to the same socket you can simply use socket.emit.
Another way is:
When a new socket connects, add this socket to a specific room socket.join("UniqueUserId") or socket.join("UniqueUserSessionId") ... Then use the 1st option io.sockets.in("UniqueUserId").emit("message") or io.sockets.in("UniqueUserSessionId").emit("message")
Examples:
io.on('connection', function (socket) {
//get the unique socket socketId on connection
var socketId = socket.id;
//you can add this socket id to a Database to use it later, etc...
//use sessionStore like Redis or memStore to get a unique sessionId
//as well you can extract a cookie with the UserId (you need to secure this to be sure that the user not modified the cookie) (you can use 2 cookies 1 for the userid other for the encrypted password and check if the cookies data is the same than in your users Database) etc etc etc. User Session is a lot better). Read about nodejs session store and socket session. Something like...
var cookies = qs.parse(socket.handshake.headers.cookie, "; ");
var user_id = cookies.user_id; //or some other cookie name;
socket.join(user_id);
socket.on( 'new_count_message', function( data ) {
//all sockets
io.sockets.emit( 'new_count_message', {
new_count_message: data.new_count_message
});
//same Socket
socket.emit( 'new_count_message', {
new_count_message: data.new_count_message
});
//specific Socket by SocketId
//io.sockets.connected["socketid"].emit( 'new_count_message', {
io.sockets.connected[socketId].emit( 'new_count_message', {
new_count_message: data.new_count_message
});
//all sockets in a specific Room
//io.sockets.in("roomname").emit( 'new_count_message', {
io.sockets.in(user_id).emit( 'new_count_message', {
new_count_message: data.new_count_message
});
});
});
I'm new to socket.io, and I'm doing a simple API with NodeJS (express 4). I'm developing an action that is similar to the old "poke" action at facebook. A user send a poke to other user, and this one gets a notification on real time (this is the reason why I am using socket.io).
This is the code:
app.js
var port = 3000;
var app = module.exports = express();
var server = require('http').Server(app);
...
server.listen(port);
require('./config/socket-io')(app, server, secret);
socket-io.js
module.exports = function(app, server, secret) {
var clients = {};
console.log("initiating sockets...");
var sio = require('socket.io').listen(server, {'log level': 2});
sio.on('connection', function (socket) {
console.log("...new connection: "+socket.client.id);
clients[socket.id] = socket;
socket.emit('identification', { data : socket.client.id });
socket.on('newShoutOut', function(data) {
var receptor = data.idTo;
var emiter = socket.client.id;
console.log("...new shout out from " +emiter+ " to "+receptor);
sio.sockets.sockets[receptor].emit({ data : data.data, from : emiter });
});
socket.on('disconnect', function() {
console.log("..."+socket.client.id + " disconnected");
});
});
};
Here you can differentiate three states:
Connection: The server detects all the clients connection to the host:port. After that, the server sends to each client his ID. This works fine.
Send message: One client sends a notification to other client. For now, the server receives the notification from one client, but the "receiver" doesn't receive anything.
Disconnection: Doesn't matter in this case.
My question is, what is the way to send a message to a client directly knowing the ID? What I am doing wrong? I tried so many options to send a message directly to a specific client ID but didn't work...
EDIT
Frontend
var socket = io('http://localhost:3000');
var id = "";
socket.on('connection', function (data) {
console.log("connected!");
console.log(data);
});
socket.on('identification', function(data) {
id = data.data;
$("#socket_info h1").html("ID: "+id);
});
socket.on('newShoutOut', function(data) {
console.log("newShoutOut received!");
});
Ok, so I assume the shoutout is coming from a user? You will need to create the event on the clientside, such as:
var button = $('#button');
button.on('click', function() {
var msg = 'message',
userID = '123'; //get the ID who they are messaging
socket.emit('sendShoutOut', {msg: msg, id: userID});
});
Then you will need to receive that response on the server, and reply to the user in that function:
socket.on('sendShoutOut', function( data ) {
socket.sockets.sockets[data.id].emit('sendPrivateMsg', { data : data.msg, from : emiter });
});
Lastly, the reciever must be notified, so you will need to handle the response on the client:
socket.on('sendPrivateMsg', function( data ) {
alert(data);
});
Hope this helps.
I have a simple client server web app that is using web sockets to send / receive information. The client can connect and receives properly the config file but then when I try to send a "test' message from the client using "socket.emit('message', {my: 'data'});" it doesn't display on the server. I did check with wireshark and the packets are arriving at the server.
var sIoPort = 8181;
var host = '192.168.4.111';
var fs = require('fs');
var iniMsg = fs.readFileSync('data.json','utf8');
var http = require("http").createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(index);
});
http.listen(sIoPort,host);
var browserServer = require('socket.io').listen(http);
browserServer.on('connection', function (socket) {
console.log('Client websocket connected');
// send the config file if available
browserServer.sockets.emit('msg',iniMsg.toString());
});
browserServer.on('message', function (message) {
console.log('received message: ' + message);
});
client side
///////////////////////////////////////////////////////////////////////////////
socket = io.connect("192.168.4.111",{"port":8181});
socket.on('connect',function() {if(DEBUG) console.log('Socket Connected');});
socket.emit('message', {my: 'data'}); // test if server receives message
socket.on('msg',function(data) {
var json = JSON.parse(data);
// add the maps to the the GUI
switch(json.type) {
case 'maps': add_maps_from_json(json, null);
break;
}
});
socket.on('disconnect',function() {if(DEBUG) console.log('Socket Disconnected');});
/////////////////////////////////////////////////////////////////////////////////
Modify the serverside listener so it's paying attention to events on a socket:
browserServer.on('connection', function (socket) {
console.log('Client websocket connected');
// send the config file if available
browserServer.sockets.emit('msg',iniMsg.toString());
socket.on('message', function (message) {
console.log('received message: ' + message);
});
});
I'm currently going through Guillermo Rauchs "Smashing Node.Js" Book. I'm stuck in chapter 7 where the task is to set up a client/server and to send a string from the client to the server over a http connection. The string should be printed from the server.
the client code:
var http = require('http'), qs = require('querystring');
function send (theName) {
http.request({
host: '127.0.0.1'
, port: 3000
, url: '/'
, method: 'GET'
}, function (res) {
res.setEncoding('utf-8');
res.on('end', function () {
console.log('\n \033[090m request complete!\033[39m');
process.stdout.write('\n your name: ');
})
}).end(qs.stringify({ name: theName}));
}
process.stdout.write('\n your name: ');
process.stdin.resume();
process.stdin.setEncoding('utf-8');
process.stdin.on('data', function (name) {
send(name.replace('\n', ''));
});
the server:
var http = require('http');
var qs = require('querystring');
http.createServer(function (req, res) {
var body = '';
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
res.writeHead(200);
res.end('Done');
console.log('\n got name \033[90m' + qs.parse(body).name + '\033[39m\n');
});
}).listen(3000);
I start the client and the server. The client seems to work:
mles#se31:~/nodejs/tweet-client$ node client.js
your name: mles
request complete!
your name:
However on the server side, it's only showing an undefined:
mles#se31:~/nodejs/tweet-client$ node server.js
got name undefined
According to the book, here should be an "mles" too.
, method: 'GET'
should be
, method: 'POST'
GET requests do not have a body so there is nothing to parse on the server's side.