1

I have a nodejs server in Google app engine where I want to delegate long running tasks to task queues using the Google Cloud Task queue.

Task queue not delivering request body but hitting the endpoint:

This is how I add task to task queue:

// Imports the Google Cloud Tasks library.
const cloudTasks = require('@google-cloud/tasks');

// Instantiates a client.
const client = new cloudTasks.CloudTasksClient();
const project = 'projectname';
const queue = 'queuename';
const location = 'us-central1';
const parent = client.queuePath(project, location, queue);

// Send create task request.
exports.sender = async function (options) {
// Construct the fully qualified queue name.
    let myMap = new Map();
    myMap.set("Content-Type", "application/json");
    const task = {
        appEngineHttpRequest: {
            httpMethod: 'POST',
            relativeUri: '/log_payload',
            headers: myMap,
            body: options/* Buffer.from(JSON.stringify(options)).toString('base64')*/
        },
    };
    
    /* if (options.payload !== undefined) {
         task.appEngineHttpRequest.body = Buffer.from(options.payload).toString(
           'base64'
         );
     }*/
    
    if (options.inSeconds !== undefined) {
        task.scheduleTime = {
            seconds: options.inSeconds + Date.now() / 1000,
        };
    }
    
    const request = {
        parent: parent,
        task: task,
    };
    client.createTask(request)
      .then(response => {
          const task = response[0].name;
          //console.log(`Created task ${task}`);
          return {'Response': String(response)}
      })
      .catch(err => {
          //console.error(`Error in createTask: ${err.message || err}`);
          return `Error in createTask: ${err.message || err}`;
      });
};

And this is the endpoint receiving:

app.post('/log_payload', async (req, res) => {
    let mailOptions = {
        subject: 'Message Delivered',
        from: 'sender@example.com',
        to: "receiver@example.com",
        text: String(JSON.stringify(JSON.stringify(req.body)))
    };
    return await mailer.sendEmail(mailOptions).then(value => {
        return res.send(`Received task payload: ${value}`).end()
    }).catch(reason => {
        res.send(`Worker error: ${reason.message}`).end()
    });
});

When the email is received, both the body is an empty Json objects. What am I doing wrong?

Joan Grau Noël
  • 3,084
  • 12
  • 21
Geek Guy
  • 710
  • 1
  • 8
  • 28

2 Answers2

2

Just to complement @Joan Grau's answer:

If you add this filter, you can assure that the result will be correctly parsed:

var bodyParser = require('body-parser');
var rawBodySaver = function (req, res, buf, encoding) {
  if (buf && buf.length) {
    req.rawBody = buf.toString(encoding || 'utf8');
  }
}

app.use(bodyParser.json({ verify: rawBodySaver }));
app.use(bodyParser.urlencoded({ verify: rawBodySaver, extended: true }));
app.use(bodyParser.raw({ verify: rawBodySaver, type: function () { return true } }));

(idea taken from this post)

In particular, a good practice is to apply parser just to wherever you need it. And, besides that, consider to send to body content as a base64 string (this is required for cloud tasks in order to work). Rewriting your code with all these:

const task = {
        appEngineHttpRequest: {
            httpMethod: 'POST',
            relativeUri: '/log_payload',
            headers: myMap,
            body: Buffer.from(JSON.stringify(options)).toString('base64')
        },
    };

and the endpoint:

var bodyParser = require('body-parser');
var rawBodySaver = function (req, res, buf, encoding) {
  if (buf && buf.length) {
    req.rawBody = buf.toString(encoding || 'utf8');
  }
}

app.use('/log_payload', bodyParser.json({ verify: rawBodySaver }));
app.use('/log_payload', bodyParser.urlencoded({ verify: rawBodySaver, extended: true }));
app.use('/log_payload', bodyParser.raw({ verify: rawBodySaver, type: function () { return true } }));

app.post('/log_payload', async (req, res) => {
    let mailOptions = {
        subject: 'Message Delivered',
        from: 'sender@example.com',
        to: "receiver@example.com",
        text: req.body //no need to parse here!
    };
    return await mailer.sendEmail(mailOptions).then(value => {
        return res.send(`Received task payload: ${value}`).end()
    }).catch(reason => {
        res.send(`Worker error: ${reason.message}`).end()
    });
});
jquinter
  • 144
  • 4
0

Guided by this example, I believe the best way to parse the body from the request is to use the body-parser package.

You can implement this by changing your receiving endpoint file, by including the following:

const bodyParser = require('body-parser');

app.use(bodyParser.raw());
app.use(bodyParser.json());
app.use(bodyParser.text());

app.post('/log_payload', async (req, res) => {
    let mailOptions = {
        subject: 'Message Delivered',
        from: 'sender@example.com',
        to: "receiver@example.com",
        text: req.body
    };
    return await mailer.sendEmail(mailOptions).then(value => {
        return res.send(`Received task payload: ${value}`).end()
    }).catch(reason => {
        res.send(`Worker error: ${reason.message}`).end()
    });
});
Joan Grau Noël
  • 3,084
  • 12
  • 21