Node.JS Restify and post Events - node.js

Hello I have a post event that takes in key value and maps it to a sproc. This part works very well. However, for some reason I cannot return the data response as JSON.
It says the return type is "octet stream" even though I'm setting it to application/json.
I'm a little new to REST, so if I'm doing something incorrectly, please let me know.
server.post({ path: PATH + '/data.json', version: '1.0' }, genericData);
function genericData(req, res, next) {
if (req.params.data != null){
var sp_code_name = config.get('data:' + req.params.data);
var connection = new sql.Connection(conn_str, function(err) {
// ... error checks
if(err) {
return console.log("Could not connect to sql: ", err);
connection.close();
}
});
var request = new sql.Request(connection);
console.log(sp_code_name);
request.execute(sp_code_name, function (err, recordset, returnValue) {
if (err) {
connection.close();
return console.log("Is this a good query or the right table?: ", err);
}
//if (recordset && returnValue == 0) {
res.setHeader('Access-Control-Allow-Origin','*');
res.setHeader('content-type', 'application/json');
res.send(200, recordset[0]);
console.log(recordset[0]);
// return next();
// }
// return next();
connection.close();
});
}
res.setHeader('content-type', 'application/text');
res.send(200, "Something is wrong with the stream.");
}

You need to wrap your error response in an else clause:
if (req.params.data != null){
...
} else {
res.setHeader('content-type', 'application/text');
res.send(200, "Something is wrong with the stream.");
}
Without the else clause, it's getting called on every request before your request.execute callback is called. That's setting your content-type to application/text instead of application/json as you're expecting.

Related

Node.js Http Write after end

