Reputation: 450
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
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
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