Using the Node Request module with AWS Lambda and API Gateway - javascript

This lambda function works:
exports.handler = function(event, context, callback) {
const done = (err, res, func) => callback(null, {
statusCode: '200',
body: res,
headers: { 'Content-Type': 'application/json' },
})
done(null, 'works'))
}
I can hit the API Gateway end point and receive the response message, 'works'.
However, when I add a request call (POSTing a message to Slack) that looks like this:
const request = require('request')
const moment = require('moment-timezone')
exports.handler = function(event, context, callback) {
// the json object is built following these docs:
// https://api.slack.com/docs/message-attachments
const SLACK_WEB_HOOK_URL = process.env.SLACK_WEB_HOOK_URL
const uri = SLACK_WEB_HOOK_URL
const method = 'POST'
const options = {
json,
uri,
method,
}
const slack = (opts) => {
request.post(opts, (err, response, body) => {
if (response.statusCode < 300) {
return 'Success!'
} else {
return 'Err!'
}
})
}
const done = (err, res, func) => callback(null, {
statusCode: '200',
body: res,
headers: { 'Content-Type': 'application/json' },
})
done(null, slack(options))
}
The server hangs when I hit the API end point ...
And I get a Task timed out after 10.00 seconds error:
{
"errorMessage": "2017-06-23T16:41:21.483Z c385e32e-5832-11e7-8c4f-673b524cf783 Task timed out after 10.00 seconds"
}
But my Slack POST request gets sent and I see the message in the channel.
How do I send the POST request and then wait for it to return, and then exit the lambda function with a custom response message?
Thanks for the help!

Put the callback into the callback of the post method
request.post(opts, (err, response, body) => {
if (response.statusCode < 300) {
return callback(null, {
statusCode: '200',
body: res,
headers: { 'Content-Type': 'application/json' },
});
} else {
return callback(err);
}
});

Related

Why is POST request showing undefined? Node JS

I am receiving a request response in the console.log, however when I try to post to Mastodon (similar site to Twitter), it posts as 'undefined'.
bot.js:
require('dotenv').config();
const Mastodon = require('mastodon-api');
var request = require('request');
console.log("Mastodon Bot starting...");
const M = new Mastodon({
access_token: process.env.ACCESS_TOKEN,
client_key: process.env.CLIENT_KEY,
client_secret: process.env.CLIENT_SECRET,
timeout_ms: 60*1000,
api_url: 'https://botsin.space/api/v1/',
})
const options = {
method: 'GET',
url: 'https://apisite.com/steps',
qs: {count: '3'},
headers: {
'X-RapidAPI-Key': 'secret-api-key',
'X-RapidAPI-Host': 'site-host',
useQueryString: true,
},
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(JSON.parse(body, undefined, 5));
});
const params = {
status: request
}
M.post('/statuses', params, (error, params) => {
if (error) {
console.log(error);
}
else {
console.log(params.request);
}
});
This posts "Mastodon bot is starting...", then the results of the API GET request to the console (the response body).
However, it does not post the response body to /statuses in Mastodon itself, it just posts 'undefined'.
How can I post the response body that I get in the console, to Mastodon?

Access the response of Node.JS Post Request

I'm trying the wrap my head around the Client Credentials Flow of Spotify API and in their documentation they have this way to get the Access Token:
var client_id = 'CLIENT_ID';
var client_secret = 'CLIENT_SECRET';
const authOptions = {
url: "https://accounts.spotify.com/api/token",
headers: {
"Authorization":
"Basic " +
new Buffer.from(clientID + ":" + clientSecret).toString("base64"),
"Content-Type": "application/x-www-form-urlencoded",
},
form: {
grant_type: "client_credentials",
},
json: true,
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
var token = body.access_token;
}
});
Now I'm trying to get that token and export or use it in the API calls but whatever I do, I cannot access that statement.
Putting the POST into a variable or function and calling it results in undefined.
This is what I'm trying to achieve:
import authOptions from "./credentials.js";
import pkg from "request";
const { post } = pkg;
const Spotify = {
getAccessToken() {
post(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
const token = body.access_token;
return token;
}
});
},
async search(input) {
const accessToken = Spotify.getAccessToken();
const response = await fetch(
`https://api.spotify.com/v1/search?q=${input}&type=artist`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
}
);
const data = await response.json();
console.log(data);
},
};
export default Spotify;
Yet of course there's no Access Token returned from that post request.
Is there any way I can convert that piece of code into Async/Await?
You can create a new promise:
async function getAccessToken(){
return new Promise((resolve, reject) => {
post(authOptions, function (error, response, body) {
if(error){
reject(error);
} else if (response.statusCode === 200) {
const token = body.access_token;
resolve(token);
}
});
});
};
The resolve/reject allows you to return that value when the callback is called, and it passes the error to the caller to handle. And you can use this format to promisify any callback-based function, by calling it inside of a promise and using resolve/reject to return the value.

