# retoor import logging import httpx from fastapi import APIRouter, Request from starlette.requests import ClientDisconnect from starlette.responses import PlainTextResponse, Response from devplacepy.config import XMLRPC_BIND, XMLRPC_PORT logger = logging.getLogger(__name__) router = APIRouter() CONNECT_HOST = XMLRPC_BIND if XMLRPC_BIND not in ("0.0.0.0", "::") else "127.0.0.1" TARGET_URL = f"http://{CONNECT_HOST}:{XMLRPC_PORT}/" TIMEOUT_SECONDS = 65.0 FORWARD_HEADERS = ( "authorization", "x-api-key", "x-real-ip", "x-forwarded-for", "content-type", ) USAGE = ( "DevPlace XML-RPC bridge\n\n" "POST XML-RPC method calls to this endpoint. Every documented REST endpoint is\n" "exposed as a method named after its id with dots (for example posts.create,\n" "feed.index, profile.update). Each method takes ONE struct of named parameters;\n" "authenticate with 'api_key' inside the struct or an X-API-KEY / Bearer header.\n\n" "Introspection: system.listMethods, system.methodHelp, system.methodSignature.\n" "Batching: system.multicall.\n\n" "Python example:\n" " import xmlrpc.client\n" " proxy = xmlrpc.client.ServerProxy('https://your-host/xmlrpc', allow_none=True)\n" " print(proxy.system.listMethods())\n" " proxy.posts.create({'title': 'Hi', 'body': '...', 'api_key': 'YOUR_KEY'})\n" ) @router.get("") @router.get("/") @router.get("/{path:path}") async def info(request: Request, path: str = "") -> PlainTextResponse: return PlainTextResponse(USAGE) @router.post("") @router.post("/") @router.post("/{path:path}") async def proxy(request: Request, path: str = "") -> Response: try: body = await request.body() except ClientDisconnect: logger.warning("XML-RPC client disconnected before request body was read") return PlainTextResponse("Client disconnected", status_code=400) headers = { key: value for key, value in request.headers.items() if key.lower() in FORWARD_HEADERS } headers.setdefault("content-type", "text/xml") try: async with httpx.AsyncClient(timeout=TIMEOUT_SECONDS) as client: upstream = await client.post(TARGET_URL, content=body, headers=headers) except httpx.HTTPError as exc: logger.warning("XML-RPC bridge unavailable: %s", exc) return PlainTextResponse( "XML-RPC bridge is not available.", status_code=502 ) return Response( content=upstream.content, status_code=upstream.status_code, media_type=upstream.headers.get("content-type", "text/xml"), )