问题
在 LocalStack 容器中运行的 SNS 主题将向在单独容器中运行的订阅 HTTP 端点发送通知,但 API 收到
{}
而不是发送到 SNS 的消息。
重现步骤
为了简洁起见,我排除了
my-api
Dockerfile 和应用程序代码。您可以填写任何 Web 服务器(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:
localstack-init-scripts/init.py
用于初始化 LocalStack 容器的 Python 脚本。import boto3
import requests
# SNS Client
sns = boto3.client('sns', endpoint_url='http://localhost:4566', region_name='us-east-1')
# Create Topic
topic_arn = sns.create_topic(Name='local-events')['TopicArn']
print('Topic Arn:', topic_arn)
#Subscribe HTTP endpoint to Topic
subscription_arn = sns.subscribe(
TopicArn=topic_arn,
Protocol='http',
Endpoint='http://host.docker.internal:3000/events',
# I've also tried Endpoint='http://my-api:3000/events',
ReturnSubscriptionArn=True
)['SubscriptionArn']
print('Subscription Arn:', subscription_arn)
# Get the Subscription Token and Confirm the Subscription
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 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": "This is a Test Message" }'
结果
运行上述命令后,您应该看到类似以下内容的 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 | Received event {}
故障排除步骤~~到目前为止~~
curl -XPOST http://host.docker.internal:3000/events -d '{ "msg": "curl test message" }'
我得到了预期的响应,并在 docker 日志中看到以下内容my-api-1 | Received event { '{ "msg": "curl test message" }': '' }
awslocal sns get-subscription-attributes --subscription-arn "<subscription-arn-from-logs>"
确认主题已订阅,并通过 "ConfirmationWasAuthenticated": "true"
和 "PendingConfirmation": "false"
参考文献
我已经遇到这个问题了。
问题似乎与 LocalStack 在发送到 HTTP 端点时如何处理 SNS 消息格式有关。这里有一些可以尝试的事情:
消息格式: SNS 通常将消息包装在 JSON 结构中。尝试修改您的 API 以期望这种格式:
@app.post("/events")
async def receive_event(event: dict):
message = event.get('Message', '{}')
print(f"Received message: {message}")
# Parse message if it's 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='http://my-api:3000/events'
您可能需要向您的
docker-compose.yml
添加网络:
services:
my-api:
# ... other config ...
networks:
- mynetwork
localstack:
# ... other config ...
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}")
本地堆栈版本: 确保您使用的是最新版本的 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
如果这些都不能解决问题,则可能是 LocalStack 的 SNS 实现中的错误。在这种情况下,您可以考虑在 LocalStack GitHub repository 上提出问题。