request(...).then is not a function error when making a POST request

I'm trying to create a firebase function that makes a HTTP POST request whenever a new document is created.
This is my code:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
const request = require("request");
exports.sendMessage = functions.firestore.document('comms/{comms}').onCreate((snap, context) => {
const newValue = snap.data();
if (newValue) {
//const email = newValue.email;
const msg = newValue.msg;
return request({
uri: "url",
method: 'POST',
body: msg,
json: true,
resolveWithFullResponse: true
}).then((response: { statusCode: number; }) => {
if (response.statusCode >= 400) {
throw new Error(`HTTP Error: ${response.statusCode}`);
}
console.log('SUCCESS! Posted', msg);
});
}
return Promise
});
Error received:
TypeError: request(...).then is not a function
at exports.sendMessage.functions.firestore.document.onCreate (/srv/lib/index.js:25:12)
at cloudFunction (/srv/node_modules/firebase-functions/lib/cloud-functions.js:127:23)
at /worker/worker.js:825:24
at
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
request supports callback interfaces natively but does not return a promise, which is what you must do within a Cloud Function.
This is explained in the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).
You could use request-promise (https://github.com/request/request-promise) and the rp() method which "returns a regular Promises/A+ compliant promise". You would then adapt your code as follows:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
const rp = require('request-promise');
exports.sendMessage = functions.firestore.document('comms/{comms}').onCreate((snap, context) => {
const newValue = snap.data();
if (newValue) {
const msg = newValue.msg;
var options = {
method: 'POST',
uri: '....',
body: msg,
json: true // Automatically stringifies the body to JSON
};
return rp(options)
.then(parsedBody => {
// POST succeeded...
console.log('SUCCESS! Posted', msg);
return null;
})
.catch(err => {
// POST failed...
console.log(err);
return null;
});
} else {
return null;
}
});
request module doesn't return a Promise instead try using a callback function for response.
return request({
uri: "url",
method: 'POST',
body: msg,
json: true,
resolveWithFullResponse: true
}, function (error, response, body) {
})
As in the documentation already mention you need to pass the callback to your request
var request = require('request');
request('http://www.google.com', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); // Print the HTML for the Google homepage.
});
If you want to chain your request you can use pipe
request
.get('url/img.png')
.on('response', function(response) {
console.log(response.statusCode) // 200
console.log(response.headers['content-type']) // 'image/png'
})
.pipe(request.put('url'))
If you want to use promise you can use request-promise
var rp = require('request-promise');
rp('http://www.google.com')
.then(function (htmlString) {
// Process html...
})
.catch(function (err) {
// Crawling failed...
});
The request module work on callbacks only, If you want to make Promisify you need to do like this
const request = require('request');
const webService = {};
webService.callApi = (url, bodyObj, method) => {
return new Promise((resolve, reject) => {
const options = {
method: method || 'POST',
url: url,
headers: {
'Content-Type': 'application/json',
},
body: bodyObj,
json: true,
};
// Error Handler
const errorMessge = { code: 500, error: 'INTERNAL_SERVER_ERROR' };
request(options, (error, response, resBody) => {
if (error) {
return reject(errorMessge);
} else if (response.statusCode !== 200) {
return reject(errorMessge);
}
return resolve(resBody);
});
});
};
module.exports = webService;

Multiple http post calls in foreach with nodejs

