diff --git a/NGINX_CONFIG.md b/NGINX_CONFIG.md new file mode 100644 index 0000000..4bcfecc --- /dev/null +++ b/NGINX_CONFIG.md @@ -0,0 +1,88 @@ +# Nginx Configuration for Flask-SocketIO with WebSocket Support + +This document provides the required Nginx configuration to properly proxy WebSocket connections for the Markov Economics application. + +## Required Nginx Configuration + +```nginx +server { + listen 80; + server_name markov.elpatron.me; + + # Optional: Redirect HTTP to HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name markov.elpatron.me; + + # SSL configuration (adjust paths as needed) + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/private.key; + + # WebSocket proxy settings + location /socket.io/ { + proxy_pass http://127.0.0.1:5000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket specific timeouts + proxy_read_timeout 86400; + proxy_send_timeout 86400; + proxy_connect_timeout 60; + + # Disable buffering for real-time communication + proxy_buffering off; + proxy_cache off; + } + + # Regular HTTP traffic + location / { + proxy_pass http://127.0.0.1:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Standard timeouts + proxy_read_timeout 30; + proxy_send_timeout 30; + proxy_connect_timeout 10; + } +} +``` + +## Critical Configuration Points + +1. **WebSocket Upgrade Headers**: The `Upgrade` and `Connection` headers are essential for WebSocket handshake +2. **HTTP Version**: Must use HTTP/1.1 for WebSocket support +3. **Extended Timeouts**: WebSocket connections need longer timeouts than regular HTTP +4. **Disable Buffering**: Real-time communication requires immediate data flow +5. **Separate Location Blocks**: Socket.IO traffic should be handled separately from regular HTTP traffic + +## Testing the Configuration + +After applying the configuration: + +1. Restart Nginx: `sudo nginx -t && sudo systemctl reload nginx` +2. Check browser console for connection messages +3. Test the debug endpoint: https://markov.elpatron.me/debug +4. Run the distribution test: Open browser console and type `testDistributionChart()` + +## Troubleshooting + +If WebSocket connections still fail: +- Check Nginx error logs: `sudo tail -f /var/log/nginx/error.log` +- Verify the application is accessible directly: `curl http://127.0.0.1:5000/health` +- Test WebSocket connection manually with tools like `wscat` +- Ensure firewall allows WebSocket traffic + +## Alternative: Fallback to Polling Only + +If WebSocket cannot be made to work, the application will automatically fall back to HTTP polling, which should work with any standard HTTP proxy configuration. \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index 24d2037..cad546f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -15,8 +15,13 @@ from app.models import SimulationManager socketio = SocketIO( cors_allowed_origins="*", async_mode='threading', - logger=True, - engineio_logger=True + logger=False, # Disable to avoid log spam in production + engineio_logger=False, + # Transport order: try websocket first, then polling + transports=['websocket', 'polling'], + # Ping settings for better connection detection + ping_timeout=60, + ping_interval=25 ) # Global simulation manager instance @@ -45,7 +50,9 @@ def create_app(config_name=None): async_mode='threading', cors_allowed_origins="*", allow_upgrades=True, - transports=['websocket', 'polling'] + transports=['websocket', 'polling'], + ping_timeout=60, + ping_interval=25 ) # Register blueprints diff --git a/app/routes/main.py b/app/routes/main.py index 64ace60..de69af0 100644 --- a/app/routes/main.py +++ b/app/routes/main.py @@ -101,6 +101,59 @@ def results(simulation_id): title=f"Results - Simulation {simulation_id[:8]}") +@main_bp.route('/debug') +def debug_info(): + """ + Debug endpoint to verify deployment and test functionality. + + Returns: + JSON response with debug information + """ + import os + from datetime import datetime + from app.models.economic_model import EconomicSimulation, SimulationParameters + + # Test distribution functionality + test_params = SimulationParameters( + r_rate=0.05, + g_rate=0.03, + initial_capital=1000, + initial_consumption=1000, + num_agents=10, + iterations=5 + ) + + test_sim = EconomicSimulation(test_params) + # Run a few steps + for _ in range(5): + test_sim.step() + + # Get distribution data + labels, counts = test_sim.get_wealth_histogram(5) + + debug_data = { + 'timestamp': datetime.now().isoformat(), + 'environment': os.getenv('FLASK_ENV', 'unknown'), + 'config': os.getenv('FLASK_CONFIG', 'unknown'), + 'python_version': os.sys.version, + 'distribution_test': { + 'labels': labels, + 'counts': counts, + 'total_agents': len(test_sim.agents), + 'snapshots': len(test_sim.snapshots) + }, + 'fixes_deployed': { + 'fallback_polling': True, + 'enhanced_socketio': True, + 'production_debug': True, + 'environment_fix': True + }, + 'version_info': 'v2.1-proxy-fixes' + } + + return jsonify(debug_data) + + @main_bp.route('/health') def health_check(): """ diff --git a/app/static/js/simulation.js b/app/static/js/simulation.js index c90f52e..237b175 100644 --- a/app/static/js/simulation.js +++ b/app/static/js/simulation.js @@ -1,3 +1,63 @@ +/** + * Production debugging test function + * Call from browser console: testDistributionChart() + */ +window.testDistributionChart = function() { + debugLog('=== DISTRIBUTION CHART DIAGNOSTIC TEST ==='); + + // Test 1: Check if elements exist + const canvas = document.getElementById('distributionChart'); + debugLog('Canvas element found', !!canvas); + + // Test 2: Check Chart.js availability + debugLog('Chart.js available', typeof Chart !== 'undefined'); + + // Test 3: Check chart instance + debugLog('Chart instance exists', !!charts.distribution); + + // Test 4: Check current data + debugLog('Current simulation data', { + hasId: !!currentSimulation.id, + isRunning: currentSimulation.isRunning, + distributionData: currentSimulation.data.distribution + }); + + // Test 5: Check WebSocket connection + debugLog('WebSocket status', { + socketExists: !!window.MarkovEconomics.socket, + isConnected: window.MarkovEconomics ? window.MarkovEconomics.isConnected : 'unknown' + }); + + // Test 6: Manually test API endpoint + if (currentSimulation.id) { + fetch(`/api/simulation/${currentSimulation.id}/distribution?bins=5`) + .then(response => response.json()) + .then(data => { + debugLog('Manual API test result', data); + }) + .catch(error => { + debugLog('Manual API test failed', error); + }); + } + + // Test 7: Try to create test data and update chart + if (charts.distribution) { + const testLabels = ['$0-100', '$100-200', '$200-300']; + const testCounts = [5, 8, 2]; + + try { + charts.distribution.data.labels = testLabels; + charts.distribution.data.datasets[0].data = testCounts; + charts.distribution.update(); + debugLog('Manual chart update test: SUCCESS'); + } catch (error) { + debugLog('Manual chart update test: FAILED', error); + } + } + + debugLog('=== TEST COMPLETE ==='); +}; + // Debug flag - can be enabled even in production for troubleshooting const DEBUG_DISTRIBUTION = true;