feat: per-domain backend routing
Each allowed_domains entry can now carry its own backend {host, port}.
That lets one proxy on port 25565 serve multiple MC servers, picking
the upstream from the domain the client typed.
- proxy/main.py: ProxyState.backend_for(domain) → tuple|None,
honors per-domain backend first, falls back to top-level backend.
handle_client uses backend_for(); blocked / disabled domains
return None (and still get a Login Disconnect on join attempts).
- api/routes/{config,domains}.py: DomainBackend model + optional
backend field on create/patch. PATCH supports clear_backend=true
to drop a per-domain override and revert to default.
- frontend/Domains.jsx: full rewrite — new-domain form has host/port
inputs, table shows each row's effective backend, inline edit +
reset button per row.
- frontend/Settings.jsx: backend section relabeled "기본 백엔드 (fallback)"
- README updated with multi-server example config.
This commit is contained in:
@@ -129,9 +129,31 @@ class ProxyState:
|
||||
return cfg_mod.allowed_domain_set(self.cfg)
|
||||
|
||||
def backend(self) -> tuple[str, int]:
|
||||
"""기본 백엔드 (도메인 entry 에 backend 가 없을 때 fallback)."""
|
||||
b = self.cfg["backend"]
|
||||
return b["host"], int(b["port"])
|
||||
|
||||
def backend_for(self, domain: str) -> tuple[str, int] | None:
|
||||
"""주어진 도메인이 활성 화이트리스트에 있으면 라우팅 대상을 돌려준다.
|
||||
|
||||
도메인 entry 에 `backend.host`/`backend.port` 가 있으면 그 값을 우선,
|
||||
없으면 top-level `backend` 로 fallback. 도메인이 비활성이거나 없으면
|
||||
None.
|
||||
"""
|
||||
d = domain.lower().strip()
|
||||
for entry in self.cfg.get("allowed_domains", []):
|
||||
if entry["domain"].lower().strip() != d:
|
||||
continue
|
||||
if not entry.get("enabled", True):
|
||||
return None
|
||||
be = entry.get("backend") or {}
|
||||
host = (be.get("host") or "").strip()
|
||||
port = be.get("port")
|
||||
if host and port:
|
||||
return host, int(port)
|
||||
return self.backend()
|
||||
return None
|
||||
|
||||
def enabled(self) -> bool:
|
||||
return bool(self.cfg.get("proxy", {}).get("enabled", True))
|
||||
|
||||
@@ -212,8 +234,8 @@ async def handle_client(
|
||||
return
|
||||
|
||||
domain = hs.server_address.lower().strip()
|
||||
allowed = state.allowed()
|
||||
if domain not in allowed:
|
||||
target = state.backend_for(domain)
|
||||
if target is None:
|
||||
log_event(client_ip, domain, hs.next_state, "blocked", "domain not allowed")
|
||||
log.info(
|
||||
"BLOCK %s domain=%r next_state=%d", client_ip, domain, hs.next_state
|
||||
@@ -230,7 +252,7 @@ async def handle_client(
|
||||
client_writer.close()
|
||||
return
|
||||
|
||||
backend_host, backend_port = state.backend()
|
||||
backend_host, backend_port = target
|
||||
try:
|
||||
backend_reader, backend_writer = await asyncio.wait_for(
|
||||
asyncio.open_connection(backend_host, backend_port), timeout=5
|
||||
|
||||
Reference in New Issue
Block a user