1

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

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

Comments

2

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.

1 Comment

Thank you. Its actually a way better solution. Sorry if it was a dumb question, I'm just a beguinner.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.