How can I execute many request in foreach without error please ?
Currently, I send a request on each entry on my array with foreach :
users.forEach(function (user) {
request({
url : 'myurl.com/api',
method: 'POST',
auth : {
'bearer': CONFIGURATION.auth.token
},
body : {
sender_id: user.sender_id
},
json : true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
resolve(body);
} else {
console.log('Error on coreService');
console.log('############### ERROR ###############');
console.log(error);
console.log('############### BODY ###############');
console.log(body);
console.log('############### RESPONSE ###############');
console.log(response);
reject(error);
}
});
});
With some request it's ok, but with some request I have this error :
Error on coreService
############### ERROR ###############
{ Error: connect ECONNRESET 127.0.0.1:80
at Object._errnoException (util.js:1022:11)
at _exceptionWithHostPort (util.js:1044:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1198:14)
code: 'ECONNRESET',
errno: 'ECONNRESET',
syscall: 'connect',
address: '127.0.0.1',
port: 80 }
############### BODY ###############
undefined
############### RESPONSE ###############
undefined
Do you have an idea how can I fix this problem please ?
I tried with :
server.timeout = 0;
or
server.timeout = 1000;
but same problem...
If I execute the request, user by user, it's fine !
But with the foreach, it's break on some request...
In the comments colinux proposes that the connection reset errors are due to the server protecting itself from too many simultaneous requests, and I think they are probably correct. This code shows how you can use async/await to make your requests to the server one at a time. This might be too slow for you, but it could help you to confirm that the problem is as explained by colinux.
Here is another answer which does not require the use of request-promise-native and instead wraps the request calls in its own Promise.
const request = require('request');
const users = [{sender_id: 1}, {sender_id: 2}, {sender_id: 3}];
// make this driver function async so we can use await which allows waiting for
// a request to finish before starting the next one
async function runUserRequests() {
for (let i = 0; i < users.length; i++) {
const user = users[i];
try {
const response = await requestPromise(user);
console.log("response for user", user, response);
} catch (error) {
console.log("error for user", user, error);
}
};
}
// wrap the request call in a Promise so that it will run synchronously
function requestPromise(user) {
return new Promise(function(resolve, reject) {
request({
url: 'http://localhost:4000/',
method: 'GET', // method 'POST'
// auth : {
// 'bearer': CONFIGURATION.auth.token
// },
// body : {
// sender_id: user.sender_id
// },
// json : true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
resolve(body);
console.log("request successful for user", user, " at ", (new Date()).getTime());
} else {
console.log('Error on coreService');
console.log('############### ERROR ###############');
console.log(error);
console.log('############### BODY ###############');
console.log(body);
console.log('############### RESPONSE ###############');
console.log(response);
reject(error);
}
});
});
}
runUserRequests();
/*
Sample server used to test the code above:
const express = require('express')
const app = express()
const port = 4000
app.get('/', (req, res) => {
console.log("spinning for a bit");
setTimeout( () => {
console.log(" now responding");
res.send('Hello World!');
}, 1000);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
*/
In the comments colinux proposes that the connection reset errors are due to the server protecting itself from too many simultaneous requests, and I think they are probably correct. This code shows how you can use async/await to make your requests to the server one at a time. This might be too slow for you, but it could help you to confirm that the problem is as explained by colinux.
To get this to work you'll need to install request-promise-native. If you can't do that let me know and I can work up an example wrapping the request api in your own Promise.
const request = require('request-promise-native');
//const users = [1, 2, 3, 4]; // dummy user array for testing
async function runUserRequests(users) {
for (let i = 0; i < users.length; i++) {
const user = users[i];
console.log("starting request for user ", user);
await request({
url: 'http://localhost:4000/',
method: 'GET',
auth : {
'bearer': CONFIGURATION.auth.token
},
body : {
sender_id: user.sender_id
},
json : true
}, function (error, response, body) {
if (!error && response.statusCode === 200) {
console.log("request successful for user", user, " at ", (new Date()).getTime());
resolve(body);
} else {
console.log('Error on coreService');
console.log('############### ERROR ###############');
console.log(error);
console.log('############### BODY ###############');
console.log(body);
console.log('############### RESPONSE ###############');
console.log(response);
reject(error);
}
});
};
}
runUserRequests();

Browsers needs a HTTP response, but server is not allowing multiple responses

This is my handler that sends out a PUT request to my server:
export function insertRandomQuestionsHandler(roomId, questions) {
return (dispatch) => {
dispatch(questionOverviewHandler(questions));
let result = questions.reduce((r, e) => (r.push(...e), r), []);
const url = . `http://localhost:3000/quizzer/room/${roomId}/selectedQuestions`;
const response = fetch(url, {
method: "PUT",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
questions: result,
})
});
if (response.error) {
throw new Error(`HTTP PUT request went wrong: got .
"${response.statusText}" for "${url}"`)
}
dispatch(insertRandomQuestions(result))
}
}
On the server it goes through this request:
quizzer.put('/room/:roomId/selectedQuestions', async function (req, res) {
for (let i = 0; i < req.body.questions.length; i++) {
await Room.findOneAndUpdate({roomId: req.params.roomId}, {
"$push": {
"questions": [{
"_id": req.body.questions[i]._id,
"question": req.body.questions[i].question,
"answer": req.body.questions[i].answer,
"category": req.body.questions[i].category,
"isShown": false,
}]
}
},(err, data) => {
if (err) {
return res.status(500).send(err);
}
return res.status(200).json(data);
});
}
});
For some reason the browser needs a response else the request will be stuck at being pending and eventually return ERR_EMPTY_RESPONSE:
But when I return a response (data) as shown above (the http request is 200)
Then my server crashes:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
So... I need that response to be sent, but it can't be sent twice..
Any ideas?

Categories