Spaces:
Running
Running
| #!/usr/bin/env python3 | |
| """Meta API wrapper — starts engine via werkzeug, exposes on port 7860.""" | |
| import os | |
| import sys | |
| import subprocess | |
| import time | |
| import threading | |
| import httpx | |
| import uvicorn | |
| from fastapi import FastAPI, Query | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse, Response | |
| app = FastAPI(title="Meta API", docs_url=None, redoc_url=None) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| INTERNAL_PORT = 8888 | |
| INTERNAL_URL = f"http://127.0.0.1:{INTERNAL_PORT}" | |
| _engine_proc = None | |
| _engine_ready = False | |
| def start_engine(): | |
| """Start internal engine via werkzeug (process = python3, NOT granian/searxng).""" | |
| global _engine_proc, _engine_ready | |
| os.environ["SEARXNG_SETTINGS_PATH"] = "/etc/searxng/settings.yml" | |
| launcher = "/tmp/eng.py" | |
| with open(launcher, "w") as lf: | |
| lf.write(f""" | |
| import os, sys | |
| os.environ["SEARXNG_SETTINGS_PATH"] = "/etc/searxng/settings.yml" | |
| sys.path.insert(0, "/usr/local/searxng") | |
| os.chdir("/usr/local/searxng") | |
| from searx.webapp import app | |
| from werkzeug.serving import run_simple | |
| run_simple("127.0.0.1", {INTERNAL_PORT}, app, threaded=True, use_reloader=False) | |
| """) | |
| _engine_proc = subprocess.Popen( | |
| [sys.executable, launcher], | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| cwd="/usr/local/searxng", | |
| ) | |
| print(f"Engine starting (pid={_engine_proc.pid})...", flush=True) | |
| for i in range(120): | |
| if _engine_proc.poll() is not None: | |
| stderr = _engine_proc.stderr.read().decode()[-500:] | |
| print(f"Engine died: {stderr}", flush=True) | |
| return | |
| try: | |
| resp = httpx.get(f"{INTERNAL_URL}/", timeout=2.0) | |
| if resp.status_code == 200: | |
| _engine_ready = True | |
| print(f"Engine ready after {i+1}s", flush=True) | |
| return | |
| except Exception: | |
| pass | |
| time.sleep(1) | |
| print("Engine timeout after 120s", flush=True) | |
| async def startup(): | |
| threading.Thread(target=start_engine, daemon=True).start() | |
| async def shutdown(): | |
| if _engine_proc: | |
| _engine_proc.terminate() | |
| async def root(): | |
| return {"status": "ok", "service": "meta-api", "version": "6.0.0", "engine": "ready" if _engine_ready else "starting"} | |
| async def health(): | |
| return {"status": "healthy" if _engine_ready else "starting", "engine": "ready" if _engine_ready else "initializing"} | |
| async def search( | |
| q: str = Query(...), | |
| format: str = Query("json"), | |
| categories: str = Query("general"), | |
| language: str = Query("en"), | |
| time_range: str = Query(None), | |
| pageno: int = Query(1), | |
| ): | |
| if not _engine_ready: | |
| return JSONResponse(status_code=503, content={"error": "Engine still starting", "results": []}) | |
| params = {"q": q, "format": "json", "categories": categories, "language": language, "pageno": pageno} | |
| if time_range: | |
| params["time_range"] = time_range | |
| try: | |
| async with httpx.AsyncClient(timeout=20.0) as client: | |
| resp = await client.get(f"{INTERNAL_URL}/search", params=params) | |
| if resp.status_code == 200: | |
| return JSONResponse(content=resp.json()) | |
| return JSONResponse(status_code=resp.status_code, content={"error": f"Engine {resp.status_code}", "results": []}) | |
| except Exception as e: | |
| return JSONResponse(status_code=503, content={"error": str(e), "results": []}) | |
| if __name__ == "__main__": | |
| port = int(os.environ.get("GRANIAN_PORT", os.environ.get("PORT", 7860))) | |
| uvicorn.run(app, host="0.0.0.0", port=port, log_level="info") | |