2
votes

I have a HTTP triggered function inside my function app - it is invoked by the webhook connector in Azure Logic Apps. The way webhooks work in Logic Apps is they need the initial response like "status:200" which puts the Logic App to sleep and then when the "work" is done the callbackURL is invoked and then the Logic App resumes work. My problem is responding with that initial response from my function app.

If you don't respond to the webhook with a status:2** within 2 minutes then the webhook "retries" which starts a new instance of the function app and that is obviously problematic.

So my code looks something like this

try 
{ 
     await function1() // this function runs more than 2 minutes
}
catch(err)
{
     context.log(err)
}
finally
{
     await function2() // this function returns to LogicApp via callbackurl
}

I have tried adding context.res = { status:200} in the try block and have tried creating an individual function that has context.res = {status:200} inside, however none of those work.

If my function runs under 2 minutes then obviously the webhook doesn't retry, however when it does take over 2 minutes it fails.

I tried to build based on the "Webhook" design from this article

Calling Long Running Functions Azure

These are the combinations I have tried:

try {
    context.bindings.res = {status:202}
    await function1()
}

try {
    context.res = {status:202}
    await function1()
}

try {
    await initialResponse(context)// function that has context.res={status:202} inside
    function1()
}

try {
    context.res = {status:202}
    context.done()
    await function1()
} // I added @UncleDave 's suggestion as well

try {
    await initialResponse(context)
    function1()
}
async function initialResponse(context)
{
    context.res = {status:202}
    context.done()
} // this attempt also just ended the function
1

1 Answers

0
votes

Consider creating a second function to handle the long running operation and change your current function to throw the request on to a queue and immediately return.

The HTTP Trigger hit by your web hook:

function.json

{
  "bindings": [
    {
      "authLevel": "anonymous", // Don't do this - demonstration only.
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "queue",
      "queueName": "process",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

index.js

module.exports = function (context, req) {
  context.log('Started processing a request.', req);

  context.done(null, {
    res: { status: 202 },
    queue: { callbackUrl: req.body.callbackUrl, data: req.body.data }
  });
};

The Queue Trigger that does the processing:

function.json

{
  "bindings": [
    {
      "name": "item",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "process",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

index.js

module.exports = async function (context, item) {
    context.log('JavaScript queue trigger function started to process work item.', item);

    const data = await process(item.data);

    const request = require('request');

    await request.post(item.callbackUrl, { body: data, json: true });
};

function process(data) {
    // Some long running operation.
    return new Promise(resolve => {
        setTimeout(() => resolve({ a: 'b', c: 'd' }), 5000);
    });
}

Note that I'm using request here, which is an external dependency you'll have to add to your package.json, but Node's built in HTTP Client will work just as well.