mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-08-10 02:43:02 +02:00
feat: implement server-level CORS support and cleanup CORS handling in API endpoints
This commit is contained in:
@@ -35,6 +35,7 @@ dependencies = [
|
||||
"pyyaml>=6.0.0",
|
||||
"cherrypy>=18.0.0",
|
||||
"paho-mqtt>=1.6.0",
|
||||
"cherrypy-cors==1.7.0",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -12,36 +12,6 @@ from .cad_calibration_engine import CADCalibrationEngine
|
||||
logger = logging.getLogger("HTTPServer")
|
||||
|
||||
|
||||
def is_cors_enabled(config: dict) -> bool:
|
||||
"""Check if CORS is enabled in the configuration"""
|
||||
return config.get("web", {}).get("cors_enabled", False)
|
||||
|
||||
|
||||
def add_cors_headers():
|
||||
"""Add CORS headers to allow cross-origin requests"""
|
||||
cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
|
||||
cherrypy.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
cherrypy.response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
|
||||
|
||||
|
||||
def cors_tool():
|
||||
"""CherryPy tool to add CORS headers"""
|
||||
if cherrypy.request.method == 'OPTIONS':
|
||||
# Handle preflight requests
|
||||
cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
|
||||
cherrypy.response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
|
||||
cherrypy.response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
|
||||
cherrypy.response.headers['Access-Control-Max-Age'] = '86400'
|
||||
return ''
|
||||
else:
|
||||
# Add CORS headers to actual requests
|
||||
add_cors_headers()
|
||||
|
||||
|
||||
# Register the CORS tool
|
||||
cherrypy.tools.cors = cherrypy.Tool('before_handler', cors_tool)
|
||||
|
||||
|
||||
# system systems
|
||||
# GET /api/stats
|
||||
# GET /api/logs
|
||||
@@ -94,23 +64,14 @@ class APIEndpoints:
|
||||
self.daemon_instance = daemon_instance
|
||||
self._config_path = config_path or '/etc/pymc_repeater/config.yaml'
|
||||
self.cad_calibration = CADCalibrationEngine(daemon_instance, event_loop)
|
||||
|
||||
|
||||
self._cors_enabled = is_cors_enabled(self.config)
|
||||
|
||||
logger.info(f"CORS {'enabled' if self._cors_enabled else 'disabled'} (config: web.cors_enabled={self._cors_enabled})")
|
||||
|
||||
# Configure CORS tool for this class if enabled
|
||||
if self._cors_enabled:
|
||||
self._cp_config = {'tools.cors.on': True}
|
||||
else:
|
||||
self._cp_config = {'tools.cors.on': False}
|
||||
|
||||
@cherrypy.expose
|
||||
def default(self, *args, **kwargs):
|
||||
"""Handle OPTIONS requests for CORS preflight"""
|
||||
"""Handle default requests"""
|
||||
if cherrypy.request.method == "OPTIONS":
|
||||
# OPTIONS handled by server-level CORS middleware
|
||||
return ""
|
||||
|
||||
# For non-OPTIONS requests, return 404
|
||||
raise cherrypy.HTTPError(404)
|
||||
|
||||
@@ -278,7 +239,6 @@ class APIEndpoints:
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
def packet_stats(self, hours=24):
|
||||
|
||||
try:
|
||||
hours = int(hours)
|
||||
stats = self._get_storage().get_packet_stats(hours=hours)
|
||||
@@ -290,7 +250,6 @@ class APIEndpoints:
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
def packet_type_stats(self, hours=24):
|
||||
|
||||
try:
|
||||
hours = int(hours)
|
||||
stats = self._get_storage().get_packet_type_stats(hours=hours)
|
||||
@@ -302,7 +261,6 @@ class APIEndpoints:
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
def route_stats(self, hours=24):
|
||||
|
||||
try:
|
||||
hours = int(hours)
|
||||
stats = self._get_storage().get_route_stats(hours=hours)
|
||||
@@ -314,7 +272,6 @@ class APIEndpoints:
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
def recent_packets(self, limit=100):
|
||||
|
||||
try:
|
||||
limit = int(limit)
|
||||
packets = self._get_storage().get_recent_packets(limit=limit)
|
||||
@@ -640,10 +597,6 @@ class APIEndpoints:
|
||||
cherrypy.response.headers['Cache-Control'] = 'no-cache'
|
||||
cherrypy.response.headers['Connection'] = 'keep-alive'
|
||||
|
||||
# Add CORS headers conditionally for SSE endpoint
|
||||
if self._cors_enabled:
|
||||
cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
|
||||
|
||||
if not hasattr(self.cad_calibration, 'message_queue'):
|
||||
self.cad_calibration.message_queue = []
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from datetime import datetime
|
||||
from typing import Callable, Optional
|
||||
|
||||
import cherrypy
|
||||
import cherrypy_cors
|
||||
from pymc_core.protocol.utils import PAYLOAD_TYPES, ROUTE_TYPES
|
||||
|
||||
from repeater import __version__
|
||||
@@ -83,6 +84,10 @@ class StatsApp:
|
||||
@cherrypy.expose
|
||||
def default(self, *args, **kwargs):
|
||||
"""Handle client-side routing - serve index.html for all non-API routes."""
|
||||
# Handle OPTIONS requests for any path
|
||||
if cherrypy.request.method == "OPTIONS":
|
||||
return ""
|
||||
|
||||
# Let API routes pass through
|
||||
if args and args[0] == 'api':
|
||||
raise cherrypy.NotFound()
|
||||
@@ -109,13 +114,27 @@ class HTTPStatsServer:
|
||||
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.config = config or {}
|
||||
self.app = StatsApp(
|
||||
stats_getter, node_name, pub_key, send_advert_func, config, event_loop, daemon_instance, config_path
|
||||
)
|
||||
|
||||
# Set up CORS at the server level if enabled
|
||||
self._cors_enabled = self.config.get("web", {}).get("cors_enabled", False)
|
||||
logger.info(f"CORS enabled: {self._cors_enabled}")
|
||||
|
||||
def _setup_server_cors(self):
|
||||
"""Set up CORS using cherrypy_cors.install()"""
|
||||
cherrypy_cors.install()
|
||||
logger.info("CORS support enabled")
|
||||
|
||||
def start(self):
|
||||
|
||||
try:
|
||||
|
||||
if self._cors_enabled:
|
||||
self._setup_server_cors()
|
||||
|
||||
# Serve static files from the html directory (compiled Vue.js app)
|
||||
html_dir = os.path.join(os.path.dirname(__file__), "html")
|
||||
assets_dir = os.path.join(html_dir, "assets")
|
||||
@@ -123,6 +142,7 @@ class HTTPStatsServer:
|
||||
config = {
|
||||
"/": {
|
||||
"tools.sessions.on": False,
|
||||
"cors.expose.on": self._cors_enabled,
|
||||
# Ensure proper content types for Vue.js files
|
||||
"tools.staticfile.content_types": {
|
||||
'js': 'application/javascript',
|
||||
@@ -133,6 +153,7 @@ class HTTPStatsServer:
|
||||
"/assets": {
|
||||
"tools.staticdir.on": True,
|
||||
"tools.staticdir.dir": assets_dir,
|
||||
"cors.expose.on": self._cors_enabled,
|
||||
# Set proper content types for assets
|
||||
"tools.staticdir.content_types": {
|
||||
'js': 'application/javascript',
|
||||
@@ -143,6 +164,7 @@ class HTTPStatsServer:
|
||||
"/favicon.ico": {
|
||||
"tools.staticfile.on": True,
|
||||
"tools.staticfile.filename": os.path.join(html_dir, "favicon.ico"),
|
||||
"cors.expose.on": self._cors_enabled,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user