""" Server HTTP untuk Syntax Checker — http://127.0.0.1:8008 Endpoint: GET /api/status — health-check POST /api/syntax — {"text": "...", "language": "id"} → {"findings": [...], "finding_count": int} Jalankan: python -m src.syntax.syntax_server python src/syntax/syntax_server.py --no-ml # nonaktifkan model (deteksi mati) """ from __future__ import annotations import json import logging import sys from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) from syntax.syntax_detector import SyntaxChecker logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)-7s %(message)s", datefmt="%H:%M:%S", ) logger = logging.getLogger(__name__) _checker: SyntaxChecker | None = None _use_ml = True def _get() -> SyntaxChecker: global _checker if _checker is None: _checker = SyntaxChecker(use_ml=_use_ml) _checker.load() return _checker class Handler(BaseHTTPRequestHandler): def _cors(self): self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") self.send_header("Access-Control-Allow-Headers", "Content-Type") def do_OPTIONS(self): self.send_response(204); self._cors(); self.end_headers() def _json(self, status: int, body: object): payload = json.dumps(body, ensure_ascii=False).encode() try: self.send_response(status) self.send_header("Content-Type", "application/json; charset=utf-8") self.send_header("Content-Length", str(len(payload))) self._cors() self.end_headers() self.wfile.write(payload) except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError): pass def _body(self) -> dict | None: n = int(self.headers.get("Content-Length", 0)) if not n: return {} try: return json.loads(self.rfile.read(n)) except (json.JSONDecodeError, ValueError): return None def log_message(self, fmt, *args): logger.info("%-6s %s", args[0] if args else "", args[1] if len(args) > 1 else "") def do_GET(self): if self.path == "/api/status": chk = _get() self._json(200, { "ready": True, "ml_active": chk.ml_active, }) else: self._json(404, {"error": "Not found"}) def do_POST(self): if self.path != "/api/syntax": self._json(404, {"error": "Not found"}); return body = self._body() if body is None: self._json(400, {"error": "JSON tidak valid."}); return text = str(body.get("text", "")).strip() if not text: self._json(400, {"error": "Field 'text' kosong."}); return language = str(body.get("language", "id")) findings = _get().check(text, language=language) self._json(200, { "text": text, "finding_count": len(findings), "findings": [ { "sentence": f.sentence, "start": f.start, "end": f.end, "score": f.score, "reason": f.reason, "confidence": f.confidence, } for f in findings ], }) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=8008) parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--no-ml", action="store_true", help="Nonaktifkan model (deteksi mati, server tetap jalan).") args = parser.parse_args() _use_ml = not args.no_ml logger.info("Memuat Syntax Checker (ML: %s)...", "aktif" if _use_ml else "nonaktif") chk = _get() logger.info("Syntax Checker siap (ML aktif: %s).", chk.ml_active) server = HTTPServer((args.host, args.port), Handler) logger.info("Syntax server berjalan di http://%s:%d", args.host, args.port) try: server.serve_forever() except KeyboardInterrupt: server.server_close()