AWS Lambda - NodeJS POST request and asynch write/read file - node.js

I am new to NodeJS and inside of AWS Lambda I am trying to make a POST request that calls an external API with a JSON object, creates a document with the response and then reads the contents of the file.
Coming from a Ruby background, I'm thinking the problem stems from my unfamiliarity with asynchronous programming, but I've tried using callbacks and readfileSync just to debug with no luck.
Any help would be appreciated.
var querystring = require('querystring');
var https = require('https');
var fs = require('fs');
exports.handler = function(event, context) {
console.log('Received event:', JSON.stringify(event, null, 2));
var operation = event.operation;
delete event.operation;
var accessKey = event.accessKey;
delete event.accessKey;
var templateName = event.templateName;
delete event.templateName;
var outputName = event.outputName;
delete event.outputName;
var req = {
"accessKey": accessKey,
"templateName": templateName,
"outputName": outputName,
"data": event.data
};
function doPost(data, callback) {
// Build the post string from an object
var post_data = JSON.stringify(data);
// An object of options to indicate where to post to
var post_options = {
host: 'hostname.com',
port: '443',
path: '/path/to/api',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': post_data.length
}
};
// Set up the request
var file = fs.createWriteStream(outputName);
var post_req = https.request(post_options, function(res) {
res.setEncoding('utf8');
res.pipe(file);
res.on('response', function(response) {
console.log(response);
});
res.on('error', function(e) {
context.fail('error:' + e.message);
})
res.on('end', function() {
context.succeed('success, end request listener');
});
});
// post the data
post_req.write(post_data);
post_req.end();
callback();
}
function printFileContents() {
fs.readFileSync(outputName, 'utf8', function (err, data) {
console.log('file contents:' + data);
});
}
switch (operation) {
case 'create':
// Make sure there's data before we post it
if(req) {
doPost(req, printFileContents);
printFileContents();
}
break;
...
}
};

In general, I'd recommend starting like this:
var querystring = require('querystring');
var https = require('https');
var fs = require('fs');
exports.handler = function(event, context) {
console.info('Received event', event);
var data = {
"accessKey": accessKey,
"templateName": templateName,
"outputName": outputName,
"data": event.data
};
// Build the post string from an object
var post_data = JSON.stringify(data);
// An object of options to indicate where to post to
var post_options = {
host: 'hostname.com',
port: '443',
path: '/path/to/api',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': post_data.length
}
};
var post_request = https.request(post_options, function(res) {
var body = '';
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
context.done(body);
});
res.on('error', function(e) {
context.fail('error:' + e.message);
});
});
// post the data
post_request.write(post_data);
post_request.end();
};
You can see I simplified your code quite a bit. I'd recommend avoiding the file system since that would slow down your program. I'm also not sure about what is the real goal of your function so I just return the HTTP response.

Related

Making https requests on AWS Lambda

Recently discovered AWS and i'm doing great, today i want to send a test notification to my iPhone X. I'm trying to do so when my database get's an update, Using the dynamoDB trigger blueprint.
Regular Notifications Delivered on the Dashboard works
This is what i have tried to so far, I'm neither getting the rep console log on CloudWatch nor an error.
console.log('Loading function');
const async = require('async');
const https = require('https');
exports.handler = async (event, context) => {
console.log('Received event:', JSON.stringify(event, null, 2));
const name = "35b83a10-9f46-4c2c-95e1-22c6d40005a8";
var message = {
app_id: "appid",
contents: {"en": "Your order has arrived at your doorstep"},
include_player_ids: ["14894201-64f7-486a-b65e-6beedf5880f1",name,"8e0f21fa-9a5a-4ae7-a9a6-ca1f24294b86"]
};
sendNotification(message);
console.log("Activation change detected. message sent");
return `Successfully processed ${event.Records.length} records.`;
};
var sendNotification = function(data) {
var headers = {
"Content-Type": "application/json; charset=utf-8",
"Authorization": "Basic hidden_in_question"
};
var options = {
host: "onesignal.com",
port: 443,
path: "/api/v1/notifications",
method: "POST",
headers: headers
};
var req = https.request(options, function(res) {
res.on('data', function(data) {
console.log("rep:");
console.log(JSON.parse(data));
});
});
req.on('error', function(e) {
console.log("ERROR:");
console.log(e);
});
req.write(JSON.stringify(data));
req.end();
};
I do not get the message on my iPhone. What seems to be the problem here?
But getting:
Activation change detected. message sent
on the console.
HTTP request is an async action which means you need to wait for the response, but in your case you are returning from the handler just after calling the function.
In order to fix this you need to wait for http request to finish before returning from handler. The following method assumes that you are using nodejs v8.x.
const https = require('https');
exports.handler = async (event, context) => {
console.log('Received event:', JSON.stringify(event, null, 2));
const name = "35b83a10-9f46-4c2c-95e1-22c6d40005a8";
var message = {
app_id: "appid",
contents: {"en": "Your order has arrived at your doorstep"},
include_player_ids: ["14894201-64f7-486a-b65e-6beedf5880f1",name,"8e0f21fa-9a5a-4ae7-a9a6-ca1f24294b86"]
};
await sendNotification(message);
console.log("Activation change detected. message sent");
return `Successfully processed ${event.Records.length} records.`;
};
var sendNotification = function(data) {
return new Promise(function(resolve, reject) {
var headers = {
"Content-Type": "application/json; charset=utf-8",
"Authorization": "Basic hidden_in_question"
};
var options = {
host: "onesignal.com",
port: 443,
path: "/api/v1/notifications",
method: "POST",
headers: headers
};
var req = https.request(options, function(res) {
res.on('data', function(data) {
console.log("rep:");
console.log(JSON.parse(data));
});
res.on('end', resolve);
});
req.on('error', function(e) {
console.log("ERROR:");
console.log(e);
reject(e);
});
req.write(JSON.stringify(data));
req.end();
});
}
var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (body) {
console.log('Body: ' + body);
});
});
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
req.write(data);
req.end();

