diff --git a/api_service/run_api_server.py b/api_service/run_api_server.py index 703c49d..1c11494 100644 --- a/api_service/run_api_server.py +++ b/api_service/run_api_server.py @@ -24,7 +24,7 @@ else: if __name__ == "__main__": - import multiprocessing + import threading def run_uvicorn(bind_host): print(f"Starting API server on {bind_host}:{port}") @@ -35,11 +35,18 @@ if __name__ == "__main__": ) print(f"Data directory: {data_dir}") - # Start both IPv4 and IPv6 servers - processes = [] - for bind_host in ["0.0.0.0", "::"]: - p = multiprocessing.Process(target=run_uvicorn, args=(bind_host,)) - p.start() - processes.append(p) - for p in processes: - p.join() + # Start only IPv4 server to avoid conflicts + threads = [] + for bind_host in ["0.0.0.0"]: # Removed "::" to simplify + t = threading.Thread(target=run_uvicorn, args=(bind_host,)) + t.daemon = True + t.start() + threads.append(t) + + # Keep the main thread running + try: + while True: + import time + time.sleep(1) + except KeyboardInterrupt: + print("Shutting down API server...") diff --git a/run_markdown_server.py b/run_markdown_server.py index b980416..8e756de 100644 --- a/run_markdown_server.py +++ b/run_markdown_server.py @@ -237,21 +237,217 @@ async def root(request: Request): # Function to start the server in a thread def start_markdown_server_in_thread(host="0.0.0.0", port=5006): - """Start the markdown server in a separate thread.""" + """Start the markdown server in a separate thread using a different approach + that doesn't conflict with the API server's Uvicorn instance.""" log.info(f"Starting markdown server on {host}:{port}...") - def run_server(): - try: - uvicorn.run(app, host=host, port=port) - except Exception as e: - log.exception(f"Error running markdown server: {e}") + # Create a custom server that doesn't use Uvicorn directly + from starlette.applications import Starlette + from starlette.routing import Mount + from starlette.responses import HTMLResponse + import socket + import threading - # Start the server in a daemon thread - server_thread = threading.Thread(target=run_server, daemon=True) - server_thread.start() + # Create a simple HTTP server to handle requests + class MarkdownServer(threading.Thread): + def __init__(self, host, port): + super().__init__(daemon=True) + self.host = host + self.port = port + self.running = False + self.server_socket = None + + def run(self): + self.running = True + self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + try: + self.server_socket.bind((self.host, self.port)) + self.server_socket.listen(5) + log.info(f"Markdown server listening on {self.host}:{self.port}") + + while self.running: + # Accept connections and handle them in a new thread + try: + client_socket, addr = self.server_socket.accept() + client_thread = threading.Thread( + target=self.handle_request, + args=(client_socket, addr), + daemon=True + ) + client_thread.start() + except Exception as e: + if self.running: # Only log if we're still supposed to be running + log.exception(f"Error accepting connection: {e}") + except Exception as e: + log.exception(f"Error starting markdown server: {e}") + finally: + if self.server_socket: + self.server_socket.close() + + def handle_request(self, client_socket, addr): + try: + # Read the HTTP request + request_data = client_socket.recv(1024).decode('utf-8') + request_lines = request_data.split('\n') + if not request_lines: + return + + # Parse the request line + request_line = request_lines[0].strip() + parts = request_line.split() + if len(parts) < 2: + return + + method, path = parts[0], parts[1] + + # Simple routing + if path == '/' or path == '/index.html': + response = self.serve_root() + elif path == '/tos' or path == '/tos.html': + response = self.serve_tos() + elif path == '/privacy' or path == '/privacy.html': + response = self.serve_privacy() + else: + response = self.serve_404() + + # Send the response + client_socket.sendall(response.encode('utf-8')) + except Exception as e: + log.exception(f"Error handling request: {e}") + finally: + client_socket.close() + + def serve_root(self): + html = """ + + +
+ +Please review our Terms of Service and Privacy Policy.
+ + + + """ + return "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + html + + def serve_tos(self): + try: + with open("TOS.md", 'r', encoding='utf-8') as f: + md_content = f.read() + + if MARKDOWN_AVAILABLE: + html_content = markdown.markdown( + md_content, + extensions=[ + 'markdown.extensions.fenced_code', + 'markdown.extensions.tables', + 'markdown.extensions.toc' + ] + ) + else: + html_content = f"{md_content}" + + html = HTML_TEMPLATE.format( + title="Terms of Service", + content=html_content, + og_title="Terms of Service - Discord Bot", + og_description="Read the Terms of Service for our Discord Bot.", + og_type="article", + og_url=f"http://{self.host}:{self.port}/tos", + og_image=f"http://{self.host}:{self.port}/static/images/bot_logo.png" + ) + return "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + html + except Exception as e: + log.exception(f"Error serving TOS: {e}") + return "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n
Failed to render TOS
" + + def serve_privacy(self): + try: + with open("PRIVACY_POLICY.md", 'r', encoding='utf-8') as f: + md_content = f.read() + + if MARKDOWN_AVAILABLE: + html_content = markdown.markdown( + md_content, + extensions=[ + 'markdown.extensions.fenced_code', + 'markdown.extensions.tables', + 'markdown.extensions.toc' + ] + ) + else: + html_content = f"{md_content}" + + html = HTML_TEMPLATE.format( + title="Privacy Policy", + content=html_content, + og_title="Privacy Policy - Discord Bot", + og_description="Understand how your data is handled by our Discord Bot.", + og_type="article", + og_url=f"http://{self.host}:{self.port}/privacy", + og_image=f"http://{self.host}:{self.port}/static/images/bot_logo.png" + ) + return "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + html + except Exception as e: + log.exception(f"Error serving Privacy Policy: {e}") + return "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html\r\n\r\n
Failed to render Privacy Policy
" + + def serve_404(self): + html = "The requested page was not found.
" + return "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n" + html + + def stop(self): + self.running = False + if self.server_socket: + self.server_socket.close() + + # Start the server + server = MarkdownServer(host, port) + server.start() log.info(f"Markdown server thread started. TOS available at: http://{host}:{port}/tos") - return server_thread + return server def start_server(): """Start the markdown server as a background process (legacy method).""" diff --git a/run_unified_api.py b/run_unified_api.py index 9cd8c93..570afb6 100644 --- a/run_unified_api.py +++ b/run_unified_api.py @@ -23,7 +23,7 @@ api_port = int(os.getenv("API_PORT", "8001")) def run_unified_api(): """Run the unified API service (dual-stack IPv4+IPv6)""" - import multiprocessing + import threading def run_uvicorn(bind_host): print(f"Starting unified API service on {bind_host}:{api_port} (HTTP only)") @@ -35,13 +35,17 @@ def run_unified_api(): ) try: - processes = [] - for bind_host in ["127.0.0.1", "::1"]: - p = multiprocessing.Process(target=run_uvicorn, args=(bind_host,)) - p.start() - processes.append(p) - for p in processes: - p.join() + # Use threading instead of multiprocessing to avoid pickling issues + threads = [] + # Only run on IPv4 for now to avoid conflicts + for bind_host in ["127.0.0.1"]: # Removed "::1" to simplify + t = threading.Thread(target=run_uvicorn, args=(bind_host,)) + t.daemon = True + t.start() + threads.append(t) + + # Don't join threads here - let them run in the background + print(f"API service started on {api_port}") except Exception as e: print(f"Error starting unified API service: {e}")