Nodejs lambda request function gets 411 Length Required - node.js

I use a node.js lambda that makes post requests to remote api, which installed on several remote locations.
I have no access to the remote api code or logs.
The lambda gets called with HTTP gateway by external application which I do not control as well.
It works perfectly for all the location but one. For one location I get this error:
411 Length Required.
I have tried to neutralize the HTTP gateway, and run lambda posts with test events.
I get the same result.
I have sent the same exact request to other locations, and got a response.
I can't find the problem as I do send a ContentLength header.
This is the lambda code:
const https = require("https");
const iconv = require("iconv-lite");
const charset = require("charset");
const qs = require("qs");
const url = require("url");
exports.handler = (event, context, callback) => {
event = JSON.parse(JSON.stringify(event));
let enc ="";
let multiValueHeaders = event["multiValueHeaders"]["Content-Type"];
let PostParams = null;
let domain = event["queryStringParameters"]["domain"] ;
let buf = Buffer.from(JSON.stringify(event["body"]), "base64");
let tstring = buf.toString("utf8");
PostParams = qs.parse(tstring);
var postData = PostParams ? qs.stringify(PostParams) : {};
let ContentLength = new Buffer.from(postData).length;
let headers = "" ;
headers += (multiValueHeaders) ? (' { "Content-Type": "'+ multiValueHeaders + '",') : '{';
headers += ('"Content-Length":'+ ContentLength + '}');
headers = JSON.parse(headers);
var q = url.parse(domain, true);
let options = {
'method': 'POST',
'hostname': q.hostname,
'path': q.pathname,
'headers': {headers}
};
var req = http.request(options, function (res) {
let chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
enc = charset(res.headers, chunk);
});
res.on("end", function (chunk) {
var decodedBody = iconv.decode(Buffer.concat(chunks), enc);
const response = {
statusCode: 200,
body: decodedBody
};
callback(null ,response );
});
res.on("error", function (error) {
console.error(error);
});
});
if (PostParams != null) req.write(postData);
req.end();
}
When a request sent to the endpoint straight form postman there is no error. Only from lambda.

Apart from why this event = JSON.parse(JSON.stringify(event));?
Apart from this is a very ugly way to build the headers object:
let headers = "";
headers += (multiValueHeaders) ? (' { "Content-Type": "'+ multiValueHeaders + '",') : '{';
headers += ('"Content-Length":'+ ContentLength + '}');
headers = JSON.parse(headers);
and I would have written as:
const headers = { "Content-Length": ContentLength };
if(multiValueHeaders) headers["Content-Type"] = multiValueHeaders;
The root cause of your problem is in this line:
'headers': {headers}
it needs to be changed in:
'headers': headers
Hope this helps

411 is returned when the server demands a valid Content-Length
event argument passed through the HTTP gateway is the entire client request object. You don't have to parse it.
event.body is an escaped string. Double-escaping it gives the wrong content-length. For example,
JSON.stringify({'double': 2}) !== JSON.stringify(JSON.stringify({'double': 2))
// false
With this in mind, you can perform your request like this:
exports.handler = (event, context, callback) => {
let enc = "";
let multiValueHeaders = event["multiValueHeaders"];
let domain = event["queryStringParameters"]["domain"] ;
const postHeaders = {...multiValueHeaders};
let postData = null;
if (event.body !== null) {
postData = qs.stringify(
qs.parse(
Buffer.from(event.body, "base64").toString("utf-8")
)
);
postHeaders['Content-Length'] = [ Buffer.byteLength(postData) ];
}
var q = url.parse(domain, true);
let options = {
'method': 'POST',
'hostname': q.hostname,
'path': q.pathname,
'headers': postHeaders
};
var req = http.request(options, function (res) {
let chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
enc = charset(res.headers, chunk);
});
res.on("end", function (chunk) {
var decodedBody = iconv.decode(Buffer.concat(chunks), enc);
const response = {
statusCode: 200,
body: decodedBody
};
callback(null, response);
});
res.on("error", function (error) {
console.error(error);
});
});
if (postData !== null) req.write(postData);
req.end();
}

Related

Save Response as variable and send it as Header - NodeJS

