From 5d02a682b01af4be9d5c4244114e04edced2ed45 Mon Sep 17 00:00:00 2001 From: Jukka Lampikoski Date: Sun, 8 Mar 2026 11:50:31 +0200 Subject: [PATCH] Switch dev server from Python to PHP built-in server The Python SimpleHTTPRequestHandler served api.php as a static file instead of executing it, breaking registration, login and all API calls. Co-Authored-By: Claude Opus 4.6 --- server.py | 110 +++++------------------------------------------------- 1 file changed, 10 insertions(+), 100 deletions(-) diff --git a/server.py b/server.py index 0d304cd..76b5b8b 100644 --- a/server.py +++ b/server.py @@ -1,110 +1,20 @@ #!/usr/bin/env python3 """ -tykkää.fi dev server -- Serves static files (GET) -- Accepts image uploads (POST /upload) → saves to images/ +tykkää.fi dev server — launches PHP built-in server +so that api.php, upload.php and static files all work. """ -import http.server, json, mimetypes, os, re, socketserver, time +import os, subprocess, sys from pathlib import Path PORT = 3000 -IMAGES_DIR = Path('images') -IMAGES_DIR.mkdir(exist_ok=True) - -ALLOWED_MIME = {'image/jpeg', 'image/png', 'image/gif', 'image/webp'} -MAX_BYTES = 8 * 1024 * 1024 # 8 MB - - -def parse_multipart(data: bytes, boundary: str): - """Return list of (headers_dict, body_bytes) for each part.""" - sep = ('--' + boundary).encode() - parts = [] - for chunk in data.split(sep): - if not chunk or chunk in (b'\r\n', b'--\r\n', b'--'): - continue - chunk = chunk.lstrip(b'\r\n') - if chunk.startswith(b'--'): - continue - if b'\r\n\r\n' not in chunk: - continue - hdr_raw, body = chunk.split(b'\r\n\r\n', 1) - if body.endswith(b'\r\n'): - body = body[:-2] - headers = {} - for line in hdr_raw.decode(errors='replace').split('\r\n'): - if ':' in line: - k, v = line.split(':', 1) - headers[k.strip().lower()] = v.strip() - parts.append((headers, body)) - return parts - - -class Handler(http.server.SimpleHTTPRequestHandler): - - def do_POST(self): - if self.path != '/upload': - self.send_error(404) - return - - ct = self.headers.get('Content-Type', '') - m = re.search(r'boundary=([^\s;]+)', ct) - if not m: - self.send_error(400, 'Missing boundary') - return - boundary = m.group(1).strip('"') - - length = int(self.headers.get('Content-Length', 0)) - if length > MAX_BYTES + 4096: - self.send_error(413, 'Request too large') - return - raw = self.rfile.read(length) - - parts = parse_multipart(raw, boundary) - file_part = None - for hdrs, body in parts: - cd = hdrs.get('content-disposition', '') - if 'name="file"' in cd: - file_part = (hdrs, body) - break - - if not file_part: - self.send_error(400, 'No file part') - return - - hdrs, data = file_part - cd = hdrs.get('content-disposition', '') - fn_match = re.search(r'filename="([^"]+)"', cd) - filename = fn_match.group(1) if fn_match else 'upload.jpg' - - if len(data) > MAX_BYTES: - self.send_error(413, 'File too large (max 8 MB)') - return - - mime = mimetypes.guess_type(filename)[0] or '' - if mime not in ALLOWED_MIME: - self.send_error(415, 'Only images (jpeg/png/gif/webp) allowed') - return - - ext = Path(filename).suffix.lower() or '.jpg' - fname = f"{int(time.time() * 1000)}{ext}" - (IMAGES_DIR / fname).write_bytes(data) - - self._json(200, {'url': f'images/{fname}'}) - - def _json(self, code, obj): - body = json.dumps(obj).encode() - self.send_response(code) - self.send_header('Content-Type', 'application/json') - self.send_header('Content-Length', str(len(body))) - self.end_headers() - self.wfile.write(body) - - def log_message(self, fmt, *args): - pass # suppress request logs - if __name__ == '__main__': os.chdir(Path(__file__).parent) print(f'tykkää.fi running at http://localhost:{PORT}') - with socketserver.TCPServer(('', PORT), Handler) as srv: - srv.serve_forever() + try: + subprocess.run( + ['php', '-S', f'localhost:{PORT}'], + check=True, + ) + except KeyboardInterrupt: + print('\nSammutettu.')