Spaces:
Paused
Paused
Add body-read fallback + request diagnostics (ver marker)
Browse files- control.py +47 -13
control.py
CHANGED
|
@@ -4,6 +4,7 @@ import hmac
|
|
| 4 |
import json
|
| 5 |
import os
|
| 6 |
import signal
|
|
|
|
| 7 |
import subprocess
|
| 8 |
import sys
|
| 9 |
import threading
|
|
@@ -12,6 +13,8 @@ from collections import deque
|
|
| 12 |
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 13 |
from urllib.parse import parse_qs, urlparse
|
| 14 |
|
|
|
|
|
|
|
| 15 |
DATA_DIR = os.environ.get("MC_DATA", "/data")
|
| 16 |
JAR = os.environ.get("MC_JAR", "/server/minecraft_server.26.3.jar")
|
| 17 |
MEM = os.environ.get("MC_MEM", "4G")
|
|
@@ -227,6 +230,7 @@ class MCController:
|
|
| 227 |
"mem_gb": round(rss / (1024 * 1024), 2),
|
| 228 |
"playit": self.playit_state(),
|
| 229 |
"mem_heap": MEM,
|
|
|
|
| 230 |
}
|
| 231 |
|
| 232 |
@staticmethod
|
|
@@ -295,17 +299,17 @@ class Handler(BaseHTTPRequestHandler):
|
|
| 295 |
def _json(self, code, obj):
|
| 296 |
self._send(code, json.dumps(obj, separators=(",", ":")), "application/json")
|
| 297 |
|
| 298 |
-
def
|
| 299 |
-
raw = self._read_body()
|
| 300 |
-
if not raw:
|
| 301 |
-
return None
|
| 302 |
try:
|
| 303 |
-
|
| 304 |
except (ValueError, json.JSONDecodeError):
|
| 305 |
-
|
|
|
|
| 306 |
|
| 307 |
def _read_body(self):
|
| 308 |
-
|
|
|
|
| 309 |
data = bytearray()
|
| 310 |
while True:
|
| 311 |
size_line = self.rfile.readline().strip()
|
|
@@ -319,10 +323,26 @@ class Handler(BaseHTTPRequestHandler):
|
|
| 319 |
data += self.rfile.read(size)
|
| 320 |
self.rfile.readline()
|
| 321 |
return bytes(data)
|
| 322 |
-
|
| 323 |
-
if
|
| 324 |
-
return
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
|
| 327 |
def log_message(self, fmt, *args):
|
| 328 |
pass
|
|
@@ -347,7 +367,8 @@ class Handler(BaseHTTPRequestHandler):
|
|
| 347 |
|
| 348 |
def do_POST(self):
|
| 349 |
u = urlparse(self.path)
|
| 350 |
-
body = self.
|
|
|
|
| 351 |
if u.path == "/api/login":
|
| 352 |
if CONTROL_PASSWORD and body.get("password") == CONTROL_PASSWORD:
|
| 353 |
self._json(200, {"ok": True, "token": token_for(CONTROL_PASSWORD)})
|
|
@@ -362,7 +383,20 @@ class Handler(BaseHTTPRequestHandler):
|
|
| 362 |
self._json(401, {"ok": False, "error": "auth required"})
|
| 363 |
return
|
| 364 |
ok, msg = controller.send_command(body.get("cmd", ""))
|
| 365 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
elif u.path == "/api/action":
|
| 367 |
if not auth_ok(self.headers.get("X-Token", "")):
|
| 368 |
self._json(401, {"ok": False, "error": "auth required"})
|
|
|
|
| 4 |
import json
|
| 5 |
import os
|
| 6 |
import signal
|
| 7 |
+
import socket
|
| 8 |
import subprocess
|
| 9 |
import sys
|
| 10 |
import threading
|
|
|
|
| 13 |
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
| 14 |
from urllib.parse import parse_qs, urlparse
|
| 15 |
|
| 16 |
+
BUILD = "diag-1"
|
| 17 |
+
|
| 18 |
DATA_DIR = os.environ.get("MC_DATA", "/data")
|
| 19 |
JAR = os.environ.get("MC_JAR", "/server/minecraft_server.26.3.jar")
|
| 20 |
MEM = os.environ.get("MC_MEM", "4G")
|
|
|
|
| 230 |
"mem_gb": round(rss / (1024 * 1024), 2),
|
| 231 |
"playit": self.playit_state(),
|
| 232 |
"mem_heap": MEM,
|
| 233 |
+
"ver": BUILD,
|
| 234 |
}
|
| 235 |
|
| 236 |
@staticmethod
|
|
|
|
| 299 |
def _json(self, code, obj):
|
| 300 |
self._send(code, json.dumps(obj, separators=(",", ":")), "application/json")
|
| 301 |
|
| 302 |
+
def _read_post(self):
|
| 303 |
+
raw = self._read_body() or b""
|
|
|
|
|
|
|
| 304 |
try:
|
| 305 |
+
obj = json.loads(raw.decode("utf-8")) if raw else None
|
| 306 |
except (ValueError, json.JSONDecodeError):
|
| 307 |
+
obj = None
|
| 308 |
+
return obj, raw
|
| 309 |
|
| 310 |
def _read_body(self):
|
| 311 |
+
te = (self.headers.get("Transfer-Encoding") or "").lower()
|
| 312 |
+
if te == "chunked":
|
| 313 |
data = bytearray()
|
| 314 |
while True:
|
| 315 |
size_line = self.rfile.readline().strip()
|
|
|
|
| 323 |
data += self.rfile.read(size)
|
| 324 |
self.rfile.readline()
|
| 325 |
return bytes(data)
|
| 326 |
+
cl = int(self.headers.get("Content-Length") or 0)
|
| 327 |
+
if 0 < cl <= 1 << 20:
|
| 328 |
+
return self.rfile.read(cl)
|
| 329 |
+
sock = self.connection
|
| 330 |
+
sock.settimeout(0.5)
|
| 331 |
+
try:
|
| 332 |
+
data = bytearray()
|
| 333 |
+
while True:
|
| 334 |
+
try:
|
| 335 |
+
chunk = self.rfile.read(4096)
|
| 336 |
+
except (socket.timeout, OSError):
|
| 337 |
+
break
|
| 338 |
+
if not chunk:
|
| 339 |
+
break
|
| 340 |
+
data += chunk
|
| 341 |
+
if len(data) > 1 << 20:
|
| 342 |
+
break
|
| 343 |
+
return bytes(data) if data else None
|
| 344 |
+
finally:
|
| 345 |
+
sock.settimeout(None)
|
| 346 |
|
| 347 |
def log_message(self, fmt, *args):
|
| 348 |
pass
|
|
|
|
| 367 |
|
| 368 |
def do_POST(self):
|
| 369 |
u = urlparse(self.path)
|
| 370 |
+
body, raw = self._read_post()
|
| 371 |
+
body = body or {}
|
| 372 |
if u.path == "/api/login":
|
| 373 |
if CONTROL_PASSWORD and body.get("password") == CONTROL_PASSWORD:
|
| 374 |
self._json(200, {"ok": True, "token": token_for(CONTROL_PASSWORD)})
|
|
|
|
| 383 |
self._json(401, {"ok": False, "error": "auth required"})
|
| 384 |
return
|
| 385 |
ok, msg = controller.send_command(body.get("cmd", ""))
|
| 386 |
+
if not ok and not body:
|
| 387 |
+
self._json(400, {
|
| 388 |
+
"ok": False,
|
| 389 |
+
"error": msg,
|
| 390 |
+
"diag": {
|
| 391 |
+
"cl": self.headers.get("Content-Length"),
|
| 392 |
+
"te": self.headers.get("Transfer-Encoding"),
|
| 393 |
+
"ct": self.headers.get("Content-Type"),
|
| 394 |
+
"proto": self.request_version,
|
| 395 |
+
"raw": raw[:64].decode("utf-8", "replace"),
|
| 396 |
+
},
|
| 397 |
+
})
|
| 398 |
+
else:
|
| 399 |
+
self._json(200 if ok else 400, {"ok": ok, "error": msg})
|
| 400 |
elif u.path == "/api/action":
|
| 401 |
if not auth_ok(self.headers.get("X-Token", "")):
|
| 402 |
self._json(401, {"ok": False, "error": "auth required"})
|