I'm creating an Node.js API with TypeScript which I want to host in Azure Functions. It sends requests to another public API. I got it to work but it has a bug which I can't resolve.
I'm sending an http post request to my API with Postman. In the body of the request there is a JSON object with one property "id".
But I don't get the last value back as a response. I get the second last value as a response. Assume the first request with id "1" gets "good" as a response. The second request with the id "2" gets a "good" as a response, too. But it should get "bad" as a response. The third request with the id "3" gets as a "bad" as response but it should get "good". The fourth request with the id "4" gets "good" as a response but it should get "bad".
The problem just exsits in my API. When I send requests directly to the another API via Postman, I don't get this problem.
index.js
module.exports = function (context, req) {
if (req.body.checkID) {
api.validationStatus(req.body.checkID)
.then(function (resp) {
if (resp.validated) { // Number is correct
context.log('Result: good');
res = { body: "Result: good" }
}
else if (resp.name === "StatusCodeError") { // Number is not correct
context.log('Result: bad ' + resp.name);
res = { body: "Result: bad " + resp.name }
}
else if (resp.name === "RequestError") { // API is not reachable
context.log('Result, bad ' + resp.name);
res = { body: "Result, bad " + resp.name }
}
else { // Something else went wrong
context.log("Result, bad.");
res = { body: "Result: bad." }
}
});
}
else {
res = {
status: 400,
body: "Please pass an id"
};
}
context.done(null, res);};
api.js
myApi.prototype.validationStatus = function (id) {
// Request options
var options = {
method: 'get',
url: this.config.validationStatusLocationUrl.replace("{id}", id),
headers: { "Authorization": this.config.apiKey }
};
// Request with a promise
return reqpromise(options)
.then(function (res) {
return JSON.parse(res);
})
.catch(function (err) {
return err;
});
};
I got it. The solution is simple and I assume I know why it had this behavior.
The context.done(); needs to be executed in the .then-Section of the promise (in the if and the else section). The problem was that I was sending a response even when the current api request (api.validationStatus()) was not finished yet (async, round robin style) and so I got just the old value of the previous requests.
index.js
module.exports = function (context, req) {
if (req.body.checkID) {
api.validationStatus(req.body.checkID)
.then(function (resp) {
if (resp.validated) { // Number is correct
context.log('Result, good.');
context.res = { body: "Result, good." }
}
else if (resp.name === "StatusCodeError") { // Number is not correct
context.log('Result, bad: ' + resp.name);
context.res = { body: "Result, bad." + resp.name }
context.done();
}
else if (resp.name === "RequestError") { // API is not reachable
context.log('Result, bad: ' + resp.name);
context.res = { body: "Result, bad." + resp.name }
context.done();
}
else { // Something else went wrong
context.log("Result, bad.");
context.res = { body: "Result, bad." }
context.done();
}
context.done();
});
}
else {
context.res = {
status: 400,
body: "Please pass an id"
};
context.done();
}
};
Related
Why I am not getting the desired output as i given in res variable for returning from this route
#Get('/signout')
signOut(#Session() session: any) {
session.userId = null;
let res = JSON.parse(
JSON.stringify({
status: 'success',
}),
);
console.log( "--> ", res);
if (!session.userId) {
return res;
}
}
i am getting empty json object as response
{}
i tried printing out my res var it outputs fine.
To help debug your code have you tried just returning the response without any logic ?
#Get('/signout')
signOut(#Session() session: any) {
const res = {
status: 'success',
}
console.log( "--> ", JSON.stringify(res));
return res;
}
and then try
#Get('/signout')
signOut(#Session() session: any) {
session.userId = null;
const res = {
status: 'success',
};
console.log( "--> ", JSON.stringify(res));
if (!session.userId) {
return res;
} else {
throw new HttpException('Error', 500);
}
}
json response can be generated with two ways in NestJs, either use response object of express Response, or just add a header on the top of route handler method
Solution with applying header;
#Get('/')
#Header('Content-type', 'application/json')
handleGet(){
return JSON.stringify({ status : "success"})
}
Solution with using response imported from Express.
#Get('/')
#Header('Content-type', 'application/json')
handleGet(#Response() res : Response){
return res.json({ status : "success"});
}
These are the two possible ways that i found to return JSON response from a route handler method. further if all your responses need to be JSON, you may create an interceptor to alter your response before it is sent
I have created a function app in azure portal having Nodejs of v12 as runtime environment.
I am able to login with service principal name and secret as follows:
module.exports = async function (context, req) {
context.log("Started Execution");
msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain, function(err, credentials) {
if (err) return console.log(err);
var client = new someAzureServiceClient(credentials, 'subscriptionId');
if(req.body.action ==="xyz") {
client.someOperationGroup.method(param1, param2, function(err, result) {
if (err) {
context.res = {
status: 500,
body: "Error: " + err
}
}
context.res = {
status: 200,
body: "action executed" + result
}
});
}
else {
context.res = {
status: 500,
body: "failed"
}
}
});
context.res = {
status: 200,
body: "Done" // Output
}
}
The problem is context variable is not accessible inside and I am not able to handle the response. The output of above method is "Done" always no matter what the execution failed or succeed.
You can acess the context variable inside other calling only by removing the async from function declaration and use context.done() when you done with the response. Your code will look like this afte change.
module.exports = function (context, req) {
context.log("Started Execution");
msRestAzure.loginWithServicePrincipalSecret(clientId, secret, domain, function(err, credentials) {
context.log("===Here you are able to access context under this call=====");
if (err) return console.log(err);
var client = new someAzureServiceClient(credentials, 'subscriptionId');
if(req.body.action ==="xyz") {
client.someOperationGroup.method(param1, param2, function(err, result) {
if (err) {
context.res = {
status: 500,
body: "Error: " + err
}
} else {
context.res = {
status: 200,
body: "action executed" + result
}
}
context.done();
});
} else {
context.res = {
status: 500,
body: "failed"
}
context.done();
}
});
}
I am calling an api to monitor the state of the response. I have to keep calling api until the response state is successfull. I am calling
I am calling getHeader function from another js file to get the response from API get call. I have to keep calling api again and again until the result.success is successfull. However when the state is changed to successfull, the function stop and doesn't return resolve. I think everytime i call function a new promise is created and it is not able to return resolve.
Can someone help me on this.
*import Promise from 'bluebird'
var requestPromise = Promise.promisifyAll(require('request'))
var instance;
const MonitorResponse = class {
constructor() {
if (instance) {
return instance;
}
instance = this;
}
/**
*
*/
getHeader(authToken,statusHeader) {
console.log(" Successeded Get Geader " + statusHeader + " " +authToken );
return new Promise(function (resolve, reject) {
var self=this;
var options = {
method: 'GET',
rejectUnauthorized: false,
strictSSL: false,
url: statusHeader,
headers: {
'Authorization': 'Bearer ' + authToken,
},
json:true
};
requestPromise.getAsync(options)
.then(function (headerResponse) {
console.log("Response Get Header " + JSON.stringify(headerResponse));
var state = headerResponse.body.state;
console.log("Response Get Header state " +state );
if(state=="SUCCESSFUL")
{
return resolve(" from loop ");
}
else{
self.getHeader(authToken,statusHeader);
console.log(" After recursion state " +state );
}
})
.catch(function (error) {
return reject(error)
});
}.bind(this));
}
}
module.exports = MonitorResponse*
Expected Result is to get return resolve(" from loop "); when getHeader function is called.
Following is my problem statement -
1. I have a Regional API Gateway endpoint
There is a Lambda function in a VPC Private subnet getting invoked by the Lambda, to access RDS.
A Postman call to the API Gateway is able to invoke the Lambda function.
The Lambda function is able to process everything inside it properly.
The Lambda function code reaches the response block of the code, but is not able to return the response.
The Lambda function's security group has all outbound allowed via NAT Gateway in a Public subnet.
Following is the function handler -
var async = require('async');
var moment = require('moment');
var service = require('./some-service');
exports.handler = (event, context, callback) => {
var requestBody = event['body-json'];
async.waterfall([
function(callback) {
var processRequest = {};
processRequest.validationMessage = validateRequest(requestBody);
if(processRequest.validationMessage == ''){
processRequest.isValid = true;
service.processService(requestBody,function(err, response) {
if(err){
callback(err, null);
}
else{
callback(null, response);
}
});
}
else{
processRequest.isValid = false;
callback(null, processRequest);
}
}
],
function(err, resp) {
let response = {};
if (err) {
response = {
statusCode: 500,
body: JSON.stringify('API Error : ' + err),
};
callback(err, response);
}
else {
if(resp.isValid){
response = {
statusCode: 200,
body: 'Record updated for user_id '+requestBody.user_id,
};
console.log('Success block! ', response);
callback(null, response);
}
else{
console.log('Failure block!');
response = {
statusCode: 500,
body: resp.validationMessage,
};
callback(null, response);
}
}
});
};
function validateRequest(requestBody){
var isValid = '';
if(requestBody['user_id'] == undefined){
console.log('user_id missing');
isValid += 'user_id, ';
}
if(requestBody['added_by'] == undefined){
isValid += 'added_by, ';
}
if(isValid != ''){
isValid = isValid.substring(0, isValid.lastIndexOf(','));
isValid += ' missing in the request';
}
return isValid;
}
The CloudWatch logs show the code is able to reach the Success Block,
Success block! { statusCode: 200, body: 'Record updated for user_id 17' }
Postman response -
{
"message": "Endpoint request timed out"
}
What could be the issue?
Resolved this by adding the following, in the first line under the handler -
context.callbackWaitsForEmptyEventLoop = false;
I've a simple express server responding to one post request:
app.post('/reportFiles', getReportLink);
The server is going to make a post request on another server then save the result into a json file.
But it seems to be not working when i make multiple request. If I call 2 times my express server and the request to the other server are slow, I'll get the result of my first request but never my second (request are the same, just called 2 times).
Here is my code for the function getReportLink:
function getReportLink(req,res){
var params = req.body;
// Send 200 response -> Doing the job in background
respond(res,200,'OK');
var json;
var requestName = params.requestName;
var sessionId = params.sessionId;
var startDate = params.startDate;
var endDate = params.endDate;
var customerId = params.customerId;
var uaid = params.uaid;
var jsonFileName = requestName+'_'+customerId+'_'+uaid+'_'+unix()+'.json';
var platformUrl = require(current_path+path.sep+'platform').getPlatformUrl(config.environment);
async.series([
// Call getrequestName on spring-ws
function(callback){
var requestBody = {sessionId:sessionId,asynch:false,startDate:startDate,endDate:endDate,formatedForTimezone:true,timeZoneOffset:timeZoneOffset};
var reportParams = params;
_.each(_.keys(reportParams), function (key) {
if(key==='reportType' && reportParams[key]<=0){
// We don't add it
}else{
requestBody[key] = reportParams[key];
}
});
logger.debug(jsonFileName);
logger.debug(requestBody);
request({
uri: platformUrl+'get'+reportParams.requestName,
method: 'POST',
json: true,
timeout: 600000,
headers: {'content-type': 'application/json'},
body: requestBody},
function(error, response, body) {
if(!_.isUndefined(response)){
logger.debug(jsonFileName);
logger.debug('Response: '+ response.statusCode);
}
if (error || response.statusCode == 500) {
logger.debug(jsonFileName);
logger.error(error);
logger.debug('Received:'+JSON.stringify(body));
if(!_.isUndefined(returnUrl) && returnUrl){
return respond(res,500, 'Error when getting ressource');
} else {
return logger.error('Error when getting ressource');
}
} else {
json = body;
if( _.isUndefined(json) || _.isNull(json)){
logger.debug(jsonFileName);
logger.debug('Received:'+JSON.stringify(json));
if(!_.isUndefined(returnUrl) && returnUrl){
return respond(res,500, 'Error when getting ressource - not a json object');
} else {
return logger.error('Error when getting ressource - not a json object');
}
} else {
logger.debug(jsonFileName+' : '+' OK go to next');
callback(null, 'getReportName');
}
}
});
},
// Save Json on filesystem
function(callback){
logger.debug(jsonFileName+' : '+' Saving on disk');
if(_.isUndefined(json)){
logger.error('Json is undefined...');
}
fs.writeFile(jsonFileName, JSON.stringify(json), 'utf8', function (err) {
if (err) return logger.error(params);
callback(null, 'writeJson');
});
}
]);
}
Solved this using node-fetch.
Code instead of request now looks like this:
fetch(uri, { method: 'POST', body: JSON.stringify(requestBody), timeout: 600000, headers: {'content-type': 'application/json'} })
.then(function(response) {
logger.debug('Response: '+ response.status + ' ok?'+response.ok);
if (!response.ok) {
logger.debug('Received: '+response.statusText);
return respond(res,500, 'Error when getting ressource. Status='+response.status);
} else{
json = response.json();
if( _.isUndefined(json) || _.isNull(json)){
return respond(res,500, 'Error when getting ressource');
} else {
callback(null, 'getReportName');
}
}
});