I'm using Express and Connect-Mongo to manage sessions for my Node.js/Socket.io app.
I looked at the connect-mongo doc and I'm trying to initialize the DB connection but whenever I try to store anything in it the store object is always null and crashes.
Here's a snippet of the code I'm using:
var mongoserver = new mongodb.Server('localhost', mongodb.Connection.DEFAULT_PORT, {safe:true}) ;
var store ;
app.use(express.session({
secret: 'secret',
store: new MongoStore({ db: 'test'}, function(){
db = new mongodb.Db('test', mongoserver, { w: 1 }) ;
db.open(function(err, db){
if(err){console.log('DB error', err) ;}
db.collection('test', function(err, collection) {
console.log('Collection...') ;
});
db.on("close", function(error){
console.log("Connection to the database was closed!");
});
});
})
}));
io.sockets.on('connection', function (socket) {
store.set('test-sid', {foo:'bar'}, function(err, session) {}) ;
)};
All other variables (express, server etc) are properly initalized, the app comes up, I see in the console that the connection to mongo is accepted. Any ideas ??
You are not storing the store in the variable "store". It is being passed into the session configuration and set as an express application setting.
To fix this you can create the store and assign it to the variable before adding it as a setting and then pass it into the session config:
// Create the store
var store = new MongoStore(...);
// Pass into session creation
app.set(expression.session({
store: store
});
// You can now access the store from the variable as you expected
store.set('test-sid', {foo: 'bar'}, function(err, session) {});
Related
I am trying to build a live dashboard that streams data from a mongodb capped collection to the front end of whichever user is logged in. The backend will be python or similar and will update user collections with new data continuously. I am using passportjs for authentication, and passport-socketio-redis to integrate socketio.
The first goal I believe is to add the username as a value to the socketio client list.
Example: one client connected, client list is: [ 'wukBbRD3vcXCuqylAAAA' ].
Add user value, now the client list looks like: [ { 'wukBbRD3vcXCuqylAAAA' : username } ]
After this I would loop through the client list periodically (every 1 second or so) and start a mongodb stream for each client. Then I would stream the resulting data to that client using their socket ID. (I am assuming that upon disconnect, the client key:val pair will be removed, may have to add additional code to kill the mongodb stream.)
I have come up with the code below, it will not do the above correctly, as I'm currently stuck trying to figure out how to pair the username with the socketio client id.
Is this even a good approach for building a live dashboard? The more I get into this, the less it seems plausible/scalable. Thanks all for your help!
/bin/www
var app = require('../app');
var debug = require('debug')('project:server');
var http = require('http');
//misc server initialization stuff left out to save space. this file calls in sockets.js below
var io = require('socket.io').listen(server);
require('../sockets')(io);
sockets.js
var passport = require('passport');
var cookieParser = require('cookie-parser');
var redis = require('redis').createClient();
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
var socketioRedis = require("passport-socketio-redis");
var History = require('./models/history');
var clientinfo;
var user_name;
module.exports = function (io) {
io.use(socketioRedis.authorize({
passport:passport,
cookieParser: cookieParser,
key: 'express.sid',
secret: 'keyboard cat',
store: new RedisStore({ host: 'localhost', port: 6379, client: redis }),
success: authorizeSuccess, //callback on success
fail: authorizeFail //callback on fail/error
}));
function authorizeSuccess(data, accept){
//get the username
user_name = data.user.id;
accept();
}
function authorizeFail(data, message, error, accept)
{
if(error)
accept(new Error(message));
}
io.on('connection', function(socket){
//get list of clients - I believe I need to somehow add the username to the client list here.
clientinfo = Object.keys(io.engine.clients)
});
setInterval(function(){
for (key in clientdict) {
//start new stream for each client (not 100% sure multiple streams can exist)
streamData (key, clientdict[key])
}
}
},1000);
//create mongodb stream and emit to client
function streamData (u_id, sockvar) {
var stream = History.find({ user_id: u_id}, {'_id':0, 'test':1}).tailable(true).stream();
stream.on('data', function (doc) {
io.to(sockvar).emit('dash_data', { dash_data: doc });
});
}
}
May have solved this - should have read the documentation better. Turns out passport-socketio-redis allows you to access a 'socket.request.user' which returns the information about the user. See the code below for creation and deletion of key:value pairs upon new connections/disconnects
io.on('connection', function(socket){
clientdict[socket.request.user.id] = socket.id;
//console.log(socket.request.user)
//all clients console.log(Object.keys(io.engine.clients))
console.log(clientdict)
socket.on('disconnect', function(){
delete clientdict[socket.request.user.id]
console.log('removed')
console.log(socket.request.user.id)
});
});
On my local host, I have the following Node code to setup a mongoDB database name "dbname":
users.js:
var MongoClient = require("mongodb").MongoClient,
Connection = require("mongodb").Connection,
Server = require("mongodb").Server;
Users = function(host, port) {
var mongoClient = new MongoClient(new Server(host, port));
mongoClient.open(function (){});
this.db = mongoClient.db("dbname");
};
Users.prototype.getCollection = function (callback) {
this.db.collection("users", function (error, users) {
if (error) callback(error);
else callback(null, users);
});
};
Users.prototype.findAll = function (callback) {
this.getCollection(function (error, users) {
if (error) {
callback(error);
} else {
users.find().toArray(function (error, results) {
if (error) {
callback(error);
} else {
callback(null,results);
}
});
}
});
}
// Bunch of other prototype functions...
exports.Users = Users;
I like to put the above database functionality in one file, and then in my main server file require that file as follows:
server.js:
var Users = require("./users").Users;
var users = new Users("localhost", 27017);
users.findAll(function (err, user) {
// Do something
});
To have this working on localhost is pretty easy. In the command line, I just type the following:
$ mongod # to launch the database server
$ node server.js # to launch the web server
and it works fine. However, now I'm trying to push the whole thing onto Heroku with the mongolab addon
heroku addons:add mongolab
but the database is not running and I have no idea how to make it run. This tutorial explains how to setup mongodb with the mongolab URI, but that's not how my code works, I use a host and a port and I create a new server based on that. How should I change my code for it to work on the heroku app? I want to keep the database code in a separate file, with the prototype functions.
Follow the example here at the "MongoClient.connect" section.
Essentially, you will need to change this part of the code:
Users = function(host, port) {
var mongoClient = new MongoClient(new Server(host, port));
mongoClient.open(function (){});
this.db = mongoClient.db("dbname");
};
To use mongoClient.connect() instead of new MongoClient:
Users = function(url) {
MongoClient.connect(url, function(err, db) {
// Find better way to set this since this callback is asynchronous.
this.db = db;
});
};
If you are using node, I recommend using a library such as mongoose npm install mongoose to handle mongodb interactions. Look at my answer here for how to structure your schemas.
Helped by Xinzz's answer, here's the modified code, so that the mongodb database is initialized with a URI instead of host + port. That's how Heroku initializes the mongodb database, and that's why it wasn't working.
var mongodb = require("mongodb");
var MONGODB_URI = process.env.MONGOLAB_URI || process.env.MONGOHQ_URL || "mongodb://localhost", // Make sure to replace that URI with the one provided by MongoLab
db,
users;
mongodb.MongoClient.connect(MONGODB_URI, function (err, database) {
if (err) throw err;
db = database;
users = db.collection("users");
accounts = db.collection("accounts");
var server = app.listen(process.env.PORT || 3000);
console.log("Express server started on port %s", server.address().port);
});
The key here is to declare the variables db and users upfront, assign them a value in the asynchronous callback of the connect function of MongoClient and also start the app (app.listen(...)) in the same callback. Then later in the code I can do the following:
users.find().toArray(function (err, results) {
// Do something
});
I also gave up on all these prototype functions, since they did not really add much.
In a nodejs-express app, in the server.js file I set the socket-io connection.
It works fine doing something like this
var server = require('http').createServer(app)
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
// how can I save globally this 'socket'?
});
I would like to save this 'socket' globally in the server.js file and so, be able to use it everywhere in the project. Like this:
app.get('/on', function(req, res){
socket.on('test', function (data) {
console.log(data);
});
});
app.get('/emit', function(req, res){
socket.emit('test', "ciao");
});
I red there is a way to do it saving the 'socket' connection in session. My sessions settings:
app.configure(function(){
// ....
app.use(express.cookieParser(SITE_SECRET));
app.use(express.session({
secret :
,store : new MongoStore({
mongoose_connection : mongoose.connection
,db: mongoose.connections[0].db
})
,cookie: { maxAge: new Date(Date.now() + (1000*60*60*24*30*12)) }
}));
...
});
What is a good way to do it?
And also, after have made this saving, how to use socket.on() and socket.emit() loading the first page if the connection is not opened yet?
What you might have heard is not saving a socket into a session, but referencing sockets by their session cookie, which is passed to the server during the socket authorization process. During authorization, this is an example of the type of object that is passed to the server:
{
headers: req.headers, // <Object> the headers of the request
time: (new Date) +'', // <String> date time of the connection
address: socket.address(), // <Object> remoteAddress and remotePort object
xdomain: !!headers.origin, // <Boolean> was it a cross domain request?
secure: socket.secure, // <Boolean> https connection
issued: +date, // <Number> EPOCH of when the handshake was created
url: request.url, // <String> the entrance path of the request
query: data.query // <Object> the result of url.parse().query or a empty object
}
What we're interested in is the headers property, where we can find the session cookies of a connecting socket. We then parse the cookies during authorization:
// pass same objects from Express to Socket.IO so they match
var parseCookie = express.cookieParser(SITE_SECRET);
var store = new MongoStore({
mongoose_connection: mongoose.connection,
db: mongoose.connections[0].db
});
io.configure(function() {
io.set('authorization', function(handshake, callback) {
if (handshake.headers.cookie) {
parseCookie(handshake, null, function(err) {
// we used the signedCookies property since we have a secret
// save the session ID to the socket object, we can access it later
handshake.sessionID = handshake.signedCookies['connect.sid'];
store.get(handshake.sessionID, function(err, session) {
// we have the same Express session, reference it
socket.session = session;
callback(null, true);
});
});
} else {
// they client has no session yet, don't let them connect
callback('No session.', false);
}
});
});
app.use(parseCookie);
app.use(express.session({
secret: SITE_SECRET,
store: store,
cookie: {maxAge: new Date(Date.now() + (1000*60*60*24*30*12))}
}));
Then once we have saved the session ID, we can use the typical connection events:
var server = require('http').createServer(app)
var io = require('socket.io').listen(server);
var clients = {};
io.sockets.on('connection', function (socket) {
// save to a global object
var session = socket.handshake.sessionID;
clients[session] = socket;
socket.on('disconnect', function() {
delete clients[session];
});
});
Then we have a global reference by cookie signature. We can then access the socket like this:
app.get('/path', function(req, res) {
var socket = clients[req.sessionID];
socket.emit('Socket client accessed route.');
});
Keep in mind you might have to add some logic into your global logic for clients with multiple tabs, which would result in two sockets with the same authorization cookie.
As for your question about using socket.on() and socket.emit(), you can't use that before the connection has been established because the socket itself does not exist. If you want to send a message to all connected clients, then you should just use the global io.sockets object. It would then be more like io.sockets.emit().
I'm using Express.js and MongoLab and I followed the Heroku setup to get MongoDB working in production throwing this code in my app.js.
//Mongo on Heroku Setup
var mongo = require('mongodb');
var mongoUri = process.env.MONGOLAB_URI ||
process.env.MONGOHQ_URL ||
'mongodb://localhost/mydb';
mongo.Db.connect(mongoUri, function (err, db) {
db.collection('mydocs', function(er, collection) {
collection.insert({'mykey': 'myvalue'}, {safe: true}, function(er,rs) {
});
});
});
and I have the following routes and field for my email form (also in app.js):
//Routes
app.get('/', function(req, res) {
res.render('index', {
title: 'DumbyApp'
});
});
//save new email
app.post('/', function(req, res){
emailProvider.save({
address: req.param('address')
}, function( error, docs) {
res.redirect('/')
});
});
This renders the new form on the index page and lets me save it locally but not in production because I don't know how to setup my email collection. Can anyone walk me through this? brand new to using MongoDB and Node.js, so could use some help.
EDIT:
In The MongoLab Database Interface, I made a collection called emails. Is this the right course of action?
EDIT 2:
Here's defining EmailProvider in app.js along with the file itself.
app.js
var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path')
, EmailProvider = require('./emailprovider').EmailProvider;
var emailProvider= new EmailProvider('localhost', 27017);
emailprovider.js
var Db = require('mongodb').Db;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
var BSON = require('mongodb').BSON;
var ObjectID = require('mongodb').ObjectID;
EmailProvider = function(host, port) {
this.db= new Db('localdb', new Server(host, port, {safe: false}, {auto_reconnect: true}, {}));
this.db.open(function(){});
};
EmailProvider.prototype.getCollection= function(callback) {
this.db.collection('emails', function(error, email_collection) {
if( error ) callback(error);
else callback(null, email_collection);
});
};
//save new email
EmailProvider.prototype.save = function(emails, callback) {
this.getCollection(function(error, email_collection) {
if( error ) callback(error)
else {
if( typeof(emails.address)=="undefined")
emails = [emails];
for( var i =0;i< emails.address;i++ ) {
email = emails[i];
email.created_at = new Date();
}
email_collection.insert(emails, function() {
callback(null, emails);
});
}
});
};
exports.EmailProvider = EmailProvider;
While the connection code in the first code box appears to be correct, the emailProvider object isn't using it. Instead, in app.js, the EmailProvider is being connected to localhost:27017 and the database name is hardcoded in emailprovider.js as 'localdb'.
What you want to do instead is use the connection information provided in the MONGOLAB_URI environment variable in your EmailProvider, which contains the host, port, and database name already.
There are a number of ways to go about doing this, but one way would be to move your connection code from that first code box into the EmailProvider constructor, and then change the constructor so that it takes a URI instead of a host and port. That way, you can pass the MONGOLAB_URI variable to the constructor in app.js.
I am currently rolling back from mongoose to node-mongodb-native.
So I am quite new at this topic. However my issue currently is that want to create a database collection on server start which I then can use through the application. Unfortunately I only find examples in the repository where you only can do database actions directly in the callback of the connect function.
docs:
var mongodb = require("mongodb"),
mongoServer = new mongodb.Server('localhost', 27017),
dbConnector = new mongodb.Db('example', mongoServer);
db_connector.open(function(err, db) {
if (err) throw new Error(err);
// here I can do my queries etc.
});
But how can I get access to the db object in the callback when I am in some route callback?
Currently the only idea I would have is wrapping the application into the callback:
var mongodb = require("mongodb"),
express = require("express"),
mongoServer = new mongodb.Server('localhost', 27017),
dbConnector = new mongodb.Db('example', mongoServer);
var app = new express();
db_connector.open(function(err, db) {
if (err) throw new Error(err);
app.get('/products', function(req, res, next) {
db.collection('products', function(err, collection) {
if (err) next(new Error(err));
collection.find({}, function(err, products) {
res.send(products);
});
});
});
});
But I do not think this is the way it should meant to be?
Isn't there the way to create a sync database connection call which I then can easily use through the whole application how it was by mongoose?
Regards bodo
Db.open opens the connection to mongodb and returns a reference to itself. See here for the sourcecode: https://github.com/mongodb/node-mongodb-native/blob/master/lib/mongodb/db.js#L245
All you want is to hold off on starting your express app listening on it's port and receiving requests until your connection to mongodb has been established.
So what you could do is this:
var mongodb = require("mongodb"),
express = require("express"),
mongoServer = new mongodb.Server('localhost', 27017),
dbConnector = new mongodb.Db('example', mongoServer),
db;
var app = new express();
app.get('/products', function(req, res, next) {
db.collection('products', function(err, collection) {
if (err) next(new Error(err));
collection.find({}, function(err, products) {
res.send(products);
});
});
});
db_connector.open(function(err, opendb) {
if (err) throw new Error(err);
db = opendb;
app.listen(3000);
});
What I'm not sure about though is whether this is a good idea. This solution doesn't allow you to recreate your connection if there has been a connection break or you restarted your mongodb process. So while the above might work, it might be a better idea to create a method that will wrap the creation of a connection to mongodb.