|
# retoor <retoor@molodetz.nl>
|
|
|
|
import logging
|
|
|
|
import httpx
|
|
from fastapi import APIRouter, Request
|
|
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:
|
|
body = await request.body()
|
|
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"),
|
|
)
|