0

I have 2 servers that talk to each other. When Users want to check the health of the Sensor, it hits the API /check/health which in turn talks to Monitoring server to get the Status field of the Sensor.. This status is sent back to Provisioning and sent to the User.

I tested this and it works fine..ie when I do a http://localhost:3000/check/health/4 (3 is the Sensor ID ), i get the response as Good. When i do another request say http://localhost:3000/check/health/3, i get an error on the Provisioning server as below :

Does anyone know why I can't do a second GET request.. I seem to get it working only for the 1st health check not subsequent ones..

Output/error:

Server listening at port 3000
Connected to Monitoring Server!
Server got a health check for Sensor ID : 4
The Status of the Sensor is : Good
Server got a health check for Sensor ID : 3
The Status of the Sensor is : Undefined
_http_outgoing.js:344
throw new Error('Can\'t set headers after they are sent.');
^

Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:344:11)

Provisioning server:

// Code to communicate with Monitoring server
var io = require('socket.io-client');
var socket = io.connect('http://localhost:4000', { reconnect: true });
socket.on('connect', function(socket) {
  console.log('Connected to Monitoring Server!');
});

// Code to serve the Sensor users
var express = require('express');
var app= express();
var path = require('path');
var bodyParser= require('body-parser');

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/sensor_db');
var Schema = mongoose.Schema;

var sensorSchema = new Schema({
  value:{ type:Number, default:0},
  format:{type:String, default:"default"},
  id:{type:Number,required:true,unique:true},
  description:{type:String},
  type:{type:String},
  groupId:{type:Number},
  users:{type:Array,default:[]},
  admin:{type:String,default:'Undefined'},
  status:{type:String,default:'InActive'},
  owner:{type:String,default:'Undefined'},
  templateId:{type:Number}

});
var Sensor = mongoose.model('Sensor',sensorSchema);

// API to get the health of the Sensor by looking up its Status, pass the Sensor ID to check
app.get('/check/health/:id', function (req, res) {

  var id = req.params.id;
  console.log("Server got a health check for Sensor ID :", id);
  socket.emit('eventToClient',{ id: id })
  socket.on('reply', function (data) {
    console.log("The Status of the Sensor is :",data.status);
    res.json(data.status);
  });
})

app.listen(3000);
console.log('Server listening at port 3000');

Monitoring Server:

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var express = require('express');

var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/sensor_db') ;
var Schema = mongoose.Schema;

var sensorSchema = new Schema({
  value:{ type:Number, default:0},
  format:{type:String, default:"default"},
  id:{type:Number,required:true,unique:true},
  description:{type:String},
  type:{type:String},
  groupId:{type:Number},
  users:{type:Array,default:[]},
  admin:{type:String,default:'Undefined'},
  status:{type:String,default:'Undefined'},
  owner:{type:String,default:'Undefined'},
  templateId:{type:Number}

});
var Sensor = mongoose.model('Sensor',sensorSchema);

io.on('connection', function(socket){
  console.log('connection received from Provisioning');
  // To get messages from Provisioning server
  socket.on('eventToClient',function(data) {
    var id = data.id
    Sensor.findOne({id: id}, function (err, doc) {
      if (err) return res.status(500).send({error: err});
      socket.emit('reply',doc)

    })
  });
});

server.listen(4000, function(){
  console.log('socket.io server listening on *:4000');
});
Kishore Barik
  • 742
  • 3
  • 15
Jessi Ann George
  • 319
  • 1
  • 2
  • 9

4 Answers4

0

You are facing this issue because when you access /check/health/3, it emits eventToClient event and wait for reply event from monitoring server. But, meanwhile it waits for the reply, the endpoint exits.

After some time, when you receive reply event, you tries to send res.json. But, res has already been ended. That's why you are getting Can't set headers after they are sent. error.

Mukesh Sharma
  • 8,914
  • 8
  • 38
  • 55
0

This is an interesting problem.

When you hit the endpoint on your provisioning server /check/health/:id you are creating a new socket event listener each time.

