2

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.

matisa
  • 497
  • 3
  • 25

2 Answers2

2

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

Daniele Ricci
  • 15,422
  • 1
  • 27
  • 55
2

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();  
}
Oluwafemi Sule
  • 36,144
  • 1
  • 56
  • 81
  • Thank you, but it's not the body. I must decode the body before sending. That's why I do things that way. – matisa Jul 05 '20 at 14:14
  • Do you mean encode the body as base64 text. If yes, as I pointed out, you don't need to `JSON.stringify` it before doing so – Oluwafemi Sule Jul 05 '20 at 15:47