Ntk123 commited on
Commit
2510bce
·
1 Parent(s): 33a046e

Add body-read fallback + request diagnostics (ver marker)

Browse files
Files changed (1) hide show
  1. 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 _read_json(self):
299
- raw = self._read_body()
300
- if not raw:
301
- return None
302
  try:
303
- return json.loads(raw.decode("utf-8"))
304
  except (ValueError, json.JSONDecodeError):
305
- return None
 
306
 
307
  def _read_body(self):
308
- if self.headers.get("Transfer-Encoding", "").lower() == "chunked":
 
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
- n = int(self.headers.get("Content-Length") or 0)
323
- if n <= 0 or n > 1 << 20:
324
- return None
325
- return self.rfile.read(n)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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._read_json() or {}
 
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
- self._json(200 if ok else 400, {"ok": ok, "error": msg})
 
 
 
 
 
 
 
 
 
 
 
 
 
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"})