Files
Dehaze/web_dehaze/server.py
2026-06-10 17:42:11 +08:00

249 lines
9.2 KiB
Python

from __future__ import annotations
import argparse
import json
import mimetypes
import threading
import traceback
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from itertools import count
from pathlib import Path
from typing import Any
from urllib.parse import parse_qs, unquote, urlparse
import pipeline
STATIC_DIR = Path(__file__).resolve().parent / "static"
JOBS: dict[str, dict[str, Any]] = {}
JOB_IDS = count(1)
JOB_LOCK = threading.Lock()
def _json_bytes(payload: Any) -> bytes:
return json.dumps(payload, ensure_ascii=False, indent=2).encode("utf-8")
def _new_job(kind: str, target, *args, **kwargs) -> dict[str, Any]:
job_id = str(next(JOB_IDS))
job = {
"id": job_id,
"kind": kind,
"status": "running",
"logs": [],
"result": None,
"error": None,
}
with JOB_LOCK:
JOBS[job_id] = job
def log(message: str) -> None:
with JOB_LOCK:
job["logs"].append(message)
job["logs"] = job["logs"][-400:]
def wrapped() -> None:
try:
job["result"] = target(log, *args, **kwargs)
job["status"] = "done"
except Exception as exc:
job["status"] = "error"
job["error"] = str(exc)
log(traceback.format_exc())
threading.Thread(target=wrapped, daemon=True).start()
return job
def _run_dehaze_job(log, payload: dict[str, Any]) -> dict[str, Any]:
filename = payload["image"]
methods = payload.get("methods") or []
options = payload.get("options") or {}
postprocessors = payload.get("postprocessors") or []
post_sources = payload.get("post_sources") or []
reference = payload.get("reference") or filename
completed = []
failed = []
for method in methods:
try:
result = pipeline.run_dehaze_method(filename, method, options.get(method, options), log)
completed.append({"method": method, "path": pipeline.relpath(result)})
except Exception as exc:
failed.append({"method": method, "error": str(exc)})
log(f"[{method}] 失败: {exc}")
if postprocessors:
sources = post_sources or [item["method"] for item in completed]
for source in sources:
try:
pipeline.run_postprocessors_for_source(
filename,
source,
postprocessors,
params=payload.get("post_params") or {},
reference_filename=reference,
log=log,
)
except Exception as exc:
failed.append({"postprocess_source": source, "error": str(exc)})
log(f"[后处理/{source}] 失败: {exc}")
return {"completed": completed, "failed": failed, "results": pipeline.get_results(filename)}
def _run_post_job(log, payload: dict[str, Any]) -> dict[str, Any]:
filename = payload["image"]
source = payload.get("source") or "DCP"
processors = payload.get("processors") or []
reference = payload.get("reference") or filename
outputs = pipeline.run_postprocessors_for_source(
filename,
source,
processors,
params=payload.get("params") or {},
reference_filename=reference,
log=log,
)
return {
"outputs": [pipeline.relpath(path) for path in outputs],
"results": pipeline.get_results(filename),
}
class DehazeRequestHandler(BaseHTTPRequestHandler):
server_version = "DehazeWeb/1.0"
def log_message(self, fmt: str, *args) -> None:
print("[%s] %s" % (self.log_date_time_string(), fmt % args))
def _send(self, status: int, body: bytes, content_type: str = "application/json; charset=utf-8") -> None:
self.send_response(status)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _send_json(self, payload: Any, status: int = HTTPStatus.OK) -> None:
self._send(int(status), _json_bytes(payload))
def _send_error_json(self, status: int, message: str) -> None:
self._send_json({"error": message}, status)
def do_HEAD(self) -> None:
parsed = urlparse(self.path)
target = STATIC_DIR / "index.html" if parsed.path == "/" else STATIC_DIR / parsed.path.removeprefix("/static/")
if parsed.path == "/" or parsed.path.startswith("/static/"):
target = target.resolve()
static_root = STATIC_DIR.resolve()
if static_root not in target.parents and target != static_root:
self.send_response(HTTPStatus.FORBIDDEN)
self.end_headers()
return
if target.exists() and target.is_file():
content_type = mimetypes.guess_type(target.name)[0] or "application/octet-stream"
if target.suffix == ".html":
content_type = "text/html; charset=utf-8"
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(target.stat().st_size))
self.end_headers()
return
self.send_response(HTTPStatus.NOT_FOUND)
self.end_headers()
def _read_json(self) -> dict[str, Any]:
length = int(self.headers.get("Content-Length", "0"))
if length <= 0:
return {}
raw = self.rfile.read(length).decode("utf-8")
return json.loads(raw)
def do_GET(self) -> None:
parsed = urlparse(self.path)
path = parsed.path
query = parse_qs(parsed.query)
try:
if path == "/":
self._serve_static("index.html")
elif path.startswith("/static/"):
self._serve_static(path.removeprefix("/static/"))
elif path == "/asset":
rel = unquote(query.get("path", [""])[0])
self._serve_asset(rel)
elif path == "/api/images":
self._send_json({"images": pipeline.list_images()})
elif path == "/api/capabilities":
self._send_json(pipeline.capabilities())
elif path == "/api/results":
filename = query.get("image", [""])[0]
self._send_json(pipeline.get_results(filename))
elif path == "/api/job":
job_id = query.get("id", [""])[0]
with JOB_LOCK:
job = JOBS.get(job_id)
payload = dict(job) if job else None
if payload is None:
self._send_error_json(HTTPStatus.NOT_FOUND, "job not found")
else:
self._send_json(payload)
else:
self._send_error_json(HTTPStatus.NOT_FOUND, "not found")
except Exception as exc:
self._send_error_json(HTTPStatus.INTERNAL_SERVER_ERROR, str(exc))
def do_POST(self) -> None:
parsed = urlparse(self.path)
try:
payload = self._read_json()
if parsed.path == "/api/run":
job = _new_job("dehaze", _run_dehaze_job, payload)
self._send_json({"job": job})
elif parsed.path == "/api/postprocess":
job = _new_job("postprocess", _run_post_job, payload)
self._send_json({"job": job})
else:
self._send_error_json(HTTPStatus.NOT_FOUND, "not found")
except Exception as exc:
self._send_error_json(HTTPStatus.BAD_REQUEST, str(exc))
def _serve_static(self, name: str) -> None:
target = (STATIC_DIR / name).resolve()
if STATIC_DIR.resolve() not in target.parents and target != STATIC_DIR.resolve():
self._send_error_json(HTTPStatus.FORBIDDEN, "forbidden")
return
if not target.exists() or not target.is_file():
self._send_error_json(HTTPStatus.NOT_FOUND, "static file not found")
return
content_type = mimetypes.guess_type(target.name)[0] or "application/octet-stream"
if target.suffix == ".html":
content_type = "text/html; charset=utf-8"
elif target.suffix == ".css":
content_type = "text/css; charset=utf-8"
elif target.suffix == ".js":
content_type = "application/javascript; charset=utf-8"
self._send(HTTPStatus.OK, target.read_bytes(), content_type)
def _serve_asset(self, rel: str) -> None:
target = pipeline.resolve_asset(rel)
content_type = mimetypes.guess_type(target.name)[0] or "application/octet-stream"
self._send(HTTPStatus.OK, target.read_bytes(), content_type)
def main() -> None:
parser = argparse.ArgumentParser(description="Local dehaze web console")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=7860)
args = parser.parse_args()
server = ThreadingHTTPServer((args.host, args.port), DehazeRequestHandler)
print(f"Dehaze web console: http://{args.host}:{args.port}")
print(f"Images: {pipeline.IMAGE_DIR}")
print(f"Results: {pipeline.RESULTS_DIR}")
server.serve_forever()
if __name__ == "__main__":
main()