I'm currently working on sending a GET request to my own private Domain, alongside
various Headers that would be populated with various values such as 'Token' etc. - that are base64 encoded. This is running perfectly fine.
My main goal here is to send the Response of another request i'm sending to a different endpoint.
This is the modified code (I've removed various fields so please ignore any best practices for now).
const fs = require('fs');
const http = require('http');
const net = require('net');
const os = require("os");
const dns = require("dns");
const https = require("https");
var token = process.env.HOME+'/token.txt';
let base64data1 = '';
try {
if (fs.existsSync(token)) {
var data1 = fs.readFileSync(token,'utf8');
let buff1 = Buffer.from(data1);
base64data1 = buff1.toString('base64');
}} catch(error) {
console.log('')
}
var options = {
hostname: "myprivatedomain.com",
port: 443,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Token": base64data1
},
};
var req = https.request(options, (res) => {
res.on("data", (d) => {
process.stdout.write(d);
});
});
req.on("error", (e) => {
// console.error(e);
});
req.write(postData);
req.end();
My goal, as mentioned, is to add additional Header (In addition to the "Token" header) to my private domain, which will be populated by the Response for the following domain - www.seconddomain.com
I was thinking about creating a simple function that would retrieve the response, save it as variable and use it as my 2nd Header. Something similar to this -
function 2ndresponse(url) {
let data = '';
http.get(url, (resp) => {resp.on('data', (chunk) => {
data += chunk;
});
});
let responsevalue = Buffer.from(data);
base64data = responsevalue.toString('base64');
return http.get(url).then((resp) => resp.json());
}
var = 2ndresponse("http://www.seconddomain.com");
Hopefully this is clear enough (:
Update
I figured it out -
The workaround is to set both request in one function like so -
function req2() {
http.get({
hostname: 'seconddomain.com',
port: 80,
path: '/blahblah',
agent: false}, (res) => {
res.setEncoding('utf8');
let data = '';
res.on("data", (d) => {
var x;
x = d;
let buff5 = Buffer.from(x);
seconddomainvalue = buff5.toString('base64');
var options = {
hostname: "myprivatedomain.com",
port: 443,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": postData.length,
"token": tokenvalue,
"seconddomain": seconddomainvalue
},
};
var req = https.request(options, (res) => {
res.on("data", (d) => {
process.stdout.write(d);
});
});
req.on("error", (e) => {
// console.error(e);
});
req.write(postData);
req.end();
});
});
}
req2();
Thanks
The same can be achieved using the HTTP REQUEST also, But AXIOS allows us to make HTTP requests from both the browser and Node. js applications. It allows us to make both GET and POST requests which are the most used HTTP methods.
const axios = require('axios'); // Axios import
const controllerFunction = async () => {
const firstResponse = await axios.get('https://seconddomain.com'); // Here the request will wait, as it is synchronous
const bufferValue = Buffer.from(firstResponse.data);
const base64data = bufferValue.toString('base64');
const secondResponse = await axios.post('https://myprivatedomain.com', {"body": data}, {
headers: {
"Content-Type": "application/json",
"Token": base64data
}
}); // Here the second request can use the first request response data, as this code is executed synchronously
};
Also adding the AXIOS documentation link: https://www.npmjs.com/package/axios

I want to use the result data of API to another JS variable by export

I'm new to nodejs, can some one help to fix. Below is the code which executes successful and display a console log when execute. But instead of printing here I want to export and use in another js files when required.
var https = require('https');
var spids={};
var headers = {
'Content-Type': 'application/json',
'x-ivx-api-key': '',
'x-ivx-api-token': ''
};
var dataString = JSON.stringify({"sps":[0]});
var options = {
'method': 'POST',
'hostname': '',
'path': '',
headers: headers,
body: dataString
}
var req = https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
str=body.toString();
spids["array"] = JSON.parse(str).data.sps;
spids["array"].forEach(function (a) {
//console.log("Here a means:",a.organization.id);
a.name = ['name', 'id'].map(function (k) { return a[k]; }).join(' ');
a.organization.name = ['name', 'id'].map(function (k) { return a.organization[k]; }).join(' ');
});
spids = JSON.stringify(spids,null,"\t");
console.log(spids);
return spids;
});
res.on("error", function (error) {
console.error(error);
});
});
req.write(dataString);
req.end();
module.exports.req = req;
I want to call and use it in separate js file.
const spidlist = require('./jsons/getSPIDlist');
var s= await spidlist.req;
console.log(s);
You can restructure you code as:
var https = require("https");
const getSpids = () => {
var spids = {};
var headers = {
"Content-Type": "application/json",
"x-ivx-api-key": "",
"x-ivx-api-token": "",
};
var dataString = JSON.stringify({ sps: [0] });
var options = {
method: "POST",
hostname: "",
path: "",
headers: headers,
body: dataString,
};
return new Promise((resolve, reject) => {
https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
str = body.toString();
spids["array"] = JSON.parse(str).data.sps;
spids["array"].forEach(function (a) {
//console.log("Here a means:",a.organization.id);
a.name = ["name", "id"]
.map(function (k) {
return a[k];
})
.join(" ");
a.organization.name = ["name", "id"]
.map(function (k) {
return a.organization[k];
})
.join(" ");
});
spids = JSON.stringify(spids, null, "\t");
console.log(spids);
resolve(spids)
});
res.on("error", function (error) {
console.error(error);
reject(error)
});
});
});
};
module.exports = getSpids
I haven't tested this piece of code but you should be able to use it as:
const spidlist = require('./jsons/getSPIDlist');
var s= await spidlist();
console.log(s);