How do I do a request.write(body) without the body?

I am using the code as shown below. However I want to include the body in the query string (which I can do fine) - however, I am unsure how to restructure thehttps.request so as to remove request.write(body) - simply using request.write() does not work as it requires a string.
Can someone help?
Thanks
var body = JSON.stringify( json.text );
const params = {
'q': body,
};
var requestUrl = url.parse( URL + queryStringify(params) );
const requestOptions = {
hostname: requestUrl.hostname,
path: requestUrl.path,
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
};
var request = https.request(requestOptions, function(res) {
var data = "";
res.on('data', function (chunk) {
//do stuff
});
res.on('end', function () {
//do stuff
});
});
request.write(body);
request.end();
To do that, you can set your params in path field, with querystring like this :
Set your query string params into requestOptions
const querystring = require('querystring');
requestOptions.path = `/your/Path?${querystring.stringify({firstName: 'John', lastName: 'doe'})}`;
// Result is '/your/Path?firstName=John&lastName=doe'
Then, do your request
var request = https.request(requestOptions, function(res) {
res.on('data', function(chunk) {
// Do stuff
});
res.on('end', function() {
// Do stuff
});
});
request.on('error', function (err) {
// Throw err
});
request.end();
Hope it helps.

express integrating middleware into a callback response

I have middleware (API calls, not part of a route) which I want to use in a callback response.
// MIDDLEWARE EXAMPLE
var postInvoice = function(req, res){
function request(callback) {
var path='/xxx?';
var data = querystring.stringify( {
'action' : 'xxx',
'appkey' : 'xxx'',
'fromapi' : 'xxx',
'fromapiuser' : 'xxx',
'username' : 'xxx',
'shipmethod' : 'TEST',
'shipping' : '0',
'taxes' : '0'
});
var options = {
port: 443,
host: xxx,
path: path,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': data.length
}
};
var postRequest = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('Invoice Response: ' + chunk);
});
});
postRequest.write(data);
}
request(function(responseData) {
console.log(responseData);
});
}
I need to access the response in another route (which itself includes callback via API)
app.get('/result', function(req, res){
var resourcePath = req.param('resourcePath');
function request(callback) {
var path = resourcePath
var options = {
port: 443,
host: xxx,
path: path,
method: 'GET'
};
var postRequest = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
jsonRes = JSON.parse(chunk);
return callback(jsonRes);
});
});
postRequest.end();
}
request(function(responseData) {
console.log(responseData);
// this is where I invoke the middleware,
if(some response condition is met) {
postinvoice();
}
res.render('result', {
check: check,
response: checkout_msg
});
});
});
I'm able to view the 'Invoice Response' in console, but I cannot manipulate it in the /result route. I'd like to be able to invoke the middleware, create locals and make the locals available in /result route.
thank you,
try this
var postRequest = http.request(options, function (response) {
var body = '';
response.setEncoding('utf8');
response.on('data', function (chunk) {
body += chunk;
});
response.on('end', function () {
body = JSON.parse(body); //if response is json
return callback(body);
});
});

