Fix WebTransport server implementation and add test client
Server fixes: - Move H3Connection initialization to ProtocolNegotiated event (matches official aioquic pattern) - Fix datagram routing to use session_id instead of flow_id - Add max_datagram_frame_size=65536 to enable QUIC datagrams - Fix send_datagram() to use keyword arguments - Add certificate chain handling for Let's Encrypt - Add no-cache headers to static server Command-line improvements: - Move settings from environment variables to argparse - Add comprehensive CLI arguments with defaults - Default mode=wt, cert=cert.pem, key=key.pem Test clients: - Add test_webtransport_client.py - Python WebTransport client that successfully connects - Add test_http3.py - Basic HTTP/3 connectivity test Client updates: - Auto-configure server URL and certificate hash from /cert-hash.json - Add ES6 module support Status: ✅ Python WebTransport client works perfectly ✅ Server properly handles WebTransport connections and datagrams ❌ Chrome fails due to cached QUIC state (QUIC_IETF_GQUIC_ERROR_MISSING) 🔍 Firefox sends packets but fails differently - to be debugged next session 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,25 +3,66 @@ from __future__ import annotations
|
||||
import ssl
|
||||
import logging
|
||||
import threading
|
||||
import json
|
||||
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
|
||||
from pathlib import Path
|
||||
from typing import Tuple
|
||||
from typing import Tuple, Optional
|
||||
|
||||
|
||||
class _Handler(SimpleHTTPRequestHandler):
|
||||
# Allow passing a base directory at construction time
|
||||
# Allow passing a base directory and cert_hash_json at construction time
|
||||
cert_hash_json: Optional[str] = None
|
||||
|
||||
def __init__(self, *args, directory: str | None = None, **kwargs):
|
||||
super().__init__(*args, directory=directory, **kwargs)
|
||||
|
||||
def end_headers(self):
|
||||
# Add no-cache headers for all static files to force browser to fetch fresh versions
|
||||
self.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
self.send_header('Pragma', 'no-cache')
|
||||
self.send_header('Expires', '0')
|
||||
super().end_headers()
|
||||
|
||||
def start_https_static(host: str, port: int, certfile: str, keyfile: str, docroot: str) -> Tuple[ThreadingHTTPServer, threading.Thread]:
|
||||
def do_GET(self):
|
||||
# Intercept /cert-hash.json requests
|
||||
if self.path == '/cert-hash.json' and self.cert_hash_json:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.end_headers()
|
||||
self.wfile.write(self.cert_hash_json.encode('utf-8'))
|
||||
else:
|
||||
# Serve regular static files
|
||||
super().do_GET()
|
||||
|
||||
|
||||
def start_https_static(
|
||||
host: str,
|
||||
port: int,
|
||||
certfile: str,
|
||||
keyfile: str,
|
||||
docroot: str,
|
||||
cert_hash_json: Optional[str] = None
|
||||
) -> Tuple[ThreadingHTTPServer, threading.Thread]:
|
||||
"""Start a simple HTTPS static file server in a background thread.
|
||||
|
||||
Args:
|
||||
host: Host to bind to
|
||||
port: Port to bind to
|
||||
certfile: Path to TLS certificate
|
||||
keyfile: Path to TLS private key
|
||||
docroot: Document root directory
|
||||
cert_hash_json: Optional JSON string to serve at /cert-hash.json
|
||||
|
||||
Returns the (httpd, thread). Caller is responsible for calling httpd.shutdown()
|
||||
to stop the server on application exit.
|
||||
"""
|
||||
docroot_path = str(Path(docroot).resolve())
|
||||
|
||||
# Set class variable for the handler
|
||||
if cert_hash_json:
|
||||
_Handler.cert_hash_json = cert_hash_json
|
||||
|
||||
def handler(*args, **kwargs):
|
||||
return _Handler(*args, directory=docroot_path, **kwargs)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user