feat: push Douyin events through shared WebSocket and SSE
douyin-release-gate / verify (push) Failing after 18m0s

This commit is contained in:
2026-10-07 23:45:32 +08:00
parent 297b813b90
commit 360bf96bab
30 changed files with 1625 additions and 140 deletions
+17 -32
View File
@@ -512,7 +512,7 @@ class Gateway:
self._require_douyin_generation(alias, input)
# Polling-session initialization performs read-only page requests.
# It must not occupy the manual-send slot while waiting on them.
result = self.subscriptions.start(alias, expected_uid, input.get("boundary_at"), input.get("notice_checkpoints", {}))
result = self.subscriptions.start(alias, expected_uid, input.get("boundary_at"), input.get("notice_checkpoints", {}), input.get('listener_generation'))
self._require_douyin_generation(alias, input)
return result
except DouyinError as exc:
@@ -522,37 +522,18 @@ class Gateway:
)
raise RequestError(f"Douyin notification polling could not start: {exc}") from exc
def poll_douyin_events(self, alias: str, input: dict, query: dict) -> list[dict]:
if not valid_douyin_generation(input):
raise RequestError("invalid Douyin event request", 400)
try:
limit = int(query.get("limit", ["50"])[0])
wait = float(query.get("wait", ["0"])[0])
except (IndexError, TypeError, ValueError) as exc:
raise RequestError("invalid event poll options", 400) from exc
if not 1 <= limit <= 100 or not 0 <= wait <= 30:
raise RequestError("invalid event poll options", 400)
try:
with self._alias_lock(alias):
self._require_douyin_generation(alias, input)
acknowledgements = query.get("ack", [])
delivery_ids = [value for raw in acknowledgements for value in raw.split(",")]
self.subscriptions.ack(alias, delivery_ids)
return self.subscriptions.poll(alias, limit, 0)
except DouyinError as exc:
LOG.warning(
"Douyin event listener poll failed alias=%s reason=%s",
alias,
exc,
)
raise RequestError(f"Douyin notification polling failed: {exc}") from exc
def stop_douyin_events(self, alias: str, input: dict) -> None:
if not valid_douyin_generation(input):
raise RequestError("invalid Douyin event request", 400)
with self._alias_lock(alias):
self._require_douyin_generation(alias, input)
self.subscriptions.stop(alias)
if input.get('stop_all') is True:
self.subscriptions.stop(alias)
else:
session_id = input.get('session_id')
if not isinstance(session_id, str) or not session_id:
raise RequestError('event listener session_id required', 400)
self.subscriptions.stop(alias, input.get('listener_generation'), session_id)
class GatewayHTTPServer(ThreadingHTTPServer):
daemon_threads = True
@@ -635,9 +616,11 @@ class GatewayHandler(BaseHTTPRequestHandler):
self._respond(401, json_bytes({"error": "gateway token rejected"}))
return
try:
needs_body = method in {"POST", "DELETE"} or (
method == "GET" and parsed.path.endswith("/douyin/events")
)
if parsed.path == '/v1/channel' and method == 'GET':
from .event_channel import serve_channel
serve_channel(self)
return
needs_body = method in {"POST", "DELETE"}
body = self._body() if needs_body else {}
result = self._route(method, parsed.path, parse_qs(parsed.query), body)
if result is None:
@@ -653,6 +636,10 @@ class GatewayHandler(BaseHTTPRequestHandler):
self._handle_exception(parsed.path, exc)
def _handle_exception(self, path: str, exc: Exception) -> None:
if getattr(self, 'websocket_handoff', False):
LOG.exception('gateway WebSocket failed after handoff', extra={'path': path})
self.close_connection = True
return
if isinstance(exc, RequestError):
payload = {"error": str(exc)}
if exc.network_id:
@@ -721,8 +708,6 @@ class GatewayHandler(BaseHTTPRequestHandler):
if action == "events":
if method == "POST":
return gateway.start_douyin_events(alias, body)
if method == "GET":
return gateway.poll_douyin_events(alias, body, query)
if method == "DELETE":
gateway.stop_douyin_events(alias, body)
return None