Nodejs stream (Error: Can't set headers after they are sent) - javascript

I am trying to build an api from the braintree servers. Referring to this doc https://developers.braintreepayments.com/javascript+node/reference/general/result-handling/search-results
To access all the transactions from their server I have to return a node stream.
ex
app.get('/project', function(req, res) {
if(req.user) {
var stream = gateway.transaction.search(function (search) {
search.customerId().is(req.user.id);
});
stream.on("ready", function () {
console.log(stream.searchResponse);
});
stream.on("data", function (data) {
res.json(data) // can't set headers after they are sent.
});
}
});
I understand a stream returns data in chunks, so the res.json() above is most likely is being called multiple times resulting in Error: Can't set headers after they are sent.
So my question is how can I send that data to the client in one chunk? The nodejs streaming is confusing to me, I am going to read up more about it, but it would be great to understand how to send the data to the client without re-sending the headers.

You shouldn't make any assumptions about data events unless the stream you are reading from is in object mode. You could get one data event or a hundred (depending on the input size of course) because TCP is a stream.
What you probably want is something like this instead (assuming stream is not in object mode):
app.get('/project', function(req, res) {
if(req.user) {
var stream = gateway.transaction.search(function (search) {
search.customerId().is(req.user.id);
});
stream.on("ready", function () {
console.log(stream.searchResponse);
});
var buf = '';
stream.on("data", function (data) {
buf += data;
});
stream.on("end", function() {
res.setHeader('Content-Type', 'application/json');
res.send(buf);
});
}
});
Or just pipe the stream to the response:
app.get('/project', function(req, res) {
if(req.user) {
var stream = gateway.transaction.search(function (search) {
search.customerId().is(req.user.id);
});
stream.on("ready", function () {
console.log(stream.searchResponse);
});
res.setHeader('Content-Type', 'application/json');
stream.pipe(res);
}
});
For an object stream you might do:
app.get('/project', function(req, res) {
if(req.user) {
var stream = gateway.transaction.search(function (search) {
search.customerId().is(req.user.id);
});
stream.on("ready", function () {
console.log(stream.searchResponse);
});
var result = [];
stream.on("data", function (data) {
result.push(data);
});
stream.on("end", function() {
res.json(result);
});
}
});

Related

Reading a stream over HTTP with Javascript

I am trying to build a web app to stream music. I use MongoDB to store the audio, a Node API to connect to the database and a Vuejs frontend. Below is the endpoint which streams the music, based on this article: https://medium.com/#richard534/uploading-streaming-audio-using-nodejs-express-mongodb-gridfs-b031a0bcb20f
trackRoute.get('/:trackID', (req, res) => {
try {
var trackID = new ObjectID(req.params.trackID);
} catch (err) {
return res.status(400).json({ message: "Invalid trackID in URL parameter. Must be a single String of 12 bytes or a string of 24 hex characters" });
}
res.set('content-type', 'audio/mp3');
res.set('accept-ranges', 'bytes');
let bucket = new mongodb.GridFSBucket(db, {
bucketName: 'tracks'
});
let downloadStream = bucket.openDownloadStream(trackID);
downloadStream.on('data', (chunk) => {
res.write(chunk);
});
downloadStream.on('error', () => {
res.sendStatus(404);
});
downloadStream.on('end', () => {
res.end();
});
});
I tested it with Postman and it works there. I am trying to read the stream in my Vuejs application. I'm just not sure how to do it. I tried the following to test it:
const url = 'http://localhost:4343/api/track/6061c90b2658b9001e65311d';
http.get(url, function (res) {
res.on('data', function (buf) {
console.log(buf);
});
res.on('end', function () {
console.log('ended');
});
})
This does not work however. How should I go about reading it in the frontend?

create a website uptime monitor in Node.js

I want to create a uptime monitor using NodeJS and MongoDB. I want to run a cron job in NodeJS and store the data into MongoDB. If the website response status code is not equal to 200 then it will be saved in the database. I want to make a database entry like this,
url : http://www.google.com
status_code : 500
start_time :- start time
end_time :- end time
I can run the cron job but not sure how to save the downtime in the database. As, I don't want to store every response into the database. Only when response status code is other than 200 , then it will start tracking (start_time) the URL and it keeps the time when website is back to 200 as end_time.
cron.js :-
var async=require('async');
const Entry = require('../models/health.model.js');
var https = require('https');
var request = require('request');
module.exports = function getHttpsRequests () {
Entry.find({},function(err,entrys){
console.log(err);
if(!err && entrys){
async.each(entrys,function(entry,callback){
request(entry.url, function (error, response, body) {
entry.statuscheck=response.statusCode;
entry.save();
callback();
});
},function (error) {
});
}
});
}
health.model.js :-
const mongoose = require('mongoose');
const EntrySchema = mongoose.Schema({
url: String,
statuscheck: String
}, {
timestamps: true
});
module.exports = mongoose.model('Entry', EntrySchema);
I would do something like this to handle updating the database. I went ahead and put standard arrow functions in, because it was easier for me that way. I put some comments in so that should clear most questions up. It may not be the most elegant solution because I wrote it in 5 minutes, but if you follow this general logic flow, you should be much closer to your solution (its completely untested mind you.)
var async=require('async');
const Entry = require('../models/health.model.js');
var https = require('https');
var request = require('request');
module.exports = function getHttpsRequests () {
Entry.find({}, (err,entrys) => {
console.log(err);
if (!err && entrys) {
async.each(entrys, (entry,callback) => {
request(entry.url, (error, response, body) => {
//first check if the url has a document in the db.
Entry.find({ url: entry.url }, (err, entry) => {
if(!entry) {
//since the document does not exist, check the statusCode.
if(response.statusCode===200) { //if the statusCode is 200, continue the loop.
callback();
} else { //if the status code is not 200, lets save this to the db.
console.log("Saving object: " + entry)
entry.status_code = response.statusCode;
entry.start_time = new Date();
entry.save();
callback();
}
} else if (entry) {
//since the document exists, lets check the statusCode.
if(response.statusCode===200) { //if the statusCode is 200, update the stop_time.
entry.end_time = new Date();
Entry.findOneAndUpdate({ url: entry.url }, entry, (err, object) => { //this returns the entry after update, so we can put that in the console for easy debug.
if (err) {
console.log(err);
callback();
} else {
console.log("Object saved: " + object);
callback();
}
});
}
} else { //there was an error finding the document in the db, just go to the next one.
callback();
});
});
});
}
});
}

Sails + Braintree : Unable to send the transactions details to client

I'm developing an application using Sails JS and Braintree. I'm trying to send the all past transaction details that the customer has made.
Here is my getTransaction action
getTransaction: function(req, res) {
var customerId = req.param('customerId');
var gateway = setUpGateway();
var stream = gateway.transaction.search(function(search) {
search.customerId().is(customerId);
}, function(err, response) {
if (err) {
return res.serverError(err);
}
res.send(response);
});
},
But the problem is, if I directly send the response which I got from braintree server, it throws the circular dependency error. So, to overcome that error I'm fetching only those details that I need from response like this
getTransaction: function(req, res) {
var customerId = req.param('customerId');
var gateway = setUpGateway();
var stream = gateway.transaction.search(function(search) {
search.customerId().is(customerId);
}, function(err, response) {
if (err) {
return res.serverError(err);
}
var transactions = [];
response.each(function(err, transaction) {
var temp = [];
temp.push(transaction.id);
temp.push(transaction.amount);
temp.push(transaction.createdAt);
transactions.push(temp);
});
res.send(transactions);
});
},
But here the .each function is getting executed asynchronously and hence res.send returns the empty array. So what should I do to return all the transaction that the user has made?
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact our support team.
You are correct that the iterator executes asynchronously. You should use Node's stream semantics to process the request
getTransaction: function(req, res) {
var customerId = req.param('customerId');
var gateway = setUpGateway();
var transactions = [];
var stream = gateway.transaction.search(function(search) {
search.customerId().is(customerId);
});
stream.on('data', function (transaction) {
transactions.push(transaction);
});
stream.on('end', function () {
res.send(transactions);
});
},
This will wait until all transactions have been processed before sending the result.
This page provides more information about searching using our Node client library and Node's Stream API.

express.js - how to intercept response.send() / response.json()

Lets say I have multiple places where I call response.send(someData). Now I want to create a single global interceptor where I catch all .send methods and make some changes to someData. Is there any way in express.js? (hooks, listeners, interceptors, ...)?
You can define a middleware as below (taken and modified from this answer)
function modifyResponseBody(req, res, next) {
var oldSend = res.send;
res.send = function(data){
// arguments[0] (or `data`) contains the response body
arguments[0] = "modified : " + arguments[0];
oldSend.apply(res, arguments);
}
next();
}
app.use(modifyResponseBody);
for those finding on google, based off the top answer:
app.use((req, res, next) => {
const oldSend = res.send
res.send = function(data) {
console.log(data) // do something with the data
res.send = oldSend // set function back to avoid the 'double-send'
return res.send(data) // just call as normal with data
}
next()
})
Yes this is possible. There are two ways to do this, one is to use a library that provides the interception, with the ability to run it based on a specific condition:
https://www.npmjs.com/package/express-interceptor
The other option is to just create your own middleware (for express) as follows:
function modify(req, res, next){
res.body = "this is the modified/new response";
next();
}
express.use(modify);
Just want to provide a practical usage example with intercepting res.json.
When we're writing express server, we might send the status and message in every response according to the situation like below.
app.post('/test', (req, res) => {
res.json({status: 1, message: "some_error", otherData: "nothing"})
})
But, what if I don't want to write the status and message in every time? I could add new function to build a template response body to send the data when using res.json.
const messageMap = {
0: "success",
1: "some_error"
}
app.use((req, res, next) => {
const originJson = res.json
res.json = (status, jsonData) => {
const fixedResponse = {
status,
message: messageMap[status]
}
originJson.call(res, {...jsonData, ...fixedResponse})
}
next()
})
Then I just need to use below function.
app.get("/test", (req, res) => {
res.json(1, {otherData: 1})
})
You can even use builder pattern to do this.
app.use((req, res) => {
res.buildFixedResponse = function (status) {
const fixedResponse = {
status,
message: messageMap[status]
}
res.json = function (jsonData) {
originJson.call(this, {...jsonData, ...fixedResponse})
}
return this
}
})
Then trigger function like below.
app.get("/test", (req, res) => {
res.buildFixedResponse(1).json({otherData: 1})
})
For my case, I had to use a middleware with typicode/json-server and be able to get a different response object than just a blunt javascript array.
While the typicode/json-server response was something like:
[
{
...
}
]
After applying the middleware:
module.exports = (req, res, next) => {
const oldSend = res.send;
res.send = (data) => {
const oldData = JSON.parse(data);
// we want to change the response only if a blunt array is sent
// also, we do this for the old sake of not sending json arrays
if(Object.prototype.toString.call(oldData) === '[object Array]') {
data = {
data: oldData
};
}
res.send = oldSend;
return res.send(data);
};
next();
}
The response looks as follows:
{
data: [
{
...
}
]
}
You can simply do it using NODEJS and Express, say you are calling an API and want to send modify the data before sending response back.
router.get('/id', (req,res) => {
... //your code here filling DATA
let testData = {
"C1": "Data1",
"C2": "Data2",
"yourdata": DATA
};
res.send(testData);
});

Send event to client, from server, and then disconnect the socket (from server)

I want to send an event back to a client, in Socket.io, and immediately after that to disconnect the client. The problem is that calling socket.emit() will not emit anything...
Do you know, or stepped into this kind of problem?
my code is like this:
- on server:
userCredentialsCheck: function (socket, next, callback) {
appLogSys.info('Check cookie!');
.....
var handshake = socket.request;
.....
parseCookie(handshake, null, function (err, data) {
sessionSrv.get(handshake, function (err, session) {
if (err) {
appLogSys.error(err.message);
next(new Error(err.message));
}
else
{
......
socket.emit('na', 'Error checking session! Good bye!');
socket.disconnect();
}
})
})
};
on client:
appSocket.on('na', function(d){
console.log('Error: '+d);
console.log('Now, redirect!');
//REDIRECT!
var delay = 1000; //Your delay in milliseconds
var URL = '/';
setTimeout(function(){ window.location = URL; }, delay);
});
Thank you!

Categories

Resources