I wrote a simple CRUD Api in node.js. I keep getting a write after end on all of my POST, PUT and DELETE functions. I have lost all sense of trying to trouble shoot this thing and was hoping for a fresh pair of eyes or some advice on trying to track it down. GET works perfectly fine and uses the same code sendJSON() in httpmgs.js.
I have scattered all kinds comments around trying to figure out where the error is happening. I didn't know if the req.on("end".... was causing an issue so I have rewritten that part to no avail.
controller.js:
switch (req.method){
case "GET":
if(req.url === "/staff"){
staff.getList(req, resp);
}
break;
case "POST":
if (req.url === "/staff/add"){
var reqBody = '';
req.on("data", function (data){
reqBody += data;
if(reqBody.length > 1e7){
msg.show413(req, resp);
}
});
req.on("end", function() {
console.log(req.data)
staff.add(req, resp, reqBody)
});
}
break;
case "PUT":
if (req.url === "/staff/update"){
var reqBody = '';
req.on("data", function (data){
reqBody += data;
if(reqBody.length > 1e7){
msg.show413(req, resp);
}
});
req.on("end", function() {
staff.update(req, resp, reqBody);
});
}
else{
msg.show404(req,resp);
}
break;
staff.js:
exports.add = function (req, resp, reqBody){
try{
if (!reqBody) throw new Error("Input not valid");
var data = JSON.parse(reqBody);
if (data){
db.executeSql("SELECT MAX(ID) AS ID FROM jarvisdb.staff",function (maxID, err){
if(err){
msg.show500(req, resp, err);
}
else{
var newID = maxID[0].ID+1
var sql = "INSERT INTO jarvisdb.staff (`ID`, `First Name`, `Last Name`) VALUES";
sql+= util.format("('%d' ,\"%s\", \"%s\")", newID, data.firstName, data.lastName);
db.executeSql(sql, (data, err)=>{
if(err){
msg.show500(req, resp, err)
}
else{
console.log('Before send')
msg.sendJSON(req,resp,data)
console.log('After send')
}
})
}
});
}
else{
throw new Error("Input not valid");
}
}
catch (ex) {
console.log('500')
msg.show500(req, resp, ex);
}
};
httpMsgs.js:
exports.sendJSON = function(req,resp,data){
if(data){
console.log('Before Write')
resp.writeHead(200, {"Content-type" : "application/json"});
console.log('Write Head')
resp.write(JSON.stringify(data));
console.log('Write body')
resp.end();
console.log('end');
}
}
Expected out:
No errors and sends back JSON
Actual results:
Listening on port 9000....
undefined
Before send
Before Write
Write Head
Write body
end
After send
events.js:174
throw er; // Unhandled 'error' event
^
Error [ERR_STREAM_WRITE_AFTER_END]: write after end
at write_ (_http_outgoing.js:572:17)
at ServerResponse.write (_http_outgoing.js:567:10)
at Object.exports.sendJSON (E:\JARVIS\API\peak-2.0\httpMsgs.js:60:14)
at db.executeSql (E:\JARVIS\API\peak-2.0\controllers\staff.js:53:33)
at Execute.conn.execute [as onResult] (E:\JARVIS\API\peak-2.0\db.js:35:9)
at process.nextTick (E:\JARVIS\API\peak-2.0\node_modules\mysql2\lib\commands\query.js:76:16)
at process._tickCallback (internal/process/next_tick.js:61:11)
Emitted 'error' event at:
at writeAfterEndNT (_http_outgoing.js:634:7)
at process._tickCallback (internal/process/next_tick.js:63:19)
There is no
break;
after your case. So, all cases beyond the current matched case will be executed sequentially until the end.
EDIT
Try restarting your SQL server
UPDATE
You have two options :
In your sendJSON function use:
OPTION 1 (Without Express):
resp.writeHead('Content-Type', 'application/json');
resp.end(JSON.stringify(data));
OR
OPTION 2 (With Express):
Here you don't need to specify the header type
res.json(data);

How to catch an empty reply from an API {}

I'm currently using Node.js to fetch an API data and converting it into my own Express.js API server to send my own data (The 2 APIs I'm using changes the structure sometime and I have some users that need to keep the same structure).
So here is the code I'm using
app.get('/app/account/:accountid', function (req, res) {
return fetch('https://server1.com/api/account/' + req.params.accountid)
.then(function (res) {
var contentType = res.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
apiServer = 'server1';
return res.json();
} else {
apiServer = 'server2';
throw "server1 did not reply properly";
}
}).then(server1Reconstruct).then(function (json) {
res.setHeader('Content-Type', 'application/json');
return res.send(json);
}).catch(function (err) {
console.log(err);
}).then(function () {
if (apiServer == 'server2') {
server2.fetchAccount({
accountID: [Number(req.params.accountid)],
language: "eng_us"
})
.then(server2Reconstruct)
.then(function (json) {
res.setHeader('Content-Type', 'application/json');
return res.send(json);
}).catch(function (err) {
console.log(err);
});
}
});
})
To quickly explain the code: I call server1 through a normal Fetch this answer might be {} which is where I have a problem. If the accountid doesn't exist the server returns an JSON response with no errors to grab...
What should I do to be able to catch it... And If I catch it switch to server 2.
(Don't be too confused about server2 call as it's another package).
If I understand your problem correctly, you should follow those steps :
fetch the initial API
call the .json() method on the result - which returns a promise
deal with the json response in the first .then(json => ...), and here check if the result is {} then call server2, else call server1
BTW, your code looks very messy with all those then and catch, I recommend putting some stuff into functions, and using async/await if you can.
Here is some pseudo-code sample that you could use :
function server2Call() {
return server2.fetchAccount({
accountID: [Number(req.params.accountid)],
language: 'eng_us'
})
.then(server2Reconstruct)
.then(function (json) {
res.setHeader('Content-Type', 'application/json');
return res.send(json);
}).catch(function (err) {
console.log(err);
});
}
app.get('/app/account/:accountid', function (req, res) {
return fetch('https://server1.com/api/account/' + req.params.accountid)
.then(res => {
var contentType = res.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return res.json();
} else {
server2Call()
}
})
.then(json => {
res.setHeader('Content-Type', 'application/json');
if (json is {}) return server2Call()
else return res.send(json);
}).catch(function (err) {
console.log(err);
})
});

How to create data and render within the one get request

