HTTP handler with SPA routing and optional hot reload support.
| 28 | |
| 29 | |
| 30 | class DevHandler(http.server.SimpleHTTPRequestHandler): |
| 31 | """HTTP handler with SPA routing and optional hot reload support.""" |
| 32 | |
| 33 | def log_message(self, format, *args): |
| 34 | pass # Suppress logging |
| 35 | |
| 36 | def do_GET(self): |
| 37 | global hot_reload_enabled |
| 38 | |
| 39 | # Handle SSE endpoint for hot reload |
| 40 | if self.path == '/__hot_reload' and hot_reload_enabled: |
| 41 | self.handle_sse() |
| 42 | return |
| 43 | |
| 44 | path = self.translate_path(self.path) |
| 45 | |
| 46 | if os.path.isfile(path): |
| 47 | if path.endswith('.html') and hot_reload_enabled: |
| 48 | self.serve_html_with_reload(path) |
| 49 | else: |
| 50 | super().do_GET() |
| 51 | return |
| 52 | |
| 53 | # SPA fallback |
| 54 | self.path = '/index.html' |
| 55 | index_path = self.translate_path(self.path) |
| 56 | if os.path.isfile(index_path): |
| 57 | if hot_reload_enabled: |
| 58 | self.serve_html_with_reload(index_path) |
| 59 | else: |
| 60 | super().do_GET() |
| 61 | else: |
| 62 | self.send_error(404) |
| 63 | |
| 64 | def serve_html_with_reload(self, path): |
| 65 | """Inject hot reload script into HTML.""" |
| 66 | try: |
| 67 | with open(path, 'rb') as f: |
| 68 | content = f.read() |
| 69 | |
| 70 | script = b'''<script>(function(){var k='__coi_scroll';if('scrollRestoration' in history)history.scrollRestoration='manual';var s=sessionStorage.getItem(k);if(s){sessionStorage.removeItem(k);var y=parseInt(s);var n=0;function r(){if(n++>30)return;window.scrollTo(0,y);if(Math.abs(window.scrollY-y)>1)setTimeout(r,60)}window.addEventListener('load',function(){requestAnimationFrame(r)});document.addEventListener('DOMContentLoaded',function(){requestAnimationFrame(r)})}var e=new EventSource('/__hot_reload');e.onmessage=function(m){if(m.data==='reload'){sessionStorage.setItem(k,window.scrollY||document.documentElement.scrollTop);location.reload()}};e.onerror=function(){console.log('[Coi] Reconnecting...')}})();</script></body>''' |
| 71 | content = content.replace(b'</body>', script) |
| 72 | |
| 73 | self.send_response(200) |
| 74 | self.send_header('Content-Type', 'text/html; charset=utf-8') |
| 75 | self.send_header('Content-Length', len(content)) |
| 76 | self.send_header('Cache-Control', 'no-cache') |
| 77 | self.end_headers() |
| 78 | self.wfile.write(content) |
| 79 | except Exception as e: |
| 80 | self.send_error(500, str(e)) |
| 81 | |
| 82 | def handle_sse(self): |
| 83 | """Server-Sent Events for hot reload.""" |
| 84 | self.send_response(200) |
| 85 | self.send_header('Content-Type', 'text/event-stream') |
| 86 | self.send_header('Cache-Control', 'no-cache') |
| 87 | self.send_header('Connection', 'keep-alive') |
nothing calls this directly
no outgoing calls
no test coverage detected