How to send back the data got from response.on('end') to the client-side

I'm new to NodeJs and I'm having the problem with response.on('end') I still can't find out the method to send the data I got from the response to the client side.
exports.getCheckoutSession = catchAsync(async (req, res, next) => {
const uuidv1 = require('uuid/v1');
const https = require('https');
const tour = await Tour.findById(req.params.tourId);
console.log(tour);
//parameters send to MoMo get get payUrl
var endpoint = 'https://test-payment.momo.vn/gw_payment/transactionProcessor';
var hostname = 'https://test-payment.momo.vn';
var path = '/gw_payment/transactionProcessor';
var partnerCode = 'MOMO';
var accessKey = 'accessKey';
var serectkey = 'secretKey';
var orderInfo = 'pay with MoMo';
var returnUrl = 'https://momo.vn/return';
var notifyurl = 'https://callback.url/notify';
var amount = (tour.price * 23000).toString();
console.log(amount);
var orderId = req.params.tourId;
var requestId = req.params.tourId;
var requestType = 'captureMoMoWallet';
var extraData = 'merchantName=;merchantId='; //pass empty value if your merchant does not have stores else merchantName=[storeName]; merchantId=[storeId] to identify a transaction map with a physical store
//before sign HMAC SHA256 with format
//partnerCode=$partnerCode&accessKey=$accessKey&requestId=$requestId&amount=$amount&orderId=$oderId&orderInfo=$orderInfo&returnUrl=$returnUrl&notifyUrl=$notifyUrl&extraData=$extraData
var rawSignature =
'partnerCode=' +
partnerCode +
'&accessKey=' +
accessKey +
'&requestId=' +
requestId +
'&amount=' +
amount +
'&orderId=' +
orderId +
'&orderInfo=' +
orderInfo +
'&returnUrl=' +
returnUrl +
'&notifyUrl=' +
notifyurl +
'&extraData=' +
extraData;
//puts raw signature
console.log('--------------------RAW SIGNATURE----------------');
console.log(rawSignature);
//signature
const crypto = require('crypto');
var signature = crypto
.createHmac('sha256', serectkey)
.update(rawSignature)
.digest('hex');
console.log('--------------------SIGNATURE----------------');
console.log(signature);
//json object send to MoMo endpoint
var body = JSON.stringify({
partnerCode: partnerCode,
accessKey: accessKey,
requestId: requestId,
amount: amount,
orderId: orderId,
orderInfo: orderInfo,
returnUrl: returnUrl,
notifyUrl: notifyurl,
extraData: extraData,
requestType: requestType,
signature: signature
});
//Create the HTTPS objects
var options = {
hostname: 'test-payment.momo.vn',
port: 443,
path: '/gw_payment/transactionProcessor',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
}
};
//Send the request and get the response
console.log('Sending....');
var req = https.request(options, res => {
console.log(`Status: ${res.statusCode}`);
console.log(`Headers: ${JSON.stringify(res.headers)}`);
console.log('Type of body', JSON.stringify(res.body));
res.setEncoding('utf8');
let fullBody = '';
res.on('data', body => {
fullBody += body;
console.log(' Real Body');
console.log(fullBody);
//console.log('Type of body', body.payUrl);
// console.log(JSON.parse(body).payUrl);
// res.redirect(JSON.parse(body).payUrl);
});
res.on('end', () => {
const payURL = JSON.parse(fullBody).payUrl;
console.log('payUrl', payURL);
console.log('No more data in response.');
});
});
req.on('error', e => {
console.log(`problem with request: ${e.message}`);
});
// write data to request body
req.write(body);
req.end();
});
This is the url I got from response
payUrl https://test-payment.momo.vn/gw_payment/payment/qr?partnerCode=MOMO&accessKey=F8BBA842ECF85&requestId=5f38cc86954a6206211e2842&amount=23000&orderId=5f38cc86954a6206211e2842&signature=37ae247d56efd9ed6630b7d7d1435b88ffb8895956da5711a62ebbab8118aa7b&requestType=captureMoMoWallet
Can you please tell how could i send the data from res.on('end'), the "payURL" in the picture above, to client-side. I have tried some methods like res.writeHead, res.send, res.json( ) .... But they all returned error: res.send, res.writeHead, res.json... is not a function
This is my client-side, . If you guys don't mind , please also show me how to automatically redirect the payURL site above when the client click my button. Should I keep using window.location.replace like above ?
export const bookTour = async tourId => {
try {
const res = await fetch(
`http://localhost:3000/api/v1/bookings/checkout-session/${tourId}`,
{
method: 'POST',
body: 'a=1'
}
).then(res => window.location.replace(res.redirectURL));
console.log('The res', res);
} catch (err) {
showAlert('error', err);
}
};
This is my index.js
if (bookBtn) {
bookBtn.addEventListener('click', e => {
e.target.textContent = 'Processing...';
const tourId = e.target.dataset.tourId;
bookTour(tourId);
});
}
You're shadowing the req/res-variables from your getCheckoutSession-handler by using the same names for your http-request. If you change it to:
const request = https.request(options, response => {
// ...
let fullBody = '';
response.on('data', body => {
fullBody += body;
});
response.on('end', () => {
const payURL = JSON.parse(fullBody).payUrl;
// access the handler "res" object here
res.send(payURL);
// alternatively use res.json({payURL}) to send a json response
});
});
it should work fine.
Note: Nowadays you should definitely use const/let instead of var (see this for more information)
Simple,
res.on('end', () => {
const payURL = JSON.parse(fullBody).payUrl;
res.json({
payURL: payURL
})
});
or other way
res.on('end', () => {
const payURL = JSON.parse(fullBody).payUrl;
res.status(200).send({
payURL: payURL
});
});