dashboardRouter.route('/:Teachersubjects/:Class/:Salary')
.get(function(req,res)
{
function handleErr(err,redir)
{
if(redir){
res.end("err");
}
else{
res.writeHead(200, {"content-type": "text/plain"});
res.end('Error occured');
}
}
Verify.verifySchoolUser(req,res,function(err,schooluserId){
if(err){
res.redirect('/schoolusers/login');
return;
}
else{
SchoolUser.findOne({_id:schooluserId}).exec(function(err,schooluser){
if(err)
{
console.log("NOUSER school")
handleErr(err);
return;
}
else
{
if(schooluser.count<4){
console.log(req.params.Class);
vacancies.create(req.params,function(err,vacancy){
if(err){
console.log(err.message);
console.log(err.name);
console.log("create error");
for (field in err.errors) {
console.log(err.errors[field].message);
}
handleErr(err);
return;
}
console.log(vacancy);
schooluser.vacancies.push(vacancy._id);
schooluser.save(function(err){
if(err){
console.log(JSON.stringify(err));
handleErr(err);
}
teacherforms.find({}, function (err, teacherforms) {
if (err) throw err;
else {
var teacherforms = teacherforms;
console.log(teacherforms);
var arr=[];
for(i=0; i<teacherforms.length; i++ ) {
arr.push(teacherforms[i]._id);
console.log(arr);
}
teacherusers.find({checking:{$in:arr}}, function (err, teacherusers){
if (err) throw err;
else{
var data =[];
for(i=0; i<teacherusers.length; i++ ) {
var kdata={};
var str = "";
kdata.firstname = teacherusers[i].firstname;
kdata.profilepic= teacherusers[i].profilepic;
kdata.experience= teacherforms[i].Teachingexperience;
var len= teacherforms[i].Qualifications.length;
console.log(teacherforms[i].Qualifications);
for(j=len-1;j>=0;j--){
if(j==0){
str = str + teacherforms[i].Qualifications[j];
}
else{
str=str + teacherforms[i].Qualifications[j] + ", ";
}
}
kdata.qualifications = str;
data.push(kdata);
}
data.push(vacancy.Class);
//data.push(vacancy.Qualification);
data.push(vacancy.Teachersubjects);
console.log(data);
res.render('../views/dashboard/dashboard_resumes.ejs',{
sdata:data
});
}
});
}
})
});
})
}
else {
res.send("Limit Exceeded");
}
}
});
}
});
});
I want to save a object firstly in MongoDB at get request and then render a page in ejs with a new object.But I am getting a error 500(Internal Server Error),so can we save a object and render the ejs page with new object at same end point?
P.S- The code below is the crucial part of the code and there is no syntax error and schooluser(object) is passed from the upper part of the code.
dashboardRouter.route('/:Teachersubjects/:Class/:Salary')
.get(function(req, res) {
vacancies.create(req.params, function(err, vacancy) {
if (err) {
console.log(err.message);
console.log(err.name);
console.log("create error");
for (field in err.errors) {
console.log(err.errors[field].message);
}
handleErr(err);
return;
}
schooluser.vacancies.push(vacancy._id);
schooluser.save(function(err) {
if (err) {
handleErr(err);
} else {
res.render('../views/dashboard/dashboard_resumes.ejs', {
sdata: data
});
}
});
});
});
There are multiple issues with your route handler. First, whenever an error occurs you run this through your handleErr() function, but you never do anything with res in these cases. This means that the request is never resolved and eventually times out on the client side.
When an error occurs, you should respond with an error code. For instance:
res.status(400).send('Something went wrong');
When you actually do send a response and render the template, you're applying a variable called data. However, this variable doesn't seem to be declared anywhere in your code. Maybe you left out that code, but if that variable is indeed undeclared, it throws a ReferenceError which is caught by the routing framework which in turn responds with a 500 Internal Server Error. So this is probably the source of your issue.
Any uncaught errors thrown by vacancies.create() or schooluser.save() would have the same effect.

Why does this request to Node server hang?

