Я пытаюсь загрузить файлы на S3 с помощью API Gateway и Lambda, все процессы работают нормально, пока я не доберусь до Lambda, моя лямбда выглядит так:
import base64
import boto3
import os
s3_client = boto3.client('s3')
bucket_name = os.environ['S3_BUCKET_NAME']
def lambda_handler(event, context):
contend_decode = base64.b64decode(event['body'])
response = s3_client.put_object(Bucket=bucket_name, Body=contend_decode)
print(response)
return {
'statusCode': 200,
'body': 'File uploaded'
}
Когда я загружаю, например, файл mp3, я получаю сообщение об ошибке:
[ERROR] ValueError: string argument should contain only ASCII characters
Traceback (most recent call last):
File "/var/task/lambda_function.py", line 10, in lambda_handler
contend_decode = base64.b64decode(event['body'])
File "/var/lang/lib/python3.8/base64.py", line 80, in b64decode
s = _bytes_from_decode_data(s)
File "/var/lang/lib/python3.8/base64.py", line 39, in _bytes_from_decode_data
raise ValueError('string argument should contain only ASCII characters')
[ERROR] ValueError: string argument should contain only ASCII characters Traceback (most recent call last): File "/var/task/lambda_function.py", line 10, in lambda_handler contend_decode = base64.b64decode(event['body']) File "/var/lang/lib/python3.8/base64.py", line 80, in b64decode s = _bytes_from_decode_data(s) File "/var/lang/lib/python3.8/base64.py", line 39, in _bytes_from_decode_data raise ValueError('string argument should contain only ASCII characters')
Любая идея об этой проблеме, пожалуйста?
Редактировать:
Содержание события примерно такое:
{
"resource": "/upload",
"path": "/upload",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-ASN": "5410",
"CloudFront-Viewer-Country": "FR",
"Content-Type": "audio/mpeg",
"Host": "um8xxxxpxx.execute-api.eu-west-1.amazonaws.com",
"Postman-Token": "fe49e15f-82c6-44c7-8399-4b6fba9b9abc",
"User-Agent": "PostmanRuntime/7.29.2",
"Via": "1.1 12bc6711250373a4xxxxxxxxxx44504.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "5Zv2MVCxxxxxxxxxxxxyzMuv_CfIAxxxxxxxxxxxxJyz4JtHb-QImYZGQ= = ",
"X-Amzn-Trace-Id": "Root=1-6383d306-4e81300e0000000c3262b7a45",
"x-api-key": "g4KOPDl5zoB0E2QBpAAXSaESDFyGkR38f000",
"X-Forwarded-For": "1XX.XX9.2XX.XX9, 1XX.XX6.XX5.XXX",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"multiValueHeaders": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate, br"
],
"CloudFront-Forwarded-Proto": [
"https"
],
"CloudFront-Is-Desktop-Viewer": [
"true"
],
"CloudFront-Is-Mobile-Viewer": [
"false"
],
"CloudFront-Is-SmartTV-Viewer": [
"false"
],
"CloudFront-Is-Tablet-Viewer": [
"false"
],
"CloudFront-Viewer-ASN": [
"5410"
],
"CloudFront-Viewer-Country": [
"FR"
],
"Content-Type": [
"audio/mpeg"
],
"Host": [
"um8xxxxpxx.execute-api.eu-west-1.amazonaws.com"
],
"Postman-Token": [
"fDDDDf-82c8-44c9-DDD1-4b6f9QASFF9abc"
],
"User-Agent": [
"PostmanRuntime/7.29.2"
],
"Via": [
"1.1 12bVASD16aeca2DDD44504.cloudfront.net (CloudFront)"
],
"X-Amz-Cf-Id": [
"5Zv2MVCnaDDDzMuv_CfIA6iC89CiUnjDDDAZXAb-QImYZGQ= = "
],
"X-Amzn-Trace-Id": [
"Root=1-6383AZDD-4e81002e022374c326hu8a45"
],
"x-api-key": [
"g4KOPDl5zoBia3cT4pYMkynzyGkX00aa"
],
"X-Forwarded-For": [
"1XX.XX9.2XX.XX9, 1XX.XX6.XX5.XXX"
],
"X-Forwarded-Port": [
"443"
],
"X-Forwarded-Proto": [
"https"
]
},
"queryStringParameters": "None",
"multiValueQueryStringParameters": "None",
"pathParameters": "None",
"stageVariables": "None",
"requestContext": {
"resourceId": "adddazq",
"resourcePath": "/upload",
"httpMethod": "POST",
"extendedRequestId": "cR3o-zddEFgazz = ",
"requestTime": "27/Nov/2022:21:13:42 +0000",
"path": "/dev/upload",
"accountId": "114782879802",
"protocol": "HTTP/1.1",
"stage": "dev",
"domainPrefix": "ua8xjwxraf",
"requestTimeEpoch": 1669583622098,
"requestId": "23e099f9-eda4-42b2-8b4f-b1aaea589978",
"identity": {
"cognitoIdentityPoolId": "None",
"cognitoIdentityId": "None",
"apiKey": "h4KOPDl5zoqsdT4pYMkynzdddaz8f95560",
"principalOrgId": "None",
"cognitoAuthenticationType": "None",
"userArn": "None",
"apiKeyId": "z887qsddox4",
"userAgent": "PostmanRuntime/7.29.2",
"accountId": "None",
"caller": "None",
"sourceIp": "176.139.21.129",
"accessKey": "None",
"cognitoAuthenticationProvider": "None",
"user": "None"
},
"domainName": "um8xxxxpxx.execute-api.eu-west-1.amazonaws.com",
"apiId": "um8xxxxpxx"
},
"body": "\x04\x08-P�\x10,Gh�m\x0c\x06K����Te�U�-��\r\x01�Y��l�,3�\x11�Q�4$�........6��\x1872Ip�d�p\x1d�M�PX�0`�x�0����d�\x0f�\x0c.ǃ��\x12\x00\x00\r \x00\x00\x01\x18��........",
"isBase64Encoded": "False"
}
Примечание: я поместил только несколько символов, которые существуют в теле, просто для демонстрации.
как кодируется event['body']
? Его байты - не base64.
Ошибка:
ValueError: строковый аргумент должен содержать только символы ASCII
Ошибка в этой строке:
contend_decode = base64.b64decode(event['body'])
Итак, говорится, что event['body']
не содержит данных в кодировке base64.
Двоичный контент будет фактически предоставлен в параметре content
.
Поэтому вместо этого строка должна быть:
contend_decode = base64.b64decode(event['content'])
Спасибо за ваш ответ @John, к сожалению, в событии нет параметра содержимого, все, что у меня есть, это показать в моем вопросе, я редактирую свой вопрос
@Shmack нет, в теле нет параметров содержимого.
Поэтому я все еще думаю, что ответ Джона Ротенштейна объективно правильный, т.е. проблема в том, что вы не можете декодировать event['body']
в байты, потому что это строка в виде байтов, которые имеют символы, отличные от ascii, и поэтому выдает ошибку .
Если вы посмотрите на event['body']
, вы, возможно, сможете собрать это воедино:
"\x04\x08-P�\x10,Gh�m\x0c\x06K����Te�U�-��\r\x01�Y��l�,3�\x11�Q�4$�........6��\x1872Ip�d�p\x1d�M�PX�0`�x�0����d�\x0f�\x0c.ǃ��\x12\x00\x00\r \x00\x00\x01\x18��........"
Обратите внимание, что он не выдает ошибку заполнения, которая возникает, когда строка имеет неправильную длину (обычно из-за завершающего "="). Вы бы использовали декодирование строки base64 (например, "TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu"
— украдено из Википедии), чтобы превратить ее в байты.
Бесплатный немного информации:
b64.b64decode("TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu")
, чтобы вернуть строку в виде строки байтов (a = b"Many hands make light work."
).b = list(a)
-> [77, 97, 110, 121, 32, 104, 97, 110, 100, 115, 32, 109, 97, 107, 101, 32, 108, 105, 103, 104, 116, 32, 119, 111, 114, 107, 46]
."".join([hex(c).replace("0x", "\\x") for c in b])
-> \x4d\x61\x6e\x79\x20\x68\x61\x6e\x64\x73\x20\x6d\x61\x6b\x65\x20\x6c\x69\x67\x68\x74\x20\x77\x6f\x72\x6b\x2e
.with open(filename, "rb") as f; a = f.read()
вернет что-то вроде того, что у вас есть в вашем event['body']
, если это изображение или что-то в этом роде, поэтому вы предполагаете, что b"hello world"
также будет байтами, подобными with open()...
, но, по-видимому, нет (? ). Я не знаю; много распаковывать.Если вы не знакомы с тем, что находится в вашем event['body']
, эта строка на самом деле представляет собой декодированные байты — если это немного двусмысленно, потому что \x
на самом деле является escape-символом для шестнадцатеричного кода в Python, но есть несколько очень легко воспроизводимых примеров, где это не так. похоже, это не так (возьмите, например, ваш event['body']
- что это вообще такое "\x1872Ip�d�p"
). Вы можете получить декодированные байты, выполнив что-то вроде приведенного ниже, с оговоркой, что он был преобразован в строку, поэтому это больше не байты, подобные объекту, - это строка:
a = "hello world"
b = a.encode("utf-8")
# or
c = bytes(a, "utf-8")
# or - the one below I think defaults to utf8
a = b"hello world"
# the closest I could get to hex representation of the string was from this
# "".join([hex(ord(c)).replace("0x", "\\x") for c in a])
Дело в том, что я не знаю, какую кодировку он использовал для декодирования в байты, и неясно, могу ли я ожидать, что тело будет состоять из байтов каждый раз, или если это будет строка base64, как isBase64Encoded
мог бы заставить меня поверить . Я не уверен на 100%, но я предполагаю, что если вы сделаете что-то вроде приведенного ниже, при условии, что результирующая декодированная строка может не быть base64, вы можете получить вывод строки base64:
Быстрое редактирование - кажется, я неправильно понял, что означает isBase64Encoded
. После написания этого, я думаю, это следует понимать как «байты закодированы как base64? Правда или ложь». Я отредактирую приведенный ниже код. Кроме того, я предполагаю, что данные для event['body']
прошли один из двух процессов: либо открыты как байты -> isBase64Encoded установлены как False -> отправлены, либо открыты как байты -> b64encoded -> преобразованы в байты -> isBase64Encoded установлены как True -> отправлены . С этого момента в этом ответе вы увидите, что я ссылаюсь на ответ перед этим редактированием как на предварительное редактирование, а после этого редактирования как на постредактирование.
import base64
# pre edit
if not event['isBase64Encoded']:
event['body'] = bytes(event[body], "whatever that encoding is").decode()
# b64encode takes a string and converts it to a bytes like object.
# b64decode takes a bytes like object and converts it to a string.
event['body'] = base64.b64decode(event['body'])
print(event['body'])
# post edit
# you might be able to read bytes with an arbitrary encoding using BytesIO
from io import BytesIO
if event['isBase64Encoded']:
# this would've been sent as the default according to my notes from the edit
# take the string, convert it to bytes, then decode it - should be a base64 string with a utf8 encoding
event['body'] = bytes(event['body']).decode()
# decode the utf8 string to base64 bytes
event['body'] = base64.b64decode(event['body'])
else:
#event['body'] = bytes(event[body], some encoding)
event['body'] = BytesIO(event[body]).read()
Предварительное редактирование. Чтобы было на 100% понятно, что это делает, это:
decode()
base64decode()
берет эту строку и, если это строка base64 (как указано выше), и преобразует ее в байты с кодировкой base64Постредактирование — я включил в код несколько полезных комментариев, но в любом случае он должен возвращать байты.
Однако вы, кажется, также хотите поместить эти байты в корзину - документы:
response = client.put_object(
#Body=bytes(event["body"], encoding),
# event['body'] should already be bytes by now as per the post edit comments
Body=event["body"],
Bucket = "my_bucket",
#ContentEncoding=event["multiValueHeaders"]["Accept-Encoding"],
ContentType=event["multiValueHeaders"]["Content-Type"],
Key = "my/object/name.mp4"
)
Предварительное редактирование. Реально, установите все эти значения ключевых слов, и вы должны быть золотыми — вам не нужно запускать операцию декодирования base64 в этом случае (на основе того, что было возвращено в вашем событии — вы могли бы, если бы оно действительно было закодировано как строку base64), просто передайте put_object()
байты.
Post Edit — по-прежнему задайте все ключевые слова и прочитайте ниже (Кодирование содержимого), но к настоящему времени мы должны были обработать оба случая isBase64Encoded
, и результатом должен быть байтовый объект, хранящийся в event['body']
, поэтому никаких существенных изменений не требуется. быть сделаны в этом абзаце в отношении put_object()
.
Вот ссылка на то, что такое ContentEncoding
по сравнению с ContentType, которая может пролить свет на то, следует ли вам его использовать или нужно ли его использовать.
Вы не должны использовать такие обобщенные операторы try/except, как я сделал ниже, но если это вас действительно беспокоит, вы можете отследить, что выдают эти ошибки, и добавить это в себя или полностью удалить их, но концептуально это должно быть то, что вы хотите .
Предварительное редактирование
import base64
import boto3
import os
s3_client = boto3.client('s3')
bucket_name = os.environ['S3_BUCKET_NAME']
def lambda_handler(event, context):
if not event['isBase64Encoded']:
try:
event['body'] = bytes(event[body], "whatever that encoding is").decode()
except:
return {
# AWS probably returns a 403, so maybe return something different for debugging?
'statusCode': 406,
'body': 'Misconfigured object.'
}
else:
try:
event['body'] = base64.b64decode(event['body'])
except:
return {
# AWS probably returns a 403, so maybe return something different for debugging?
'statusCode': 406,
'body': 'Misconfigured object.'
}
try:
response = client.put_object(
Body=bytes(event["body"], encoding),
Bucket = "my_bucket",
#ContentEncoding=event["multiValueHeaders"]["Accept-Encoding"],
ContentType=event["multiValueHeaders"]["Content-Type"],
Key = "my/object/name.mp4"
)
except:
return {
# AWS probably returns a 403, so maybe return something different for debugging?
'statusCode': 406,
'body': 'Misconfigured object.'
}
else:
print(response)
return {
'statusCode': 200,
'body': 'File uploaded'
}
Сообщение Редактировать
import base64
import boto3
import os
from io import BytesIO
s3_client = boto3.client('s3')
bucket_name = os.environ['S3_BUCKET_NAME']
def lambda_handler(event, context):
if event['isBase64Encoded']:
# this would've been sent as the default according to my notes from the edit
# take the string, convert it to bytes, then decode it - should be a base64 string with a utf8 encoding
event['body'] = bytes(event['body']).decode()
# decode the utf8 string to base64 bytes
event['body'] = base64.b64decode(event['body'])
else:
#event['body'] = bytes(event[body], some encoding)
event['body'] = BytesIO(event[body]).read()
try:
response = client.put_object(
Body=bytes(event["body"], encoding),
Bucket = "my_bucket",
#ContentEncoding=event["multiValueHeaders"]["Accept-Encoding"],
ContentType=event["multiValueHeaders"]["Content-Type"],
Key = "my/object/name.mp4"
)
except:
return {
# AWS probably returns a 403, so maybe return something different for debugging?
'statusCode': 406,
'body': 'Misconfigured object.'
}
else:
print(response)
return {
'statusCode': 200,
'body': 'File uploaded'
}
Как работает Base64? - википедия
Кодировка Base64 - документы
Декодирование Base64 - документы
Можете ли вы предоставить полную ошибку трассировки?