Python Snippets

HTTP Server with Graceful Shutdown and Request Logging

import http.server
import socketserver
import threading
import time
import logging
from datetime import datetime
import signal
import sys

class LoggingHandler(http.server.SimpleHTTPRequestHandler):
    """Custom HTTP handler that logs all requests with timestamp and client info"""
    
    def log_message(self, format, *args):
        """Override to log messages to file and console with timestamp"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        client_ip = self.client_address[0]
        message = f"[{timestamp}] {client_ip} - {format % args}"
        logging.info(message)
        print(message)
    
    def do_GET(self):
        """Handle GET requests with custom logging"""
        start_time = time.time()
        # Call the parent method to handle the request
        super().do_GET()
        # Log request duration
        duration = time.time() - start_time
        self.log_message('"%s" %s %s (%.3fs)', 
                        self.requestline, str(self.status), str(self.bytes_sent), duration)

class GracefulHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
    """HTTP server that can be shut down gracefully"""
    
    def __init__(self, server_address, handler_class):
        super().__init__(server_address, handler_class)
        self.running = True
        self.allow_reuse_address = True
    
    def serve_forever(self):
        """Override serve_forever to check for running flag"""
        while self.running:
            self.handle_request()
    
    def shutdown(self):
        """Gracefully shut down the server"""
        self.running = False
        self.server_close()

def setup_logging():
    """Setup logging configuration"""
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(message)s',
        handlers=[
            logging.FileHandler('server.log'),
            logging.StreamHandler()
        ]
    )

def signal_handler(signum, frame):
    """Handle shutdown signals"""
    print(f"\nReceived signal {signum}. Shutting down server...")
    server.shutdown()
    sys.exit(0)

def run_server(host='localhost', port=8000):
    """Run the HTTP server with graceful shutdown"""
    global server
    
    # Setup logging
    setup_logging()
    
    # Create server
    server = GracefulHTTPServer((host, port), LoggingHandler)
    
    # Register signal handlers for graceful shutdown
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    # Start server
    print(f"Server starting at http://{host}:{port}")
    print("Press Ctrl+C to stop")
    print(f"Server logs are written to 'server.log'")
    
    try:
        # Serve forever (or until interrupted)
        while True:
            server.handle_request()
    except KeyboardInterrupt:
        print("\nKeyboard interrupt received. Shutting down...")
    finally:
        server.shutdown()

# Run the server if this script is executed directly
if __name__ == "__main__":
    run_server()

What This Code Does

This is a simple but feature-rich HTTP server that provides a modern web server experience with:

  1. Request Logging: Every HTTP request is logged with timestamp, client IP address, request details, response status, and response time
  2. Graceful Shutdown: The server can be stopped with Ctrl+C or system signals, properly closing all connections
  3. Persistent Logging: All requests are logged to both the console and a file (server.log)
  4. Performance Monitoring: Request response times are measured and logged for performance monitoring
  5. Multi-threading: Handles multiple requests concurrently using threading
  6. Port Reuse: Allows quick restart of the server without waiting for port to be released

Why It’s Useful

This snippet solves several common problems when working with local HTTP servers:

How to Run It

  1. Save the code to a file (e.g., web_server.py)
  2. Place the script in a directory containing files you want to serve
  3. Run the script from the terminal:
    python web_server.py
    
  4. The server will start at http://localhost:8000
  5. Access the server in a web browser
  6. Press Ctrl+C to stop the server

Features

The server will:

Customization

You can modify the server behavior by changing the parameters:

This is a simple but powerful tool for any Python developer working with static files or web content.