Files
team-tryouts/run_https.py
T

130 lines
4.8 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 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(f'\n╔══════════════════════════════════════════════════════╗')
print(f'║ TEAM TRYOUTS - Development HTTPS Server ║')
print(f'╠══════════════════════════════════════════════════════╣')
print(f'║ URL: https://{host}:{port} ║')
print(f'║ Cert: self-signed (accept browser warning) ║')
print(f'║ Press Ctrl+C to stop ║')
print(f'╚══════════════════════════════════════════════════════╝\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()