NodeJS http post request read timeout

I'm trying to execute the following code inside AWS Lambda which only makes a POST http request to an ElasticSearch.
The problem I'm facing is that it seems the nodejs request has a read timeout and the response is almost always cut and an error is thrown. I've checked that the problem is not related with AWS Lambda timeout which is set to 10 seconds and the code throws an error in less than a second.
As you can see, I've tried to put a timeout to 5secs but I think that's a connection timeout and not a read timeout.
What am I doing wrong?
var http = require('http');
exports.handler = (event, context, callback) => {
var options = {
hostname: '172.31.40.10',
port: 9200,
path: '/articles/es/_search?_source=reference',
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
};
var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (body) {
var parsed = JSON.parse(body);
var b = [];
for (var i = 0; i < parsed.hits.hits.length; i++) {
b.push(parsed.hits.hits[i]._source.reference);
}
var response = {
statusCode: '200',
body: JSON.stringify(b),
headers: {
'Content-Type': 'application/json',
}
};
callback(null, response);
});
});
req.on('error', function(e) {
callback(new Error('fallo'));
});
req.setTimeout(5000, function() {req.abort;})
req.on('socket', function (socket) {
socket.setTimeout(5000);
socket.on('timeout', function() {
req.abort();
});
});
req.write(MY_QUERY_HERE);
req.end();
};
I think you should let the stream of incoming data finish before performing any data manipulation.
Example :
var http = require('http');
//var _ = require('underscore');
function MyPostRequest(callback) {
var options = {
hostname:'172.31.40.10',
port:9200,
path:'/articles/es/_search?_source=reference',
method:'POST',
headers:{'Content-Type':'application/json'}
};
http.request(options, function(res) {
var tmpstore = ''; //temp. data storage
//:Store the continuous incoming data stream
res.on('data', function(d){tmpstore += d;});
//:Data reception is done, use it now...
res.on('end', function() {
var parsed = JSON.parse(tmpstore); var b = [];
for (var i = 0; i < parsed.hits.hits.length; i++) {
b.push(parsed.hits.hits[i]._source.reference);
}
/* //I suggest using underscore module :
_.each(parsed.hits.hits,function(element, index, list){
b.push(element._source.reference);
});
*/
var response = {
statusCode:'200',
body:JSON.stringify(b),
headers:{'Content-Type':'application/json'}
};
callback(null, response);
});
//:Response contained an error
res.on('error', function(e){/*error handling*/callback(e,null);});
});
}

Callback / asynchronous request in nodejs

I do a http request in nodejs with the following code
var buffer = "";
var postToPHP = function(data, path){
var httpreq = require('http');
var querystring = require("querystring");
var data = querystring.stringify(data);
var options = {
host : 'localhost',
path : path,
method : 'POST',
headers : {
'Content-Type' : 'application/x-www-form-urlencoded',
'Content-Length' : data.length
}
};
var buffer = "";
var reqPost = httpreq.request(options, function(res) {
res.on('data', function(d) {
buffer = buffer+d;
});
res.on('end', function() {
console.log("buffer",buffer); //this logs the buffer correctly
return buffer;
});
});
reqPost.write(data);
reqPost.end();
}
var buffer = postToPHP(message,path); //this buffer displays nothing because the call is async
I'd like to know exactly what is the standard procedure to "wait" for the server response in the nodejs or how to implement a callback that would react accordingly to what I want after I receive the message... Could someone give me an example of a callback on this please?
You have to pass in a callback if you're performing an asynchronous task inside a function:
var http = require('http'),
querystring = require('querystring');
function postToPHP(data, path, cb) {
var data = querystring.stringify(data);
var options = {
host: 'localhost',
path: path,
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(data.length)
}
};
http.request(options, function(res) {
var buffer = '',
calledBack = false;
res.on('data', function(d) {
buffer += d;
}).on('error', function(err) {
if (!calledBack) {
calledBack = true;
cb(err);
}
}).on('end', function() {
if (!calledBack) {
calledBack = true;
cb(null, buffer);
}
});
}).on('error', function(err) {
if (!calledBack) {
calledBack = true;
cb(err);
}
}).end(data)
}
Then use it:
postToPHP({ foo: 'bar' }, '/foo', function(err, data) {
if (err) throw err;
console.dir(data);
});

Resources