I'm building an application in node, using express for simplify things and socket.io for sockets manipulation.
So, while I'm writing my code I realize that it works, but is not the best and elegant solution.
The only thing I want to do is to wrap all the code of the sockets events, and then reuse in more than one page request, so:
app.js
var express = require('express')
, io;
var app = express()
, PORT = 7777;
io = require('socket.io').listen(app.listen(PORT));
app.get("/", function ( req, res ) {
io.socket.on('connection', function ( socket ) {
socket.on('user', function () {
});
socket.on('message', function () {
});
socket.on('getConversation', function ( socket ) {
});
});
res.render('index');
});
But what happens if I want to assign to the /foo and /bar files the same approach?
i.e. I need to get it in a modular or object oriented way, as can be possible.
There is a pattern to do it?
All you're doing here is attaching event listeners to socket.io after someone makes a page request. Then every single request after that would be attaching new event listeners and your server slowly begins to run out of memory as you clog it with new event listeners every time someone makes a page request. It makes no sense to put socket.io code in your route handlers. Take a look at this:
https://www.npmjs.com/package/express.io
Related
I have this simple code. This must show me my error object ({error:'error'}) upon each request. But it shows only "[Object object]".
And moreover - debugger never stops in the error handler function.
What is going on?
var domain = require('domain');
var express = require('express');
var server = express();
server.get('/', function(req, res)
{
var d = domain.create();
d.on('error', function(e)
{
debugger;
console.log(JSON.stringify(e));
});
d.run(function()
{
throw {error:'error'};
res.send('ok');
});
});
server.listen(8080);
The problem is that domains catch the error all the way at the bottom of the call stack, and since express has its own error handling code, the error gets caught by express before reaching the domain. Similar issue here: Node.js - Domain per Express request, inside another domain
I don't see any particularly good reason it should be this way, but that's how the code seems to work [1] (source here: https://github.com/joyent/node/blob/master/lib/domain.js). Possible workarounds are to surround the d.run() call with your own try/catch, or to do something like
d.run(process.nextTick(function() {
// do stuff
}));
[1] The way domains work is that they basically hook into all of the "asynchronous" callbacks and add some information that records who initiated the asynchronous operation. I don't know why they don't also try/catch on the initial synchronous block.
This must have been asked already a thousand times, but I do not find any of the answers satisfying, so I'll try having another go, being as clear as possible.
I am starting out with a clean Express; the one that is usually done via the following terminal commands:
user$ express
user$ npm install
then I proceed installing socket.io, this way:
user$ npm install socket.io --save
on my main.js file I then have the following:
//app.js
var express = require('express'),
http = require('http'),
path = require('path'),
io = require('socket.io'),
routes = require('./routes');
var app = express();
I start my socket.io server by attaching it to my express one:
//app.js
var server = http.createServer(app).listen(app.get('port'), function(){
console.log('express server started!');
});
var sIo = io.listen(server);
What I do now is to set the usual routes for Express to work with:
//app.js
app.get('/', routes.index);
app.get('/send/:recipient/:text', routes.sendMessage);
Now, Since I like to keep things organized, I want to put my socket.io code in another file, so instead of using the usual code:
//app.js
sIo.sockets.on('connection', function(socket){
console.log('got a connection');
});
I use the following to be able to access both the socket and the sIo object (as that object contains all the connections infos (important)):
//app.js
sIo.sockets.on('connection', function(socket){
routes.connection(sIo, socket);
});
// index.js (./routes)
exports.connection = function(sIo, socket){
console.log('got a connection.');
};
This way I can do all my socket.io jobs in here. I know that I can access all my clients information now from the sIo object, but of course, they do not contain any information about their session data.
My questions now are the following:
Suppose a user makes an HTTP request to send a message and the handler in my routes is like this:
exports.sendMessage = function(req, res){
//do stuff here
};
How can I get this to "fire" something in my socket.io to send a message? I do not want to know all the underlying work that needs to be done, like keeping track of messages, users, etc. I only want to understand how to "fire" socket.io to do something.
How can I make sure that socket.io sends the message only to a person in particular and be 100% sure that nobody else gets it? From what I can see, there is no way to get the session infos from the sIo object.
Thanks in advance.
question one: The cleanest way to separate the two would probably be to use an EventEmitter. You create an EventEmitter that emits when an http message comes in. You can pass session information along with the event to tie it back to the user who sent the message if necessary.
// index.js (./routes)
var EventEmitter = require('events').EventEmitter;
module.exports.messageEmitter = messageEmitter = new EventEmitter();
module.exports.sendMessage = function(req, res) {
messageEmitter.emit('private_message', req.params.recipient, req.params.text);
};
question 2: You can access the socket when the initial connection is made. An example mostly borrowed from this answer:
var connect = require('connect'),
userMap = {};
routes.messageEmitter.on('private_message', function(recipient, text) {
userMap[recipient].emit('private_message', text);
});
io.on('connection', function(socket_client) {
var cookie_string = socket_client.request.headers.cookie;
var parsed_cookies = connect.utils.parseCookie(cookie_string);
var connect_sid = parsed_cookies['connect.sid'];
if (connect_sid) {
session_store.get(connect_sid, function (error, session) {
userMap[session.username] = socket_client;
});
}
socket_client.on('private_message', function(username, message) {
userMap[username].emit(private_message, message)
});
});
So we're just creating a map between a session's username and a socket connection. Now whenever you need to send a message you can easily lookup what socket is associated with that user and send a message to them using their socket. Just make sure to handle disconnects, and reconnects and connecting in multiple tabs, etc.
I have built something like what you are saying. If a user can make a socket request, it pushes the message via the socket, and then the server does a broadcast or emit of it. But, if a user can't connect to the socket, it then does the http post, like what you are saying by calling the sendMessage. What I have done, rather than having sendMessage shoot off a socket is that I also have my clients doing an ajax request every 5 seconds or so. That will bring back new messages, and if any of the messages were not received via socket.io, I then add them to my clientside array. This acts as sort of a safety net, so I don't have to always fully trust socket.io.
see below in pseudo code
client
if canSendSocketMessage()
sendSocketMessage(message)
else
sendAjaxMessage(message)
setInterval( ->
// ajax call
getNewMessages()
), 5000
server
socket stuff
socket.on 'message' ->
saveMessage()
socket.emit(message)
ajax endpoints
app.post 'sendMessage'
saveMessage()
app.get 'getNewMessages'
res.send getNewMessages()
I've created a basic node.js server program and used socket.io to pass some field data from a client (see below). Pretty chuffed as I'm new to this business. I liked this node-express-socket.io approach as its all Javascript and is apparently usable by most browsers (incl' mobile). The problem is I've kind of fumbled my way through and do not not fully understand what I have created! Two questions...
1) Do I need to use the "//ajax.googleapis.com...jquery..."? This is annoying as the browser will need to have an internet connection to work. Is there another way to access the html doc elements without needing an internet connection?
2) What does the "app.use(express.static...." line do? The "app.get..." function seems to require this to work.
If there are any other general comments about my code please let me have it!
Cheers,
Kirbs
Client side code:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect(document.location.protocol+'//'+document.location.host);
function clicked(){
$(function(){
var makeInput=$('.app').find('#make').val();
var modelInput=$('.app').find('#model').val();
socket.emit('make', makeInput);
socket.emit('model', modelInput);
});
};
</script>
Server side code:
var express = require('express');
var http = require('http');
var socketio = require('socket.io');
var app = express();
var server = http.createServer(app);
var io = socketio.listen(server);
app.use(express.static(__dirname));
app.get('/', function (req, res) {
res.render(__dirname + '/index.html');
});
io.sockets.on('connection', function (socket) {
socket.on('make', function (make) {
socket.on('model',function (model){
console.log('recieved message:', make+','+model);
});
});
});
server.listen(8000);
1) As you have setup a static web server (see answer 2), you could simply download the jquery source and serve the .js file from there.
2) "app.use(express.static...." configure a static webserver and setting up the http root directory to the directory that your node.js script lives, as indicated by the __dirname variable. For more detail, see app.use API reference.
As result, I would recommend you change you app.use to:
app.use(express.static(__dirname + '/public'));
and place all your static files, including your jquery file(s), under a public subdirectory.
Also, your server side code has a dependency on sequence of make and model which should be changed. For example, if you switch the emit order to model then make, you should see that your server's console.log will be picking up the make from the previous call.
Instead, try something like:
// On server:
socket.on('info', function (info) {
console.log('recieved message:', info.make+','+info.model);
});
// On client:
socket.emit('info', { make: makeInput, model: modelInput })
1) You can serve the jQuery library also from your server if you like that better. You should put it in the public/vendor or public/js folder in your project.
2) This is a middleware call from Express framework, which uses in turn the Connect middleware stack. Read up on this here.
Now, after some hours of playing around with nodejs and socket.io, I'm getting a couple more problems - one being, that I need to get the sessionID inside node.js, whitout using app.get('/' ... - since that event doesnt seem to fire when socket.io connects, it only fires .on('connection', function( ...
var express = require('express')()
express.set('port', process.env.PORT || 8080)
var server = require('http').createServer(express)
var socket = require('socket.io').listen(server)
server.listen(express.get('port'))
// this event is fired, get('/', ... isnt!
server.on('connection', function(stream) {
// ??
} )
The Session is initially created by the PHP application, as the user logs in. Session data is stored in the database, and the key I need to access that data is the SESSION ID. What's the easiest way to get to it? Like mentioned, I found a couple examples that used app.get('/' ... but I couldnt get that event to fire at all.
Thanks.
If the session data is being stored as a cookie (most likely), then you should be able to re-parse that data during the socket handshake. I posted code for that on this answer, but copied the code here as well:
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
var cookie = handshakeData.headers.cookie;
// parse the cookie to get user data...
// second argument to the callback decides whether to authorize the client
callback(null, true);
});
});
If the session data is being propagated in the URL, then you may be able to gather this information from handshakeData.url or handshakeData.query. You'll probably have to do your own experimentation.
I'm a noob to Node.js and Express :( I'm having an issue with accessing the socket.io object in other modules. I have created a global variable to hold a socket object however as soon as that user closes the connection the connection isn't available anymore. Technically I guess the connection still lives on but I delete it for resource reasons.
Notice below everytime a connection is made we assign that socket to the global variable which is accessible to all other modules.
// App.js
var io = require('socket.io').listen(server);
var sessionsConnections = {};
io.sockets.on('connection', function (socket)
{
global.socket = socket;
sessionsConnections[socket.id] = socket;
.....
}
socket.on("disconnect", function()
{
delete sessionsConnections[socket.id];
});
// Match.js
global.socket.emit('lobby:createMatch', data);
If the connection last assigned to the global closes Match.js will be screwed. At this point Match.js is the only module who will ever need that socket.io reference. Match.js has a bunch of exports for handling events, emitting the changes and rendering the view.
Are there any suggestions to how this is handled? Is it possible to instantiate an initial socket connection to live in App.js for the purpose of being a global reference?
The socket variable in io.sockets.on('connection', function(socket) {...}) is different for each connection.
Since in your code global.socket is always a reference to the socket relative to the last connected client, it is normal that if this client disconnects, this socket will die.
In any case, I don't see any reason to use a global variable. If you need to send a message to a specific client, you can use the socket variable inside the connection callback:
io.sockets.on('connection', function (socket)
{
socket.emit('foo', 'bar');
}
If you need to access the sockets in another module, you can export the sessionsConnections object, and access the socket you need by its id:
//In app.js
exports.connections = sessionsConnections;
//In match.js
var app = require('./app.js');
app.connections[clientId].emit('foo', 'bar');
Of course, you need to keep track of the id's somewhere.
You might try express.io,
http://express-io.org/
npm install express.io
It works the same as express, except that it has socket.io integration built-in. It also has basic routing, so that it is a little easier to deal with your issue.
Check out this simple example:
app = require('express.io')()
app.http().io()
app.io.route('some-event', function(req) {
req.io.broadcast('announce', 'someone triggered some-event')
})
app.listen(7076)
You can also do routing with objects, which makes it more like a traditional controller:
app = require('express.io')()
app.http().io()
app.io.route('posts', {
create: function(req) {
// create a post
},
remove: function(req) {
// remove a post
}
})
app.listen(7076)
Hope that helps!