Gettting error while fetching response from api in lambda function node js

While i am intent AMAZON ALEXA my lambda function didnt receive repsonse from api ...
and getting response - Sorry, an error occurred. Please say again.
function httpsGet(myData, callback)
{
var options = {
host: 'cp6gckjt97.execute-api.us-east-1.amazonaws.com',
port: 80,
path: '/prod/stateresource?usstate=' + encodeURIComponent(myData),
method: 'GET',
};
var req = https.request(options, res => {
res.setEncoding('utf8');
var returnData = "";
res.on('data', chunk => {
returnData = returnData + chunk;
});
res.on('end', () => {
console.log(JSON.stringify(returnData))
var pop = JSON.parse(returnData).population;
callback(pop); // this will execute whatever function the caller defined, with one argument
});
});
req.end();
}
const GetProductList_Handler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest' && request.intent.name === 'GetProductList' ;
},
async handle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
const responseBuilder = handlerInput.responseBuilder;
let sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
var myRequest = 'Florida';
httpsGet(myRequest, (myResult) => {
say = "there2"+JSON.stringify(myResult);
});
return responseBuilder
.speak(say)
.reprompt('try again, ' + say)
.getResponse();
},
}

Node http get request can't handle Hebrew content

When using get Request, there are question mark instead of hebrew:
Bellow is a snippet of the code invoking during the request:
var req = ((/^https/.test(url))?https:http).request(opts,function(res) {
res.setEncoding('utf8');
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.payload = "";
res.on('data',function(chunk) {
msg.payload += chunk;
});
res.on('end',function() {
node.send(msg);
node.status({});
});
});
Finally I found out the solution, although it's very specific:
we need to require iconv and correctly handle the decoding:
var req = ((/^https/.test(url))?https:http).request(opts,function(res) {
//res.setEncoding('binary');
msg.statusCode = res.statusCode;
msg.headers = res.headers;
msg.payload = "";
res.on('data',function(chunk) {
msg.payload += iconv.decode(new Buffer(chunk), "Windows-1255");
console.log(msg.payload);
});
res.on('end',function() {
node.send(msg);
node.status({});
});
});

Resources