Загрузить файл любого типа на S3 с помощью Lambda

Я пытаюсь загрузить файлы на 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"
}

Примечание: я поместил только несколько символов, которые существуют в теле, просто для демонстрации.

Можете ли вы предоставить полную ошибку трассировки?

codester_09 27.11.2022 19:18

как кодируется event['body']? Его байты - не base64.

Shmack 27.11.2022 22:49
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
2
287
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ошибка:

ValueError: строковый аргумент должен содержать только символы ASCII

Ошибка в этой строке:

contend_decode = base64.b64decode(event['body'])

Итак, говорится, что event['body'] не содержит данных в кодировке base64.

Двоичный контент будет фактически предоставлен в параметре content.

Поэтому вместо этого строка должна быть:

contend_decode = base64.b64decode(event['content'])

Спасибо за ваш ответ @John, к сожалению, в событии нет параметра содержимого, все, что у меня есть, это показать в моем вопросе, я редактирую свой вопрос

Doesn't Matter 27.11.2022 22:23

@Shmack нет, в теле нет параметров содержимого.

Doesn't Matter 27.11.2022 22:46
Ответ принят как подходящий

Ошибка и куча компьютерных наук

Поэтому я все еще думаю, что ответ Джона Ротенштейна объективно правильный, т.е. проблема в том, что вы не можете декодировать 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% понятно, что это делает, это:

  1. Проверяет, не является ли это строкой base64
  2. Если нет, преобразуйте тело в байты с кодировкой, а затем в строку с decode()
  3. 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 - документы

Другие вопросы по теме