Vinícius Gabriel
Vinícius Gabriel

Reputation: 450

How to download a file from AWS Lambda Python Rest API

So I been trying to download a file from a s3 bucket through a python rest api hosted in AWS. I followed a template of an proper rest api in such environment using this video. However, I have no ideia at all how to pass a file to the response. Here is my code:

import json, traceback, boto3
from AWSHelper import DynamoDBHelper, S3Helper

dynamodbH = DynamoDBHelper()
s3H = S3Helper('vini-bucket2')

def lambda_handler(event, context):
    response = {}
    response['headers'] = {}
    try:
        for key in event['queryStringParameters']:
            try:
                event['queryStringParameters'][key] = int(event['queryStringParameters'][key])
            except:
                pass
        fn = dynamodbH.select('files', event['queryStringParameters'])['FileName']
        s3client = boto3.client('s3')
        fileobj = s3client.get_object(Bucket=s3H.bucket,Key=fn) 
        filedata = fileobj['Body'].read()
        response['statusCode'] = 200
        response_type = fn.split('.')[-1]
        response['headers']['Content-Type'] = f'application/{response_type}'
        # the problem starts here. None of these solutions work: 
        #response['body'] = json.dumps(filedata) -> Object of type bytes is not JSON serializable
        #response['body'] = filedata.decode(encoding='UTF-8') -> 'utf-8' codec can't decode byte 0x9c in position 146: invalid start byte
        #response['body'] = filedata  -> 'utf-8' codec can't decode byte 0x9c in position 146: invalid start byte
    except Exception as ex:
        response['statusCode'] = 500
        response['headers']['Content-Type'] = 'application/json'
        tr = {}
        tr['Type'] = str(type(ex))
        tr['Message'] = ex.message if hasattr(ex, 'message') else str(ex)
        tr['StackTrace'] = str(traceback.format_exc())
        tr['Pameters'] = event['queryStringParameters']
        response['body'] = json.dumps(tr)
    except:
        response['statusCode'] = 500
        response['headers']['Content-Type'] = 'application/json'
        tr = {}
        tr['Message'] = 'Unexpected error'
        response['body'] = json.dumps(tr)
    return response

Upvotes: 1

Views: 4619

Answers (2)

Dre
Dre

Reputation: 2192

The answer by Stargazer is correct and probably a best practice in most cases, but it doesn't answer the question directly. To return a file download you can use something like the following to return an empty zip file:

    from io import BytesIO()
    import base64

    zip = BytesIO()
    return {
        'headers': {
            'Content-Type': 'application/zip',
            'Content-Disposition': 'attachment; filename="details.zip"'
        },
        'statusCode': 200,
        'body': base64.b64encode(zip.getvalue()).decode('utf-8'),
        'isBase64Encoded': True
    }

You'll have to modify it accordingly for different file types. For an image you'll have to load the image and change the mime-type, etc.

Upvotes: 3

Stargazer
Stargazer

Reputation: 1530

You don't need to return a file, just create a pre-signed download url and redirect to it. According to boto3 docs, should be something like this:

s3_client = boto3.client('s3')
presign_url = s3_client.generate_presigned_url('get_object',
                                                Params={'Bucket': bucket_name,
                                                        'Key': object_name},
                                                ExpiresIn=expiration)

response["headers"]["Location"] = presign_url
return response

And that should do it, the response will redirect the request to the object and it should be downloaded.

Upvotes: 2

Related Questions