Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Azure Cloud Functions HTTP file upload with Python

I've created a Cloud Function on Azure with an HTTP trigger and I want to be able to handle a file upload using a form.
My entry point looks like this:

def main(req: func.HttpRequest) -> func.HttpResponse:
    body = req.get_body()
    headers=req.headers
    ...

The problem is that what I get in body is raw binary and I don't know how to decode it and get the uploaded file.

Does anyone know about a good method to achieve this?

like image 565
marco romelli Avatar asked Mar 04 '23 03:03

marco romelli


1 Answers

There's, I'd say, an Azure-native way to do that, and there's a Python-native way.

Azure-native method

azure.functions.HttpRequest has a files property which is a MultiDict generator. Here's how to use that:

import logging

import azure.functions as func


def main(req: func.HttpRequest) -> func.HttpResponse:
    for input_file in req.files.values():
        filename = input_file.filename
        contents = input_file.stream.read()

        logging.info('Filename: %s' % filename)
        logging.info('Contents:')
        logging.info(contents)

        [..process the file here as you want..]

    return func.HttpResponse(f'Done\n')

Python-native method

If you want to do it only with the standard Python library (e.g. for more portability), then you should know that what you need to parse in the body is the MIME multipart message, in a binary form:

Content-Type: multipart/form-data; boundary=--------------------------1715cbf149d89cd9

--------------------------1715cbf149d89cd9
Content-Disposition: form-data; name="data"; filename="test.txt"
Content-Type: application/octet-stream

this is a test document

--------------------------1715cbf149d89cd9--

Sample script to reproduce:

echo 'this is a test document' > test.txt && curl -F '[email protected]' 'https://[..yourfunctionname..].azurewebsites.net/api/HttpTrigger'

You will be surprised but the email module would do it for you (I personally think it's poorly named).

Sample code below (note: with no error handling!) to highlight the core idea:

import cgi
import email
import logging

import azure.functions as func


def main(req: func.HttpRequest) -> func.HttpResponse:
    # Content-Type should be 'multipart/form-data' with some boundary
    content_type = req.headers.get('Content-Type')
    body = req.get_body().decode('utf-8')

    mime_template = 'MIME-Version: 1.0\nContent-Type: %s\n%s'
    post_data = email.parser.Parser().parsestr(mime_template % (content_type, body))

    # Content-Disposition header format:
    #  'form-data; name="data"; filename="test.txt"'
    disposition = post_data.get_payload()[0]['Content-Disposition']
    disposition_value = cgi.parse_header('Content-Disposition: %s' % disposition)[1]

    filename = disposition_value['filename']
    contents = post_data.get_payload()[0].get_payload()

    logging.info('Filename: %s' % filename)
    logging.info('Contents:')
    logging.info(contents)

    [..process the file here as you want..]

    return func.HttpResponse(f'Done\n')
like image 103
ximaera Avatar answered Mar 12 '23 18:03

ximaera