I am very new to express and node. I was trying to upload an image using multiparty and code given here.
I have put a check for file size. When I upload a file of size greater than the limit it lands in the "problem section". The problem is the server hangs and responds back only after request timeout. I have tried many solution on stack overflow but nothing seems to work. It works if the file size is below the limit. I am very sure that the code reaches the problem section and there is no problem with the upload logic. But it seems that I have to do something in the "problem section". Please tell me what am I missing.
I have replaced the code in the problem section with
next(), res.send(), res.end(), next(err), return; but It does not work. It hangs no matter what.
Following is the code:
router.post("/image", function(req, res, next) {
if(req.user) {
upload.uploadToS3(req, S3_BUCKET, S3_PROFILE_IMAGE_FOLDER, function(result) {
if(result.error != null && result.error === false) {
models.Customer.update({
orignalImage : result.fileLocation
},{
where : { mobileNumber : req.user.mobileNumber}
}).then(function(customer) {
if(customer) {
res.send({
url: result.fileLocation,
error : false
});
} else {
res.status(400);
res.send({error : true,
error_message : 'Image upload failed'});
}
});
} else {
//PROBLEM SECTION
res.status(404);
res.json({error : true, error_message : result.error_message});
}
});
} else {
res.status(403);
res.send({message: "access denied"});
}
});
response after timeout
Please tell me if you need more details I will upload it.
var uploadToS3 = function(req, S3_BUCKET, folder, callback) {
var s3Client = knox.createClient({
secure: false,
key: awsConfig.accessKeyId,
secret: awsConfig.secretAccessKey,
bucket: S3_BUCKET,
});
var headers = {
'x-amz-acl': 'public-read',
};
var form = new multiparty.Form();
var batch = new Batch();
batch.push(function(cb) {
form.on('part', function(part) {
var validity = validateFile({type : part.headers['content-type'], name : part.filename, length : part.byteCount});
console.log(validity);
if(validity.isValid) {
cb(null, { filename : folder+"/"+generateFileName({name : part.filename}), part : part});
} else {
cb({error : true, error_message : validity.reason, part:part }, "advra kedavra");
}
});
});
batch.end(function(err, results) {
if (err) {
console.log(err);
err.statusCode = 200;
callback(err);
} else {
form.removeListener('close', onEnd);
var result = results[0];
var part = result.part;
var destPath = result.filename;
var counter = new ByteCounter();
part.pipe(counter); // need this until knox upgrades to streams2
headers['Content-Length'] = part.byteCount;
s3Client.putStream(part, destPath, headers, function(err, s3Response) {
result = {};
if(err) {
console.log(err);
result.error = true;
result.error_message = "Problem in uploading!";
} else {
console.log(s3Response.req.url);
result = {error: false, statusCode : s3Response.statusCode, message : "file upload successful.", fileLocation : s3Response.req.url};
}
callback(result);
});
part.on('end', function() {
console.log("File upload complete", counter.bytes);
});
}
});
function onEnd() {
console.log("no uploaded file");
callback({error:false, error_message:"no uploaded file."});
}
form.on('error', function(err) {
console.log('Error parsing form: ' + err.stack);
});
form.on('close', onEnd);
form.parse(req);
}
After a 3 day long search for the answer I found one answer. Express.js close response
The problem section should be the following :
res.status(400);
res.set("Connection", "close");
res.json({error:true, error_message : "<some - error message>"});
You should simply add a .end() after setting the status as: res.status(400).end();
See official docs
res.end([data] [, encoding])
Ends the response process. This method actually comes from Node core, specifically the response.end() method of http.ServerResponse.
Use to quickly end the response without any data. If you need to respond with data, instead use methods such as res.send() and res.json().
res.end();
res.status(404).end();
res.status(400);
res.set("Connection", "close");
res.json({error:true, error_message : "<some - error message>"});
I am not sure that solves your issue. The 'problem section' is in your callback, which would only run after the upLoadToS3 function runs. The 'problem' is probably with that function. You might have to refactor that to handle large file uploads.

Is there a way to return a 5XX error from a node.js express basicAuth handler?

My express app uses the following basic auth function:
exports.basicAuth = express.basicAuth(function (user, pass, callback) {
findUserByEmail(user, function (err, account) {
var isPasswordMatch = false;
log.info("Authenticate request user-pass: " + user + ":" + pass);
if (err) {
log.info("Error occurred when authenticate user: " + err);
}
if (account == null || account == undefined) {
log.info("Account not found");
} else {
if (!isActive(account)) {
log.info("Account is not active");
}
if (account.password != encryptPassword(account.salt, pass)) {
log.info("Wrong pass");
isPasswordMatch = false;
} else {
isPasswordMatch = true;
}
}
var authenticated = err == null && account != null && account != undefined && isActive(account) && isPasswordMatch;
callback(null, authenticated);
});
});
Sometimes, when the server is overloaded the findUserByEmail request will fail. In this case, the function above causes the server to return a 401 error. Instead, I want to return a 5XX error code, so that clients know that there is a problem at the server, not necessarily with the credentials they passed. What is the best way to cause an error in findUserByEmail to return a 5XX error code instead of a 401?
I found a solution. I just wrapped the authentication handler in a standard request handler and send an error response if something goes wrong.
exports.basicAuth = function(req, res, next) {
express.basicAuth(function (user, pass, callback) {
findUserByEmail(user, function (err, account) {
var isPasswordMatch = false;
log.info("Authenticate request user-pass: " + user + ":" + pass);
if (err) {
log.info("Error occurred when authenticate user");
res.send(500, { error: 'Unable to authenticate request' });
return;
}
...
});
})(req, res, next);
};

Resources