prompt_template/web/handler.py
Michael 9890763f0f refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory

Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt

Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00

107 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""
HTTP-Handler für den Template-Server.
Behandelt GET- und PUT-Anfragen für Templates und das Frontend.
"""
import http.server
import logging
from pathlib import Path
from urllib.parse import urlparse
# Support für direkte Ausführung und Package-Import
try:
from .path_validator import PathValidator
from .file_ops import read_file_binary, write_file, directory_exists
from .content_types import get_content_type
except ImportError:
from path_validator import PathValidator
from file_ops import read_file_binary, write_file, directory_exists
from content_types import get_content_type
logger = logging.getLogger(__name__)
MAX_BODY_SIZE = 10 * 1024 * 1024 # 10 MB
class Handler(http.server.SimpleHTTPRequestHandler):
"""HTTP-Handler für Template-Anfragen."""
validator = None # Wird von serve.py gesetzt
directory = None # Wird von serve.py gesetzt
def __init__(self, *args, directory=None, **kwargs):
super().__init__(*args, directory=directory or self.directory, **kwargs)
def do_PUT(self):
"""Speichert eine Template-Datei."""
file_path = self.validator.resolve_template_path(urlparse(self.path).path)
if file_path is None:
self.send_error(403, "Forbidden: Invalid path")
return
# Verzeichnis prüfen - muss existieren
file_dir = Path(file_path).parent
if not directory_exists(str(file_dir)):
self.send_error(404, "Directory not found")
return
# Content-Type prüfen
content_type = self.headers.get("Content-Type", "")
if "text/plain" not in content_type and not content_type.startswith("text/"):
self.send_error(400, "Unsupported content type")
return
# Inhalt lesen und speichern
content_length = int(self.headers.get("Content-Length", 0))
if content_length > MAX_BODY_SIZE:
self.send_error(413, "Request body too large")
return
if content_length <= 0:
self.send_error(400, "No content provided")
return
try:
file_content = self.rfile.read(content_length)
if not write_file(file_path, file_content):
raise Exception("write_file returned False")
response_content_type = get_content_type(file_path)
self.send_response(200)
self.send_header("Content-type", response_content_type)
self.end_headers()
self.wfile.write(b"File saved successfully")
except Exception as e:
logger.error("Failed to save file: %s", e)
self.send_error(500, f"Failed to save file: {e}")
def do_GET(self):
"""Liefert Dateien aus."""
parsed_path = urlparse(self.path).path
# Für Root-Pfad: index.html servieren
if parsed_path == "/" or parsed_path == "/index.html":
self.path = "/index.html"
return super().do_GET()
# Anfragen für /templates.json oder /templates/* umleiten
file_path = self.validator.resolve_template_path(urlparse(self.path).path)
if file_path is not None:
content_type = get_content_type(file_path)
file_content = read_file_binary(file_path)
if file_content is None:
self.send_error(404, "File not found")
return
self.send_response(200)
self.send_header("Content-type", content_type)
self.end_headers()
self.wfile.write(file_content)
return
return super().do_GET()