Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ExpressJS res.render() error (JSON.stringify can't work on circular reference)

What's wrong here?

res.render('/somepage', {user:req.session.user})

It leads to

Converting circular structure to JSON
errors, (results in session element that has a circular user reference.)

exports.home =  function (req, res) {
    var entityFactory = new require('../lib/entity-factory.js').EntityFactory();
    entityFactory.get_job_task_lists({
        callback : function (err, job_task_lists) {
            res.render('home.jade', {
                        locals:{
                            title: 'Logged in.', 
                            user:req.session.user, // does not work
                            job_task_lists:job_task_lists || [] 
                        }
            });
        }
    });
};


I added some logging in node_modules/express/node_modules/connect/lib/middleware/session/memory.js

MemoryStore.prototype.set = function(sid, sess, fn){
  var self = this;
  process.nextTick(function(){

    console.log(sess); //this is giving the output listed

    self.sessions[sid] = JSON.stringify(sess);
...

This is what I expect the session to look like, in terms of structure:

{ lastAccess: 1330979534026,
  cookie: 
   { path: '/',
     httpOnly: true,
     _expires: Tue, 06 Mar 2012 00:32:14 GMT,
     originalMaxAge: 14399999 },
  user: // this is the object I added to the session
   { id: 1,
     username: 'admin',
     password: '8e3f8d3a98481a9073d2ab69f93ce73b',
     creation_date: Mon, 05 Mar 2012 18:08:55 GMT } } 

But here's what I find:

{ lastAccess: 1330979534079, // new session
  cookie: 
   { path: '/',
     httpOnly: true,
     _expires: Tue, 06 Mar 2012 00:32:14 GMT,
     originalMaxAge: 14399999 },
  user:  // but here it is again, except now it's a mashup,
         // containing members it shouldn't have, like locals,
         // and, well, everything but the first 4 properties
   { id: 1,
     username: 'admin',
     password: '8e3f8d3a98481a9073d2ab69f93ce73b',
     creation_date: '2012-03-05T18:08:55.701Z',
     locals: 
      { title: 'Logged in.',
        user: [Circular], //and now it's circular
        job_task_lists: [Object] },
     title: 'Logged in.',
     user: [Circular],
     job_task_lists: [ [Object], [Object], [Object], getById: [Function] ],
     attempts: [ '/home/dan/development/aqp/views/home.jade' ],
     scope: {},
     parentView: undefined,
     root: '/home/dan/development/aqp/views',
     defaultEngine: 'jade',
     settings: 
      { env: 'development',
        hints: true,
        views: '/home/dan/development/aqp/views',
        'view engine': 'jade' },
     app: 
      { stack: [Object],
        connections: 6,
        allowHalfOpen: true,
        _handle: [Object],
        _events: [Object],
        httpAllowHalfOpen: false,
        cache: [Object],
        settings: [Object],
        redirects: {},
        isCallbacks: {},
        _locals: [Object],
        dynamicViewHelpers: {},
        errorHandlers: [],
        route: '/',
        routes: [Object],
        router: [Getter],
        __usedRouter: true },
     partial: [Function],
     hint: true,
     filename: '/home/dan/development/aqp/views/home.jade',
     layout: false,
     isPartial: true } }

node.js:201
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
TypeError: Converting circular structure to JSON
    at Object.stringify (native)
    at Array.0 (/home/dan/development/aqp/node_modules/express/node_modules/connect/lib/middleware/session/memory.js:77:31)
    at EventEmitter._tickCallback (node.js:192:40)

See how the user object is nested?

  • Note that this time I did not send values in explicitly with 'locals' but it ended up in one (thats the source of the circular reference.

It looks like the session is being used to transfer objects to the view.

Here's my only middleware (it only reads from the session):

function requiresAuthentication(req, res, next){
    if (req.session.user){
        next();
    } else {
        next(new Error('Unauthorized. Please log in with a valid account.'))
    }
}

and the only time I modify the req.session is in this route:

app.post('/home', function (req,res,next) {
    var auth = require('./lib/authentication');
    auth.authenticate_user(req.body.user, function (user) {
        if (user){
            req.session.user = user;
            console.log('authenticated');
            res.redirect(req.body.redir || '/home');
            //next();
        } else {
            console.log('not authenticated');
            res.render('logins/new.jade', {title: 'Login Failed', redir:''})
        }
    });
});

I don't have much else going on in my application yet, as it's still quite young. I know I'm not mangling the session anywhere myself; I checked.

I did some more testing, and it appears this is only an issue when I then try to use the local variable on a page. For instance, here is my view home.jade

div(data-role="page")
    div(data-role="header")
        a(href='/logout', data-icon='delete', data-ajax="false") Log out
        h1= title
        a(href='/account', data-icon='info', data-ajax="false") Account

        != partial('user', user)


    each jtl in job_task_lists
        div(id=jtl.name, class = 'draggable_item', style='border:2px solid black;')
            #{jtl.name} - #{jtl.description} 
        a(data-icon='plus') 


    div(data-role="footer")
        h3 footer

script(src="/javascripts/home.js")

If I comment out the user partial, it renders, else I get this Converting circular structure to JSON issue.

UPDATE So after hooking up eclipse and the v8 debugger, I have been stepping through the code and I know where the mashup of session and user objects is occurring,

in node_modules/connect/lib/middleware/session/session.js

utils.union ends up mashing the members of the user object into the session, causing the circular reference. I'm just not sure why (admittedly probably my code)

like image 689
dwerner Avatar asked Mar 05 '12 03:03

dwerner


1 Answers

This was a problem with session data being modified in a view.

After much digging, I found that it was a bug in the way partials are handled in 2.5.8. I submitted an issue, and subsequently a patch. (in case anyone needs this info at a future date) as npm is still serving up Express 2.5.8 AFAIK.

Thanks for your help @freakish and @Ryan

like image 77
dwerner Avatar answered Oct 23 '22 13:10

dwerner