57 lines
1.5 KiB
Python
57 lines
1.5 KiB
Python
|
|
import logging
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Request, Response
|
||
|
|
|
||
|
|
from yuxi.channel.gateway.routes import webhook_registry
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/webhook", tags=["webhook"])
|
||
|
|
|
||
|
|
|
||
|
|
def _extract_signature(headers: dict[str, str]) -> str | None:
|
||
|
|
sig = headers.get("x-hub-signature-256") or headers.get("x-signature")
|
||
|
|
if sig and sig.startswith("sha256="):
|
||
|
|
sig = sig[7:]
|
||
|
|
return sig
|
||
|
|
|
||
|
|
|
||
|
|
@router.api_route(
|
||
|
|
"/{channel_type}",
|
||
|
|
methods=["POST", "PUT", "PATCH"],
|
||
|
|
summary="接收渠道 Webhook 回调",
|
||
|
|
)
|
||
|
|
async def webhook_receive(
|
||
|
|
channel_type: str,
|
||
|
|
request: Request,
|
||
|
|
):
|
||
|
|
body = await request.body()
|
||
|
|
method = request.method
|
||
|
|
content_type = request.headers.get("content-type")
|
||
|
|
headers = {k.lower(): v for k, v in request.headers.items()}
|
||
|
|
|
||
|
|
guard_result, handler_result = await webhook_registry.dispatch_guarded(
|
||
|
|
channel_type=channel_type,
|
||
|
|
method=method,
|
||
|
|
content_type=content_type,
|
||
|
|
body=body,
|
||
|
|
signature=_extract_signature(headers),
|
||
|
|
extra_headers=headers,
|
||
|
|
)
|
||
|
|
|
||
|
|
if guard_result is not None:
|
||
|
|
logger.warning(
|
||
|
|
"Webhook guard blocked: channel=%s method=%s status=%d error=%s",
|
||
|
|
channel_type,
|
||
|
|
method,
|
||
|
|
guard_result.http_status,
|
||
|
|
guard_result.error_code,
|
||
|
|
)
|
||
|
|
return Response(
|
||
|
|
content=guard_result.error_message or "",
|
||
|
|
status_code=guard_result.http_status,
|
||
|
|
media_type="text/plain",
|
||
|
|
)
|
||
|
|
|
||
|
|
return handler_result or {"status": "ok"}
|