← Volver al listado de tecnologías

Capítulo 6: Servidor Web Básico

Por: SiempreListo
pythonzighttpservidoravanzado

Servidor Web Básico

Un servidor HTTP con routing, manejo de métodos y respuestas JSON.

Conceptos que Aprenderás

Python

from http.server import HTTPServer, BaseHTTPRequestHandler
import json
from typing import Callable
from urllib.parse import urlparse, parse_qs

Handler = Callable[['Router', dict], tuple[int, dict]]

class Router:
    def __init__(self):
        self.rutas: dict[str, dict[str, Handler]] = {}

    def ruta(self, metodo: str, path: str):
        def decorator(fn: Handler):
            if path not in self.rutas:
                self.rutas[path] = {}
            self.rutas[path][metodo] = fn
            return fn
        return decorator

    def resolver(self, metodo: str, path: str) -> Handler | None:
        ruta = self.rutas.get(path)
        if ruta:
            return ruta.get(metodo)
        return None

router = Router()
datos = {"items": []}

@router.ruta("GET", "/")
def index(ctx, params):
    return 200, {"mensaje": "API funcionando", "endpoints": ["/items"]}

@router.ruta("GET", "/items")
def listar_items(ctx, params):
    return 200, {"items": datos["items"]}

@router.ruta("POST", "/items")
def crear_item(ctx, params):
    item = {"id": len(datos["items"]) + 1, **params}
    datos["items"].append(item)
    return 201, item

class Handler(BaseHTTPRequestHandler):
    def responder(self, status: int, body: dict):
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(body).encode())

    def do_GET(self):
        parsed = urlparse(self.path)
        handler = router.resolver("GET", parsed.path)
        if handler:
            status, body = handler({}, parse_qs(parsed.query))
            self.responder(status, body)
        else:
            self.responder(404, {"error": "No encontrado"})

    def do_POST(self):
        length = int(self.headers.get("Content-Length", 0))
        body = json.loads(self.rfile.read(length)) if length else {}
        handler = router.resolver("POST", self.path)
        if handler:
            status, resp = handler({}, body)
            self.responder(status, resp)
        else:
            self.responder(404, {"error": "No encontrado"})

def main():
    server = HTTPServer(("", 8080), Handler)
    print("Servidor en http://localhost:8080")
    server.serve_forever()

if __name__ == "__main__":
    main()

Zig

const std = @import("std");
const net = std.net;

const Respuesta = struct {
    status: []const u8,
    body: []const u8,
};

fn construirRespuesta(status: []const u8, body: []const u8) []const u8 {
    return std.fmt.allocPrint(std.heap.page_allocator,
        "HTTP/1.1 {s}\r\nContent-Type: application/json\r\nContent-Length: {d}\r\n\r\n{s}",
        .{ status, body.len, body },
    ) catch "";
}

fn manejarConexion(conn: net.Server.Connection) void {
    defer conn.stream.close();

    var buf: [4096]u8 = undefined;
    const len = conn.stream.read(&buf) catch return;
    const request = buf[0..len];

    // Parsear primera línea
    var lines = std.mem.splitScalar(u8, request, '\n');
    const primera_linea = lines.next() orelse return;
    var partes = std.mem.splitScalar(u8, primera_linea, ' ');

    const metodo = partes.next() orelse return;
    const path = partes.next() orelse return;

    const respuesta = if (std.mem.eql(u8, path, "/"))
        construirRespuesta("200 OK", "{\"mensaje\":\"API funcionando\"}")
    else if (std.mem.eql(u8, path, "/items") and std.mem.eql(u8, metodo, "GET"))
        construirRespuesta("200 OK", "{\"items\":[]}")
    else
        construirRespuesta("404 Not Found", "{\"error\":\"No encontrado\"}");

    _ = conn.stream.write(respuesta) catch {};
}

pub fn main() !void {
    const address = net.Address.initIp4(.{ 0, 0, 0, 0 }, 8080);
    var server = try address.listen(.{});
    defer server.deinit();

    const stdout = std.io.getStdOut().writer();
    try stdout.print("Servidor en http://localhost:8080\n", .{});

    while (true) {
        const conn = server.accept() catch continue;
        manejarConexion(conn);
    }
}

Diferencias Clave

AspectoPythonZig
ServidorHTTPServernet.Address.listen
RoutingDecoradoresCondicionales
ParsingBaseHTTPRequestHandlerManual
HilosThreadingMixInSecuencial

Ejercicios

  1. Agrega soporte para archivos estáticos
  2. Implementa middleware de logging
  3. Agrega autenticación básica

Proyecto Final

Combina todos los capítulos: crea una API REST que gestione tareas (Cap. 3), las exporte a CSV (Cap. 4), y se comunique con servicios externos (Cap. 5).

← Anterior | Índice