Azure Functions how to return an HttpResponse or display a message before the script finishes

Viewed 4329

I have a Python Azure Function that is one file and one main function:

def main(req: func.HttpRequest) -> func.HttpResponse:
    [bunch of code]

    return func.HttpResponse("the file will be deleted in 10 minutes", status_code=200)

It creates a file inside Azure Blob storage for the user and deletes it in 10 minutes. I use time.sleep(600) to do this. However, the message only arrives at the end of this timer, after the file has already been deleted.

How can I make the HttpResponse show the message before the script ends, then wait 10 minutes before deleting the message?

I've tried adding func.HttpResponse('the file will be deleted in 10 minutes') before the time.sleep(600) but it doesn't return anything.

2 Answers

For a Function with http output binding like this you have to return the http response at the end for the response to work. So with a single Function, you cannot achieve this. Continue reading for the alternate solution.

This problem is typically an 'asynchronous' processing example where you want to respond immediately like "ok, I am going to do this" while it "queues further processing" to be continued in the backend. To achieve this in Azure Function you will need 2 functions as below:

  1. Function 1 : Http trigger, http output and Queue output binding (for simplicity I will use storage queue).
  2. Function 2 : Queue trigger (will get triggered by the message queued by function 1).

Function 1 (update according to your need):

JSON:

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureStorageQueuesConnectionString"
    }
  ]
}

Code:

import azure.functions as func

def main(req: func.HttpRequest, msg: func.Out[str]) -> func.HttpResponse:
    [bunch of code]
    input_msg = "<create the message body required by function 2>"
    msg.set(input_msg)
    return func.HttpResponse("the file will be deleted in 10 minutes", status_code=201)

Function 2 (update according to your need):

JSON:

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "name": "msg",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "messages",
      "connection": "AzureStorageQueuesConnectionString"
    }
  ]
}

Code:

import json
import azure.functions as func

def main(msg: func.QueueMessage):  
     # below is just an example of parsing the message, in your case it might be taking the blob info required for deleting
     message = json.dumps({
        'id': msg.id,
        'body': msg.get_body().decode('utf-8'),
        'expiration_time': (msg.expiration_time.isoformat()
                            if msg.expiration_time else None),
        'insertion_time': (msg.insertion_time.isoformat()
                           if msg.insertion_time else None),
        'time_next_visible': (msg.time_next_visible.isoformat()
                              if msg.time_next_visible else None),
        'pop_receipt': msg.pop_receipt,
        'dequeue_count': msg.dequeue_count
    })

    [bunch of code]

NOTE: You can also look at Durable Functions where you can handle complex workflow and would not need to manage the queueing yourself. But since your scenario in this case is quite simple, I did not cover it.

This is because the actual response isn't sent before the function itself returns something to the pipeline - the pipeline will then return the result to the caller.

And instead of doing this wonky 10-minute waiting inside a function app (which is something you really never should do), I'd create a queue message, set the initial invisibility to 10 minutes, add to e.g. delete-file-queue. Have a QueueTrigger somewhere, listening to delete-file-queue, and do the deletion of the file.

So instead, do something like this (I'm not super familiar with Functions in python, so treat this as pseudo code):

def main(req: func.HttpRequest) -> func.HttpResponse:
    # handle whatever you have to, but do NOT include time.sleep
    queue_client.send_message("path/to/blob", visibility_timeout=600)
    # the message will end up in the back of the queue, and 
    # it'll stay invisible for 600 seconds
    # this is something we don't have to wait for, and thus, the following
    # will return immediately
    return func.HttpResponse("file will be deleted in 10 minutes")

Your QueueTrigger would then be something like this:

def main(filename: func.QueueMessage, inputblob: func.InputStream) -> None:
    # check if inputblob is none, if not, delete it

In your functions.json, you should include bindings for the filename and inputblob:

{
  "name": "filename",
  "type": "queueTrigger",
  "direction": "in",
  "queueName": "delete-file-queue",
  "connection": "MyStorageConnectionString"
},
{
  "name": "inputblob",
  "type": "blob",
  "path": "{queueTrigger}",
  "connection": "MyStorageConnectionString",
  "direction": "in"
}

Guide to initializing a queue_client.
And more info here.

Related