The first time it works because there is only one listener expecting a 'reply' -- but the next time you hit the endpoint there are two competing listeners. The first listener sends a response and before you receive that response the second listener has attempted to set the header and errored out.

tgk
  • 3,857
  • 2
  • 27
  • 42
  • No, it is not creating new socket event listener each time. Socket connection is done only once, – Mukesh Sharma Apr 26 '16 at 06:14
  • @user3384225 : Reg your comment on :The first listener sends a response and before you receive that response the second listener has attempted to set the header and errored out. After I get the response only, i make the second request which is when I get the error. so they are not competing – Jessi Ann George Apr 26 '16 at 06:18
  • @JessiAnnGeorge no in in nodejs if you send request 100 back to back requests also it will process them accordingly and asynchronously. It's just it's not getting the *res* context when *reply* event is fired. The socket is not like a function you called and got value on its success. – Kishore Barik Apr 26 '16 at 06:27
  • Ah, yes sorry to not be clear, the first request response is not competing with the second. But when you make the second request (the first completed successfully), we create another event listeners that will send a response from the monitoring server, it is these two listeners that will compete and generate the error as they both try to set the headers. – tgk Apr 26 '16 at 06:29
  • Also I don't think it's a good idea to attach event listener every time you make a request. The event listener should be attached once. If you put the `socket.on('reply', function (data) { console.log("The Status of the Sensor is :",data.status); });` outside of the get endpoint also, still it can be called as many time from the monitoring server. It's just the res variable we have to take care. – Kishore Barik Apr 26 '16 at 06:35
  • So when I get the response from the 1st request saying Staus is Good, does it mean that the response from the Monitoring Server was handled and taken care of as we got the Good response? So they are not competing right? Sorry i want to understnd this better – Jessi Ann George Apr 26 '16 at 06:35
  • @Kishore : So I'll put that code outside the endpoint and check? – Jessi Ann George Apr 26 '16 at 06:39
0

I think the res variable is loosing its context. As socket.on('eventToClient',Fn) is an event listener which is fired only when a particular event is fired. It's a success call back type function. As if you put the socket.on('eventToClient',Fn) outside of get endpoint also, still it'll be fired. It only took the res context when the event listener was attached. So once the res was send for 1st request, next time the context was expired.

Update: : Check below code changes

Provisioning server:

// Code to communicate with Monitoring server
var io = require('socket.io-client');
var socket = io.connect('http://localhost:4000', { reconnect: true });
socket.on('connect', function(socket) {
  console.log('Connected to Monitoring Server!');
});

// Code to serve the Sensor users
var express = require('express');
var app= express();
var path = require('path');
var bodyParser= require('body-parser');

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/sensor_db');
var Schema = mongoose.Schema;

var sensorSchema = new Schema({
  value:{ type:Number, default:0},
  format:{type:String, default:"default"},
  id:{type:Number,required:true,unique:true},
  description:{type:String},
  type:{type:String},
  groupId:{type:Number},
  users:{type:Array,default:[]},
  admin:{type:String,default:'Undefined'},
  status:{type:String,default:'InActive'},
  owner:{type:String,default:'Undefined'},
  templateId:{type:Number}

});
var Sensor = mongoose.model('Sensor',sensorSchema);
var healthResObj = {};

socket.on('reply', function (data) {
    console.log("The Status of the Sensor is :", data.result.status);
    healthResObj[data.resKey].json(data.result.status); // get the response object from by providing its key
 delete healthResObj[data.resKey]; //remove the object from memory
  });

// API to get the health of the Sensor by looking up its Status, pass the Sensor ID to check
app.get('/check/health/:id', function (req, res) {

  var id = req.params.id;
  console.log("Server got a health check for Sensor ID :", id);
  socket.emit('eventToClient',{ id: id })
  var key = Date.now();
  healthResObj[key] = res;
  socket.emit('eventToClient',{ id: id , resKey: key}); // pass the key where the response object stored so that can be retrieved later 
})

app.listen(3000);
console.log('Server listening at port 3000');

