Вопрос или проблема
Проблема
Тема SNS, работающая в контейнере LocalStack, отправляет уведомление на подписанный HTTP конечный пункт, работающий в отдельном контейнере, но API получает {}
вместо сообщения, отправленного в SNS.
Шаги для воспроизведения
Для краткости я исключил Dockerfile и код приложения my-api
. Вы можете использовать любой веб-сервер (FastAPI, Express и т.д.), лишь бы он прослушивал порт 3000 и имел конечный пункт POST /events
. В этом API сейчас ничего особенного не происходит, он просто регистрирует полученное событие.
- Создайте следующий файл
docker-compose.yml
version: '3.8'
services:
my-api:
build:
context: .
ports:
- 3000:3000
volumes:
- ./src:/opt/my-api/src
localstack:
image: localstack/localstack
container_name: localstack
ports:
- 4566:4566
environment:
- SERVICES=sns
- AWS_DEFAULT_REGION=us-east-1
volumes:
- localstack-data:/var/lib/localstack
# https://docs.localstack.cloud/references/init-hooks/
- ./localstack-init-scripts/:/etc/localstack/init/ready.d/
volumes:
localstack-data:
- Создайте Python-скрипт
localstack-init-scripts/init.py
для инициализации контейнера LocalStack.
import boto3
import requests
# Клиент SNS
sns = boto3.client('sns', endpoint_url="http://localhost:4566", region_name="us-east-1")
# Создание темы
topic_arn = sns.create_topic(Name="local-events")['TopicArn']
print('Topic Arn:', topic_arn)
# Подписка HTTP конечного пункта на тему
subscription_arn = sns.subscribe(
TopicArn=topic_arn,
Protocol="http",
Endpoint="http://host.docker.internal:3000/events",
# Я также пробовал Endpoint="http://my-api:3000/events",
ReturnSubscriptionArn=True
)['SubscriptionArn']
print('Subscription Arn:', subscription_arn)
# Получение токена подписки и подтверждение подписки
token_res = requests.get(f'http://localhost:4566/_aws/sns/subscription-tokens/{subscription_arn}').json()
print('Token Res:', token_res)
confirmation_status_code = sns.confirm_subscription(
TopicArn=topic_arn,
Token=token_res['subscription_token']
).get('ResponseMetadata', {}).get('HTTPStatusCode', 500)
confirmed = True if confirmation_status_code == 200 else False
print('Subscription Confirmed:', confirmed)
- Запустите Docker-сервисы
docker compose up
- Отправьте тестовое уведомление на тему
local-events
aws --endpoint-url http://localhost:4566 sns publish \
--topic-arn arn:aws:sns:us-east-1:000000000000:local-events \
--message '{ "msg": "Это тестовое сообщение" }'
Результаты
После выполнения указанных выше команд вы должны увидеть логи docker, похожие на
localstack | 2024-10-01T01:38:29.993 INFO --- [et.reactor-0] localstack.request.aws : AWS sns.CreateTopic => 200
localstack | Topic Arn: arn:aws:sns:us-east-1:000000000000:local-events
localstack | 2024-10-01T01:38:29.997 INFO --- [et.reactor-0] localstack.request.aws : AWS sns.Subscribe => 200
localstack | Subscription Arn: arn:aws:sns:us-east-1:000000000000:local-events:e98cb2ad-565c-4160-a77c-6a4bdd21350f
localstack | 2024-10-01T01:38:30.001 INFO --- [et.reactor-0] localstack.request.http : GET /_aws/sns/subscription-tokens/arn:aws:sns:us-east-1:000000000000:local-events:e98cb2ad-565c-4160-a77c-6a4bdd21350f => 200
localstack | Token Res: {'subscription_token': '75732d656173742d312f8c8e1e8b8c8e1e8b8c8e1e8b8c8e1e8b8c8e1e8b8c8e', 'subscription_arn': 'arn:aws:sns:us-east-1:000000000000:local-events:e98cb2ad-565c-4160-a77c-6a4bdd21350f'}
localstack | 2024-10-01T01:38:30.004 INFO --- [et.reactor-0] localstack.request.aws : AWS sns.ConfirmSubscription => 200
localstack | Subscription Confirmed: True
localstack | Ready.
my-api-1 | Получено событие {}
Шаги по устранению неполадок ~~на данный момент~~
- Я зашел в контейнер Localstack и выполнил
curl -XPOST http://host.docker.internal:3000/events -d '{ "msg": "сообщение теста curl" }'
, получил ожидаемый ответ и увидел следующее в логах docker
my-api-1 | Получено событие { '{ "msg": "сообщение теста curl" }': '' }
- Подтвердил, что тема была подписана, выполнив
awslocal sns get-subscription-attributes --subscription-arn "<subscription-arn-from-logs>"
и получил ответ с"ConfirmationWasAuthenticated": "true"
и"PendingConfirmation": "false"
Ссылки
У меня уже была эта проблема.
Похоже, проблема связана с тем, как LocalStack обрабатывает формат сообщений SNS при отправке на HTTP конечный пункт. Вот несколько вещей, которые можно попробовать:
-
Формат сообщения:
SNS обычно оборачивает сообщение в JSON-структуру. Попробуйте изменить ваш API, чтобы он ожидал этот формат:@app.post("/events") async def receive_event(event: dict): message = event.get('Message', '{}') print(f"Получено сообщение: {message}") # Парсинг сообщения, если это JSON try: message_json = json.loads(message) print(f"Парсированное сообщение: {message_json}") except json.JSONDecodeError: print("Сообщение не является JSON")
-
Доставка сырого сообщения:
Включите доставку сырого сообщения для подписки. Добавьте это в вашinit.py
:sns.set_subscription_attributes( SubscriptionArn=subscription_arn, AttributeName="RawMessageDelivery", AttributeValue="true" )
-
Сетевая конфигурация:
Убедитесь, что ваши контейнеры могут общаться. Попробуйте использовать имя службы вместоhost.docker.internal
:Endpoint="http://my-api:3000/events"
Возможно, вам потребуется добавить сеть в ваш
docker-compose.yml
:services: my-api: # ... другая конфигурация ... networks: - mynetwork localstack: # ... другая конфигурация ... networks: - mynetwork networks: mynetwork:
-
Отладка:
Добавьте больше логирования в ваш API, чтобы увидеть полный запрос:@app.post("/events") async def receive_event(request: Request): body = await request.body() headers = request.headers print(f"Получены заголовки: {headers}") print(f"Получено тело: {body}")
-
Версия LocalStack:
Убедитесь, что вы используете последнюю версию LocalStack. В более старых версиях могут быть ошибки. -
Структура сообщения:
При публикации попытайтесь включить параметрMessageStructure
:aws --endpoint-url http://localhost:4566 sns publish \ --topic-arn arn:aws:sns:us-east-1:000000000000:local-events \ --message '{"default": "{ \"msg\": \"Это тестовое сообщение\" }"}' \ --message-structure json
Если ни одно из этих решений не устраняет проблему, возможно, это ошибка в реализации SNS в LocalStack. В этом случае вы можете рассмотреть возможность открытия проблемы в репозитории LocalStack на GitHub репозиторий.
Ответ или решение
Проблема, с которой вы столкнулись, заключается в том, что LocalStack не передает тело сообщения в SNS Topic на подписанный HTTP-эндпоинт, и вы получаете пустой объект {}
вместо ожидаемого сообщения. Рассмотрим несколько решений для этой проблемы и шаги, которые вы можете предпринять, чтобы исправить ситуацию.
Шаги для решения проблемы
-
Формат сообщения: SNS обычно оборачивает сообщение в JSON-структуру. Измените ваш API, чтобы он ожидал этот формат сообщения. Пример кода для вашего FastAPI или Express приложения:
@app.post("/events") async def receive_event(event: dict): message = event.get('Message', '{}') print(f"Received message: {message}") # Пытаемся распарсить сообщение, если это JSON try: message_json = json.loads(message) print(f"Parsed message: {message_json}") except json.JSONDecodeError: print("Message is not JSON")
-
Сырой формат сообщения: Включите сырую доставку сообщений для подписки. Добавьте следующий код в ваш
init.py
для конфигурации подписки:sns.set_subscription_attributes( SubscriptionArn=subscription_arn, AttributeName="RawMessageDelivery", AttributeValue="true" )
-
Сетевая конфигурация: Убедитесь, что ваши контейнеры могут связываться друг с другом. Попробуйте использовать имя сервиса вместо
host.docker.internal
вEndpoint
. Пример:Endpoint="http://my-api:3000/events"
Также рекомендуется добавить пользовательскую сеть к вашему
docker-compose.yml
:services: my-api: # ... другая конфигурация ... networks: - mynetwork localstack: # ... другая конфигурация ... networks: - mynetwork networks: mynetwork:
-
Отладка: Добавьте больше логирования в ваш API, чтобы видеть полный запрос и его содержимое:
@app.post("/events") async def receive_event(request: Request): body = await request.body() headers = request.headers print(f"Received headers: {headers}") print(f"Received body: {body.decode('utf-8')}")
-
Версия LocalStack: Проверьте, используете ли вы последнюю версию LocalStack. В более ранних версиях могли быть ошибки, которые уже исправлены в новых релизах.
-
Структура сообщения: При публикации сообщения попробуйте использовать параметр
MessageStructure
. Пример команды для публикации:aws --endpoint-url http://localhost:4566 sns publish \ --topic-arn arn:aws:sns:us-east-1:000000000000:local-events \ --message '{"default": "{ \"msg\": \"This is a Test Message\" }"}' \ --message-structure json
Заключение
Если ни одно из предложенных решений не позволяет вам решить проблему, возможно, это баг в реализации SNS в LocalStack. В таком случае вы можете рассмотреть возможность открытия запроса на исправление на GitHub репозитории LocalStack.
Следуя этим шагам, вы сможете устранить проблемы с передачей сообщения от SNS Topic к вашему HTTP-эндпоинту. Удачи!