Mini servidor HTTP en Python desde socket puro

Servidor HTTP/1.1 básico en Python usando sockets raw sin frameworks ni librerías. Parsea request line y headers, sirve ficheros estáticos con Content-Type correcto, devuelve 404 cuando no existe el recurso y soporta index.html como directorio raíz.
				"""
Mini servidor HTTP/1.1 en Python desde socket puro.
Sin http.server, sin frameworks. Compatible Python 3.8+
Uso: python code_1910_http_server.py [puerto] [directorio]
"""

import socket
import os
import sys

PORT      = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
ROOT      = sys.argv[2]      if len(sys.argv) > 2 else '.'

MIME = {
    '.html': 'text/html; charset=utf-8',
    '.css':  'text/css',
    '.js':   'application/javascript',
    '.json': 'application/json',
    '.png':  'image/png',
    '.jpg':  'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.gif':  'image/gif',
    '.svg':  'image/svg+xml',
    '.ico':  'image/x-icon',
    '.txt':  'text/plain; charset=utf-8',
}


def parse_request(raw: bytes):
    """Parsear request HTTP, devolver (method, path, headers, body)."""
    header_end = raw.find(b'rnrn')
    if header_end == -1:
        return None, None, {}, b''
    header_part = raw[:header_end].decode('utf-8', errors='replace')
    body        = raw[header_end + 4:]
    lines       = header_part.split('rn')
    request_line = lines[0].split()
    if len(request_line) < 2:
        return None, None, {}, b''
    method = request_line[0]
    path   = request_line[1]
    headers = {}
    for line in lines[1:]:
        if ':' in line:
            k, _, v = line.partition(':')
            headers[k.strip().lower()] = v.strip()
    return method, path, headers, body


def build_response(status: int, content_type: str, body: bytes) -> bytes:
    reasons = {200: 'OK', 404: 'Not Found', 405: 'Method Not Allowed', 500: 'Internal Server Error'}
    reason  = reasons.get(status, 'Unknown')
    headers = (
        f'HTTP/1.1 {status} {reason}rn'
        f'Content-Type: {content_type}rn'
        f'Content-Length: {len(body)}rn'
        f'Connection: closern'
        'rn'
    )
    return headers.encode() + body


def handle(conn: socket.socket) -> None:
    try:
        raw = b''
        conn.settimeout(5.0)
        while True:
            chunk = conn.recv(4096)
            if not chunk:
                break
            raw += chunk
            if b'rnrn' in raw:
                break

        method, path, headers, _ = parse_request(raw)
        if method is None:
            conn.sendall(build_response(400, 'text/plain', b'Bad Request'))
            return

        if method != 'GET':
            conn.sendall(build_response(405, 'text/plain', b'Method Not Allowed'))
            return

        # Limpiar path y evitar directory traversal
        safe_path = os.path.normpath(path.split('?')[0].lstrip('/'))
        if safe_path.startswith('..'):
            safe_path = ''
        full_path = os.path.join(ROOT, safe_path)

        # Si es directorio, buscar index.html
        if os.path.isdir(full_path):
            full_path = os.path.join(full_path, 'index.html')

        if not os.path.isfile(full_path):
            body = b'

404 Not Found

' conn.sendall(build_response(404, 'text/html', body)) return ext = os.path.splitext(full_path)[1].lower() content_type = MIME.get(ext, 'application/octet-stream') with open(full_path, 'rb') as f: body = f.read() conn.sendall(build_response(200, content_type, body)) except Exception as e: try: conn.sendall(build_response(500, 'text/plain', f'Error: {e}'.encode())) except Exception: pass finally: conn.close() def serve() -> None: server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('', PORT)) server.listen(10) print(f'Servidor en http://localhost:{PORT} (root: {os.path.abspath(ROOT)})') print('Ctrl+C para detenern') try: while True: conn, addr = server.accept() print(f' {addr[0]}:{addr[1]}') handle(conn) except KeyboardInterrupt: print('nServidor detenido.') finally: server.close() if __name__ == '__main__': serve()
Descargar adjuntos
COMPARTE ESTE TUTORIAL

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP