#!/usr/bin/env python3 """ Enhanced HTTP Server that binds to all available network interfaces (0.0.0.0) Can serve multiple directories mapped to different URL paths. Uses ThreadingHTTPServer for improved performance with multiple connections. """ import http.server import socketserver import argparse import os import socket import urllib.parse from datetime import datetime import sys # Global variables DIRECTORIES = {} # Dictionary to store path mappings: {url_path: filesystem_path} PORT = 2001 def get_local_ip_addresses(): """Get a list of all local IP addresses on the machine.""" try: # Get hostname hostname = socket.gethostname() # Get all IP addresses associated with this hostname addresses = socket.getaddrinfo(hostname, None) # Filter only IPv4 addresses ipv4_addresses = [addr[4][0] for addr in addresses if addr[0] == socket.AF_INET] # Remove duplicates return list(set(ipv4_addresses)) except Exception as e: print(f"Error getting IP addresses: {e}") return [] class MultiDirectoryHTTPHandler(http.server.SimpleHTTPRequestHandler): """HTTP request handler that can serve files from multiple directories.""" def log_message(self, format, *args): """Override to provide more detailed logs.""" timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") client_address = self.client_address[0] message = format % args print(f"[{timestamp}] {client_address} - {message}") def translate_path(self, path): """ Override the path translation to use our directory mappings. This allows serving files from multiple directories. """ # Parse path to get URL path component parsed_path = urllib.parse.urlparse(path) path = parsed_path.path # Decode URL encoding (important for spaces and special characters) path = urllib.parse.unquote(path) # If root path or server-info, use default behavior if path == "/" or path == "/server-info": return super().translate_path(urllib.parse.unquote(self.path)) # Special case for Windows user directories if path.startswith('/C/Users/') or path.startswith('/c/Users/'): # Convert /C/Users/username to C:\Users\username windows_path = path[1].upper() + ":" + path[2:].replace('/', '\\') return windows_path # Check if this path matches any of our directory mappings for url_path, fs_path in DIRECTORIES.items(): if path.startswith(url_path): # Remove the URL path prefix and get the relative path relative_path = path[len(url_path):] if not relative_path.startswith('/'): relative_path = '/' + relative_path # Normalize the path to prevent directory traversal attacks normalized_path = os.path.normpath(relative_path).lstrip('/') # Combine with the filesystem path result = os.path.join(fs_path, normalized_path) return result # If no match, use default behavior with proper decoding return super().translate_path(urllib.parse.unquote(self.path)) def do_GET(self): """Handle GET requests, serving files and providing server info.""" # If requesting the root path or /server-info, show server information if self.path == "/" or self.path == "/server-info": self.send_response(200) self.send_header("Content-type", "text/html") self.end_headers() # Get the local interface IP that received this request local_ip = self.connection.getsockname()[0] # Create a simple HTML response with server info and directory mappings html = f"""
Server is running on: {local_ip}:{PORT}
Request received on interface: {local_ip}
Request path: {self.path}
Client address: {self.client_address[0]}
Server hostname: {socket.gethostname()}
Available interfaces: {', '.join(get_local_ip_addresses())}
Multi-threading: Enabled
| URL Path | File System Path | Links |
|---|---|---|
| {url_path} | {fs_path} | Browse |
| /C/Users/ | C:\\Users\\ | Browse |
Note: To see this page again, visit /server-info
""" self.wfile.write(html.encode()) else: # For all other paths, use the default behavior to serve files try: super().do_GET() except Exception as e: self.send_error(500, f"Server error: {str(e)}") def prompt_for_directories(): """Prompt the user to enter multiple directories to serve.""" directories = {} print("\nDirectory Mapping Setup") print("----------------------") print("You'll map URL paths to directories on your file system.") print("Example: /files -> C:\\MyFiles") print("This will make C:\\MyFiles accessible at http://yourserver:port/files/") # Add default mapping for current directory default_dir = os.getcwd() print(f"\nCurrent directory: {default_dir}") add_default = input("Add current directory as root path '/'? (y/n): ").strip().lower() if add_default == 'y': directories['/'] = default_dir print(f"Added mapping: / -> {default_dir}") # Add drive roots for Windows if os.name == 'nt': # Windows print("\nAvailable drives:") available_drives = [] for letter in range(ord('A'), ord('Z')+1): drive = chr(letter) + ":\\" if os.path.exists(drive): available_drives.append(drive) print(f" {drive}") if available_drives: add_drives = input("\nAdd all available drives? (y/n): ").strip().lower() if add_drives == 'y': for drive in available_drives: letter = drive[0].upper() url_path = f"/{letter}" directories[url_path] = drive print(f"Added mapping: {url_path} -> {drive}") # Allow manual directory additions while True: print("\nAdd another directory mapping? (Enter 'q' to finish)") url_path = input("URL path (e.g., /docs) or 'q' to finish: ").strip() if url_path.lower() == 'q' or not url_path: break # Ensure the URL path starts with a slash if not url_path.startswith('/'): url_path = '/' + url_path fs_path = input("File system path: ").strip() # Check if the directory exists if os.path.isdir(fs_path): directories[url_path] = os.path.abspath(fs_path) print(f"Added mapping: {url_path} -> {fs_path}") else: print(f"Error: '{fs_path}' is not a valid directory.") # If no directories were added, use current directory as root if not directories: print("\nNo directories specified. Using current directory as root.") directories['/'] = default_dir return directories # Create a ThreadingHTTPServer class that works with Python 3.6+ class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): """HTTP Server that handles each request in a new thread""" daemon_threads = True def run_server(port, directories=None): """Run the HTTP server on the specified port.""" global PORT, DIRECTORIES PORT = port # If no directories provided, prompt for them if directories is None: DIRECTORIES = prompt_for_directories() else: DIRECTORIES = directories # Display the directory mappings print("\nDirectory Mappings:") for url_path, fs_path in DIRECTORIES.items(): print(f" {url_path} -> {fs_path}") # Use 0.0.0.0 to listen on all available interfaces server_address = ('0.0.0.0', port) # Create the multi-threaded HTTP server handler = MultiDirectoryHTTPHandler httpd = ThreadingHTTPServer(server_address, handler) # Show all available IP addresses ip_list = get_local_ip_addresses() print(f"\nServer started on port {port}, binding to all interfaces (0.0.0.0)") print("Multi-threading enabled - each connection handled in its own thread") print("Available on the following addresses:") for ip in ip_list: print(f" http://{ip}:{port}/") print("\nServer info available at: /server-info") print("Press Ctrl+C to stop the server.") try: # Start serving forever httpd.serve_forever() except KeyboardInterrupt: print("\nServer stopped.") finally: httpd.server_close() if __name__ == "__main__": # Parse command line arguments parser = argparse.ArgumentParser(description="Multi-threaded HTTP Server serving multiple directories") parser.add_argument("-p", "--port", type=int, default=2001, help="Port to run the server on (default: 8000)") args = parser.parse_args() try: # Run the server run_server(args.port) except Exception as e: print(f"Error starting server: {e}") sys.exit(1)