QUA-002, premiere moitie. **Ce commit ne fait que reformater** : aucun changement de comportement, aucune ligne de logique touchee. 72 fichiers, 4 restaient deja conformes. Il est isole exprès, pour que `git log -p` sur les commits voisins reste lisible. `quote-style = "preserve"` etait deja pose dans pyproject.toml, ce qui evite le brassage guillemets simples / doubles : le diff porte sur les retours a la ligne, l indentation des appels longs et les virgules finales, pas sur le style de chaine. Verification : 263 tests passent avant et apres, ruff check propre. L activation en CI arrive dans le commit suivant, separement, pour que ce diff-ci ne contienne rien d autre. Co-Authored-By: Claude Opus 5 <[email protected]>
142 lines
4.9 KiB
Python
142 lines
4.9 KiB
Python
"""Development HTTPS server for testing production settings locally.
|
|
|
|
Generates a self-signed certificate (if not present) and runs the
|
|
application via Waitress wrapped in a TLS socket. This simulates the
|
|
production environment where Nginx handles TLS termination.
|
|
|
|
Usage:
|
|
python run_https.py
|
|
|
|
The server will listen on https://localhost:8443
|
|
Accept the self-signed certificate warning in your browser to proceed.
|
|
"""
|
|
|
|
import os
|
|
import socket
|
|
import subprocess
|
|
import ssl
|
|
import sys
|
|
from waitress.server import create_server
|
|
from app.app import create_app
|
|
|
|
CERT_FILE = 'certs/localhost.pem'
|
|
KEY_FILE = 'certs/localhost-key.pem'
|
|
|
|
|
|
def generate_self_signed_cert():
|
|
"""Generate a self-signed certificate for local HTTPS testing.
|
|
|
|
Uses OpenSSL to create a key and certificate valid for 365 days.
|
|
Skips generation if certificate files already exist.
|
|
"""
|
|
if os.path.exists(CERT_FILE) and os.path.exists(KEY_FILE):
|
|
print('[OK] Self-signed certificate already exists.')
|
|
return
|
|
|
|
os.makedirs('certs', exist_ok=True)
|
|
|
|
print('[INFO] Generating self-signed certificate for localhost...')
|
|
try:
|
|
subprocess.run(
|
|
[
|
|
'openssl',
|
|
'req',
|
|
'-x509',
|
|
'-newkey',
|
|
'rsa:2048',
|
|
'-keyout',
|
|
KEY_FILE,
|
|
'-out',
|
|
CERT_FILE,
|
|
'-days',
|
|
'365',
|
|
'-nodes',
|
|
'-subj',
|
|
'/CN=localhost',
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
)
|
|
print('[OK] Certificate generated: certs/localhost.pem')
|
|
except FileNotFoundError:
|
|
print('[ERROR] OpenSSL not found. Install OpenSSL or use:')
|
|
print(' winget install OpenSSL.OpenSSL')
|
|
print(' OR download from https://slproweb.com/products/Win32OpenSSL.html')
|
|
sys.exit(1)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f'[ERROR] Certificate generation failed: {e}')
|
|
sys.exit(1)
|
|
|
|
|
|
def main():
|
|
"""Start the HTTPS development server."""
|
|
port = int(os.getenv('HTTPS_PORT', 8443))
|
|
host = os.getenv('HOST', '127.0.0.1')
|
|
|
|
# Ensure certificate exists
|
|
generate_self_signed_cert()
|
|
|
|
# Create the Flask application
|
|
app = create_app()
|
|
|
|
# Create SSL context
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
context.load_cert_chain(CERT_FILE, KEY_FILE)
|
|
|
|
# Create a TCP socket, wrap it with TLS, then pass to Waitress
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
sock.bind((host, port))
|
|
sock.listen(5)
|
|
|
|
# Wrap the socket with TLS
|
|
ssl_sock = context.wrap_socket(sock, server_side=True)
|
|
|
|
# =====================================================================
|
|
# WSGI Middleware: Tell Flask the connection is HTTPS
|
|
#
|
|
# Waitress doesn't know the underlying socket is TLS, so Flask sees
|
|
# wsgi.url_scheme = "http". Without this middleware, force_https()
|
|
# would cause an infinite redirect loop (ERR_TOO_MANY_REDIRECTS).
|
|
# =====================================================================
|
|
class ForceHTTPSMiddleware:
|
|
"""WSGI middleware that sets url_scheme to 'https'.
|
|
|
|
Since we're wrapping the TCP socket with SSL before passing it
|
|
to Waitress, Flask's request.is_secure returns False because
|
|
Waitress reports wsgi.url_scheme='http'. This middleware fixes
|
|
that so Flask correctly identifies the connection as HTTPS.
|
|
"""
|
|
|
|
def __init__(self, wsgi_app):
|
|
self.wsgi_app = wsgi_app
|
|
|
|
def __call__(self, environ, start_response):
|
|
environ['wsgi.url_scheme'] = 'https'
|
|
environ['HTTPS'] = 'on'
|
|
return self.wsgi_app(environ, start_response)
|
|
|
|
# Wrap the Flask app with the HTTPS middleware
|
|
app.wsgi_app = ForceHTTPSMiddleware(app.wsgi_app)
|
|
|
|
print('\n╔══════════════════════════════════════════════════════╗')
|
|
print('║ TEAM TRYOUTS - Development HTTPS Server ║')
|
|
print('╠══════════════════════════════════════════════════════╣')
|
|
print(f'║ URL: https://{host}:{port} ║')
|
|
print('║ Cert: self-signed (accept browser warning) ║')
|
|
print('║ Press Ctrl+C to stop ║')
|
|
print('╚══════════════════════════════════════════════════════╝\n')
|
|
|
|
# Create Waitress server with the SSL-wrapped socket
|
|
server = create_server(
|
|
app,
|
|
sockets=[ssl_sock],
|
|
threads=4,
|
|
channel_timeout=30,
|
|
)
|
|
server.run()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|