Files
mengyastore/test/server.py
2026-05-13 12:11:46 +08:00

112 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
简易 Webhook 接收服务:监听 POST /webhook打印请求内容与部分头信息。
生产请将手机/脚本转发指向萌芽小店后端:`POST {YOUR_API_BASE}/api/webhooks/mengya-pay`
并在请求头附带与后端一致的 `X-Webhook-Secret`(对应环境变量 `WEBHOOK_MENGYA_SECRET`)。
本地测试环境变量:
HOST 监听地址,默认 0.0.0.0
PORT 监听端口,默认 8000
WEBHOOK_SECRET 若设置,则要求请求头 X-Webhook-Secret 与其一致,否则返回 403
"""
from __future__ import annotations
import json
import logging
import os
import secrets
from datetime import datetime, timezone
from typing import Annotated
from fastapi import FastAPI, Header, HTTPException, Request, Response
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
log = logging.getLogger("webhook")
app = FastAPI(title="Webhook 测试接收器", docs_url="/docs")
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
@app.get("/health")
def health() -> dict:
return {"status": "ok", "time": _now_iso()}
@app.post("/webhook")
async def webhook(
request: Request,
response: Response,
x_webhook_secret: Annotated[str | None, Header(alias="X-Webhook-Secret")] = None,
) -> dict:
expected = os.environ.get("WEBHOOK_SECRET")
if expected is not None:
if x_webhook_secret is None:
raise HTTPException(status_code=403, detail="missing X-Webhook-Secret")
if not secrets.compare_digest(x_webhook_secret, expected):
raise HTTPException(status_code=403, detail="invalid X-Webhook-Secret")
raw = await request.body()
ct = request.headers.get("content-type", "")
try:
if "application/json" in ct and raw.strip():
body_preview = json.loads(raw.decode())
elif raw.strip():
body_preview = raw.decode(errors="replace")
if len(body_preview) > 2000:
body_preview = body_preview[:2000] + "…(truncated)"
else:
body_preview = None
except json.JSONDecodeError:
body_preview = raw.decode(errors="replace")
interesting_headers = {
k: v
for k, v in request.headers.items()
if k.lower()
in (
"content-type",
"user-agent",
"x-github-event",
"x-gitlab-token",
"x-request-id",
)
}
event_id = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S%f")
info = {
"event_id": event_id,
"time": _now_iso(),
"client": getattr(request.client, "host", None),
"method": request.method,
"path": str(request.url.path),
"query": dict(request.query_params),
"content_type": ct or None,
"interesting_headers": interesting_headers,
"body": body_preview,
"body_bytes": len(raw),
}
log.info("webhook %s | %s", event_id, json.dumps(info, ensure_ascii=False))
response.headers["X-Webhook-Received"] = event_id
return {"ok": True, "event_id": event_id, "received_at": info["time"]}
def main() -> None:
import uvicorn
host = os.environ.get("HOST", "0.0.0.0")
port = int(os.environ.get("PORT", "8000"))
uvicorn.run("server:app", host=host, port=port, reload=False)
if __name__ == "__main__":
main()