Monitoring Server:

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var express = require('express');

var mongoose = require('mongoose');
mongoose.connect('mongodb://127.0.0.1:27017/sensor_db') ;
var Schema = mongoose.Schema;

var sensorSchema = new Schema({
  value:{ type:Number, default:0},
  format:{type:String, default:"default"},
  id:{type:Number,required:true,unique:true},
  description:{type:String},
  type:{type:String},
  groupId:{type:Number},
  users:{type:Array,default:[]},
  admin:{type:String,default:'Undefined'},
  status:{type:String,default:'Undefined'},
  owner:{type:String,default:'Undefined'},
  templateId:{type:Number}

});
var Sensor = mongoose.model('Sensor',sensorSchema);

io.on('connection', function(socket){
  console.log('connection received from Provisioning');
  // To get messages from Provisioning server
  socket.on('eventToClient',function(data) {
    var id = data.id
    Sensor.findOne({id: id}, function (err, doc) {
      if (err) return res.status(500).send({error: err});
      socket.emit('reply',{result:doc, resKey: data.resKey}); //forwarding the resKey( the key of the response);

    })
  });
});

server.listen(4000, function(){
  console.log('socket.io server listening on *:4000');
});
Kishore Barik
  • 742
  • 3
  • 15
  • Thanks Kishore! Do you mind if you could type the code needed as I don't fully understand :( Its this piece of code that needs changing - app.get('/check/health/:id', function (req, res) { var id = req.params.id; console.log("Server got a health check for Sensor ID :", id); socket.emit('eventToClient',{ id: id }) socket.on('reply', function (data) { console.log("The Status of the Sensor is :",data.status); res.json(data.status); }); }) – Jessi Ann George Apr 26 '16 at 06:27
  • Yah, I could have.. But right now I can't :( may be later – Kishore Barik Apr 26 '16 at 06:41
  • Thanks Kishore! I've tried your code.. I get this error on the provisionng side – Jessi Ann George Apr 26 '16 at 16:51
  • Server listening at port 3000 Connected to Monitoring Server! Server got a health check for Sensor ID : 4 The Status of the Sensor is : Good c:\Users\jessig\WebstormProjects\Provisioning_server\app.js:227 healthResObj[data.resKey].json(data.result.status); // get the response object from by providing its key ^ – Jessi Ann George Apr 26 '16 at 16:51
  • Error on this line : healthResObj[data.resKey].json(data.result.status); // get the response object from by provid – Jessi Ann George Apr 26 '16 at 16:54
  • ran perfectly on my system. can you console.log data.resKey just before the line you are getting error? make sure you are sending the resKey back from Monitoring server. – Kishore Barik Apr 26 '16 at 17:29
  • Found the mistake, i think this line of code on the provisiong was added acidently.. socket.emit('eventToClient',{ id: id }) After removing its working perfectlyy.. Much much thanks – Jessi Ann George Apr 26 '16 at 18:35
  • @JessiAnnGeorge remove the response object from memory to avoid memory leaks by adding `delete healthResObj[data.resKey];` after you are done with the response object. I have updated the answer – Kishore Barik Apr 26 '16 at 19:09
0

You could demonstrate my point by updating the provisioning endpoint to always return an OK but leaving the socket.on('reply', function())

app.get('/check/health/:id', function (req, res) {

  var id = req.params.id;
  console.log("Server got a health check for Sensor ID :", id);
  socket.emit('eventToClient',{ id: id })
  socket.on('reply', function (data) {
    console.log("The Status of the Sensor is :",data.status);
  });
  res.json("ok")
})

you'll first see 1 response from the 'reply' then 2... then 3

Server got a health check for Sensor ID : 3
The Status of the Sensor is : Good
Server got a health check for Sensor ID : 3
The Status of the Sensor is : Good
The Status of the Sensor is : Good
Server got a health check for Sensor ID : 3
The Status of the Sensor is : Good
The Status of the Sensor is : Good
The Status of the Sensor is : Good
tgk
  • 3,857
  • 2
  • 27
  • 42